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.

Overview

EntityTrackers are the core controller for model instances in BetterModel. They manage the lifecycle, rendering, animation, and player interaction of a model attached to an entity.

Understanding Trackers

BetterModel provides three types of trackers:
  • EntityTracker: Attached to a living entity, synchronizes position and rotation
  • PlayerTracker: Specialized for player entities with player-mode body rotation
  • DummyTracker: Standalone models at fixed locations, not bound to any entity

Creating Entity Trackers

Basic Entity Tracker

Create a tracker attached to an entity:
import kr.toxicity.model.api.BetterModel;
import kr.toxicity.model.api.bukkit.platform.BukkitAdapter;
import kr.toxicity.model.api.tracker.EntityTracker;
import org.bukkit.entity.Entity;

Entity entity = // your entity

// Get or create a tracker
EntityTracker tracker = BetterModel.model("demon_knight")
    .map(r -> r.getOrCreate(BukkitAdapter.adapt(entity)))
    .orElse(null);

if (tracker != null) {
    // Model is now attached to the entity
    System.out.println("Model spawned: " + tracker.name());
}
getOrCreate() returns an existing tracker if the entity already has this model attached, preventing duplicates.

Creating with Custom Modifier

Use TrackerModifier to customize behavior:
import kr.toxicity.model.api.tracker.TrackerModifier;

TrackerModifier modifier = TrackerModifier.builder()
    .sightTrace(true)        // Only show when player can see it
    .damageAnimation(true)   // Play damage animation when hit
    .damageTint(true)        // Apply red tint when damaged
    .build();

EntityTracker tracker = BetterModel.model("demon_knight")
    .map(r -> r.create(BukkitAdapter.adapt(entity), modifier))
    .orElse(null);

Pre-spawn Configuration

Configure the tracker before it spawns to players:
EntityTracker tracker = BetterModel.model("demon_knight")
    .map(r -> r.create(
        BukkitAdapter.adapt(entity),
        TrackerModifier.DEFAULT,
        t -> {
            // Pre-spawn configuration
            t.update(TrackerUpdateAction.tint(0x0026FF));  // Blue tint
            t.update(TrackerUpdateAction.brightness(15, 15)); // Full bright
            t.animate("idle"); // Start with idle animation
        }
    ))
    .orElse(null);

Creating Dummy Trackers

DummyTrackers are perfect for stationary models, decorations, or custom NPCs:
import kr.toxicity.model.api.tracker.DummyTracker;
import org.bukkit.Location;

Location location = // your location

DummyTracker tracker = BetterModel.model("statue")
    .map(r -> r.create(BukkitAdapter.adapt(location)))
    .orElse(null);

if (tracker != null) {
    tracker.animate("idle");
}

Dummy Tracker with Player Skin

Create a dummy tracker using a player’s skin:
import kr.toxicity.model.api.profile.ModelProfile;
import org.bukkit.entity.Player;

Player player = // your player
Location location = // display location

DummyTracker tracker = BetterModel.limb("steve")
    .map(r -> r.create(
        BukkitAdapter.adapt(location),
        ModelProfile.of(BukkitAdapter.adapt(player))
    ))
    .orElse(null);

Moving Dummy Trackers

Update the location of a dummy tracker:
DummyTracker tracker = // your dummy tracker
Location newLocation = // new location

tracker.location(BukkitAdapter.adapt(newLocation));

Working with the Registry

Retrieving Existing Trackers

Get a tracker for an entity that already has a model:
import kr.toxicity.model.api.tracker.EntityTrackerRegistry;
import java.util.Optional;

Entity entity = // your entity

Optional<EntityTrackerRegistry> registry = BetterModel.registry(entity.getUniqueId());
registry.ifPresent(reg -> {
    EntityTracker tracker = reg.tracker("demon_knight");
    if (tracker != null) {
        tracker.animate("attack");
    }
});

Listing All Trackers for an Entity

BetterModel.registry(entity.getUniqueId()).ifPresent(registry -> {
    registry.trackers().forEach((name, tracker) -> {
        System.out.println("Model: " + name);
        System.out.println("Players viewing: " + tracker.playerCount());
    });
});

Managing Tracker Lifecycle

Spawning to Specific Players

Control which players can see the model:
import kr.toxicity.model.api.platform.PlatformPlayer;

EntityTracker tracker = // your tracker
PlatformPlayer player = BukkitAdapter.adapt(bukkitPlayer);

