Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/toxicity188/BetterModel/llms.txt

Use this file to discover all available pages before exploring further.

Learn how to add cosmetic models to players, including hats, wings, pets, and other accessories.

Overview

Player cosmetics allow you to:
  • Attach custom models to players
  • Create wearable accessories (hats, wings, capes)
  • Build companion pets that follow players
  • Add particle effects and animations
  • Support multiple cosmetics simultaneously

Basic Cosmetic System

Cosmetic Manager

import kr.toxicity.model.api.BetterModel;
import kr.toxicity.model.api.entity.BaseEntity;
import kr.toxicity.model.api.profile.ModelProfile;
import kr.toxicity.model.api.tracker.PlayerTracker;
import kr.toxicity.model.api.tracker.TrackerModifier;
import org.bukkit.entity.Player;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class CosmeticManager {
    
    private final Map<UUID, Map<CosmeticSlot, PlayerTracker>> playerCosmetics = new ConcurrentHashMap<>();
    
    public void equipCosmetic(Player player, CosmeticSlot slot, String modelName) {
        // Remove existing cosmetic in slot
        unequipCosmetic(player, slot);
        
        BetterModel.limb(modelName).ifPresent(renderer -> {
            PlayerTracker tracker = (PlayerTracker) renderer.create(
                BaseEntity.of(player),
                ModelProfile.of(player),
                TrackerModifier.builder()
                    .sightTrace(false)
                    .damageAnimation(false)
                    .damageTint(false)
                    .build()
            );
            
            // Store tracker
            playerCosmetics
                .computeIfAbsent(player.getUniqueId(), k -> new ConcurrentHashMap<>())
                .put(slot, tracker);
            
            // Start idle animation
            tracker.animate("idle");
        });
    }
    
    public void unequipCosmetic(Player player, CosmeticSlot slot) {
        Map<CosmeticSlot, PlayerTracker> cosmetics = playerCosmetics.get(player.getUniqueId());
        if (cosmetics == null) return;
        
        PlayerTracker tracker = cosmetics.remove(slot);
        if (tracker != null) {
            tracker.close();
        }
    }
    
    public void removeAllCosmetics(Player player) {
        Map<CosmeticSlot, PlayerTracker> cosmetics = playerCosmetics.remove(player.getUniqueId());
        if (cosmetics != null) {
            cosmetics.values().forEach(PlayerTracker::close);
        }
    }
    
    public Map<CosmeticSlot, PlayerTracker> getCosmetics(Player player) {
        return playerCosmetics.getOrDefault(player.getUniqueId(), Collections.emptyMap());
    }
    
    public enum CosmeticSlot {
        HAT,
        BACK,      // Wings, capes
        SHOULDER,  // Parrots, pets
        AURA,      // Particle effects
        COMPANION  // Following pets
    }
}

Animated Wings

Wings with Movement Animation

import kr.toxicity.model.api.animation.AnimationModifier;
import org.bukkit.entity.Player;

public class AnimatedWings {
    
    public void equipWings(Player player, CosmeticManager manager) {
        manager.equipCosmetic(player, CosmeticManager.CosmeticSlot.BACK, "angel_wings");
        
        PlayerTracker tracker = manager.getCosmetics(player)
            .get(CosmeticManager.CosmeticSlot.BACK);
        
        if (tracker != null) {
            setupWingAnimations(tracker, player);
        }
    }
    
    private void setupWingAnimations(PlayerTracker tracker, Player player) {
        // Idle wings
        tracker.animate("wings_idle");
        
        // Flap when flying
        tracker.tick(5, (t, bundler) -> {
            if (player.isGliding()) {
                t.stopAnimation("wings_idle");
                t.animate("wings_flap", AnimationModifier.builder()
                    .speed(1.5F)
                    .build()
                );
            } else if (!t.renderer().animations().containsKey("wings_idle")) {
                t.stopAnimation("wings_flap");
                t.animate("wings_idle");
            }
        });
    }
}

Companion Pets

Pet That Follows Player

import kr.toxicity.model.api.BetterModel;
import kr.toxicity.model.api.tracker.DummyTracker;
import kr.toxicity.model.api.tracker.ModelRotation;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;

public class CompanionPet {
    
    private final DummyTracker tracker;
    private final Player owner;
    private Location targetLocation;
    
    public CompanionPet(Player owner, String modelName) {
        this.owner = owner;
        this.targetLocation = owner.getLocation();
        
        this.tracker = BetterModel.model(modelName)
            .map(renderer -> renderer.create(owner.getLocation()))
            .orElse(null);
        
        if (tracker != null) {
            setupPetBehavior();
        }
    }
    
