Skip to content

API example

toxicity188 edited this page Feb 4, 2026 · 49 revisions

🏛️ Architecture

To effectively utilize the BetterModel API, it is essential to understand the underlying architecture and the lifecycle of a model from file to world instance.

  1. Raw Data: The source model files (e.g., .bbmodel) containing the initial design and geometry.
  2. Blueprint: Converted data optimized for Minecraft's model structure, serving as the foundational template.
  3. Renderer: A component that processes Blueprints (Models or Limbs) to construct a Render Pipeline—a hierarchical tree structure of model bones.
  4. Tracker: An active instance (Entity or Dummy) that executes the Render Pipeline's scheduling and ensures real-time synchronization with the game world or a specific entity.

📦 Repository

The BetterModel API is officially published and can be integrated into your project via the following repositories:

Gradle (Kotlin)

Release

repositories {
    mavenCentral()
    maven("https://maven.blamejared.com/") // For transitive dependency in bettermodel-fabric
    maven("https://maven.nucleoid.xyz/") // For transitive dependency in bettermodel-fabric
}

dependencies {
    compileOnly("io.github.toxicity188:bettermodel-bukkit-api:VERSION") // bukkit(spigot, paper, etc) api
    //modApi("io.github.toxicity188:bettermodel-fabric:VERSION") // mod(fabric)
}

Snapshot

repositories {
    maven("https://maven.pkg.github.com/toxicity188/BetterModel") {
        credentials {
            username = YOUR_GITHUB_USERNAME
            password = YOUR_GITHUB_TOKEN
        }
    }
    maven("https://maven.blamejared.com/") // For transitive dependency in bettermodel-fabric
    maven("https://maven.nucleoid.xyz/") // For transitive dependency in bettermodel-fabric
}

dependencies {
    compileOnly("io.github.toxicity188:bettermodel-bukkit-api:VERSION-SNAPSHOT") // bukkit(spigot, paper, etc) api
    //modApi("io.github.toxicity188:bettermodel-fabric:VERSION-SNAPSHOT") // mod(fabric)
}
Gradle (Groovy)

Release

repositories {
    mavenCentral()
    maven 'https://maven.blamejared.com/' // For transitive dependency in bettermodel-fabric
    maven 'https://maven.nucleoid.xyz/' // For transitive dependency in bettermodel-fabric
}

dependencies {
    compileOnly 'io.github.toxicity188:bettermodel-bukkit-api:VERSION' // bukkit(spigot, paper, etc) api
    //modApi 'io.github.toxicity188:bettermodel-fabric:VERSION' // mod(fabric)
}

Snapshot

repositories {
    maven {
        url "https://maven.pkg.github.com/toxicity188/BetterModel"
        credentials {
            username = YOUR_GITHUB_USERNAME
            password = YOUR_GITHUB_TOKEN
        }
    }
    maven 'https://maven.blamejared.com/' // For transitive dependency in bettermodel-fabric
    maven 'https://maven.nucleoid.xyz/' // For transitive dependency in bettermodel-fabric
}

dependencies {
    compileOnly 'io.github.toxicity188:bettermodel-bukkit-api:VERSION-SNAPSHOT' // bukkit(spigot, paper, etc) api
    //modApi 'io.github.toxicity188:bettermodel-fabric:VERSION-SNAPSHOT' // mod(fabric)
}
Maven

Release

<repositories>
    <repository>
        <id>central</id>
        <url>https://repo.maven.apache.org/maven2</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>io.github.toxicity188</groupId>
        <artifactId>bettermodel-bukkit-api</artifactId>
        <version>VERSION</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

Snapshot

<repositories>
    <repository>
        <id>github</id>
        <url>https://maven.pkg.github.com/toxicity188/BetterModel</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>io.github.toxicity188</groupId>
        <artifactId>bettermodel-api</artifactId>
        <version>VERSION-SNAPSHOT</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>io.github.toxicity188</groupId>
        <artifactId>bettermodel-bukkit-api</artifactId>
        <version>VERSION-SNAPSHOT</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

📚 Reference & Implementation Examples

If you want to see the BetterModel API in action, we provide a test plugin and official integration examples. These resources will help you understand how to implement complex animations and mechanics.

1. Official Test Plugin

For a comprehensive look at how to use the API for movement, rotation, and basic entity handling, check out the RollTester source code.

2. MythicMobs Integration (Mechanics)

For developers looking to integrate BetterModel with third-party plugins, the MythicMobs support module is the best reference.

  • Source: MythicMobs Compatibility Directory
  • Key Insight: Shows how to wrap BetterModel features into high-level mechanics and handle platform-independent logic using Kotlin.

📝 Adapter Usage

To interact with the BetterModel API, you must use the appropriate adapter corresponding to your server platform. These adapters ensure seamless compatibility between platform-specific objects and the BetterModel system.

**Method: .adapt()**

Converts platform-specific entities or locations into a format compatible with BetterModel.

Platform Syntax Description
Bukkit BukkitAdapter.adapt(Object) Supports Bukkit Entity or Location objects.
Fabric FabricAdapter.adapt(Object) Supports Fabric Entity objects.

Usage Examples

For Bukkit/Spigot/Paper:

// Adapting a Bukkit Entity
PlatformEntity entity = BukkitAdapter.adapt(bukkitEntity);

// Adapting a Bukkit Location
PlatformLocation loc = BukkitAdapter.adapt(bukkitLocation);

For Fabric:

// Adapting a Fabric Entity
PlatformEntity entity = FabricAdapter.adapt(fabricEntity);

🚀 Platform Entry Points

To access the core features and management systems of BetterModel, you must use the entry point specific to your server platform. These classes provide the singleton instance required to interact with the platform-independent API.

