Skip to main content
Build epic boss encounters with custom 3D models, dynamic animations, phase transitions, and interactive mechanics.

Overview

This example demonstrates:
  • Multi-phase boss system with state transitions
  • Health-based animation changes
  • Custom hitbox interactions
  • Skill system with animations
  • Boss bar integration

Boss Entity Structure

Core Boss Class

import kr.toxicity.model.api.BetterModel;
import kr.toxicity.model.api.animation.AnimationModifier;
import kr.toxicity.model.api.entity.BaseEntity;
import kr.toxicity.model.api.tracker.EntityTracker;
import kr.toxicity.model.api.tracker.TrackerUpdateAction;
import kr.toxicity.model.api.event.hitbox.HitBoxInteractEvent;
import kr.toxicity.model.api.nms.HitBoxListener;
import kr.toxicity.model.api.util.function.BonePredicate;
import org.bukkit.Location;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarStyle;
import org.bukkit.boss.BossBar;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Zombie;

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

public class DynamicBoss {
    
    private final String bossId;
    private final EntityTracker tracker;
    private final LivingEntity entity;
    private final BossBar bossBar;
    
    private BossPhase currentPhase = BossPhase.PHASE_1;
    private final Map<UUID, Long> lastHitTime = new ConcurrentHashMap<>();
    private final Set<UUID> playersInRange = ConcurrentHashMap.newKeySet();
    
    private double maxHealth;
    private boolean isEnraged = false;
    
    public DynamicBoss(String bossId, Location location, String modelName) {
        this.bossId = bossId;
        
        // Spawn entity
        this.entity = location.getWorld().spawn(location, Zombie.class, zombie -> {
            zombie.setCustomName("§c§lDemon Lord");
            zombie.setCustomNameVisible(false);
            zombie.setHealth(500);
            zombie.getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH).setBaseValue(500);
        });
        
        this.maxHealth = entity.getMaxHealth();
        
        // Create boss bar
        this.bossBar = org.bukkit.Bukkit.createBossBar(
            "§c§lDemon Lord",
            BarColor.RED,
            BarStyle.SEGMENTED_10
        );
        
        // Create model
        this.tracker = BetterModel.model(modelName)
            .map(renderer -> renderer.getOrCreate(BaseEntity.of(entity)))
            .orElse(null);
        
