Overview
BetterModel provides powerful event systems for:- Hitbox interactions (click, attack, damage)
- Model lifecycle events (spawn, despawn, close)
- Animation events
- Per-player events
Hitbox Events
Basic Hitbox Interaction
import kr.toxicity.model.api.BetterModel;
import kr.toxicity.model.api.entity.BaseEntity;
import kr.toxicity.model.api.tracker.EntityTracker;
import kr.toxicity.model.api.event.hitbox.HitBoxInteractEvent;
import kr.toxicity.model.api.event.hitbox.HitBoxAttackEvent;
import kr.toxicity.model.api.event.hitbox.HitBoxDamageEvent;
import kr.toxicity.model.api.nms.HitBoxListener;
import kr.toxicity.model.api.util.function.BonePredicate;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
public class HitBoxEvents {
public void setupHitBoxEvents(EntityTracker tracker, LivingEntity entity) {
// Listen to all hitbox events
tracker.listenHitBox((bone, builder) -> builder
.interact(event -> handleInteract(event))
.attack(event -> handleAttack(event))
.damage(event -> handleDamage(event))
);
// Create hitboxes on specific bones
tracker.createHitBox(
BaseEntity.of(entity),
null, // Listener applied above
BonePredicate.name("body").or(BonePredicate.name("head"))
);
}
private void handleInteract(HitBoxInteractEvent event) {
Player player = event.player().player();
if (player == null) return;
String boneName = event.bone().name().value();
player.sendMessage("§aYou interacted with: " + boneName);
// Right-click interaction
if (event.hand() != null) {
player.sendMessage("§7Hand: " + event.hand().name());
}
}
private void handleAttack(HitBoxAttackEvent event) {
Player player = event.player().player();
if (player == null) return;
// Left-click attack
player.sendMessage("§cYou attacked: " + event.bone().name().value());
// Apply custom damage logic
event.hitBox().source().ifPresent(entity -> {
if (entity instanceof LivingEntity living) {
living.damage(5.0, player);
}
});
}
private void handleDamage(HitBoxDamageEvent event) {
// Handle when model takes damage
double damage = event.damage();
String boneName = event.bone().name().value();
System.out.println("Bone " + boneName + " took " + damage + " damage");
// Apply damage multipliers based on bone
if (boneName.contains("head")) {
event.damage(damage * 2.0); // Headshot multiplier
}
}
}
Mount and Dismount Events
Handling Entity Mounting
import kr.toxicity.model.api.event.MountModelEvent;
import kr.toxicity.model.api.event.DismountModelEvent;
import kr.toxicity.model.api.BetterModel;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
public class MountEvents implements Listener {
@EventHandler
public void onModelMount(MountModelEvent event) {
EntityTracker tracker = event.tracker();
RenderedBone bone = event.bone();
BaseEntity mountedEntity = event.entity();
// Handle mount logic
System.out.println("Entity mounted on " + bone.name().value());
// Play mount animation
tracker.animate("mount");
// Apply visual effects
tracker.update(
TrackerUpdateAction.glow(true),
BonePredicate.from(b -> b == bone)
);
}
@EventHandler
public void onModelDismount(DismountModelEvent event) {
EntityTracker tracker = event.tracker();
RenderedBone bone = event.bone();
System.out.println("Entity dismounted from " + bone.name().value());
// Play dismount animation
tracker.animate("dismount");
// Remove visual effects
tracker.update(
TrackerUpdateAction.glow(false),
BonePredicate.from(b -> b == bone)
);
}
}
Tracker Lifecycle Events
Model Creation and Destruction
import kr.toxicity.model.api.event.*;
import kr.toxicity.model.api.tracker.Tracker;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
public class TrackerLifecycleEvents implements Listener {
@EventHandler
public void onEntityTrackerCreate(CreateEntityTrackerEvent event) {
EntityTracker tracker = event.tracker();
System.out.println("Entity tracker created: " + tracker.name());
// Setup custom behavior
setupTrackerBehavior(tracker);
}
@EventHandler
public void onDummyTrackerCreate(CreateDummyTrackerEvent event) {
DummyTracker tracker = event.tracker();
System.out.println("Dummy tracker created: " + tracker.name());
}
@EventHandler
public void onTrackerClose(CloseTrackerEvent event) {
Tracker tracker = event.tracker();
Tracker.CloseReason reason = event.reason();
System.out.println("Tracker closed: " + tracker.name() + " (" + reason + ")");
// Save data if needed
if (reason.shouldBeSave() && tracker instanceof EntityTracker entityTracker) {
if (entityTracker.canBeSaved()) {
saveTrackerData(entityTracker);
}
}
}
private void setupTrackerBehavior(EntityTracker tracker) {
// Custom setup logic
}
private void saveTrackerData(EntityTracker tracker) {
// Save tracker state
}
}
Player Visibility Events
Per-Player Animation Events
import kr.toxicity.model.api.event.PlayerPerAnimationStartEvent;
import kr.toxicity.model.api.event.PlayerPerAnimationEndEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
public class PlayerAnimationEvents implements Listener {
@EventHandler
public void onPerAnimationStart(PlayerPerAnimationStartEvent event) {
Tracker tracker = event.tracker();
Player player = event.player().player();
if (player != null) {
player.sendMessage("§ePer-player animation started for: " + tracker.name());
}
}
@EventHandler
public void onPerAnimationEnd(PlayerPerAnimationEndEvent event) {
Tracker tracker = event.tracker();
Player player = event.player().player();
if (player != null) {
player.sendMessage("§7Per-player animation ended for: " + tracker.name());
}
}
}
Custom Event System
Creating Custom Model Events
import kr.toxicity.model.api.tracker.EntityTracker;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class CustomModelEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
private final EntityTracker tracker;
private final String eventType;
private final Object data;
public CustomModelEvent(EntityTracker tracker, String eventType, Object data) {
this.tracker = tracker;
this.eventType = eventType;
this.data = data;
}
public EntityTracker getTracker() {
return tracker;
}
public String getEventType() {
return eventType;
}
public Object getData() {
return data;
}
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
// Usage
public void triggerCustomEvent(EntityTracker tracker) {
CustomModelEvent event = new CustomModelEvent(
tracker,
"skill_used",
Map.of("skillName", "fireball", "damage", 50)
);
Bukkit.getPluginManager().callEvent(event);
}
Hitbox Builder Pattern
Advanced Hitbox Configuration
import kr.toxicity.model.api.nms.HitBoxListener;
import kr.toxicity.model.api.event.hitbox.*;
public class AdvancedHitBoxSetup {
public void setupComplexHitBox(EntityTracker tracker, BaseEntity entity) {
HitBoxListener listener = HitBoxListener.builder()
// Interaction handler
.interact(event -> {
Player player = event.player().player();
if (player == null) return;
if (player.isSneaking()) {
// Special interaction when sneaking
player.sendMessage("§6Secret interaction!");
} else {
// Normal interaction
player.sendMessage("§aNormal interaction");
}
})
// Attack handler
.attack(event -> {
Player player = event.player().player();
if (player == null) return;
// Check if player has permission
if (player.hasPermission("model.attack")) {
player.sendMessage("§cAttack successful!");
} else {
event.setCancelled(true);
player.sendMessage("§cYou can't attack this!");
}
})
// Damage handler
.damage(event -> {
String boneName = event.bone().name().value();
double damage = event.damage();
// Apply bone-specific damage multipliers
double multiplier = switch (boneName) {
case "head" -> 2.0;
case "body" -> 1.0;
case "legs" -> 0.5;
default -> 1.0;
};
event.damage(damage * multiplier);
})
// Create handler
.create(hitBox -> {
System.out.println("HitBox created: " + hitBox.uuid());
})
// Remove handler
.remove(hitBox -> {
System.out.println("HitBox removed: " + hitBox.uuid());
})
// Mount handler
.mount((hitBox, mountedEntity) -> {
System.out.println("Entity mounted: " + mountedEntity.uuid());
})
// Dismount handler
.dismount((hitBox, dismountedEntity) -> {
System.out.println("Entity dismounted: " + dismountedEntity.uuid());
})
.build();
// Create hitbox with listener
tracker.createHitBox(
entity,
listener,
BonePredicate.TRUE
);
}
}
Best Practices
- Use HitBoxListener.builder() for complex hitbox configurations
- Register event listeners in your plugin’s onEnable()
- Handle null checks for player() calls in events
- Use event cancellation to prevent default behavior
- Store event data for analytics and debugging
- Avoid heavy computations in event handlers
- Don’t create infinite event loops
- Always unregister listeners when cleaning up
- Test event handlers with multiple concurrent players
Next Steps
Animated NPC
Use events for NPC interactions
Dynamic Boss
Implement boss mechanics with events
Multi-part Entities
Handle per-part events
HitBox API
Complete hitbox API documentation
