Content generated with AI — it may contain mistakes.

Gameplay

Displays and NPCs

Solid objects and player-shaped bodies, sent as packets and animated by the client.

Three modules with the same shape: none of them creates a real entity, all three are drawn straight to a client, and all three own the lifetime of what they show.

ModuleEntry pointWhat it draws
DisplaysDisplays.of(plugin)Item, block, head and text displays that move, spin and fall.
NPCsNpcs.of(plugin)Player-shaped bodies: a corpse, a statue, a double.
RagdollsRagdolls.of(plugin)A body cut into its six parts, thrown, opened out or carried off.

All three need PacketEvents. Without it the module says so once and draws nothing.

Most callers never touch either API

All three are reachable from a sequence line, which is how every cosmetic plugin in the ecosystem uses them. The API is for what configuration cannot describe.

Why the client animates it

A display is told a pose and how long it has to get there, and draws every frame in between at the viewer's own frame rate. A two-second animation is about six packets per viewer, and it stays smooth on a server running at fifteen ticks a second, because the smoothness never depended on the tick rate.

Moving something by re-sending its position every tick is a twenty-frames-a-second animation that gets worse under load — which is what a particle trail pretending to be an object looks like.

Displays are not a replacement for particles

Displays give an effect weight, silhouette and shadow. Particles give it light, smoke and atmosphere. Effects that look expensive are both.

Drawing a display from a sequence

Any shape line becomes a display line by naming what it is drawn with:

effects:
  # Twelve swords that fall out of the sky in a ring, spinning as they come.
  - '[CIRCLE] NETHERITE_SWORD;as:item;radius:2.6;points:12;from:0,9,0;to:0,0,0;spin:2;axis:x;life:0.9;face_out:true;light:15'
  # The floor buckling: a ring of blocks, flattened, growing outwards.
  - '[CIRCLE] CRYING_OBSIDIAN;as:block;radius:0.3;points:20;size:0.1;size_to:1.4;to:0,0.2,0;life:0.5;light:15'
  # One item, thrown up, tumbling, falling back down.
  - '[DISPLAY] TRIDENT;from:0,0.5,0;to:0,4,0;gravity:14;spin:3;axis:z;life:1.4;size:1.5'

How it moves

ParameterWhat it doesDefault
life:Seconds it exists.1
from:x,y,zWhere it starts, relative to its point.0,0,0
to:x,y,zWhere it ends.0,0,0
rise:Shorthand for to:0,n,0.
gravity:Blocks per second squared, added to the line.0; vanilla is about 32
ease:in, out, in_out.linear
spin:Turns over its whole life; x,y,z tumbles on three axes.0
axis:Which axis a single-number spin turns around.y
orbit:Turns each point carries round the anchor.0
vary:How much the pieces differ in size, as a fraction.0
size:One number, or x,y,z for a plate, a pillar or a blade.1
size_to:The size it ends at.same as size
tilt: roll: turn:A fixed rotation, in degrees.0
face_out:Each point faces away from the centre.false
pull:How far each point travels towards the centre; negative throws it outwards.0
glow:Outline colour: a name, #rrggbb or a {palette} token.none
light:Fixed light level, 0 to 15.the light where it stands
model:Custom model data.none
billboard:FIXED, VERTICAL, HORIZONTAL, CENTER.FIXED, CENTER for text
hold:Item display context: 0 the model, 5 head, 7 dropped, 8 item frame.0

orbit: is the movement a straight line cannot express and the one that separates a shape that appears from a shape that is alive. vary:0.4 gives the pieces sizes that differ by up to two fifths — a hash of each point's position, not a random number, so it is the same every play. spin:0.5,2,1 tumbles on three axes, because nothing thrown in the world spins about exactly one.

Making a movement land

ease:in for a slam

It holds the movement back and spends it late: a wind-up and a strike, from the same two numbers.

gravity: for debris only

It is added to the line rather than replacing it, so from:0,9,0;to:0,1,0;gravity:40 descends eight blocks and then falls twelve more, through the floor. Half of gravity times the life squared is how far it drops: a second in the air wants about 5.

size: for weight

