-
-
Notifications
You must be signed in to change notification settings - Fork 40
API example

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.
-
Raw Data: The source model files (e.g.,
.bbmodel) containing the initial design and geometry. - Blueprint: Converted data optimized for Minecraft's model structure, serving as the foundational template.
- Renderer: A component that processes Blueprints (Models or Limbs) to construct a Render Pipeline—a hierarchical tree structure of model bones.
- 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.
The BetterModel API is officially published and can be integrated into your project via the following repositories:
- Maven Central: View on Central Sonatype
- GitHub Packages: View on GitHub Packages
Gradle (Kotlin)
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)
}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)
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)
}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
<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><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>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.
For a comprehensive look at how to use the API for movement, rotation, and basic entity handling, check out the RollTester source code.
- Source: RollTester.java on GitHub
- Key Insight: Demonstrates the core lifecycle of a model and manual transformations.
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.
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.
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. |
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);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.
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. |
For Bukkit/Spigot/Paper:
// Accessing the Bukkit platform instance
BetterModelBukkit platform = BetterModelBukkit.platform();For Fabric:
// Accessing the Fabric platform instance
BetterModelFabric platform = BetterModelFabric.platform();A Tracker represents an active instance of a model in the world.
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);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);Every entity with an active model is managed via a Registry.
-
Accessing Registry: Use
BetterModel.registry(uuid)orBetterModel.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);
}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
}));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);
}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));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 foundApply 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"));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());
});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);