API
Animorph expone dos APIs: una de servidor (IMorphAPI) para plugins Bukkit/Paper y mods Fabric server,
y una de cliente (ClientMorphAPI) para mods Fabric client.
Ambas incluyen un sistema de eventos para reaccionar a cambios de modelo, emotes y layers.
Agregar la dependencia
Agrega el repositorio de JitPack y la dependencia correspondiente:
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
} Plugin Bukkit/Paper
dependencies {
compileOnly 'com.github.feeldev12.Animorph-API:server:{version}'
} Mod de servidor (Fabric)
dependencies {
implementation 'com.github.feeldev12.Animorph-API:server:{version}'
} Cliente (mod Fabric)
dependencies {
modImplementation 'com.github.feeldev12.Animorph-API:client:{version}'
} API de Servidor (IMorphAPI)
IMorphAPI<P> es la interfaz principal para controlar Animorph desde el servidor.
Funciona tanto en plugins Bukkit/Paper como en mods Fabric server.
import me.feeldev.animorph.api.AnimorphProvider;
import me.feeldev.animorph.api.IMorphAPI;
// Verificar disponibilidad
if (AnimorphProvider.isAvailable()) {
IMorphAPI<Player> api = AnimorphProvider.getApi();
// usar la API...
} P es Player en Bukkit/Paper
o ServerPlayerEntity en Fabric server.
Todos los métodos que envían datos aceptan viewers opcionales al final:
si se omiten, se envía al jugador y a todos sus trackers.
Modelos
IMorphAPI<Player> api = AnimorphProvider.getApi();
// Aplicar un modelo al jugador
api.updateModel(player, "skeleton", false);
// Aplicar modelo forzando re-envío de datos
api.updateModel(player, "skeleton", true);
// Quitar el morph
api.clearModel(player);
// Re-enviar el modelo actual (sincronización con nuevos viewers)
api.updateModelPersistent(player);
// Re-enviar forzando datos completos
api.updateModelPersistent(player, true);
// Consultar el modelo activo
Optional<? extends IModelData> model = api.getModel(player);
// Obtener un modelo registrado por ID
Optional<? extends IModelData> modelo = api.getModel("skeleton");
// Listar todos los modelos registrados
Map<String, ? extends IModelData> modelos = api.registeredModels(); Emotes
Los emotes pueden reproducirse a nivel de modelo o de layers específicos.
// Reproducir un emote en el modelo
api.playEmote(player, "greet:wave", null);
// Reproducir un emote en layers específicos
api.playEmote(player, "dance:spin", Set.of("pompompurin"));
// Reproducir emote desde un tick específico (sincronización)
api.playEmote(player, "dance:spin", 40.0, null);
// Reproducir emote en layers desde un tick específico
api.playEmote(player, "dance:spin", 40.0, Set.of("pompompurin"));
// Detener todos los emotes (modelo + layers)
api.clearEmote(player);
// Detener emotes solo en layers específicos
api.clearEmote(player, Set.of("pompompurin"));
// Re-enviar emote de modelo (sincronización)
api.playEmote(player);
// Re-enviar emote de un tipo específico
api.playEmote(player, EmoteType.MODEL);
api.playEmote(player, EmoteType.LAYER);
// Consultar emote activo en un layer específico
String layerEmote = api.getLayerEmoteId(player, "pompompurin");
// Consultar animaciones registradas
Optional<? extends IEmoteData> emote = api.getAnimation("greet");
Map<String, ? extends IEmoteData> emotes = api.registeredAnimations(); Layers
// Activar un layer
api.applyLayer(player, "pompompurin", true);
// Desactivar un layer
api.applyLayer(player, "pompompurin", false);
// Activar layer en un modelo específico
api.applyLayer(player, "player", "pompompurin", true);
// Re-enviar todos los layers (sincronización)
api.updateLayers(player); Texto
// Actualizar un placeholder
api.updateTextPlaceholder(player, "{name}", "Animorph Pro");
// Actualizar un placeholder solo para viewers específicos
api.updateTextPlaceholder(player, "{name}", "Animorph Pro", viewer1, viewer2);
// Re-enviar todos los placeholders
api.updateTextPlaceholder(player); Primera persona
Controla la configuración de primera persona de un jugador en tiempo real, sin modificar el YAML del modelo. Las propiedades se aplican por jugador y sobrescriben la configuración del modelo.
import me.feeldev.animorph.api.IFirstPersonProperty;
// Crear propiedades de primera persona con el Builder
IFirstPersonProperty property = IFirstPersonProperty.builder()
.showModel(true)
.modelOffset(0, 1.5, 0)
.customArmsShow(true)
.customArmsBothHands(true)
.customArmsRenderItems(true)
.showEquipment(false)
.build();
// Aplicar al jugador
api.updateFirstPersonProperty(player, property);
// Consultar las propiedades actuales
Optional<IFirstPersonProperty> fp = api.getFirstPersonProperty(player);
// Restaurar al valor por defecto del modelo
api.clearFirstPersonProperty(player); Referencia del Builder
| Método | Tipo | Descripción |
|---|---|---|
showModel(bool) | boolean | Muestra el cuerpo completo del modelo en primera persona. |
modelOffset(x, y, z) | double | Desplazamiento del modelo respecto a la cámara. |
customArmsShow(bool) | boolean | Habilita los brazos del modelo en lugar de los vanilla. |
customArmsBothHands(bool) | boolean | Renderiza ambas manos (derecha e izquierda). |
customArmsRenderItems(bool) | boolean | Renderiza ítems a través de las manos del modelo. |
showEquipment(bool) | boolean | Muestra la armadura en primera persona. |
Estado del jugador (servidor)
// ID del modelo actual ("empty" si no tiene)
String modelId = api.getModelId(player);
// ¿Tiene un modelo aplicado?
boolean hasMorph = api.hasModel(player);
// ID del emote actual ("empty" si no tiene)
String emoteId = api.getEmoteId(player);
// ¿Está reproduciendo un emote?
boolean isEmoting = api.isEmoting(player);
// Emote en un layer específico
String layerEmote = api.getLayerEmoteId(player, "pompompurin");
// ¿Un layer está activo?
boolean active = api.isLayerActive(player, "pompompurin");
// Todos los layers y su estado
Map<String, Boolean> layers = api.getLayerStates(player);
// Todos los placeholders y sus valores
Map<String, String> texts = api.getTextPlaceholders(player);
// Propiedades de primera persona
Optional<IFirstPersonProperty> fp = api.getFirstPersonProperty(player); Sistema de Eventos
Animorph incluye un AnimorphEventBus ligero y thread-safe.
Los eventos de servidor se disparan antes de aplicar el cambio y son cancelables.
Los eventos de cliente se disparan después de aplicar el cambio y no son cancelables.
AnimorphEventBus bus = api.getEventBus();
// Registrar un listener
bus.register(ModelUpdateEvent.class, event -> {
Player player = event.getPlayer();
String newModel = event.getModelId();
String oldModel = event.getPreviousModelId();
// Cancelar si es un modelo prohibido
if (newModel.equals("banned_model")) {
event.setCancelled(true);
}
});
// Desregistrar un listener
bus.unregister(ModelUpdateEvent.class, myListener); Eventos de servidor
Todos los eventos de servidor extienden AnimorphEvent<P> y los cancelables implementan Cancellable.
| Evento | Cancelable | Descripción | Métodos |
|---|---|---|---|
ModelUpdateEvent | Sí | Se aplica o quita un modelo. | getModelId(), getPreviousModelId(), isClearing() |
EmotePlayEvent | Sí | Se reproduce un emote. | getEmoteId(), getPreviousEmoteId(), getLayerIds(), getStartTick(), isLayerEmote() |
EmoteStopEvent | Sí | Se detiene un emote. | getEmoteId(), getLayerIds(), isLayerEmote() |
LayerUpdateEvent | Sí | Se activa o desactiva un layer. | getLayerId(), getState() |
FirstPersonPropertyUpdateEvent | Sí | Se cambian las propiedades de primera persona. | getProperty() |
AnimorphReloadEvent | No | Se ejecuta /animorph reload. | — |
api.getEventBus().register(EmotePlayEvent.class, event -> {
Player player = event.getPlayer();
if (isInCombat(player)) {
event.setCancelled(true);
player.sendMessage("No puedes usar emotes en combate.");
}
}); Eventos de cliente
Los eventos de cliente se registran en ClientMorphAPI.getEventBus(). Se disparan después de que el cambio ya se aplicó.
| Evento | Descripción |
|---|---|
ClientModelUpdateEvent | El modelo de un jugador cambió. |
ClientEmotePlayEvent | Se inició un emote. |
ClientEmoteStopEvent | Se detuvo un emote. |
ClientLayerUpdateEvent | Un layer cambió de estado. |
ClientFirstPersonPropertyUpdateEvent | Cambiaron las propiedades de primera persona. |
import me.feeldev.animorph.client.api.ClientMorphAPI;
import me.feeldev.animorph.client.api.event.ClientModelUpdateEvent;
ClientMorphAPI.getEventBus().register(ClientModelUpdateEvent.class, event -> {
UUID playerId = event.getPlayerId();
String modelId = event.getModelId();
// Actualizar UI, efectos visuales, etc.
}); API de Cliente (ClientMorphAPI)
ClientMorphAPI permite extender Animorph desde el lado del cliente:
registrar controladores de animación custom, agregar queries Molang personalizadas,
consultar el estado de los jugadores y escuchar eventos.
Solo disponible en mods Fabric client.
import me.feeldev.animorph.client.api.ClientMorphAPI;
// Verificar disponibilidad
if (ClientMorphAPI.isAvailable()) {
// usar la API...
} Controladores de animación custom
Extiende AnimorphController para crear controladores de animación propios.
Una vez registrados, los modelos pueden referenciarlos por su ID en animation_controllers.
import me.feeldev.animorph.client.api.AnimorphController;
import me.feeldev.animorph.client.interfaces.IPlayerData;
import software.bernie.geckolib.animation.AnimationController;
import software.bernie.geckolib.animation.PlayState;
import software.bernie.geckolib.animation.RawAnimation;
public class WingsController extends AnimorphController {
public WingsController() {
// nombre del controlador, tiempo de transición en ticks
super("wings", 5);
}
@Override
protected AnimationController.AnimationStateHandler<IPlayerData>
createAnimationStateHandler(IPlayerData entity) {
return animationState -> {
var player = entity.animorph$getPlayer();
if (player.isFallFlying()) {
return animationState.setAndContinue(
RawAnimation.begin().thenLoop("animation.wings_flap")
);
}
return PlayState.STOP;
};
}
@Override
protected boolean interrupt(AnimationState<IPlayerData> animationState) {
// Interrumpir si hay un emote activo, por ejemplo
return false;
}
} import me.feeldev.animorph.client.api.ClientMorphAPI;
// En la inicialización de tu mod
ClientMorphAPI.registerController("wings", new WingsController());
// Verificar si un controlador existe
boolean exists = ClientMorphAPI.hasController("wings"); Después, referéncialo en el YAML del modelo:
properties:
animation_controllers:
idle:
simple_pose:
wings: # tu controlador custom Controladores de primera persona
Los controladores custom también pueden usarse para los brazos en primera persona.
Se registran de la misma forma con ClientMorphAPI.registerController() y se referencian
en fp_animation_controllers en el YAML del modelo.
Si no se especifica fp_animation_controllers, se usan los controladores por defecto:
fp_arm_right, fp_arm_left y fp_arms.
properties:
fp_animation_controllers:
- fp_arm_right
- fp_arm_left
- fp_arms
- my_custom_fp_controller # tu controlador custom de primera persona Variables y queries Molang custom
Puedes registrar variables Molang personalizadas y actualizarlas cada tick de animación. Esto permite crear condiciones custom en animation controllers de Blockbench.
import me.feeldev.animorph.client.api.ClientMorphAPI;
import software.bernie.geckolib.loading.math.MathParser;
// 1. Registrar la variable (una vez, durante la inicialización del mod)
ClientMorphAPI.registerMolangVariable("query.my_stamina");
// 2. Agregar un query que actualice la variable cada tick
ClientMorphAPI.addMolangQuery((animationState, animTime) -> {
var playerData = animationState.getAnimatable();
var player = playerData.animorph$getPlayer();
// Calcular el valor que quieres exponer
double stamina = getStamina(player); // tu lógica
// Actualizar la variable Molang
MathParser.setVariable("query.my_stamina", () -> stamina);
}); Después puedes usar query.my_stamina en las condiciones de transición de tus animation controllers de Blockbench.
Estado del jugador (cliente)
Consulta el estado de cualquier jugador visible desde el cliente:
import me.feeldev.animorph.client.api.ClientMorphAPI;
import java.util.UUID;
UUID playerId = player.getUuid();
// Obtener el modelo activo
Optional<String> modelId = ClientMorphAPI.getPlayerModelId(playerId);
// ¿Tiene un modelo aplicado?
boolean hasModel = ClientMorphAPI.hasModel(playerId);
// Obtener el emote activo
Optional<String> emoteId = ClientMorphAPI.getEmoteId(playerId);
// ¿Está reproduciendo un emote?
boolean isEmoting = ClientMorphAPI.isEmoting(playerId); Huesos de modelo y layers (cliente)
Para integraciones de gameplay (por ejemplo, raycasts o partículas desde un punto del modelo),
puedes consultar huesos del modelo principal o de un layer de tipo MODEL.
import me.feeldev.animorph.client.api.ClientMorphAPI;
import software.bernie.geckolib.cache.object.GeoBone;
UUID playerId = player.getUuid();
// Hueso del modelo principal
Optional<GeoBone> headBone = ClientMorphAPI.getPlayerModelBone(playerId, "head");
// Hueso de un layer de modelo activo
Optional<GeoBone> muzzleBone = ClientMorphAPI.getPlayerLayerBone(playerId, "rifle_layer", "muzzle");
if (muzzleBone.isPresent()) {
GeoBone bone = muzzleBone.get();
float x = bone.getPosX();
float y = bone.getPosY();
float z = bone.getPosZ();
// Usa también rotación/escala si tu lógica lo necesita
} getPlayerLayerBone solo funciona para layers de tipo
MODEL. Los layers de textura no tienen esqueleto/huesos.
Primera persona (cliente)
Consulta las propiedades de primera persona desde el cliente:
import me.feeldev.animorph.client.api.ClientMorphAPI;
import me.feeldev.animorph.api.IFirstPersonProperty;
UUID playerId = player.getUuid();
// Obtener las propiedades de primera persona
Optional<IFirstPersonProperty> fp = ClientMorphAPI.getFirstPersonProperty(playerId);
fp.ifPresent(property -> {
boolean showsModel = property.showModel();
boolean showsCustomArms = property.customArms().show();
boolean showsEquipment = property.showEquipment();
// Acceder a las opciones del modelo
double offsetY = property.modelOptions().offsetY();
// Acceder a la configuración de brazos custom
boolean bothHands = property.customArms().bothHands();
});