// Show to player
tracker.show(player);

// Hide from player
tracker.hide(player);

// Check if visible to player
boolean isVisible = !tracker.isHide(player);

Mark for Spawn

Control which players the model spawns for:
EntityTracker tracker = // your tracker

// Only spawn for specific players
tracker.markPlayerForSpawn(BukkitAdapter.adapt(player1));
tracker.markPlayerForSpawn(BukkitAdapter.adapt(player2));

// Check if can spawn for player
boolean canSpawn = tracker.canBeSpawnedAt(BukkitAdapter.adapt(player));

Pausing and Resuming

Pause tracker updates temporarily:
// Pause animations and updates
tracker.pause(true);

// Resume
tracker.pause(false);

Closing Trackers

Properly close a tracker when you’re done:
// Despawn the model (can be respawned)
tracker.despawn();

// Completely close and remove the tracker
tracker.close();

// Check if closed
if (tracker.isClosed()) {
    System.out.println("Tracker is closed");
}

Advanced Features

Custom Scaling

import kr.toxicity.model.api.tracker.ModelScaler;

tracker.scaler(ModelScaler.of(2.0F)); // Double size
tracker.forceUpdate(true); // Apply immediately

Custom Rotation

import kr.toxicity.model.api.tracker.ModelRotator;
import kr.toxicity.model.api.tracker.ModelRotation;

// Fixed rotation
tracker.rotation(() -> new ModelRotation(0F, 90F)); // Face east

// Dynamic rotation based on entity
tracker.rotator(ModelRotator.YAW); // Follow entity yaw only
tracker.rotator(ModelRotator.PITCH_AND_YAW); // Follow both

Close Event Handling

Listen for when a tracker closes:
tracker.handleCloseEvent((t, reason) -> {
    System.out.println("Tracker closed: " + reason);
    
    switch (reason) {
        case REMOVE -> System.out.println("Manually removed");
        case DESPAWN -> System.out.println("Entity despawned");
        case PLUGIN_DISABLE -> System.out.println("Plugin disabled");
    }
});

Per-Tick Tasks

Run code every tracker tick (25ms):
tracker.frame((t, bundlerSet) -> {
    // Runs every 25ms
    // Fast operations only!
});

tracker.tick((t, bundlerSet) -> {
    // Runs every Minecraft tick (50ms)
    // Use for regular updates
});

tracker.tick(20, (t, bundlerSet) -> {
    // Runs every 20 Minecraft ticks (1 second)
});

Player-Specific Models

Creating Player Trackers

Player trackers automatically handle player-specific body rotation:
import org.bukkit.entity.Player;

Player player = // your player

EntityTracker tracker = BetterModel.limb("custom_player_model")
    .map(r -> r.getOrCreate(BukkitAdapter.adapt(player)))
    .orElse(null);

// This creates a PlayerTracker automatically
if (tracker instanceof kr.toxicity.model.api.tracker.PlayerTracker playerTracker) {
    System.out.println("Player tracker created");
}

Simplified Player Animation API

import kr.toxicity.model.api.animation.AnimationModifier;

// Shorthand for playing animations on players
boolean success = BetterModel.platform().modelManager().animate(
    BukkitAdapter.adapt(player),
    "custom_player_model",
    "wave",
    AnimationModifier.DEFAULT_WITH_PLAY_ONCE
);
Player limb models are not saved when the server restarts. They must be recreated on player join.

Common Patterns

Entity Spawn Integration

@EventHandler
public void onEntitySpawn(EntitySpawnEvent event) {
    Entity entity = event.getEntity();
    
    if (entity.getType() == EntityType.ZOMBIE) {
        BetterModel.model("demon_knight")
            .ifPresent(model -> {
                model.create(
                    BukkitAdapter.adapt(entity),
                    TrackerModifier.DEFAULT,
                    tracker -> tracker.animate("idle")
                );
            });
    }
}

Removing Models on Death

@EventHandler
public void onEntityDeath(EntityDeathEvent event) {
    Entity entity = event.getEntity();
    
    BetterModel.registry(entity.getUniqueId()).ifPresent(registry -> {
        registry.trackers().values().forEach(tracker -> {
            tracker.animate("death", AnimationModifier.DEFAULT_WITH_PLAY_ONCE, tracker::close);
        });
    });
}

Next Steps

Playing Animations

Control and sequence animations

Custom Hitboxes

Add interactive hitboxes to your models

Build docs developers (and LLMs) love