2026-01-25 20:39:01 -05:00
|
|
|
package kamkeel.npcs.entity;
|
|
|
|
|
|
2026-02-18 05:13:51 -05:00
|
|
|
import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData;
|
|
|
|
|
import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData;
|
|
|
|
|
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
|
|
|
|
|
import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData;
|
|
|
|
|
import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData;
|
|
|
|
|
import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData;
|
|
|
|
|
import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData;
|
2026-01-26 23:55:07 -05:00
|
|
|
import kamkeel.npcs.util.AnchorPointHelper;
|
2026-01-25 20:39:01 -05:00
|
|
|
import net.minecraft.entity.Entity;
|
|
|
|
|
import net.minecraft.entity.EntityLivingBase;
|
|
|
|
|
import net.minecraft.nbt.NBTTagCompound;
|
|
|
|
|
import net.minecraft.util.AxisAlignedBB;
|
|
|
|
|
import net.minecraft.util.Vec3;
|
|
|
|
|
import net.minecraft.world.World;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Disc projectile - flat spinning disc with optional boomerang behavior.
|
|
|
|
|
* Has a wider, thinner hitbox compared to Orb.
|
2026-02-18 05:13:51 -05:00
|
|
|
* <p>
|
2026-01-25 20:39:01 -05:00
|
|
|
* Design inspired by LouisXIV's energy attack system.
|
|
|
|
|
*/
|
2026-02-17 14:32:13 -05:00
|
|
|
public class EntityAbilityDisc extends EntityEnergyProjectile {
|
2026-01-25 20:39:01 -05:00
|
|
|
|
2026-02-03 17:20:36 -03:00
|
|
|
// Disc-specific movement propertie
|
2026-01-25 20:39:01 -05:00
|
|
|
|
|
|
|
|
// Boomerang properties
|
|
|
|
|
private boolean boomerang = false;
|
|
|
|
|
private int boomerangDelay = 40; // Ticks before returning
|
|
|
|
|
private boolean returning = false;
|
|
|
|
|
private int ticksSinceMiss = 0;
|
|
|
|
|
|
|
|
|
|
// Disc shape properties
|
|
|
|
|
private float discRadius = 1.0f; // Width of disc
|
|
|
|
|
private float discThickness = 0.2f; // Height of disc
|
2026-02-19 02:14:34 -05:00
|
|
|
|
|
|
|
|
// Boomerang owner-gone tracking
|
|
|
|
|
private int returnOwnerNullTicks = 0;
|
2026-02-06 16:28:38 -05:00
|
|
|
private boolean vertical = false; // false = horizontal (flat), true = vertical (thin edge forward)
|
2026-01-25 20:39:01 -05:00
|
|
|
|
2026-02-14 15:21:56 -05:00
|
|
|
// Charging state (disc-specific target sizes)
|
2026-01-26 23:55:07 -05:00
|
|
|
private float targetDiscRadius = 1.0f; // Full radius to grow to during charging
|
|
|
|
|
private float targetDiscThickness = 0.2f; // Full thickness to grow to during charging
|
|
|
|
|
|
2026-01-25 20:39:01 -05:00
|
|
|
public EntityAbilityDisc(World world) {
|
|
|
|
|
super(world);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-29 14:19:57 -05:00
|
|
|
* Full constructor with all parameters using data classes.
|
2026-01-25 20:39:01 -05:00
|
|
|
*/
|
2026-01-30 05:01:54 -05:00
|
|
|
public EntityAbilityDisc(World world, EntityLivingBase owner, EntityLivingBase target,
|
2026-02-03 15:45:45 -03:00
|
|
|
double x, double y, double z,
|
|
|
|
|
float discRadius, float discThickness,
|
2026-02-03 17:20:36 -03:00
|
|
|
EnergyDisplayData display, EnergyCombatData combat,
|
|
|
|
|
EnergyHomingData homing, EnergyLightningData lightning,
|
2026-02-03 21:26:27 -03:00
|
|
|
EnergyLifespanData lifespan, EnergyTrajectoryData trajectory,
|
2026-02-03 15:45:45 -03:00
|
|
|
boolean boomerang, int boomerangDelay) {
|
2026-01-25 20:39:01 -05:00
|
|
|
super(world);
|
|
|
|
|
|
2026-01-29 14:19:57 -05:00
|
|
|
// Initialize base properties
|
2026-02-03 21:26:27 -03:00
|
|
|
initProjectile(owner, target, x, y, z, discRadius, display, combat, lightning, lifespan, trajectory);
|
2026-01-29 14:19:57 -05:00
|
|
|
|
2026-02-03 17:20:36 -03:00
|
|
|
this.homingData = homing;
|
2026-01-25 20:39:01 -05:00
|
|
|
|
|
|
|
|
this.boomerang = boomerang;
|
|
|
|
|
this.boomerangDelay = boomerangDelay;
|
|
|
|
|
|
|
|
|
|
this.discRadius = discRadius;
|
|
|
|
|
this.discThickness = discThickness;
|
|
|
|
|
|
|
|
|
|
// Calculate initial velocity toward target
|
2026-02-14 15:21:56 -05:00
|
|
|
calculateInitialVelocity(owner, target, x, y, z);
|
2026-01-25 20:39:01 -05:00
|
|
|
}
|
|
|
|
|
|
2026-01-26 23:55:07 -05:00
|
|
|
/**
|
2026-01-29 14:19:57 -05:00
|
|
|
* Setup this disc in charging mode (for windup phase).
|
2026-01-26 23:55:07 -05:00
|
|
|
* The disc will grow from 0 to discRadius over chargeDuration ticks.
|
|
|
|
|
* Position follows the owner based on anchor point.
|
|
|
|
|
*/
|
2026-02-06 16:28:38 -05:00
|
|
|
public void setupCharging(EnergyAnchorData anchor, int chargeDuration, boolean vertical) {
|
2026-01-29 14:19:57 -05:00
|
|
|
this.targetDiscRadius = this.discRadius;
|
|
|
|
|
this.targetDiscThickness = this.discThickness;
|
2026-02-14 15:21:56 -05:00
|
|
|
super.setupCharging(anchor, chargeDuration);
|
|
|
|
|
this.vertical = vertical;
|
2026-01-29 14:19:57 -05:00
|
|
|
this.discRadius = 0.01f;
|
|
|
|
|
this.discThickness = 0.01f;
|
2026-01-26 23:55:07 -05:00
|
|
|
}
|
|
|
|
|
|
2026-01-28 20:43:32 -05:00
|
|
|
/**
|
2026-01-29 14:19:57 -05:00
|
|
|
* Setup this disc in preview mode for GUI display.
|
2026-01-28 20:43:32 -05:00
|
|
|
* Follows anchor point and animations like in the real game.
|
|
|
|
|
* Can be fired when transitioning to active phase.
|
|
|
|
|
*/
|
2026-02-06 16:28:38 -05:00
|
|
|
public void setupPreview(EntityLivingBase owner, float discRadius, float discThickness, EnergyDisplayData display, EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration, boolean vertical) {
|
2026-01-29 14:19:57 -05:00
|
|
|
this.setPreviewMode(true);
|
|
|
|
|
this.setPreviewOwner(owner);
|
2026-01-28 20:43:32 -05:00
|
|
|
|
|
|
|
|
// Set visual properties
|
2026-02-03 17:20:36 -03:00
|
|
|
this.displayData = display;
|
|
|
|
|
this.lightningData = lightning;
|
2026-01-28 20:43:32 -05:00
|
|
|
|
|
|
|
|
// Set charging state
|
2026-01-29 14:19:57 -05:00
|
|
|
this.setCharging(true);
|
|
|
|
|
this.chargeDuration = chargeDuration;
|
|
|
|
|
this.chargeTick = 0;
|
2026-01-29 20:34:10 -03:00
|
|
|
this.anchorData = anchor;
|
2026-02-06 16:28:38 -05:00
|
|
|
this.vertical = vertical;
|
2026-01-28 20:43:32 -05:00
|
|
|
|
|
|
|
|
// Store target size and start at 0 for grow effect
|
2026-01-29 14:19:57 -05:00
|
|
|
this.targetDiscRadius = discRadius;
|
|
|
|
|
this.targetDiscThickness = discThickness;
|
|
|
|
|
this.discRadius = 0.01f;
|
|
|
|
|
this.discThickness = 0.01f;
|
|
|
|
|
this.size = 0.01f;
|
2026-02-19 19:27:56 -05:00
|
|
|
this.renderCurrentSize = 0.01f;
|
|
|
|
|
this.prevRenderSize = 0.01f;
|
2026-01-28 20:43:32 -05:00
|
|
|
|
|
|
|
|
// Initial position at anchor point
|
2026-01-29 20:34:10 -03:00
|
|
|
Vec3 pos = AnchorPointHelper.calculateAnchorPosition(owner, anchorData);
|
2026-01-29 14:19:57 -05:00
|
|
|
this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord);
|
|
|
|
|
this.prevPosX = pos.xCoord;
|
|
|
|
|
this.prevPosY = pos.yCoord;
|
|
|
|
|
this.prevPosZ = pos.zCoord;
|
|
|
|
|
this.startX = pos.xCoord;
|
|
|
|
|
this.startY = pos.yCoord;
|
|
|
|
|
this.startZ = pos.zCoord;
|
|
|
|
|
|
|
|
|
|
// Clear motion
|
|
|
|
|
this.motionX = 0;
|
|
|
|
|
this.motionY = 0;
|
|
|
|
|
this.motionZ = 0;
|
2026-01-28 20:43:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Start preview firing (simulates firing toward a point in front of NPC).
|
|
|
|
|
*/
|
|
|
|
|
public void startPreviewFiring() {
|
2026-02-22 03:07:37 -05:00
|
|
|
beginLookVectorLaunch(true);
|
|
|
|
|
setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0);
|
2026-01-28 20:43:32 -05:00
|
|
|
}
|
|
|
|
|
|
2026-01-26 23:55:07 -05:00
|
|
|
/**
|
|
|
|
|
* Start the disc moving (exit charging mode).
|
|
|
|
|
* Called by ability when windup ends.
|
|
|
|
|
*/
|
|
|
|
|
public void startMoving(EntityLivingBase target) {
|
2026-02-22 03:07:37 -05:00
|
|
|
beginLookVectorLaunch(false);
|
|
|
|
|
setMotionTowardTargetOrLookVector(target, posX, posY, posZ, getSpeed(), getSpeed(), 0, 0);
|
2026-01-26 23:55:07 -05:00
|
|
|
}
|
|
|
|
|
|
2026-01-25 20:39:01 -05:00
|
|
|
@Override
|
|
|
|
|
protected void updateRotation() {
|
|
|
|
|
// Disc spins ONLY on Y axis (flat spin like a saw blade)
|
2026-02-03 17:20:36 -03:00
|
|
|
this.rotationValY += getRotationSpeed();
|
2026-01-25 20:39:01 -05:00
|
|
|
// No wobble - keep X and Z at 0 for stable flight
|
|
|
|
|
if (this.rotationValY > 360.0f) this.rotationValY -= 360.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void updateProjectile() {
|
2026-01-26 23:55:07 -05:00
|
|
|
// Handle charging state (windup phase)
|
|
|
|
|
if (isCharging()) {
|
|
|
|
|
updateCharging();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 20:43:32 -05:00
|
|
|
// In preview mode, run movement on client (no server)
|
|
|
|
|
if (previewMode) {
|
|
|
|
|
updatePreviewMovement();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-25 20:39:01 -05:00
|
|
|
if (worldObj.isRemote) {
|
|
|
|
|
handleClientInterpolation();
|
|
|
|
|
} else {
|
|
|
|
|
// Server-side logic
|
|
|
|
|
if (returning) {
|
|
|
|
|
updateReturnToOwner();
|
|
|
|
|
} else {
|
|
|
|
|
updateHoming();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
checkBlockCollision();
|
|
|
|
|
checkEntityCollision();
|
|
|
|
|
this.moveEntity(motionX, motionY, motionZ);
|
|
|
|
|
|
2026-01-26 23:55:07 -05:00
|
|
|
// Track ticks for boomerang delay (checkMaxDistance handles the actual return trigger)
|
2026-01-25 20:39:01 -05:00
|
|
|
if (boomerang && !returning && !hasHit) {
|
|
|
|
|
ticksSinceMiss++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check wall collision
|
2026-02-21 20:39:16 -05:00
|
|
|
handleSolidCollisionTermination();
|
2026-01-25 20:39:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected boolean checkMaxDistance() {
|
2026-01-26 23:55:07 -05:00
|
|
|
double distTraveled = Math.sqrt(
|
|
|
|
|
(posX - startX) * (posX - startX) +
|
2026-02-18 05:13:51 -05:00
|
|
|
(posY - startY) * (posY - startY) +
|
|
|
|
|
(posZ - startZ) * (posZ - startZ)
|
2026-01-26 23:55:07 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (boomerang) {
|
|
|
|
|
// If not returning yet, check if we should start returning
|
|
|
|
|
if (!returning && !hasHit) {
|
2026-02-03 17:20:36 -03:00
|
|
|
if (distTraveled >= getMaxDistance() * 0.8 || ticksSinceMiss >= boomerangDelay) {
|
2026-01-26 23:55:07 -05:00
|
|
|
returning = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If returning, only die when close to owner
|
|
|
|
|
if (returning) {
|
2026-02-03 17:20:36 -03:00
|
|
|
Entity owner = getOwnerEntity();
|
2026-01-26 23:55:07 -05:00
|
|
|
if (owner != null) {
|
2026-02-19 02:14:34 -05:00
|
|
|
returnOwnerNullTicks = 0;
|
2026-01-26 23:55:07 -05:00
|
|
|
double distToOwner = Math.sqrt(
|
|
|
|
|
(posX - owner.posX) * (posX - owner.posX) +
|
2026-02-18 05:13:51 -05:00
|
|
|
(posY - owner.posY) * (posY - owner.posY) +
|
|
|
|
|
(posZ - owner.posZ) * (posZ - owner.posZ)
|
2026-01-26 23:55:07 -05:00
|
|
|
);
|
|
|
|
|
return distToOwner < 1.5;
|
|
|
|
|
}
|
2026-02-19 02:14:34 -05:00
|
|
|
// Owner gone while returning - grace period then die
|
|
|
|
|
returnOwnerNullTicks++;
|
|
|
|
|
return returnOwnerNullTicks > 100;
|
2026-01-25 20:39:01 -05:00
|
|
|
}
|
2026-01-26 23:55:07 -05:00
|
|
|
|
|
|
|
|
// Not returning yet - don't die from distance (we'll trigger return above)
|
|
|
|
|
return false;
|
2026-01-25 20:39:01 -05:00
|
|
|
}
|
2026-01-26 23:55:07 -05:00
|
|
|
|
|
|
|
|
// Non-boomerang: standard max distance check
|
2026-02-03 17:20:36 -03:00
|
|
|
return distTraveled >= getMaxDistance();
|
2026-01-25 20:39:01 -05:00
|
|
|
}
|
|
|
|
|
|
2026-01-28 20:43:32 -05:00
|
|
|
/**
|
|
|
|
|
* Update movement in preview mode (client-side only, no damage).
|
|
|
|
|
*/
|
|
|
|
|
private void updatePreviewMovement() {
|
|
|
|
|
// Simple movement - just apply motion
|
|
|
|
|
this.posX += motionX;
|
|
|
|
|
this.posY += motionY;
|
|
|
|
|
this.posZ += motionZ;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-25 20:39:01 -05:00
|
|
|
private void updateReturnToOwner() {
|
2026-02-03 17:20:36 -03:00
|
|
|
Entity owner = getOwnerEntity();
|
2026-01-26 23:55:07 -05:00
|
|
|
|
|
|
|
|
// If owner is gone (unloaded/dead), head back toward start position
|
|
|
|
|
double targetX, targetY, targetZ;
|
|
|
|
|
if (owner != null) {
|
|
|
|
|
targetX = owner.posX;
|
|
|
|
|
targetY = owner.posY + owner.height * 0.5;
|
|
|
|
|
targetZ = owner.posZ;
|
|
|
|
|
} else {
|
|
|
|
|
// Return to start position if owner not available
|
|
|
|
|
targetX = startX;
|
|
|
|
|
targetY = startY;
|
|
|
|
|
targetZ = startZ;
|
2026-01-25 20:39:01 -05:00
|
|
|
}
|
|
|
|
|
|
2026-01-26 23:55:07 -05:00
|
|
|
double dx = targetX - posX;
|
|
|
|
|
double dy = targetY - posY;
|
|
|
|
|
double dz = targetZ - posZ;
|
2026-01-25 20:39:01 -05:00
|
|
|
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
|
|
|
|
|
|
|
|
if (dist > 0) {
|
|
|
|
|
// Stronger homing when returning
|
2026-02-03 17:20:36 -03:00
|
|
|
double returnStrength = Math.min(1.0, getHomingStrength() * 2.5);
|
|
|
|
|
double desiredVX = (dx / dist) * getSpeed();
|
|
|
|
|
double desiredVY = (dy / dist) * getSpeed();
|
|
|
|
|
double desiredVZ = (dz / dist) * getSpeed();
|
2026-01-25 20:39:01 -05:00
|
|
|
|
|
|
|
|
motionX += (desiredVX - motionX) * returnStrength;
|
|
|
|
|
motionY += (desiredVY - motionY) * returnStrength;
|
|
|
|
|
motionZ += (desiredVZ - motionZ) * returnStrength;
|
|
|
|
|
|
|
|
|
|
double vLen = Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ);
|
|
|
|
|
if (vLen > 0) {
|
2026-02-03 17:20:36 -03:00
|
|
|
motionX = (motionX / vLen) * getSpeed();
|
|
|
|
|
motionY = (motionY / vLen) * getSpeed();
|
|
|
|
|
motionZ = (motionZ / vLen) * getSpeed();
|
2026-01-25 20:39:01 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void checkBlockCollision() {
|
2026-02-21 20:39:16 -05:00
|
|
|
handleBlockImpact(rayTraceBlocks(posX, posY, posZ, posX + motionX, posY + motionY, posZ + motionZ), true);
|
2026-01-25 20:39:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void checkEntityCollision() {
|
|
|
|
|
// Disc hitbox: wider but thinner
|
|
|
|
|
double halfWidth = discRadius * 0.5;
|
|
|
|
|
double halfHeight = discThickness * 0.5;
|
|
|
|
|
AxisAlignedBB hitBox = AxisAlignedBB.getBoundingBox(
|
|
|
|
|
posX - halfWidth, posY - halfHeight, posZ - halfWidth,
|
|
|
|
|
posX + halfWidth, posY + halfHeight, posZ + halfWidth
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-21 20:39:16 -05:00
|
|
|
processEntitiesInHitBox(hitBox, posX, posY, posZ);
|
2026-01-25 20:39:01 -05:00
|
|
|
}
|
|
|
|
|
|
2026-01-26 23:55:07 -05:00
|
|
|
/**
|
|
|
|
|
* Update during charging state - follow owner based on anchor point.
|
2026-02-14 15:21:56 -05:00
|
|
|
* Overrides base to grow disc-specific radius/thickness instead of just size.
|
2026-01-26 23:55:07 -05:00
|
|
|
*/
|
2026-02-14 15:21:56 -05:00
|
|
|
@Override
|
|
|
|
|
protected void updateCharging() {
|
2026-01-26 23:55:07 -05:00
|
|
|
chargeTick++;
|
|
|
|
|
|
|
|
|
|
// Grow size based on charge progress
|
|
|
|
|
float progress = getChargeProgress();
|
|
|
|
|
this.discRadius = targetDiscRadius * progress;
|
|
|
|
|
this.discThickness = targetDiscThickness * progress;
|
|
|
|
|
this.size = this.discRadius; // Base size for interpolation
|
|
|
|
|
|
2026-02-15 21:29:26 -05:00
|
|
|
Entity owner = getOwnerEntity();
|
2026-01-26 23:55:07 -05:00
|
|
|
if (owner instanceof EntityLivingBase) {
|
2026-01-29 20:34:10 -03:00
|
|
|
Vec3 pos = AnchorPointHelper.calculateAnchorPosition((EntityLivingBase) owner, anchorData);
|
2026-01-26 23:55:07 -05:00
|
|
|
setPosition(pos.xCoord, pos.yCoord, pos.zCoord);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update rotation during charging (disc still spins)
|
|
|
|
|
updateRotation();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-25 20:39:01 -05:00
|
|
|
// ==================== GETTERS ====================
|
|
|
|
|
|
|
|
|
|
public float getDiscRadius() {
|
|
|
|
|
return discRadius;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 01:03:05 -05:00
|
|
|
public void setDiscRadius(float radius) {
|
|
|
|
|
this.discRadius = radius;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-25 20:39:01 -05:00
|
|
|
public float getDiscThickness() {
|
|
|
|
|
return discThickness;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 01:03:05 -05:00
|
|
|
public void setDiscThickness(float thickness) {
|
|
|
|
|
this.discThickness = thickness;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-25 20:39:01 -05:00
|
|
|
public boolean isBoomerang() {
|
|
|
|
|
return boomerang;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 01:03:05 -05:00
|
|
|
public void setBoomerang(boolean boomerang) {
|
|
|
|
|
this.boomerang = boomerang;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int getBoomerangDelay() {
|
|
|
|
|
return boomerangDelay;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setBoomerangDelay(int delay) {
|
|
|
|
|
this.boomerangDelay = delay;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-25 20:39:01 -05:00
|
|
|
public boolean isReturning() {
|
|
|
|
|
return returning;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 16:28:38 -05:00
|
|
|
public boolean isVertical() {
|
|
|
|
|
return vertical;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 01:03:05 -05:00
|
|
|
public void setVertical(boolean vertical) {
|
|
|
|
|
this.vertical = vertical;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 16:28:38 -05:00
|
|
|
/**
|
|
|
|
|
* Get the yaw angle (degrees) from motion vector for vertical disc orientation.
|
|
|
|
|
* During charging, uses owner's facing direction.
|
|
|
|
|
*/
|
|
|
|
|
public float getTravelYaw() {
|
|
|
|
|
if (isCharging()) {
|
|
|
|
|
Entity owner = previewMode ? previewOwner : getOwnerEntity();
|
|
|
|
|
if (owner != null) {
|
|
|
|
|
return owner.rotationYaw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
double speed = Math.sqrt(motionX * motionX + motionZ * motionZ);
|
|
|
|
|
if (speed > 0.001) {
|
|
|
|
|
return (float) (Math.atan2(-motionX, motionZ) * 180.0 / Math.PI);
|
|
|
|
|
}
|
|
|
|
|
return 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-25 20:39:01 -05:00
|
|
|
// ==================== NBT ====================
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void readProjectileNBT(NBTTagCompound nbt) {
|
|
|
|
|
this.boomerang = nbt.hasKey("Boomerang") && nbt.getBoolean("Boomerang");
|
|
|
|
|
this.boomerangDelay = nbt.hasKey("BoomerangDelay") ? nbt.getInteger("BoomerangDelay") : 40;
|
2026-02-19 02:14:34 -05:00
|
|
|
this.discRadius = sanitize(nbt.hasKey("DiscRadius") ? nbt.getFloat("DiscRadius") : 1.0f, 1.0f, MAX_ENTITY_SIZE);
|
|
|
|
|
this.discThickness = sanitize(nbt.hasKey("DiscThickness") ? nbt.getFloat("DiscThickness") : 0.2f, 0.2f, MAX_ENTITY_SIZE);
|
2026-02-06 16:28:38 -05:00
|
|
|
this.vertical = nbt.hasKey("Vertical") ? nbt.getBoolean("Vertical") : false;
|
2026-01-25 20:39:01 -05:00
|
|
|
this.returning = nbt.hasKey("Returning") && nbt.getBoolean("Returning");
|
2026-02-14 15:21:56 -05:00
|
|
|
// Charging state (common fields handled by base)
|
|
|
|
|
readChargingNBT(nbt);
|
2026-01-26 23:55:07 -05:00
|
|
|
this.targetDiscRadius = nbt.hasKey("TargetDiscRadius") ? nbt.getFloat("TargetDiscRadius") : this.discRadius;
|
|
|
|
|
this.targetDiscThickness = nbt.hasKey("TargetDiscThickness") ? nbt.getFloat("TargetDiscThickness") : this.discThickness;
|
2026-01-25 20:39:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void writeProjectileNBT(NBTTagCompound nbt) {
|
|
|
|
|
nbt.setBoolean("Boomerang", boomerang);
|
|
|
|
|
nbt.setInteger("BoomerangDelay", boomerangDelay);
|
|
|
|
|
nbt.setFloat("DiscRadius", discRadius);
|
|
|
|
|
nbt.setFloat("DiscThickness", discThickness);
|
2026-02-06 16:28:38 -05:00
|
|
|
nbt.setBoolean("Vertical", vertical);
|
2026-01-25 20:39:01 -05:00
|
|
|
nbt.setBoolean("Returning", returning);
|
2026-02-14 15:21:56 -05:00
|
|
|
// Charging state (common fields handled by base)
|
|
|
|
|
writeChargingNBT(nbt);
|
2026-01-26 23:55:07 -05:00
|
|
|
nbt.setFloat("TargetDiscRadius", targetDiscRadius);
|
|
|
|
|
nbt.setFloat("TargetDiscThickness", targetDiscThickness);
|
2026-01-25 20:39:01 -05:00
|
|
|
}
|
|
|
|
|
}
|