Files
CustomNPC-Plus/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java
T

816 lines
29 KiB
Java

package kamkeel.npcs.entity;
import kamkeel.npcs.controllers.data.ability.data.*;
import kamkeel.npcs.util.AnchorPointHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import noppes.npcs.LogWriter;
import java.util.ArrayList;
import java.util.List;
/**
* Beam projectile - head with trailing path that curves with homing.
* Stays attached to origin point, trail shows path of head.
*
* IMPORTANT: Trail points are stored RELATIVE to startX/Y/Z (origin).
* This ensures rendering is stable regardless of entity position interpolation.
*
* Design inspired by LouisXIV's energy attack system.
*/
public class EntityAbilityBeam extends EntityAbilityProjectile {
// Beam shape properties
private float beamWidth = 0.3f;
private float headSize = 0.5f;
// Trail - list of points showing beam path (RELATIVE to origin!)
private List<Vec3> trailPoints = new ArrayList<>();
private static final int MAX_TRAIL_POINTS = 200;
private static final double MIN_POINT_DISTANCE = 0.2;
// Head position relative to origin
private double headOffsetX, headOffsetY, headOffsetZ;
private double prevHeadOffsetX, prevHeadOffsetY, prevHeadOffsetZ;
// Origin stays fixed (or follows owner)
private boolean attachedToOwner = true;
// Whether to render tail orb (only when anchored)
private boolean renderTailOrb = true;
// Charging state (during windup)
private boolean charging = false;
private int chargeDuration = 40;
private int chargeTick = 0;
private float chargeOffsetDistance = 1.0f;
// Trail fading for non-anchored beams (comet effect)
private boolean fadeTrail = false;
private int trailFadeTime = 20; // Ticks for trail to fully fade
private List<Integer> trailPointAges = new ArrayList<>();
// Data watcher index for charging state (synced to clients)
private static final int DW_CHARGING = 20;
// Debug logging
private static final boolean DEBUG_LOGGING = false;
public EntityAbilityBeam(World world) {
super(world);
}
@Override
protected void entityInit() {
super.entityInit();
// Register data watcher for charging state
this.dataWatcher.addObject(DW_CHARGING, (byte) 0);
}
/**
* Check if beam is in charging state (synced via data watcher).
* In preview mode, uses local field since data watcher isn't synced.
*/
public boolean isCharging() {
if (previewMode) {
return this.charging;
}
return this.dataWatcher.getWatchableObjectByte(DW_CHARGING) == 1;
}
/**
* Set charging state (server only, synced to clients via data watcher).
*/
private void setCharging(boolean value) {
this.charging = value;
if (!worldObj.isRemote) {
this.dataWatcher.updateObject(DW_CHARGING, (byte) (value ? 1 : 0));
}
}
/**
* Full constructor with all parameters using data classes.
* @param anchoredMode If true, origin follows owner and tail orb is rendered.
* If false, beam is free-moving with trailing length (no tail orb).
*/
public EntityAbilityBeam(World world, EntityLivingBase owner, EntityLivingBase target,
double x, double y, double z,
float beamWidth, float headSize,
EnergyDisplayData display, EnergyCombatData combat,
EnergyHomingData homing, EnergyLightningData lightning,
EnergyLifespanData lifespan, EnergyTrajectoryData trajectory,
boolean anchoredMode) {
super(world);
// Initialize base properties via parent
initProjectile(owner, target, x, y, z, headSize, display, combat, lightning, lifespan, trajectory);
// Beam-specific properties from homing data
this.homingData = homing;
this.beamWidth = beamWidth;
this.headSize = headSize;
// Anchored mode controls whether origin follows owner and tail orb is rendered
this.attachedToOwner = anchoredMode;
this.renderTailOrb = anchoredMode;
// Initialize head offset at origin (0,0,0 relative)
this.headOffsetX = 0;
this.headOffsetY = 0;
this.headOffsetZ = 0;
this.prevHeadOffsetX = 0;
this.prevHeadOffsetY = 0;
this.prevHeadOffsetZ = 0;
// Add initial trail point at origin (relative 0,0,0)
trailPoints.add(Vec3.createVectorHelper(0, 0, 0));
if (DEBUG_LOGGING && !world.isRemote) {
LogWriter.info("[Beam] Created at origin " + x + ", " + y + ", " + z + " maxDist=" + lifespan.maxDistance);
}
// Calculate initial velocity toward target
if (target != null) {
double dx = target.posX - x;
double dy = (target.posY + target.getEyeHeight()) - y;
double dz = target.posZ - z;
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (len > 0) {
this.motionX = (dx / len) * getSpeed();
this.motionY = (dy / len) * getSpeed();
this.motionZ = (dz / len) * getSpeed();
}
} else {
float yaw = (float) Math.toRadians(owner.rotationYaw);
float pitch = (float) Math.toRadians(owner.rotationPitch);
this.motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed();
this.motionY = -Math.sin(pitch) * getSpeed();
this.motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed();
}
}
public void setupCharging(EnergyAnchorData anchor, int chargeDuration, float chargeOffsetDistance) {
setCharging(true);
this.chargeDuration = chargeDuration;
this.chargeTick = 0;
this.chargeOffsetDistance = chargeOffsetDistance;
this.anchorData = anchor;
this.fadeTrail = !attachedToOwner;
this.motionX = 0;
this.motionY = 0;
this.motionZ = 0;
}
public void setupPreview(EntityLivingBase owner, float beamWidth, float headSize, EnergyDisplayData display, EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration, float chargeOffsetDistance) {
this.setPreviewMode(true);
this.setPreviewOwner(owner);
// Set visual properties
this.beamWidth = beamWidth;
this.headSize = headSize;
this.size = headSize;
this.displayData = display;
this.lightningData = lightning;
// Set charging state
this.setCharging(true);
this.chargeDuration = chargeDuration;
this.chargeTick = 0;
this.chargeOffsetDistance = chargeOffsetDistance;
this.anchorData = anchor;
// Initial position at anchor point
Vec3 pos = AnchorPointHelper.calculateAnchorPosition(owner, anchorData, chargeOffsetDistance);
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;
// Initialize head offsets
this.headOffsetX = 0;
this.headOffsetY = 0;
this.headOffsetZ = 0;
this.prevHeadOffsetX = 0;
this.prevHeadOffsetY = 0;
this.prevHeadOffsetZ = 0;
// Attach to owner for anchor following
this.attachedToOwner = true;
this.renderTailOrb = true;
// Clear motion
this.motionX = 0;
this.motionY = 0;
this.motionZ = 0;
}
/**
* Start preview firing (simulates firing toward a point in front of NPC).
*/
public void startPreviewFiring() {
if (!isCharging()) return;
setCharging(false);
// Origin (tail) stays at the charged position
startX = posX;
startY = posY;
startZ = posZ;
// Reset head offset to origin
headOffsetX = 0;
headOffsetY = 0;
headOffsetZ = 0;
prevHeadOffsetX = 0;
prevHeadOffsetY = 0;
prevHeadOffsetZ = 0;
// Origin no longer follows owner after firing
attachedToOwner = false;
// Sync prev position to prevent visual jump on first frame
prevPosX = posX;
prevPosY = posY;
prevPosZ = posZ;
// Reset trail
trailPoints.clear();
trailPointAges.clear();
trailPoints.add(Vec3.createVectorHelper(0, 0, 0));
// Fire forward based on owner facing direction
Entity owner = getOwnerEntity();
if (owner != null) {
float yaw = (float) Math.toRadians(owner.rotationYaw);
motionX = -Math.sin(yaw) * getSpeed();
motionY = 0;
motionZ = Math.cos(yaw) * getSpeed();
} else {
motionX = getSpeed();
motionY = 0;
motionZ = 0;
}
}
/**
* Start the beam firing (exit charging mode).
* Called by ability when windup ends.
*
* For anchored beams: origin follows owner, head starts at charged position
* For non-anchored beams: origin fixed at charged position, head starts there
*/
public void startFiring(EntityLivingBase target) {
if (!isCharging()) return;
setCharging(false);
// For player casters, snap to look vector for crosshair-aligned launch
snapToPlayerLookVector();
// Origin (tail) stays at the charged position - where the orb was
// This is the same for both anchored and non-anchored modes
startX = posX;
startY = posY;
startZ = posZ;
// Head starts at the origin (tail position)
headOffsetX = 0;
headOffsetY = 0;
headOffsetZ = 0;
prevHeadOffsetX = 0;
prevHeadOffsetY = 0;
prevHeadOffsetZ = 0;
// Origin no longer follows owner after firing starts
// (for anchored mode, tail is fixed in space; for non-anchored, there's no tail)
attachedToOwner = false;
// Initialize trail with just the origin point
trailPoints.clear();
trailPointAges.clear();
trailPoints.add(Vec3.createVectorHelper(0, 0, 0));
if (fadeTrail) trailPointAges.add(0);
// Calculate velocity toward target (head starts at origin = startX/Y/Z)
Entity owner = getOwnerEntity();
if (target != null) {
double dx = target.posX - startX;
double dy = (target.posY + target.getEyeHeight()) - startY;
double dz = target.posZ - startZ;
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (len > 0) {
motionX = (dx / len) * getSpeed();
motionY = (dy / len) * getSpeed();
motionZ = (dz / len) * getSpeed();
}
} else if (owner != null) {
float yaw = (float) Math.toRadians(owner.rotationYaw);
float pitch = (float) Math.toRadians(owner.rotationPitch);
motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed();
motionY = -Math.sin(pitch) * getSpeed();
motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed();
}
if (DEBUG_LOGGING && !worldObj.isRemote) {
LogWriter.info("[Beam] startFiring: origin=(" + startX + "," + startY + "," + startZ +
") headOffset=(" + headOffsetX + "," + headOffsetY + "," + headOffsetZ +
") motion=(" + motionX + "," + motionY + "," + motionZ + ")");
}
}
@Override
protected boolean checkMaxDistance() {
// Check distance using head offset (distance from origin)
double distFromOrigin = Math.sqrt(
headOffsetX * headOffsetX +
headOffsetY * headOffsetY +
headOffsetZ * headOffsetZ
);
boolean exceeded = distFromOrigin >= getMaxDistance();
if (exceeded && !worldObj.isRemote && DEBUG_LOGGING) {
LogWriter.info("[Beam] DEAD: Max distance exceeded. dist=" + distFromOrigin + " max=" + getMaxDistance());
}
return exceeded;
}
@Override
protected void updateProjectile() {
// Handle charging state (windup phase) - use isCharging() for synced value
if (isCharging()) {
updateCharging();
return;
}
// Store previous head offset for interpolation
prevHeadOffsetX = headOffsetX;
prevHeadOffsetY = headOffsetY;
prevHeadOffsetZ = headOffsetZ;
// Age trail points for fading effect
if (fadeTrail) {
ageTrailPoints();
}
// In preview mode, run movement on client (no server)
if (previewMode) {
updatePreviewMovement();
return;
}
if (worldObj.isRemote) {
// Client: interpolate entity position
handleClientInterpolation();
// Client: derive head offset from entity position and origin
// Entity position = head world position, so headOffset = pos - origin
// Note: During charging, updateCharging() handles origin positioning
// After firing, origin is fixed at the charged position
headOffsetX = posX - startX;
headOffsetY = posY - startY;
headOffsetZ = posZ - startZ;
// Add trail point on client
addTrailPoint();
} else {
// Server-side logic
updateHoming();
// Move head offset
headOffsetX += motionX;
headOffsetY += motionY;
headOffsetZ += motionZ;
// Update origin if attached to owner
if (attachedToOwner) {
Entity owner = getOwnerEntity();
if (owner != null) {
startX = owner.posX;
startY = owner.posY + owner.getEyeHeight() * 0.7;
startZ = owner.posZ;
}
}
// Calculate world position of head for collision detection
double headWorldX = startX + headOffsetX;
double headWorldY = startY + headOffsetY;
double headWorldZ = startZ + headOffsetZ;
// Update entity position to head (for network sync)
this.setPosition(headWorldX, headWorldY, headWorldZ);
// Add trail point
addTrailPoint();
// Debug log periodically
if (DEBUG_LOGGING && ticksExisted % 20 == 0) {
LogWriter.info("[Beam] Server tick=" + ticksExisted + " headOffset=(" +
String.format("%.2f", headOffsetX) + "," +
String.format("%.2f", headOffsetY) + "," +
String.format("%.2f", headOffsetZ) + ") origin=(" +
String.format("%.2f", startX) + "," +
String.format("%.2f", startY) + "," +
String.format("%.2f", startZ) + ") trail=" + trailPoints.size());
}
// Skip collision checks on first few ticks
if (ticksExisted > 2) {
checkBlockCollision(headWorldX, headWorldY, headWorldZ);
checkEntityCollision(headWorldX, headWorldY, headWorldZ);
}
}
}
/**
* Update during charging state - follow owner based on anchor point.
*/
private void updateCharging() {
chargeTick++;
Entity owner = getOwnerEntity();
if (owner == null || owner.isDead) {
setDead();
return;
}
// Calculate position based on anchor point
Vec3 pos;
if (owner instanceof EntityLivingBase) {
pos = AnchorPointHelper.calculateAnchorPosition((EntityLivingBase) owner, anchorData, chargeOffsetDistance);
} else {
// Fallback for non-living entities (shouldn't happen normally)
float yaw = (float) Math.toRadians(owner.rotationYaw);
double offsetX = -Math.sin(yaw) * chargeOffsetDistance;
double offsetZ = Math.cos(yaw) * chargeOffsetDistance;
pos = Vec3.createVectorHelper(
owner.posX + offsetX,
owner.posY + owner.getEyeHeight() * 0.7,
owner.posZ + offsetZ
);
}
setPosition(pos.xCoord, pos.yCoord, pos.zCoord);
// Also update origin for when firing starts
startX = pos.xCoord;
startY = pos.yCoord;
startZ = pos.zCoord;
}
/**
* Age trail points and remove old ones (for comet effect).
*/
private void ageTrailPoints() {
// Age all trail points
for (int i = 0; i < trailPointAges.size(); i++) {
trailPointAges.set(i, trailPointAges.get(i) + 1);
}
// Remove trail points that have fully faded
while (!trailPointAges.isEmpty() && trailPointAges.get(0) >= trailFadeTime) {
trailPointAges.remove(0);
if (!trailPoints.isEmpty()) {
trailPoints.remove(0);
}
}
}
private void addTrailPoint() {
// Trail points are RELATIVE to origin
if (trailPoints.isEmpty()) {
trailPoints.add(Vec3.createVectorHelper(headOffsetX, headOffsetY, headOffsetZ));
if (fadeTrail) {
trailPointAges.add(0);
}
return;
}
Vec3 lastPoint = trailPoints.get(trailPoints.size() - 1);
double dist = Math.sqrt(
(headOffsetX - lastPoint.xCoord) * (headOffsetX - lastPoint.xCoord) +
(headOffsetY - lastPoint.yCoord) * (headOffsetY - lastPoint.yCoord) +
(headOffsetZ - lastPoint.zCoord) * (headOffsetZ - lastPoint.zCoord)
);
if (dist >= MIN_POINT_DISTANCE) {
trailPoints.add(Vec3.createVectorHelper(headOffsetX, headOffsetY, headOffsetZ));
if (fadeTrail) {
trailPointAges.add(0);
}
// Limit trail length (only if not using fading - fading handles its own cleanup)
if (!fadeTrail) {
while (trailPoints.size() > MAX_TRAIL_POINTS) {
trailPoints.remove(0);
}
}
}
}
/**
* Update movement in preview mode (client-side only, no damage).
*/
private void updatePreviewMovement() {
// Move head offset
headOffsetX += motionX;
headOffsetY += motionY;
headOffsetZ += motionZ;
// Update origin if attached to owner
if (attachedToOwner) {
Entity owner = getOwnerEntity();
if (owner != null) {
startX = owner.posX;
startY = owner.posY + owner.getEyeHeight() * 0.7;
startZ = owner.posZ;
}
}
// Calculate world position of head
double headWorldX = startX + headOffsetX;
double headWorldY = startY + headOffsetY;
double headWorldZ = startZ + headOffsetZ;
// Update entity position to head
this.setPosition(headWorldX, headWorldY, headWorldZ);
// Add trail point
addTrailPoint();
}
private void updateHoming() {
if (!isHoming()) return;
Entity target = getTargetEntity();
if (target == null || !target.isEntityAlive()) return;
// Calculate world position of head
double headWorldX = startX + headOffsetX;
double headWorldY = startY + headOffsetY;
double headWorldZ = startZ + headOffsetZ;
double dx = target.posX - headWorldX;
double dy = (target.posY + target.getEyeHeight()) - headWorldY;
double dz = target.posZ - headWorldZ;
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (dist <= getHomingRange() && dist > 0) {
// Calculate effective homing strength - increases when closer to commit to target
float effectiveStrength = getHomingStrength();
if (dist < getHomingRange() * 0.3) {
effectiveStrength = Math.min(1.0f, getHomingStrength() * 2.5f);
} else if (dist < getHomingRange() * 0.6) {
effectiveStrength = Math.min(0.8f, getHomingStrength() * 1.5f);
}
double desiredVX = (dx / dist) * getSpeed();
double desiredVY = (dy / dist) * getSpeed();
double desiredVZ = (dz / dist) * getSpeed();
motionX += (desiredVX - motionX) * effectiveStrength;
motionY += (desiredVY - motionY) * effectiveStrength;
motionZ += (desiredVZ - motionZ) * effectiveStrength;
double vLen = Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ);
if (vLen > 0) {
motionX = (motionX / vLen) * getSpeed();
motionY = (motionY / vLen) * getSpeed();
motionZ = (motionZ / vLen) * getSpeed();
}
}
}
private void checkBlockCollision(double headX, double headY, double headZ) {
// Previous head world position
double prevHeadWorldX = startX + prevHeadOffsetX;
double prevHeadWorldY = startY + prevHeadOffsetY;
double prevHeadWorldZ = startZ + prevHeadOffsetZ;
Vec3 currentPos = Vec3.createVectorHelper(prevHeadWorldX, prevHeadWorldY, prevHeadWorldZ);
Vec3 nextPos = Vec3.createVectorHelper(headX, headY, headZ);
// Use full raytrace that doesn't stop at liquids and checks all blocks
MovingObjectPosition blockHit = worldObj.func_147447_a(currentPos, nextPos, false, true, false);
if (blockHit != null && blockHit.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) {
hasHit = true;
if (isExplosive()) {
posX = blockHit.hitVec.xCoord;
posY = blockHit.hitVec.yCoord;
posZ = blockHit.hitVec.zCoord;
doExplosion();
}
this.setDead();
}
}
private void checkEntityCollision(double headX, double headY, double headZ) {
double hitSize = headSize * 0.5;
AxisAlignedBB hitBox = AxisAlignedBB.getBoundingBox(
headX - hitSize, headY - hitSize, headZ - hitSize,
headX + hitSize, headY + hitSize, headZ + hitSize
);
@SuppressWarnings("unchecked")
List<EntityLivingBase> entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox);
for (EntityLivingBase entity : entities) {
if (shouldIgnoreEntity(entity)) continue;
if (DEBUG_LOGGING) {
LogWriter.info("[Beam] DEAD: Entity collision with " + entity.getClass().getSimpleName() + " at tick " + ticksExisted);
}
hasHit = true;
if (isExplosive()) {
posX = headX;
posY = headY;
posZ = headZ;
doExplosion();
} else {
applyDamage(entity);
}
this.setDead();
return;
}
}
// ==================== GETTERS FOR RENDERER ====================
public float getBeamWidth() {
return beamWidth;
}
public float getHeadSize() {
return headSize;
}
/**
* Get interpolated head offset for smooth rendering.
* Returns offset RELATIVE to origin.
*/
public double getInterpolatedHeadOffsetX(float partialTicks) {
return prevHeadOffsetX + (headOffsetX - prevHeadOffsetX) * partialTicks;
}
public double getInterpolatedHeadOffsetY(float partialTicks) {
return prevHeadOffsetY + (headOffsetY - prevHeadOffsetY) * partialTicks;
}
public double getInterpolatedHeadOffsetZ(float partialTicks) {
return prevHeadOffsetZ + (headOffsetZ - prevHeadOffsetZ) * partialTicks;
}
/**
* Get trail points. These are RELATIVE to origin (startX/Y/Z).
*/
public List<Vec3> getTrailPoints() {
return trailPoints;
}
public double getOriginX() {
return startX;
}
public double getOriginY() {
return startY;
}
public double getOriginZ() {
return startZ;
}
/**
* Whether the tail orb should be rendered (only true in anchored mode).
*/
public boolean shouldRenderTailOrb() {
return renderTailOrb;
}
/**
* Whether the beam is attached to its owner (anchored mode).
*/
public boolean isAttachedToOwner() {
return attachedToOwner;
}
/**
* Get the charge progress (0-1).
*/
public float getChargeProgress() {
if (chargeDuration <= 0) return 1.0f;
return Math.min(1.0f, (float) chargeTick / chargeDuration);
}
/**
* Get interpolated charge progress for smooth rendering.
*/
public float getInterpolatedChargeProgress(float partialTicks) {
if (chargeDuration <= 0) return 1.0f;
float prevProgress = Math.max(0, (float) (chargeTick - 1) / chargeDuration);
float currProgress = Math.min(1.0f, (float) chargeTick / chargeDuration);
return prevProgress + (currProgress - prevProgress) * partialTicks;
}
/**
* Whether trail should fade (comet effect for non-anchored beams).
*/
public boolean hasFadingTrail() {
return fadeTrail;
}
/**
* Get trail point ages for fading calculation.
*/
public List<Integer> getTrailPointAges() {
return trailPointAges;
}
/**
* Get trail fade time in ticks.
*/
public int getTrailFadeTime() {
return trailFadeTime;
}
// ==================== NBT ====================
@Override
protected void readProjectileNBT(NBTTagCompound nbt) {
this.beamWidth = nbt.hasKey("BeamWidth") ? nbt.getFloat("BeamWidth") : 0.3f;
this.headSize = nbt.hasKey("HeadSize") ? nbt.getFloat("HeadSize") : 0.5f;
this.headOffsetX = nbt.hasKey("HeadOffsetX") ? nbt.getDouble("HeadOffsetX") : 0;
this.headOffsetY = nbt.hasKey("HeadOffsetY") ? nbt.getDouble("HeadOffsetY") : 0;
this.headOffsetZ = nbt.hasKey("HeadOffsetZ") ? nbt.getDouble("HeadOffsetZ") : 0;
this.prevHeadOffsetX = this.headOffsetX;
this.prevHeadOffsetY = this.headOffsetY;
this.prevHeadOffsetZ = this.headOffsetZ;
this.attachedToOwner = !nbt.hasKey("AttachedToOwner") || nbt.getBoolean("AttachedToOwner");
this.renderTailOrb = !nbt.hasKey("RenderTailOrb") || nbt.getBoolean("RenderTailOrb");
// Read charging state and sync to data watcher
boolean isCharging = nbt.hasKey("Charging") && nbt.getBoolean("Charging");
this.charging = isCharging;
this.dataWatcher.updateObject(DW_CHARGING, (byte) (isCharging ? 1 : 0));
this.chargeDuration = nbt.hasKey("ChargeDuration") ? nbt.getInteger("ChargeDuration") : 40;
this.chargeTick = nbt.hasKey("ChargeTick") ? nbt.getInteger("ChargeTick") : 0;
this.chargeOffsetDistance = nbt.hasKey("ChargeOffsetDistance") ? nbt.getFloat("ChargeOffsetDistance") : 1.0f;
this.fadeTrail = nbt.hasKey("FadeTrail") && nbt.getBoolean("FadeTrail");
this.trailFadeTime = nbt.hasKey("TrailFadeTime") ? nbt.getInteger("TrailFadeTime") : 20;
// Read trail points (relative to origin)
trailPoints.clear();
if (nbt.hasKey("Trail")) {
NBTTagList trailList = nbt.getTagList("Trail", 10);
for (int i = 0; i < trailList.tagCount(); i++) {
NBTTagCompound point = trailList.getCompoundTagAt(i);
trailPoints.add(Vec3.createVectorHelper(
point.getDouble("X"),
point.getDouble("Y"),
point.getDouble("Z")
));
}
}
}
@Override
protected void writeProjectileNBT(NBTTagCompound nbt) {
nbt.setFloat("BeamWidth", beamWidth);
nbt.setFloat("HeadSize", headSize);
nbt.setDouble("HeadOffsetX", headOffsetX);
nbt.setDouble("HeadOffsetY", headOffsetY);
nbt.setDouble("HeadOffsetZ", headOffsetZ);
nbt.setBoolean("AttachedToOwner", attachedToOwner);
nbt.setBoolean("RenderTailOrb", renderTailOrb);
nbt.setBoolean("Charging", isCharging());
nbt.setInteger("ChargeDuration", chargeDuration);
nbt.setInteger("ChargeTick", chargeTick);
nbt.setFloat("ChargeOffsetDistance", chargeOffsetDistance);
nbt.setBoolean("FadeTrail", fadeTrail);
nbt.setInteger("TrailFadeTime", trailFadeTime);
this.anchorData.writeNBT(nbt);
// Write trail points
NBTTagList trailList = new NBTTagList();
for (Vec3 point : trailPoints) {
NBTTagCompound pointNbt = new NBTTagCompound();
pointNbt.setDouble("X", point.xCoord);
pointNbt.setDouble("Y", point.yCoord);
pointNbt.setDouble("Z", point.zCoord);
trailList.appendTag(pointNbt);
}
nbt.setTag("Trail", trailList);
}
}