        if (tracker != null) {
            setupBoss();
        }
    }
    
    private void setupBoss() {
        // Setup hitbox interactions
        tracker.createHitBox(
            BaseEntity.of(entity),
            HitBoxListener.builder()
                .interact(this::handleHit)
                .build(),
            BonePredicate.name("body").or(BonePredicate.name("head"))
        );
        
        // Start phase 1 animation
        enterPhase(BossPhase.PHASE_1);
        
        // Health monitoring
        tracker.tick(5, (t, bundler) -> {
            updateHealth();
            checkPhaseTransition();
            updateBossBar();
        });
        
        // Range detection for players
        tracker.tick(10, (t, bundler) -> {
            updatePlayersInRange();
        });
        
        // Periodic skills
        scheduleSkills();
    }
    
    private void handleHit(HitBoxInteractEvent event) {
        Player player = event.player().player();
        if (player == null) return;
        
        UUID playerId = player.getUniqueId();
        long currentTime = System.currentTimeMillis();
        
        // Cooldown check (500ms)
        Long lastHit = lastHitTime.get(playerId);
        if (lastHit != null && currentTime - lastHit < 500) return;
        
        lastHitTime.put(playerId, currentTime);
        
        // Apply damage
        entity.damage(10, player);
        
        // Play hit reaction
        if (Math.random() < 0.3) { // 30% chance
            playSkill(BossSkill.COUNTER_ATTACK);
        }
        
        // Damage animation
        tracker.damageTint();
    }
    
    private void updateHealth() {
        double healthPercent = entity.getHealth() / maxHealth;
        
        // Check for enrage at 25% health
        if (healthPercent <= 0.25 && !isEnraged) {
            enterEnrage();
        }
    }
    
    private void checkPhaseTransition() {
        double healthPercent = entity.getHealth() / maxHealth;
        
        BossPhase newPhase = null;
        
        if (healthPercent <= 0.3 && currentPhase != BossPhase.PHASE_3) {
            newPhase = BossPhase.PHASE_3;
        } else if (healthPercent <= 0.6 && currentPhase == BossPhase.PHASE_1) {
            newPhase = BossPhase.PHASE_2;
        }
        
        if (newPhase != null) {
            enterPhase(newPhase);
        }
    }
    
    private void enterPhase(BossPhase phase) {
        if (currentPhase == phase) return;
        
        currentPhase = phase;
        
        // Stop current animations
        tracker.stopAnimation("idle");
        tracker.stopAnimation("walk");
        
        // Play phase transition
        tracker.animate("phase_transition", AnimationModifier.builder()
            .type(AnimationIterator.Type.PLAY_ONCE)
            .build(),
            () -> {
                // Start phase idle
                tracker.animate("phase_" + phase.getId() + "_idle");
            }
        );
        
        // Visual effects based on phase
        switch (phase) {
            case PHASE_1 -> {
                tracker.update(TrackerUpdateAction.tint(0xFFFFFF));
                bossBar.setColor(BarColor.RED);
            }
            case PHASE_2 -> {
                tracker.update(TrackerUpdateAction.composite(
                    TrackerUpdateAction.tint(0xFF8800),
                    TrackerUpdateAction.glow(true),
                    TrackerUpdateAction.glowColor(0xFF4400)
                ));
                bossBar.setColor(BarColor.YELLOW);
            }
            case PHASE_3 -> {
                tracker.update(TrackerUpdateAction.composite(
                    TrackerUpdateAction.tint(0xFF0000),
                    TrackerUpdateAction.glow(true),
                    TrackerUpdateAction.glowColor(0xFF0000),
                    TrackerUpdateAction.brightness(15, 15)
                ));
                bossBar.setColor(BarColor.RED);
            }
        }
        
        // Announce phase change
        playersInRange.forEach(uuid -> {
            Player player = org.bukkit.Bukkit.getPlayer(uuid);
            if (player != null) {
                player.sendMessage("§c§lDemon Lord entered " + phase.getName() + "!");
            }
        });
    }
    
    private void enterEnrage() {
        isEnraged = true;
        
        tracker.animate("enrage", AnimationModifier.builder()
            .type(AnimationIterator.Type.PLAY_ONCE)
            .build(),
            () -> {
                // Faster animations in enraged state
                tracker.animate(currentPhase.getIdleAnimation(), AnimationModifier.builder()
                    .speed(1.5F)
                    .build()
                );
            }
        );
        
        // Red pulsing effect
        tracker.update(TrackerUpdateAction.composite(
            TrackerUpdateAction.tint(0xFF0000),
            TrackerUpdateAction.glow(true),
            TrackerUpdateAction.glowColor(0xFF0000)
        ));
        
        bossBar.setColor(BarColor.RED);
        bossBar.setTitle("§c§lDemon Lord §f[§4ENRAGED§f]");
    }
    
    private void scheduleSkills() {
        // Fireball every 5 seconds
        tracker.tick(100, (t, bundler) -> {
            if (currentPhase.ordinal() >= BossPhase.PHASE_2.ordinal()) {
                playSkill(BossSkill.FIREBALL);
            }
        });
        
        // Ground slam every 8 seconds
        tracker.tick(160, (t, bundler) -> {
            playSkill(BossSkill.GROUND_SLAM);
        });
        
        // Summon minions in phase 3
        tracker.tick(300, (t, bundler) -> {
            if (currentPhase == BossPhase.PHASE_3) {
                playSkill(BossSkill.SUMMON_MINIONS);
            }
        });
    }
    
    private void playSkill(BossSkill skill) {
        tracker.animate(skill.getAnimation(), AnimationModifier.builder()
            .type(AnimationIterator.Type.PLAY_ONCE)
            .speed(isEnraged ? 1.5F : 1.0F)
            .build(),
            () -> {
                // Return to idle
                tracker.animate(currentPhase.getIdleAnimation());
            }
        );
        
        // Execute skill logic
        skill.execute(this);
    }
    
    private void updatePlayersInRange() {
        playersInRange.clear();
        
        entity.getWorld().getNearbyPlayers(entity.getLocation(), 50)
            .forEach(player -> {
                playersInRange.add(player.getUniqueId());
                bossBar.addPlayer(player);
            });
        
        // Remove players out of range
        new HashSet<>(bossBar.getPlayers()).forEach(player -> {
            if (!playersInRange.contains(player.getUniqueId())) {
                bossBar.removePlayer(player);
            }
        });
    }
    
    private void updateBossBar() {
        double progress = entity.getHealth() / maxHealth;
        bossBar.setProgress(Math.max(0, Math.min(1, progress)));
    }
    
    public void remove() {
        if (tracker != null) {
            tracker.close();
        }
        bossBar.removeAll();
        entity.remove();
    }
    
    public EntityTracker getTracker() {
        return tracker;
    }
    
    public LivingEntity getEntity() {
        return entity;
    }
    
    public enum BossPhase {
        PHASE_1(1, "Phase 1", "phase_1_idle"),
        PHASE_2(2, "Phase 2", "phase_2_idle"),
        PHASE_3(3, "Phase 3", "phase_3_idle");
        
        private final int id;
        private final String name;
        private final String idleAnimation;
        
        BossPhase(int id, String name, String idleAnimation) {
            this.id = id;
            this.name = name;
            this.idleAnimation = idleAnimation;
        }
        
        public int getId() { return id; }
        public String getName() { return name; }
        public String getIdleAnimation() { return idleAnimation; }
    }
    
    public enum BossSkill {
        FIREBALL("skill_fireball"),
        GROUND_SLAM("skill_slam"),
        COUNTER_ATTACK("skill_counter"),
        SUMMON_MINIONS("skill_summon");
        
        private final String animation;
        
        BossSkill(String animation) {
            this.animation = animation;
        }
        
        public String getAnimation() {
            return animation;
        }
        
        public void execute(DynamicBoss boss) {
            // Implement skill logic here
            switch (this) {
                case FIREBALL -> boss.executeFireball();
                case GROUND_SLAM -> boss.executeGroundSlam();
                case COUNTER_ATTACK -> boss.executeCounter();
                case SUMMON_MINIONS -> boss.executeSummon();
            }
        }
    }
    
    private void executeFireball() {
        // Launch fireball projectile
    }
    
    private void executeGroundSlam() {
        // AoE damage around boss
        entity.getWorld().getNearbyPlayers(entity.getLocation(), 5)
            .forEach(player -> {
                player.damage(15);
                player.setVelocity(player.getLocation().toVector()
                    .subtract(entity.getLocation().toVector())
                    .normalize()
                    .multiply(1.5)
                    .setY(0.5));
            });
    }
    
    private void executeCounter() {
        // Quick counter attack
    }
    
    private void executeSummon() {
        // Spawn minion entities
    }
}