    private void setupPetBehavior() {
        // Start idle animation
        tracker.animate("pet_idle");
        
        // Follow player
        tracker.tick(2, (t, bundler) -> {
            Location ownerLoc = owner.getLocation();
            Location currentLoc = tracker.location().toBukkit();
            
            // Calculate offset behind player
            Vector direction = ownerLoc.getDirection().multiply(-1).normalize();
            targetLocation = ownerLoc.clone()
                .add(direction.multiply(2))
                .add(0, 0.5, 0);
            
            double distance = currentLoc.distance(targetLocation);
            
            // Move if too far
            if (distance > 0.5) {
                // Calculate movement
                Vector movement = targetLocation.toVector()
                    .subtract(currentLoc.toVector())
                    .normalize()
                    .multiply(Math.min(distance * 0.3, 0.5));
                
                Location newLoc = currentLoc.add(movement);
                
                // Update rotation to face movement direction
                float yaw = (float) Math.toDegrees(
                    Math.atan2(-movement.getX(), movement.getZ())
                );
                
                tracker.rotation(() -> new ModelRotation(0, yaw));
                tracker.location(newLoc);
                
                // Play walk animation if moving fast
                if (distance > 2) {
                    tracker.stopAnimation("pet_idle");
                    tracker.animate("pet_walk");
                }
            } else {
                // Play idle when close
                tracker.stopAnimation("pet_walk");
                tracker.animate("pet_idle");
            }
        });
    }
    
    public void remove() {
        if (tracker != null) {
            tracker.close();
        }
    }
    
    public DummyTracker getTracker() {
        return tracker;
    }
}

Cosmetic Menu System

GUI for Equipping Cosmetics

import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;

import java.util.Arrays;

public class CosmeticMenu {
    
    private final CosmeticManager manager;
    
    public CosmeticMenu(CosmeticManager manager) {
        this.manager = manager;
    }
    
    public void openMenu(Player player) {
        Inventory inv = Bukkit.createInventory(null, 27, "§6Cosmetics");
        
        // Hat slot
        inv.setItem(10, createCosmeticItem(
            Material.GOLDEN_HELMET,
            "§6Crown",
            "golden_crown",
            CosmeticManager.CosmeticSlot.HAT
        ));
        
        // Wings slot
        inv.setItem(12, createCosmeticItem(
            Material.ELYTRA,
            "§fAngel Wings",
            "angel_wings",
            CosmeticManager.CosmeticSlot.BACK
        ));
        
        // Pet slot
        inv.setItem(14, createCosmeticItem(
            Material.BONE,
            "§7Ghost Pet",
            "ghost_pet",
            CosmeticManager.CosmeticSlot.COMPANION
        ));
        
        player.openInventory(inv);
    }
    
    private ItemStack createCosmeticItem(Material material, String name, 
                                         String modelName, CosmeticManager.CosmeticSlot slot) {
        ItemStack item = new ItemStack(material);
        ItemMeta meta = item.getItemMeta();
        
        meta.setDisplayName(name);
        meta.setLore(Arrays.asList(
            "§7Click to equip",
            "§8Model: " + modelName
        ));
        
        item.setItemMeta(meta);
        return item;
    }
    
    public void handleClick(Player player, ItemStack item) {
        if (item == null || !item.hasItemMeta()) return;
        
        ItemMeta meta = item.getItemMeta();
        if (meta.getLore() == null || meta.getLore().size() < 2) return;
        
        String modelLine = meta.getLore().get(1);
        String modelName = modelLine.replace("§8Model: ", "");
        
        // Determine slot based on item type
        CosmeticManager.CosmeticSlot slot = switch (item.getType()) {
            case GOLDEN_HELMET -> CosmeticManager.CosmeticSlot.HAT;
            case ELYTRA -> CosmeticManager.CosmeticSlot.BACK;
            case BONE -> CosmeticManager.CosmeticSlot.COMPANION;
            default -> null;
        };
        
        if (slot != null) {
            manager.equipCosmetic(player, slot, modelName);
            player.sendMessage("§aEquipped: " + meta.getDisplayName());
        }
    }
}

Best Practices

  • Use player models (limbs) for cosmetics attached to players
  • Disable damage animations and tints for cosmetic trackers
  • Use DummyTracker for companion pets that move independently
  • Cache cosmetic trackers for easy management
  • Update cosmetic animations based on player state
  • Always remove cosmetics when players disconnect
  • Limit the number of simultaneous cosmetics per player
  • Test cosmetic compatibility with player skins
  • Validate model existence before equipping

Next Steps

Custom Armor

Learn about custom armor models

Conditional Animations

State-based animation systems

Performance Optimization

Optimize cosmetic rendering

API Reference

Tracker API documentation

Build docs developers (and LLMs) love