Displays y NPCs
Objetos sólidos y cuerpos con forma de jugador, enviados por paquetes y animados por el cliente.
Tres módulos con la misma forma: ninguno crea una entidad real, los tres se dibujan directo al cliente y los tres son dueños de la vida de lo que muestran.
| Módulo | Punto de entrada | Qué dibuja |
|---|---|---|
| Displays | Displays.of(plugin) | Displays de objeto, bloque, cabeza y texto que se mueven, giran y caen. |
| NPCs | Npcs.of(plugin) | Cuerpos con forma de jugador: un cadáver, una estatua, un doble. |
| Ragdolls | Ragdolls.of(plugin) | Un cuerpo cortado en sus seis partes, lanzado, abierto o llevado. |
Los tres necesitan PacketEvents. Sin él el módulo lo dice una vez y no dibuja nada.
A los tres se llega desde una línea de secuencia, que es como los usa cada plugin cosmético del ecosistema. La API es para lo que la configuración no puede describir.
Por qué anima el cliente
A un display se le dice una pose y cuánto tiempo tiene para llegar, y él dibuja cada frame intermedio al framerate de quien mira. Una animación de dos segundos son unos seis paquetes por espectador, y sigue fluida en un servidor a quince ticks por segundo, porque la fluidez nunca dependió del tick.
Mover algo reenviando su posición cada tick es una animación de veinte fotogramas por segundo que empeora bajo carga — que es a lo que se parece una estela de partículas fingiendo ser un objeto.
Los displays dan peso, silueta y sombra. Las partículas dan luz, humo y ambiente. Los efectos que parecen caros son las dos cosas.
Dibujar un display desde una secuencia
Cualquier línea de forma pasa a ser una línea de display diciendo con qué se dibuja:
effects:
# Doce espadas que caen del cielo en anillo, girando al venir.
- '[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'
# El suelo cediendo: un anillo de bloques, aplastado, creciendo hacia fuera.
- '[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'
# Un objeto lanzado hacia arriba, volteando, que vuelve a caer.
- '[DISPLAY] TRIDENT;from:0,0.5,0;to:0,4,0;gravity:14;spin:3;axis:z;life:1.4;size:1.5'Cómo se mueve
| Parámetro | Qué hace | Por defecto |
|---|---|---|
life: | Segundos que existe. | 1 |
from:x,y,z | Dónde empieza, relativo a su punto. | 0,0,0 |
to:x,y,z | Dónde termina. | 0,0,0 |
rise: | Atajo de to:0,n,0. | |
gravity: | Bloques por segundo al cuadrado, sumados a la línea. | 0; lo vanilla ronda 32 |
ease: | in, out, in_out. | lineal |
spin: | Vueltas en toda su vida; x,y,z voltea en tres ejes. | 0 |
axis: | Sobre qué eje gira un spin de un solo número. | y |
orbit: | Vueltas que cada punto da alrededor del ancla. | 0 |
vary: | Cuánto difieren de tamaño las piezas, como fracción. | 0 |
size: | Un número, o x,y,z para una placa, un pilar o una hoja. | 1 |
size_to: | El tamaño con el que acaba. | igual que size |
tilt: roll: turn: | Una rotación fija, en grados. | 0 |
face_out: | Cada punto mira hacia fuera del centro. | false |
pull: | Cuánto viaja cada punto hacia el centro; negativo lo lanza hacia fuera. | 0 |
glow: | Color de contorno: un nombre, #rrggbb o un token {paleta}. | ninguno |
light: | Nivel de luz fijo, de 0 a 15. | la luz del sitio |
model: | Custom model data. | ninguno |
billboard: | FIXED, VERTICAL, HORIZONTAL, CENTER. | FIXED, CENTER en texto |
hold: | Contexto del display de objeto: 0 el modelo, 5 cabeza, 7 tirado, 8 marco. | 0 |
orbit: es el movimiento que una línea recta no puede expresar y el que separa una forma que aparece
de una forma que está viva. vary:0.4 da a las piezas tamaños que difieren hasta en dos quintos — es
un hash de la posición de cada punto, no un número al azar, así que sale igual cada vez. spin:0.5,2,1
voltea en tres ejes, porque nada lanzado en el mundo gira sobre exactamente uno.
Hacer que un movimiento aterrice
ease:in para un golpe
Retiene el movimiento y lo gasta tarde: carga y golpe, con los mismos dos números.
gravity: solo para escombros
Se suma a la línea en vez de reemplazarla, así que from:0,9,0;to:0,1,0;gravity:40 baja ocho
bloques y luego cae doce más, atravesando el suelo. La mitad de gravity por el cuadrado de
life es cuánto cae: un segundo en el aire quiere un 5, no un 50.
size: para dar peso
size:3,0.15,3 es un bloque aplastado en placa. Crecida desde nada es una onda de choque;
estirada al revés es un pilar o una hoja.
size_to: para la llegada
Un poco más grande al impactar, o más pequeño en la carga. Es la diferencia entre un objeto que llega y un objeto que se coloca.
El modelo de un objeto es una placa plana en su propio plano XY, mirando al sur, con la punta de una
espada arriba y a la derecha. roll:135 la deja punta abajo, roll:315 punta arriba y roll:225
tumbada de punta sobre su cara. El giro va al revés que la regla de la mano derecha, así que a una
hoja que sale horizontal hay que sumarle o restarle 90. face_out: y turn: se aplican después
de roll: y tilt:, que es el orden que necesita un anillo. Un display de bloque es un cubo y no
necesita nada de esto.
Vale la pena poner light:15 en casi todos los efectos: un display iluminado por el mundo es negro de
noche, y un efecto que desaparece al anochecer es un efecto que los jugadores reportan como roto.
La API de displays
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);| Método | Qué hace |
|---|---|
show(model, motion, at, viewers) | Muestra un display; se quita solo al terminar el movimiento. Devuelve un DisplayHandle, o null si nadie puede verlo o falta PacketEvents. |
removeAll() | Todo lo que este plugin tenga en pantalla. |
active() | Cuántos tiene en pantalla. |
isSupported() | Si se pueden mostrar displays en este servidor. |
NPCs
effects:
# La víctima, boca abajo, con lo que llevaba al morir, durante cuatro segundos.
- '[NPC] {victim};pose:lying;life:4;equip:true'
# De pie, girado hacia quien lo hizo, con contorno.
- '[NPC] {victim};pose:standing;life:3;glow:{error};face:true'| Parámetro | Qué hace | Por defecto |
|---|---|---|
pose: | lying, standing, crawling, sneaking o spinning. | lying |
life: | Segundos que se queda, de 0.2 a 120. | 5 |
equip: | Lleva la armadura y el arma con las que murió. | true |
glow: | Color de contorno. | ninguno |
y: | Altura sobre el ancla. | 0 |
face: | Se gira hacia quien disparó la secuencia. | true |
from:x,y,z / to:x,y,z | Dónde aparece y dónde acaba. | 0,0,0 |
over: | Segundos que dura el movimiento. | 0.7 |
ease: | out, in, in_out o linear. | out |
gravity: | Bloques por segundo al cuadrado, sumados a la línea. | 0 |
turn: | Grados que gira sobre sí mismo durante el movimiento. | 0 |
pose_to: / after: | Una segunda pose, y cuánto tarda en llegar. | ninguna / 0.4 |
hurt: | Parpadea en rojo al aparecer. | false |
spin: | Grados por segundo que sigue girando, durante toda su vida. | 0 |
bob: / bob_every: | Bloques que sube y baja en bucle, y cuánto dura una subida y bajada. | 0 / 1.6 |
swing: | Segundos entre golpes de brazo. | 0 |
scale: | Cómo de grande se dibuja, siendo 1 el tamaño de un jugador. | 1 |
hold: / offhand: | Un material puesto en cada mano. | el suyo |
pitch: | Inclinación de la cabeza en grados, negativo hacia arriba. | 0 |
spin: es el que se usa cuando un cuerpo no debe asentarse: turn: es un giro repartido en el
movimiento y luego se acaba; spin: no para nunca. Un cuerpo atrapado en un vórtice es spin:220,
uno colgando en la luz es bob:0.3;spin:40, y uno que sigue golpeando a lo que lo mató es
swing:0.5;hold:NETHERITE_SWORD. scale: es el atributo de escala del propio cliente, así que crece
el modelo entero — 0.4 es un muñeco y 2.5 es algo para lo que la sala se queda pequeña; necesita
un cliente 1.20.5, y uno más viejo ve un cuerpo de tamaño normal.
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());Un cuerpo se lee mejor a la mitad de la gravedad vanilla: uno real pesa más de lo que el ojo
espera y va más lento de lo que dice el número. pose:lying es la pose de un jugador durmiendo, que
es la única forma de dejar un cuerpo en el suelo sin un modelo propio; pose:standing con face:true
es otro efecto completamente distinto: alguien que se ha parado y te está mirando.
NpcModel.of(player) lee la textura de la conexión que este servidor ya tiene: sin consulta, sin
espera, sin fallar a medias. Un nombre sería una petición a Mojang, y un efecto no puede esperarla.
Una textura en base64 escrita en el archivo sí vale, y se resuelve al leer el archivo.
Se anuncia bajo un UUID propio, nunca el del portador: una segunda entrada bajo el id de un jugador real le quita su propia skin de su propio cuerpo hasta que se reconecte. También se anuncia sin listar, así que nunca aparece en el tab junto a jugadores reales.
Ragdolls
Un cuerpo que se deshace. El jugador se corta en sus seis partes — cabeza, torso, dos brazos, dos piernas —, cada una con los colores de su propia skin, y esas partes se mueven como una sola coreografía: lanzadas, abiertas, girando, aplastadas, deletreadas en letras o llevadas enteras.
effects:
# Reventado. Las piezas salen hacia fuera, caen, rebotan y se quedan.
- '[RAGDOLL] {victim};intact:0.3;life:2.6;speed:4.2;up:6.4;spin:2.2;detail:2;light:15'
# Levantado, abierto y llevado hacia arriba. No queda nada en el suelo.
- '[RAGDOLL] {victim};pose:vortex;rise:3.0;open:0.7;turns:1.8;life:3.0;detail:2;glow:{highlight}'La cabeza acepta {victim}, {killer} o una textura en base64, igual que [NPC]. Si lo que murió no
era un jugador, la línea lo dice una vez y no dibuja nada.
Las poses
| Pose | Qué pasa | También se escribe |
|---|---|---|
burst | Reventado: las piezas salen hacia fuera, caen, rebotan y se quedan. La de por defecto. | |
spread | Se levanta del suelo y se abre, brazos y piernas en cruz, girando despacio — y luego se suelta. | starfish, open |
knocked | Abierto como spread y golpeado varias veces desde distintos lados. | knock, batted, hit |
vortex | Llevado: las piezas suben en espiral hacia dentro y desaparecen arriba. No queda nada. | taken, ascend, spiral |
balloon | La cabeza se hincha demasiado, se tambalea y revienta. El cuerpo espera debajo. | bighead, swell, pop |
helicopter | Los brazos se ponen planos sobre la cabeza como un rotor, el cuerpo gira al revés y se va hacia delante. | chopper, rotor |
plane | Brazos como alas, morro arriba, inclinándose mientras sube. Con subida negativa es un picado. | fly, glide, jet |
flatten | Clavado contra el suelo y dejado plano: una mancha con forma de jugador, no un montón de bloques. | pancake, squash, flat |
melt | Se hunde. Las piezas pierden su altura donde están y se van. Ni lanzamiento ni rebote. | sink, dissolve |
sign | Las piezas se colocan formando letras y aguantan ahí; luego se sueltan y caen. | letters, spell, word |
thrown | Enviado a algún sitio de una pieza, dando vueltas. Sin gravedad no vuelve. | launched, ejected, carried |
Sus parámetros
| Parámetro | Qué hace | Por defecto |
|---|---|---|
pose: | Cuál de las once de arriba. | burst |
life: | Segundos que duran las piezas. | 2.2 |
intact: | Segundos que el cuerpo aguanta entero antes de que le pase nada. | 0.3 |
detail: | Celdas en que se corta cada parte por eje, de 1 a 4. 1 es un color por miembro. | 1 |
size: | Cómo de grande es; 1 es tamaño jugador. | 1 |
light: | Nivel de luz, de 0 a 15. | la del mundo |
glow: | Color del contorno — un nombre, #rrggbb o un token {palette}. | ninguno |
fade: | Se encoge al final en vez de desaparecer. | false |
settle: | Deja de girar al aterrizar. | true |
y: | Altura sobre el ancla. | 0 |
face: | Se gira hacia quien lanzó la secuencia. | true |
speed: up: | Con qué velocidad salen las piezas, hacia fuera y hacia arriba. | 3.2 / 6.5 |
spread: | Cuánto se diferencian las piezas entre sí, de 0 a 1. | 0.45 |
gravity: bounce: | Bloques por segundo al cuadrado, y la velocidad que conservan al caer. | 26 / 0.32 |
spin: | Vueltas por segundo. | 1.8 |
rise: open: | A qué altura cuelga un cuerpo sostenido, y cuánto abre brazos y piernas. | 1.1 / 0.55 |
lift: hang: turns: | Segundos que tarda en subir, segundos que cuelga y vueltas que da mientras cuelga. | 0.45 / 0.9 / 0.35 |
hits: every: force: | Para knocked: cuántos golpes, segundos entre ellos y cuántos bloques lo empuja cada uno. | 3 / 0.32 / 0.85 |
swell: | Para balloon: cuántas veces su tamaño alcanza la cabeza. | 3 |
squash: | Para flatten: qué queda de la altura de una pieza. | 0.14 |
sign: letters: | Para sign: qué deletrea el cuerpo, y cuánto mide una letra en bloques. | EZ / 2.4 |
dir: | Hacia dónde se lanza o vuela, en grados. 0 es este, 90 es sur. | la orientación del ancla |
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);Cada parte se dibuja como una rejilla de detail × detail, así que seis partes cuestan
6 × detail² displays: detail:1 son seis, detail:2 son veinticuatro y detail:4 son noventa y
seis. Dos ya se lee como un cuerpo a cualquier distancia desde la que alguien lo mire de verdad;
cuatro es para un cuerpo que se queda quieto delante de la cámara.
Todas se resuelven de antemano y se le entregan al cliente como poses, así que lo que las separa es lo que el archivo quería decir, nunca lo que el servidor podía permitirse. Aquí no se simula nada, y no se tickea nada.
Skins reales: mineskin-key
De serie, la cabeza de un ragdoll lleva la cara real del jugador, pero el resto del cuerpo se dibuja
con bloques: cada pieza toma el bloque de color más parecido a esa parte de la skin. Por eso una ropa
oscura se ve como un cuerpo negro hecho de bloques. Para que todo el cuerpo lleve la skin real,
ExyliaLib necesita una clave de la API de MineSkin, puesta en el plugins/ExyliaLib/config.yml de la
propia ExyliaLib:
mineskin-key: 'tu-clave'
ragdoll-skin-quality: normal| Clave | Qué hace | Por defecto |
|---|---|---|
mineskin-key | Una clave gratuita de account.mineskin.org/keys. Vacía, todos los cuerpos siguen en bloques. | vacía |
ragdoll-skin-quality | Lo fino que se corta un cuerpo con su skin real, y por tanto cuántas subidas cuesta una skin nueva. | normal |
| Calidad | Subidas por skin nueva | Cómo se ve | Skins nuevas por hora en el plan gratuito |
|---|---|---|---|
high | 18 | Cubos de 4 píxeles; un cuerpo que se rompe sale en 19 piezas. | unas 5 |
normal | 10 | Cada píxel de la skin, exacto; 11 piezas más grandes. | unas 10 |
low | 5 | Una cabeza por parte; se pierde un tercio de las filas. | unas 20 |
- Se hace una vez por skin. Una skin se prepara cuando entra su dueño, se sube en segundo plano y
se guarda en la base de datos del plugin que muestra los cuerpos (su
database.yml, tablaexylia_ragdoll_skins). Ese servidor, o una network que comparta esa base de datos, no la vuelve a subir nunca. - Los límites del plan gratuito. El plan gratuito de MineSkin permite 20 subidas por minuto y 100 por hora. Pasada la hora, las subidas esperan a que se reinicie y la consola dice cuándo vuelven a empezar.
- Nunca se espera. Una pieza cuya textura aún no ha llegado se dibuja con bloques hasta que llega.
- Se aplica en caliente.
/exylialib reloadlee las dos claves, y un cambio de clave o de calidad vuelve a preparar las skins de todos los conectados.
Lo que garantizan los tres
| Nada que el servidor cargue | No son entidades: ni se tickean, ni se guardan, ni están en ningún chunk, ni tienen hitbox. Dos jugadores juntos pueden ver cosas distintas. |
| Nada se puede quedar atrás | Nada más los limpiaría, así que el módulo es dueño de su vida: se van al acabar el movimiento o la vida, al desactivar el plugin o al parar el servidor. No hay un cuarto caso. |
| Un temporizador, no uno por objeto | Todos los displays del servidor se mueven desde un único driver asíncrono. |
| El giro se trocea por ti | El cliente gira por el arco más corto, así que los giros se parten en sextos de vuelta; spin:3 sale bien sin que nadie lo sepa. |
| Trabajo con techo | Un archivo que pide doscientas vueltas se limita a cuarenta y ocho poses en vez de mandar doscientos paquetes. |
| PacketEvents o nada | No hay respaldo que valga la pena fingir. |
El techo de lo que pueden costar los displays
plugins/ExyliaLib/displays.yml tiene dos números para todo el servidor, porque lo que se le puede
mandar a un cliente es un hecho del servidor y no del plugin que lo mandó.
max-viewer-displays: 20000
max-per-effect: 128| Clave | Qué limita |
|---|---|
max-viewer-displays | Pares display-espectador a la vez: un display que ven treinta jugadores cuenta como treinta. Los efectos que se pasarían se descartan antes de construir sus paquetes, así que una arena llena pierde la cola de un efecto y no su tick rate. La consola lo dice como mucho una vez por minuto. 0 quita el techo. |
max-per-effect | Cuántos displays puede dibujar una sola forma — un points:900 escrito a mano se caza al cargar y no en el cable. |
El presupuesto cuenta espectadores porque es el número que llega a la red: doscientos displays en una
esquina vacía no son nada, y esos mismos doscientos con treinta jugadores alrededor son seis mil
entidades en clientes que además tienen una pelea que renderizar. /exylialib reload recoge un cambio;
no hace falta reiniciar.
Flags de región
Flags de WorldGuard que registra la librería, para que una región pueda rechazar efectos sin que ningún plugin dependa de WorldGuard:
| Flag | Negarla impide |
|---|---|
kill-effects | Que suenen efectos de muerte donde alguien murió dentro de la región. |
hit-effects | Que suenen efectos de golpe por un impacto dado dentro de la región. |
arrows-effects | Los efectos de lanzamiento, estela e impacto de flechas dentro de la región. |
Se registran desde el onLoad de la propia librería, porque WorldGuard cierra su registro en cuanto
arranca. En un servidor sin WorldGuard cada flag permite, y una flag no registrada, un mundo que no
existe o un registro que se negó responden igual: una puerta que no se puede preguntar nunca quita
nada.
¿Falta algo en esta página? Dínoslo en Discord