Boss Manager

Managing Multiple Bosses

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class BossManager {
    
    private final Map<String, DynamicBoss> bosses = new ConcurrentHashMap<>();
    
    public DynamicBoss spawnBoss(String id, Location location, String modelName) {
        // Remove existing boss with same ID
        removeBoss(id);
        
        DynamicBoss boss = new DynamicBoss(id, location, modelName);
        bosses.put(id, boss);
        
        return boss;
    }
    
    public void removeBoss(String id) {
        DynamicBoss boss = bosses.remove(id);
        if (boss != null) {
            boss.remove();
        }
    }
    
    public DynamicBoss getBoss(String id) {
        return bosses.get(id);
    }
    
    public void removeAllBosses() {
        bosses.values().forEach(DynamicBoss::remove);
        bosses.clear();
    }
}

Best Practices

  • Use phase-based animations to keep boss fights dynamic
  • Implement cooldowns on hitbox interactions to prevent spam
  • Update boss bars regularly for real-time health feedback
  • Use TrackerUpdateAction.composite() for complex visual effects
  • Cache player references for performance
  • Always remove boss bars when boss is defeated
  • Close trackers properly to prevent memory leaks
  • Test phase transitions thoroughly
  • Limit the number of simultaneous skill animations

Next Steps

Custom Events

Learn more about hitbox events

Multi-part Entities

Create bosses with multiple body parts

Conditional Animations

Advanced animation state machines

Performance Optimization

Optimize boss performance

Build docs developers (and LLMs) love