Overview
Multi-part entities allow you to:- Create large bosses with multiple body segments
- Build modular entities with detachable parts
- Synchronize animations across multiple models
- Handle individual part interactions
Basic Multi-Part System
Core Multi-Part Entity
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.tracker.DummyTracker;
import kr.toxicity.model.api.tracker.TrackerModifier;
import org.bukkit.Location;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Zombie;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class MultiPartEntity {
private final String entityId;
private final Map<String, EntityPart> parts = new LinkedHashMap<>();
private final LivingEntity coreEntity;
public MultiPartEntity(String entityId, Location location) {
this.entityId = entityId;
// Create core entity
this.coreEntity = location.getWorld().spawn(location, Zombie.class, zombie -> {
zombie.setInvisible(true);
zombie.setInvulnerable(true);
});
// Create parts
createParts(location);
}
private void createParts(Location baseLocation) {
// Create head part
addPart("head", "dragon_head", baseLocation.clone().add(0, 2, 0), true);
// Create body parts
addPart("body_1", "dragon_body", baseLocation.clone().add(0, 1, 0), true);
addPart("body_2", "dragon_body", baseLocation.clone().add(0, 0, -1), true);
// Create tail parts
addPart("tail_1", "dragon_tail", baseLocation.clone().add(0, 0, -2), true);
addPart("tail_2", "dragon_tail", baseLocation.clone().add(0, 0, -3), true);
// Create wings (dummy trackers)
addPart("wing_left", "dragon_wing", baseLocation.clone().add(-1.5, 1.5, 0), false);
addPart("wing_right", "dragon_wing", baseLocation.clone().add(1.5, 1.5, 0), false);
}
private void addPart(String partId, String modelName, Location location, boolean useEntityTracker) {
EntityPart part;
if (useEntityTracker) {
// Create entity-based part
ArmorStand stand = location.getWorld().spawn(location, ArmorStand.class, as -> {
as.setVisible(false);
as.setGravity(false);
as.setMarker(true);
});
EntityTracker tracker = BetterModel.model(modelName)
.map(renderer -> renderer.getOrCreate(BaseEntity.of(stand)))
.orElse(null);
part = new EntityPart(partId, tracker, stand);
} else {
// Create dummy tracker part
DummyTracker tracker = BetterModel.model(modelName)
.map(renderer -> renderer.create(location))
.orElse(null);
part = new EntityPart(partId, tracker, null);
}
if (part.tracker != null) {
parts.put(partId, part);
}
}
public void synchronizeAnimations(String animationName) {
// Play animation on all parts
parts.values().forEach(part -> {
if (part.tracker != null) {
part.tracker.animate(animationName);
}
});
}
public void updatePositions() {
// Update part positions relative to core entity
Location coreLocation = coreEntity.getLocation();
// Example: Simple chain positioning
List<EntityPart> bodyParts = new ArrayList<>(parts.values());
for (int i = 0; i < bodyParts.size(); i++) {
EntityPart part = bodyParts.get(i);
if (part.entity != null) {
Location targetLoc = coreLocation.clone().add(0, 0, -i);
part.entity.teleport(targetLoc);
}
}
}
public void damagePart(String partId, double damage) {
EntityPart part = parts.get(partId);
if (part != null && part.tracker instanceof EntityTracker tracker) {
// Play damage animation on specific part
tracker.damageTint();
tracker.animate("damage");
}
}
public void removePart(String partId) {
EntityPart part = parts.remove(partId);
if (part != null) {
// Play destruction animation
if (part.tracker != null) {
part.tracker.animate("destroy", AnimationModifier.DEFAULT_WITH_PLAY_ONCE, () -> {
part.tracker.close();
});
}
if (part.entity != null) {
part.entity.remove();
}
}
}
public void remove() {
parts.values().forEach(part -> {
if (part.tracker != null) part.tracker.close();
if (part.entity != null) part.entity.remove();
});
parts.clear();
coreEntity.remove();
}
public Optional<EntityPart> getPart(String partId) {
return Optional.ofNullable(parts.get(partId));
}
public Collection<EntityPart> getAllParts() {
return parts.values();
}
public static class EntityPart {
private final String id;
private final kr.toxicity.model.api.tracker.Tracker tracker;
private final LivingEntity entity;
public EntityPart(String id, kr.toxicity.model.api.tracker.Tracker tracker, LivingEntity entity) {
this.id = id;
this.tracker = tracker;
this.entity = entity;
}
public String getId() { return id; }
public kr.toxicity.model.api.tracker.Tracker getTracker() { return tracker; }
public LivingEntity getEntity() { return entity; }
}
}
Segmented Dragon Boss
Advanced Dragon with Multiple Segments
import kr.toxicity.model.api.tracker.ModelRotation;
import org.bukkit.util.Vector;
public class SegmentedDragon extends MultiPartEntity {
private final List<DragonSegment> segments = new ArrayList<>();
public SegmentedDragon(String id, Location location) {
super(id, location);
initializeSegments();
}
private void initializeSegments() {
// Create head segment
segments.add(new DragonSegment(
getPart("head").orElse(null),
SegmentType.HEAD,
100.0
));
// Create body segments
segments.add(new DragonSegment(
getPart("body_1").orElse(null),
SegmentType.BODY,
150.0
));
segments.add(new DragonSegment(
getPart("body_2").orElse(null),
SegmentType.BODY,
150.0
));
// Setup smooth following
setupSegmentFollowing();
}
private void setupSegmentFollowing() {
// Each segment follows the one in front
for (int i = 1; i < segments.size(); i++) {
DragonSegment current = segments.get(i);
DragonSegment previous = segments.get(i - 1);
if (current.part != null && current.part.getTracker() != null) {
current.part.getTracker().tick(2, (t, bundler) -> {
updateSegmentPosition(current, previous);
});
}
}
}
private void updateSegmentPosition(DragonSegment current, DragonSegment previous) {
if (previous.part == null || previous.part.getEntity() == null) return;
if (current.part == null || current.part.getEntity() == null) return;
Location targetLoc = previous.part.getEntity().getLocation();
Location currentLoc = current.part.getEntity().getLocation();
// Calculate direction from previous segment
Vector direction = targetLoc.toVector()
.subtract(currentLoc.toVector())
.normalize();
// Move toward target with smooth interpolation
Location newLoc = currentLoc.add(direction.multiply(0.3));
current.part.getEntity().teleport(newLoc);
// Update rotation
float yaw = (float) Math.toDegrees(Math.atan2(-direction.getX(), direction.getZ()));
if (current.part.getTracker() instanceof EntityTracker tracker) {
tracker.rotation(() -> new ModelRotation(0, yaw));
}
}
public void damageSegment(int segmentIndex, double damage) {
if (segmentIndex < 0 || segmentIndex >= segments.size()) return;
DragonSegment segment = segments.get(segmentIndex);
segment.health -= damage;
if (segment.health <= 0) {
destroySegment(segmentIndex);
} else {
// Damage animation
damagePart(segment.part.getId(), damage);
}
}
private void destroySegment(int segmentIndex) {
DragonSegment segment = segments.get(segmentIndex);
removePart(segment.part.getId());
segments.remove(segmentIndex);
// Check if dragon is defeated
if (segments.isEmpty()) {
onDefeat();
}
}
private void onDefeat() {
remove();
}
private static class DragonSegment {
private final EntityPart part;
private final SegmentType type;
private double health;
public DragonSegment(EntityPart part, SegmentType type, double health) {
this.part = part;
this.type = type;
this.health = health;
}
}
private enum SegmentType {
HEAD, BODY, TAIL
}
}
Best Practices
- Use entity trackers for parts that need collision/hitboxes
- Use dummy trackers for decorative parts (wings, effects)
- Synchronize animations across all parts for cohesive movement
- Implement smooth interpolation for segment following
- Cache part references for quick access
- Limit the number of parts to maintain performance
- Clean up all parts when removing the entity
- Test part interactions thoroughly
- Consider network overhead with many entities
Next Steps
Dynamic Boss
Create complex boss entities
Custom Events
Handle part-specific interactions
Performance Optimization
Optimize multi-part entities
API Reference
EntityTracker API documentation