size:3,0.15,3 is a block flattened into a plate. Grown from nothing it is a shockwave; stretched the other way it is a pillar or a blade.

size_to: for arrival

A few percent larger on impact, or smaller on a wind-up. It is the difference between an object arriving and an object being placed.

A blade is aimed with roll:, not tilt:

An item model is a flat plate standing in its own XY plane, facing south, with a sword's tip up and to the right. roll:135 puts it tip-down, roll:315 tip-up, roll:225 point-first along its face. The turn runs the opposite way from the right-hand rule, so a blade that comes out horizontal wants 90 added or taken. face_out: and turn: are applied after roll: and tilt:, which is the order a ring needs. A block display is a cube and needs none of it.

light:15 is worth setting on nearly every effect: a display lit by the world is black at night, and an effect that disappears after sunset is an effect players report as broken.

The display API

PluginDisplays displays = Displays.of(this);
 
DisplayModel blade = DisplayModel.item(new ItemStack(Material.NETHERITE_SWORD))
        .glow(0xFF6B9D)
        .light(15);
 
DisplayMotion thrown = DisplayMotion.builder()
        .life(1200)
        .from(0, 7, 0).to(0, 0, 0)
        .spin(Rotation.Axis.Z, 3)
        .build();
 
displays.show(blade, thrown, where, observers);
MethodWhat it does
show(model, motion, at, viewers)Shows one display; it removes itself when the motion ends. Returns a DisplayHandle, or null when nobody can see it or PacketEvents is absent.
removeAll()Everything this plugin is showing.
active()How many it has on screen.
isSupported()Whether displays can be shown at all here.

NPCs

effects:
  # The victim, face down, wearing what they died in, for four seconds.
  - '[NPC] {victim};pose:lying;life:4;equip:true'
  # Standing, turned to face whoever did it, outlined.
  - '[NPC] {victim};pose:standing;life:3;glow:{error};face:true'
ParameterWhat it doesDefault
pose:lying, standing, crawling, sneaking or spinning.lying
life:Seconds it stays, from 0.2 to 120.5
equip:Wears the armour and weapon they died in.true
glow:Outline colour.none
y:Height above the anchor.0
face:Turns to face whoever set the sequence off.true
from:x,y,z / to:x,y,zWhere it appears and where it ends up.0,0,0
over:Seconds the movement takes.0.7
ease:out, in, in_out or linear.out
gravity:Blocks per second squared, added to the line.0
turn:Degrees it turns on the spot over the movement.0
pose_to: / after:A second pose, and how long before it.none / 0.4
hurt:Flinches red as it appears.false
spin:Degrees a second it keeps turning, for its whole life.0
bob: / bob_every:Blocks it rises and falls on a loop, and how long one rise and fall takes.0 / 1.6
swing:Seconds between arm swings.0
scale:How big it is drawn, 1 being player-sized.1
hold: / offhand:A material put in each hand.its own
pitch:Head pitch in degrees, negative being up.0

spin: is the one to reach for when a body should not settle: turn: is one turn spread over the movement and then finished, spin: never stops. A body caught in a vortex is spin:220, one hanging in the light is bob:0.3;spin:40, and one still swinging at whatever killed it is swing:0.5;hold:NETHERITE_SWORD. scale: is the client's own scale attribute, so the whole model grows — 0.4 is a doll and 2.5 is something the room is not big enough for; it needs a 1.20.5 client, and an older one sees a player-sized body.

PluginNpcs npcs = Npcs.of(this);
 
NpcHandle body = npcs.show(NpcModel.of(victim)
                .wearing(victim)
                .pose(NpcPose.LYING)
                .glow(0xA33B53),
        victim.getLocation(), 4000, observers);
 
body.lookAt(killer.getLocation());

A body reads better at about half vanilla gravity — a real one is heavier than the eye expects and slower than the number says. pose:lying is the pose a sleeping player is drawn in, which is the only way to put a body on the floor without a model of your own; pose:standing with face:true is a different effect entirely: somebody who has stopped, and is looking at you.

Why an NPC cannot take a player name

NpcModel.of(player) reads the texture from the connection this server already holds — no lookup, no waiting, no failure halfway. A name would be a request to Mojang, and an effect cannot wait for one. A base64 texture written into the file works, and is resolved when the file is read.

