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
}
}
}
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.*
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
class HitBoxEvents {
fun setupHitBoxEvents(tracker: EntityTracker, entity: LivingEntity) {
tracker.listenHitBox { bone, builder ->
builder
.interact { event -> handleInteract(event) }
.attack { event -> handleAttack(event) }
.damage { event -> handleDamage(event) }
}
tracker.createHitBox(
BaseEntity.of(entity),
null,
BonePredicate.name("body").or(BonePredicate.name("head"))
)
}
private fun handleInteract(event: HitBoxInteractEvent) {
val player = event.player().player() ?: return
val boneName = event.bone().name().value()
player.sendMessage("§aYou interacted with: $boneName")
event.hand()?.let { hand ->
player.sendMessage("§7Hand: ${hand.name}")
}
}
private fun handleAttack(HitBoxAttackEvent event) {
val player = event.player().player() ?: return
player.sendMessage("§cYou attacked: ${event.bone().name().value()}")
event.hitBox().source().ifPresent { entity ->
if (entity is LivingEntity) {
entity.damage(5.0, player)
}
}
}
private fun handleDamage(event: HitBoxDamageEvent) {
val damage = event.damage()
val boneName = event.bone().name().value()
println("Bone $boneName took $damage damage")
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)
);
}
}
import kr.toxicity.model.api.event.MountModelEvent
import kr.toxicity.model.api.event.DismountModelEvent
import kr.toxicity.model.api.tracker.TrackerUpdateAction
import kr.toxicity.model.api.util.function.BonePredicate
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
class MountEvents : Listener {
@EventHandler
fun onModelMount(event: MountModelEvent) {
val tracker = event.tracker()
val bone = event.bone()
val mountedEntity = event.entity()
println("Entity mounted on ${bone.name().value()}")
tracker.animate("mount")
tracker.update(
TrackerUpdateAction.glow(true),
BonePredicate.from { it == bone }
)
}
@EventHandler
fun onModelDismount(event: DismountModelEvent) {
val tracker = event.tracker()
val bone = event.bone()
println("Entity dismounted from ${bone.name().value()}")
tracker.animate("dismount")
tracker.update(
TrackerUpdateAction.glow(false),
BonePredicate.from { it == 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
}
}
import kr.toxicity.model.api.event.*
import kr.toxicity.model.api.tracker.EntityTracker
import kr.toxicity.model.api.tracker.Tracker
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
class TrackerLifecycleEvents : Listener {
@EventHandler
fun onEntityTrackerCreate(event: CreateEntityTrackerEvent) {
val tracker = event.tracker()
println("Entity tracker created: ${tracker.name()}")
setupTrackerBehavior(tracker)
}
@EventHandler
fun onDummyTrackerCreate(event: CreateDummyTrackerEvent) {
val tracker = event.tracker()
println("Dummy tracker created: ${tracker.name()}")
}
@EventHandler
fun onTrackerClose(event: CloseTrackerEvent) {
val tracker = event.tracker()
val reason = event.reason()
println("Tracker closed: ${tracker.name()} ($reason)")
if (reason.shouldBeSave() && tracker is EntityTracker) {
if (tracker.canBeSaved()) {
saveTrackerData(tracker)
}
}
}
private fun setupTrackerBehavior(tracker: EntityTracker) {
// Custom setup logic
}
private fun saveTrackerData(tracker: EntityTracker) {
// 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());
}
}
}
import kr.toxicity.model.api.event.PlayerPerAnimationStartEvent
import kr.toxicity.model.api.event.PlayerPerAnimationEndEvent
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
class PlayerAnimationEvents : Listener {
@EventHandler
fun onPerAnimationStart(event: PlayerPerAnimationStartEvent) {
val tracker = event.tracker()
val player = event.player().player()
player?.sendMessage("§ePer-player animation started for: ${tracker.name()}")
}
@EventHandler
fun onPerAnimationEnd(event: PlayerPerAnimationEndEvent) {
val tracker = event.tracker()
val player = event.player().player()
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