**Method: .platform()**

Retrieves the core platform instance for the respective environment.

Platform Syntax Description
Bukkit BetterModelBukkit.platform() Accesses the BetterModel instance for Bukkit/Spigot/Paper.
Fabric BetterModelFabric.platform() Accesses the BetterModel instance for the Fabric loader.

Usage Examples

For Bukkit/Spigot/Paper:

// Accessing the Bukkit platform instance
BetterModelBukkit platform = BetterModelBukkit.platform();

For Fabric:

// Accessing the Fabric platform instance
BetterModelFabric platform = BetterModelFabric.platform();

1. Creating Trackers

A Tracker represents an active instance of a model in the world.

Entity Tracker

Attaches a model to an existing entity.

// Simple Get or Create
EntityTracker tracker = BetterModel.model("demon_knight")
    .map(r -> r.getOrCreate(entity))
    .orElse(null);

// Create with pre-spawn configuration (e.g., setting a tint)
EntityTracker customTracker = BetterModel.model("demon_knight")
    .map(r -> r.create(entity, TrackerModifier.DEFAULT, t -> 
        t.update(TrackerUpdateAction.tint(0x0026FF))
    ))
    .orElse(null);

Dummy Tracker

Spawns a model at a specific location without being tied to a living entity.

// Spawn a model at a location
DummyTracker dummy = BetterModel.model("demon_knight")
    .map(r -> r.create(location))
    .orElse(null);

// Spawn a player limb with a specific skin profile
DummyTracker skinDummy = BetterModel.limb("steve")
    .map(r -> r.create(location, ModelProfile.of(player)))
    .orElse(null);

2. Registry & Hitbox Management

Every entity with an active model is managed via a Registry.

  • Accessing Registry: Use BetterModel.registry(uuid) or BetterModel.registry(entity) to get the model data associated with an entity.
  • Hitbox Interaction:
if (entity instanceof HitBox hitbox) {
    Entity source = hitbox.source(); // The base entity owning the hitbox
    EntityTrackerRegistry reg = hitbox.registry().orElse(null);
}

5. Animation & Bone Control

Playing Animations

BetterModel.registry(entity)
    .map(reg -> reg.tracker("model"))
    .ifPresent(tracker -> tracker.animate("attack_pose", AnimationModifier.DEFAULT_WITH_PLAY_ONCE, () -> { 
        // Logic to execute after animation finishes
    }));

Bone Positioning

You can calculate the exact world position of a specific bone (e.g., for particle effects at the tip of a sword).

Vector3f relativePos = BetterModel.registry(entity)
    .map(reg -> reg.tracker("model"))
    .map(tracker -> tracker.bone("right_hand"))
    .map(HitBoxSource::hitBoxPosition)
    .orElse(null);

if (relativePos != null) {
    // Final World Position = Entity Location + Bone Relative Position
    Location boneLocation = entity.getLocation().add(relativePos.x, relativePos.y, relativePos.z);
}

Per-Player Animation (Visibility Control)

BetterModel supports triggering animations that are visible only to a specific player. This is particularly useful for personalized feedback, cutscenes, or UI-oriented 3D models.

// 1. Build an AnimationModifier to define target player and playback type
AnimationModifier modifier = AnimationModifier.builder()
    .player(player) // The animation will only be visible to this player
    .type(AnimationIterator.Type.PLAY_ONCE)
    .build();

// 2. Apply the animation with the modifier
BetterModel.registry(entity)
    .map(reg -> reg.tracker("model"))
    .ifPresent(tracker -> tracker.animate("animation_name", modifier));

Retrieving Animation Metadata

You can access metadata directly from the model's blueprint without spawning an entity. This is particularly useful for synchronizing server-side tasks, cooldowns, or sound effects with the precise duration of an animation.

// Retrieving the length (in seconds/ticks) of a specific animation
float animationLength = BetterModel.limb("steve") // 1. Retrieve the model/limb
    .flatMap(r -> r.animation("roll"))            // 2. Access a specific animation by name
    .map(BlueprintAnimation::length)              // 3. Get the duration (length)
    .orElse(0F);                                  // 4. Default value if not found

3. Advanced Customization & Logic

Visual Updates (Tint, Enchant, Billboard)

Apply visual modifications dynamically using TrackerUpdateAction.

tracker.update(TrackerUpdateAction.composite(
    TrackerUpdateAction.brightness(15, 15),
    TrackerUpdateAction.billboard(PlatformBillboard.CENTER)
));

// Apply enchantment glow only to specific bones
tracker.update(TrackerUpdateAction.enchant(true), BonePredicate.name("weapon"));

Pipeline Filters

Control model visibility or behavior based on custom conditions.

BetterModel.eventBus().subscribe(platform, CreateEntityTrackerEvent.class, event -> {
    event.tracker().getPipeline().viewFilter(player -> !config.usePurpurAfk() || !((BukkitPlayer) player).source().isAfk());
});

4. Cleanup & Lifecycle

Properly managing the lifecycle of a tracker is crucial. Closing a tracker is not just about preventing memory leaks; it is the definitive way to stop all model scheduling and physically remove the model from the world.

When you invoke the .close() method:

  • Task Termination: All scheduled tasks, including animation updates, transformation syncs, and logic processors, are immediately terminated.
  • World Cleanup: All display entities and hitboxes associated with the tracker are despawned from the world.
  • Resource Release: Memory and CPU cycles used for calculations are freed.

Caution

Failing to close trackers properly can lead to "ghost" models persisting in the world and orphaned scheduling tasks that consume significant server resources.

// Close a specific tracker
tracker.close();

// Close all trackers associated with an entity
BetterModel.registry(entity).ifPresent(EntityTrackerRegistry::close);

Clone this wiki locally