An NPC always has its own identity

It is announced under a UUID of its own, never the wearer's: a second entry under a real player's id takes that player's skin off their own body until they relog. It is also announced unlisted, so it never appears in the tab list beside real players.

Ragdolls

A body that comes apart. The player is cut into its six parts — head, torso, two arms, two legs — each one wearing the colours of their own skin, and then those parts are moved as a single piece of choreography: thrown, opened out, spun, flattened, spelled into letters, or carried off whole.

effects:
  # Thrown apart. The pieces leave outwards, fall, bounce and rest.
  - '[RAGDOLL] {victim};intact:0.3;life:2.6;speed:4.2;up:6.4;spin:2.2;detail:2;light:15'
  # Lifted, opened out, and taken upwards. Nothing is left on the floor.
  - '[RAGDOLL] {victim};pose:vortex;rise:3.0;open:0.7;turns:1.8;life:3.0;detail:2;glow:{highlight}'

The head takes {victim}, {killer}, or a base64 texture, exactly as [NPC] does. If what died was not a player the line says so once and draws nothing.

The poses

PoseWhat happensAlso written
burstThrown apart: the pieces leave outwards, fall, bounce and rest. The default.
spreadLifted off the ground and opened out, arms and legs wide, turning slowly — then let go.starfish, open
knockedHeld open like spread, then struck several times from different sides.knock, batted, hit
vortexTaken: the pieces spiral inwards and upwards and are gone at the top. Nothing is left.taken, ascend, spiral
balloonThe head swells far too big, wobbles, and bursts. The body waits underneath it.bighead, swell, pop
helicopterThe arms go flat above the head as a rotor, the body counter-turns, and it leaves forwards.chopper, rotor
planeArms out as wings, nose up, banking as it climbs. A negative climb is a dive.fly, glide, jet
flattenDriven straight down and left flat: a player-shaped stain, not a pile of blocks.pancake, squash, flat
meltSinks. The pieces lose their height where they stand and are gone. No throw, no bounce.sink, dissolve
signThe pieces lay themselves out into letters and hold there, then let go and fall.letters, spell, word
thrownSent somewhere in one piece, turning end over end. With no gravity it never comes back.launched, ejected, carried

Its parameters

ParameterWhat it doesDefault
pose:Which of the eleven above.burst
life:Seconds the pieces last.2.2
intact:Seconds the body stands whole before anything happens to it.0.3
detail:Cells each part is cut into on each axis, 1 to 4. 1 is one colour per limb.1
size:How big it is; 1 is player-sized.1
light:Light level, 0 to 15.the world's own
glow:Outline colour — a name, #rrggbb or a {palette} token.none
fade:Shrinks away at the end instead of vanishing.false
settle:Stops turning once it lands.true
y:Height above the anchor.0
face:Turns to face whoever set the sequence off.true
speed: up:How fast the pieces leave, outwards and upwards.3.2 / 6.5
spread:How much the pieces differ from each other, 0 to 1.0.45
gravity: bounce:Blocks per second squared, and the speed kept on landing.26 / 0.32
spin:Turns a second.1.8
rise: open:How far off the ground a held body hangs, and how far its arms and legs open out.1.1 / 0.55
lift: hang: turns:Seconds the lift takes, seconds it hangs there, and turns it makes while it hangs.0.45 / 0.9 / 0.35
hits: every: force:For knocked: how many blows, seconds between them, and how far one shoves it in blocks.3 / 0.32 / 0.85
swell:For balloon: how many times its own size the head reaches.3
squash:For flatten: what is left of a piece's height.0.14
sign: letters:For sign: what the body spells, and how tall one letter is in blocks.EZ / 2.4
dir:Which way it is thrown or flies, in degrees. 0 is east, 90 is south.the anchor's own facing
PluginRagdolls ragdolls = Ragdolls.of(this);
 
ragdolls.show(RagdollModel.of(victim).detail(2).light(15),
        RagdollMotion.builder().pose(RagdollPose.VORTEX).rise(3.0).build(),
        victim.getLocation(), observers);
`detail` is the only expensive parameter here

A part is drawn as a detail × detail grid, so six parts cost 6 × detail² displays: detail:1 is six, detail:2 is twenty-four, and detail:4 is ninety-six. Two reads as a body at any distance somebody actually watches one from; four is for a body that hangs still in front of the camera.

A pose is choreography, not physics

Every one of these is solved in advance and handed to the client as poses, so what separates them is what the file wanted to say, never what the server could afford. Nothing here is simulated, and nothing here is ticked.

Real skins: mineskin-key

Out of the box a ragdoll's head wears the player's real face, but the rest of the body is drawn in blocks: each piece takes the block nearest in colour to that part of the skin. A dark outfit therefore reads as a black body made of blocks. To have the whole body wear the real skin, ExyliaLib needs a MineSkin API key, set in ExyliaLib's own plugins/ExyliaLib/config.yml:

plugins/ExyliaLib/config.yml
mineskin-key: 'your-key'
ragdoll-skin-quality: normal
KeyWhat it doesDefault
mineskin-keyA free key from account.mineskin.org/keys. Empty keeps every body in blocks.empty
ragdoll-skin-qualityHow finely a body in its real skin is cut, and so how many uploads a new skin costs.normal
QualityUploads per new skinWhat it looks likeNew skins an hour on the free plan
high184-pixel cubes; a breaking body comes apart in 19 pieces.about 5
normal10Every skin pixel kept exactly; 11 larger pieces.about 10
low5One head per part; a third of the rows lost.about 20
  • Made once per skin. A skin is prepared when its owner joins, uploaded in the background, and kept in the database of the plugin that shows the bodies (its database.yml, table exylia_ragdoll_skins). That server, or a network sharing that database, never uploads it again.
  • The free plan's limits. MineSkin's free plan allows 20 uploads a minute and 100 an hour. Past the hour, uploads wait for it to reset and the console says when they start again.
  • Never a wait. A piece whose texture has not arrived yet is drawn in blocks until it does.
  • Applied live. /exylialib reload reads both keys, and a changed key or quality prepares the skins of everybody online again.

What all three guarantee

Nothing the server carriesNot entities: not ticked, not saved, not in any chunk, no hitbox. Two players standing together can be shown different ones.
Nothing can be left behindNothing else would clean them up, so the module owns their lives: they go when the motion or life ends, when the plugin is disabled, or when the server stops. There is no fourth case.
One timer, not one per objectEvery display on the server is moved from a single asynchronous driver.
A spin is cut up for youThe client turns by the shortest arc, so spins are split into sixths of a turn; spin:3 is correct without anybody knowing that.
Capped workA file asking for two hundred turns is capped at forty-eight poses rather than sending two hundred packets.
PacketEvents or nothingThere is no fallback worth pretending about.

The ceiling on what displays may cost

plugins/ExyliaLib/displays.yml holds two numbers for the whole server, because what a client can be sent is a fact about the server rather than about the plugin that happened to send it.

max-viewer-displays: 20000
max-per-effect: 128
KeyWhat it caps
max-viewer-displaysDisplay-viewer pairs at once: one display seen by thirty players counts as thirty. Effects that would go over are dropped before their packets are built, so a crowded arena loses the tail of an effect instead of its tick rate. The console says so at most once a minute. 0 removes the ceiling.
max-per-effectHow many displays one shape may draw — a points:900 written by hand is caught at load rather than on the wire.

The budget counts viewers because that is the number that reaches the network: two hundred displays in an empty corner are nothing, and the same two hundred with thirty players around them are six thousand entities on clients that also have a fight to render. /exylialib reload picks up an edit; no restart.

Region flags

WorldGuard flags the library registers, so a region can refuse effects without any plugin depending on WorldGuard:

FlagDenying it stops
kill-effectsKill effects playing where somebody died inside the region.
hit-effectsHit effects playing on a blow landed inside the region.
arrows-effectsArrow launch, trail and impact effects inside the region.

They are registered from the library's own onLoad, because WorldGuard locks its registry the instant it enables. On a server without WorldGuard every flag allows, and an unregistered flag, a missing world or a refused registry all answer the same way: a gate that cannot be asked never withholds anything.

Something missing on this page? Tell us on Discord