mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 16:37:45 +00:00
398 lines
12 KiB
Java
398 lines
12 KiB
Java
package kamkeel.npcs.controllers.data.telegraph;
|
|
|
|
import kamkeel.npcs.controllers.data.ability.Ability;
|
|
import net.minecraft.entity.Entity;
|
|
import net.minecraft.nbt.NBTTagCompound;
|
|
import net.minecraft.world.World;
|
|
|
|
import java.util.UUID;
|
|
|
|
/**
|
|
* Runtime instance of an active telegraph.
|
|
* Tracks position, timing, and state for rendering.
|
|
*/
|
|
public class TelegraphInstance {
|
|
|
|
// Identity
|
|
private String instanceId;
|
|
private Telegraph telegraph;
|
|
|
|
// Position (current)
|
|
private double x;
|
|
private double y;
|
|
private double z;
|
|
private float yaw;
|
|
|
|
// Position (previous tick - for interpolation)
|
|
private double prevX;
|
|
private double prevY;
|
|
private double prevZ;
|
|
private float prevYaw;
|
|
|
|
// Entity tracking
|
|
private int entityIdToFollow = -1;
|
|
private int casterEntityId = -1;
|
|
private int targetEntityId = -1; // For LINE telegraphs: entity to face (yaw tracking)
|
|
private boolean trackFollowedEntityYaw = false; // Track followed entity's rotationYaw when no targetEntity
|
|
|
|
// Timing
|
|
private int remainingTicks;
|
|
private int totalTicks;
|
|
|
|
// State
|
|
private boolean isWarning = false;
|
|
|
|
// Server-side tracking (for removal)
|
|
private transient World world;
|
|
private transient int dimensionId;
|
|
|
|
public TelegraphInstance() {
|
|
this.instanceId = UUID.randomUUID().toString().substring(0, 8);
|
|
}
|
|
|
|
public TelegraphInstance(Telegraph telegraph, double x, double y, double z, float yaw) {
|
|
this();
|
|
this.telegraph = new Telegraph(telegraph);
|
|
this.x = x;
|
|
this.y = y;
|
|
this.z = z;
|
|
this.yaw = yaw;
|
|
// Initialize prev positions to current for first frame
|
|
this.prevX = x;
|
|
this.prevY = y;
|
|
this.prevZ = z;
|
|
this.prevYaw = yaw;
|
|
this.remainingTicks = telegraph.getDurationTicks();
|
|
this.totalTicks = telegraph.getDurationTicks();
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// TICK LOGIC
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
/**
|
|
* Called every tick to update telegraph state.
|
|
*
|
|
* @param world The world (for entity lookup)
|
|
* @return false if telegraph should be removed
|
|
*/
|
|
public boolean tick(World world) {
|
|
if (remainingTicks <= 0) {
|
|
return false;
|
|
}
|
|
|
|
// Save previous position for interpolation
|
|
this.prevX = this.x;
|
|
this.prevY = this.y;
|
|
this.prevZ = this.z;
|
|
this.prevYaw = this.yaw;
|
|
|
|
remainingTicks--;
|
|
|
|
// Update position if following an entity
|
|
if (entityIdToFollow >= 0 && world != null) {
|
|
Entity entity = world.getEntityByID(entityIdToFollow);
|
|
if (entity != null) {
|
|
this.x = entity.posX;
|
|
this.z = entity.posZ;
|
|
// Find ground level below the entity for proper telegraph placement
|
|
int searchRange = (telegraph != null) ? telegraph.getGroundSearchRange() : 3;
|
|
this.y = Ability.findGroundLevel(world, entity.posX, entity.posY, entity.posZ, searchRange);
|
|
|
|
// Track followed entity's yaw for directional telegraphs (player casters)
|
|
if (trackFollowedEntityYaw && targetEntityId < 0) {
|
|
this.yaw = entity.rotationYaw;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update yaw to face target entity (for LINE telegraphs)
|
|
if (targetEntityId >= 0 && world != null) {
|
|
Entity target = world.getEntityByID(targetEntityId);
|
|
if (target != null) {
|
|
double dx = target.posX - this.x;
|
|
double dz = target.posZ - this.z;
|
|
this.yaw = (float) (Math.atan2(-dx, dz) * 180.0 / Math.PI);
|
|
}
|
|
}
|
|
|
|
// Check for warning phase transition
|
|
if (!isWarning && telegraph != null) {
|
|
if (remainingTicks <= telegraph.getWarningStartTick()) {
|
|
isWarning = true;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get progress from 0.0 (just started) to 1.0 (finished).
|
|
*/
|
|
public float getProgress() {
|
|
if (totalTicks <= 0) return 1.0f;
|
|
return 1.0f - ((float) remainingTicks / (float) totalTicks);
|
|
}
|
|
|
|
/**
|
|
* Get the current display color (normal or warning).
|
|
*/
|
|
public int getCurrentColor() {
|
|
if (telegraph == null) return 0x80FF0000;
|
|
return isWarning ? telegraph.getWarningColor() : telegraph.getColor();
|
|
}
|
|
|
|
/**
|
|
* Get alpha-modulated color for breathing animation.
|
|
*/
|
|
public int getAnimatedColor(float partialTicks) {
|
|
int baseColor = getCurrentColor();
|
|
if (telegraph == null || !telegraph.isAnimated()) {
|
|
return baseColor;
|
|
}
|
|
|
|
// Breathing animation: slow, smooth modulation of alpha
|
|
float time = (totalTicks - remainingTicks) + partialTicks;
|
|
// Slower breathing: 0.08 for ~4 second cycle, smoother sine curve
|
|
float breathPhase = (float) Math.sin(time * 0.08);
|
|
// Smooth easing: square the sine for more organic feel, range 0.5 to 1.0
|
|
float pulse = 0.5f + (breathPhase * breathPhase * 0.5f * Math.signum(breathPhase));
|
|
|
|
int alpha = (baseColor >> 24) & 0xFF;
|
|
int r = (baseColor >> 16) & 0xFF;
|
|
int g = (baseColor >> 8) & 0xFF;
|
|
int b = baseColor & 0xFF;
|
|
|
|
alpha = (int) (alpha * pulse);
|
|
|
|
return (alpha << 24) | (r << 16) | (g << 8) | b;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// INTERPOLATED POSITION (for smooth rendering)
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
/**
|
|
* Get interpolated X position for smooth rendering.
|
|
*/
|
|
public double getInterpolatedX(float partialTicks) {
|
|
return prevX + (x - prevX) * partialTicks;
|
|
}
|
|
|
|
/**
|
|
* Get interpolated Y position for smooth rendering.
|
|
*/
|
|
public double getInterpolatedY(float partialTicks) {
|
|
return prevY + (y - prevY) * partialTicks;
|
|
}
|
|
|
|
/**
|
|
* Get interpolated Z position for smooth rendering.
|
|
*/
|
|
public double getInterpolatedZ(float partialTicks) {
|
|
return prevZ + (z - prevZ) * partialTicks;
|
|
}
|
|
|
|
/**
|
|
* Get interpolated yaw for smooth rendering.
|
|
* Handles angle wrapping for smooth rotation.
|
|
*/
|
|
public float getInterpolatedYaw(float partialTicks) {
|
|
// Handle angle wrapping to prevent spinning the wrong way
|
|
float diff = yaw - prevYaw;
|
|
while (diff > 180.0f) diff -= 360.0f;
|
|
while (diff < -180.0f) diff += 360.0f;
|
|
return prevYaw + diff * partialTicks;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// NBT SERIALIZATION
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
public NBTTagCompound writeNBT() {
|
|
NBTTagCompound nbt = new NBTTagCompound();
|
|
nbt.setString("instanceId", instanceId);
|
|
|
|
if (telegraph != null) {
|
|
nbt.setTag("telegraph", telegraph.writeNBT());
|
|
}
|
|
|
|
nbt.setDouble("x", x);
|
|
nbt.setDouble("y", y);
|
|
nbt.setDouble("z", z);
|
|
nbt.setFloat("yaw", yaw);
|
|
|
|
nbt.setInteger("entityIdToFollow", entityIdToFollow);
|
|
nbt.setInteger("casterEntityId", casterEntityId);
|
|
nbt.setInteger("targetEntityId", targetEntityId);
|
|
nbt.setBoolean("trackFollowedYaw", trackFollowedEntityYaw);
|
|
|
|
nbt.setInteger("remainingTicks", remainingTicks);
|
|
nbt.setInteger("totalTicks", totalTicks);
|
|
|
|
nbt.setBoolean("isWarning", isWarning);
|
|
|
|
return nbt;
|
|
}
|
|
|
|
public void readNBT(NBTTagCompound nbt) {
|
|
this.instanceId = nbt.getString("instanceId");
|
|
|
|
if (nbt.hasKey("telegraph")) {
|
|
this.telegraph = new Telegraph();
|
|
this.telegraph.readNBT(nbt.getCompoundTag("telegraph"));
|
|
}
|
|
|
|
this.x = nbt.getDouble("x");
|
|
this.y = nbt.getDouble("y");
|
|
this.z = nbt.getDouble("z");
|
|
this.yaw = nbt.getFloat("yaw");
|
|
|
|
this.entityIdToFollow = nbt.getInteger("entityIdToFollow");
|
|
this.casterEntityId = nbt.getInteger("casterEntityId");
|
|
this.targetEntityId = nbt.getInteger("targetEntityId");
|
|
this.trackFollowedEntityYaw = nbt.hasKey("trackFollowedYaw") && nbt.getBoolean("trackFollowedYaw");
|
|
|
|
this.remainingTicks = nbt.getInteger("remainingTicks");
|
|
this.totalTicks = nbt.getInteger("totalTicks");
|
|
|
|
this.isWarning = nbt.getBoolean("isWarning");
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// GETTERS & SETTERS
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
public String getInstanceId() {
|
|
return instanceId;
|
|
}
|
|
|
|
public void setInstanceId(String instanceId) {
|
|
this.instanceId = instanceId;
|
|
}
|
|
|
|
public Telegraph getTelegraph() {
|
|
return telegraph;
|
|
}
|
|
|
|
public void setTelegraph(Telegraph telegraph) {
|
|
this.telegraph = telegraph;
|
|
}
|
|
|
|
public double getX() {
|
|
return x;
|
|
}
|
|
|
|
public void setX(double x) {
|
|
this.x = x;
|
|
}
|
|
|
|
public double getY() {
|
|
return y;
|
|
}
|
|
|
|
public void setY(double y) {
|
|
this.y = y;
|
|
}
|
|
|
|
public double getZ() {
|
|
return z;
|
|
}
|
|
|
|
public void setZ(double z) {
|
|
this.z = z;
|
|
}
|
|
|
|
public float getYaw() {
|
|
return yaw;
|
|
}
|
|
|
|
public void setYaw(float yaw) {
|
|
this.yaw = yaw;
|
|
}
|
|
|
|
public int getEntityIdToFollow() {
|
|
return entityIdToFollow;
|
|
}
|
|
|
|
public void setEntityIdToFollow(int entityIdToFollow) {
|
|
this.entityIdToFollow = entityIdToFollow;
|
|
}
|
|
|
|
/**
|
|
* Locks the telegraph at its current position and direction.
|
|
* Stops following any entity - used when ability commits to action.
|
|
*/
|
|
public void lockPosition() {
|
|
this.entityIdToFollow = -1;
|
|
this.targetEntityId = -1;
|
|
}
|
|
|
|
public int getCasterEntityId() {
|
|
return casterEntityId;
|
|
}
|
|
|
|
public void setCasterEntityId(int casterEntityId) {
|
|
this.casterEntityId = casterEntityId;
|
|
}
|
|
|
|
public int getTargetEntityId() {
|
|
return targetEntityId;
|
|
}
|
|
|
|
public void setTargetEntityId(int targetEntityId) {
|
|
this.targetEntityId = targetEntityId;
|
|
}
|
|
|
|
public boolean isTrackFollowedEntityYaw() {
|
|
return trackFollowedEntityYaw;
|
|
}
|
|
|
|
public void setTrackFollowedEntityYaw(boolean track) {
|
|
this.trackFollowedEntityYaw = track;
|
|
}
|
|
|
|
public int getRemainingTicks() {
|
|
return remainingTicks;
|
|
}
|
|
|
|
public void setRemainingTicks(int remainingTicks) {
|
|
this.remainingTicks = remainingTicks;
|
|
}
|
|
|
|
public int getTotalTicks() {
|
|
return totalTicks;
|
|
}
|
|
|
|
public void setTotalTicks(int totalTicks) {
|
|
this.totalTicks = totalTicks;
|
|
}
|
|
|
|
public boolean isWarning() {
|
|
return isWarning;
|
|
}
|
|
|
|
public void setWarning(boolean warning) {
|
|
isWarning = warning;
|
|
}
|
|
|
|
public World getWorld() {
|
|
return world;
|
|
}
|
|
|
|
public void setWorld(World world) {
|
|
this.world = world;
|
|
if (world != null) {
|
|
this.dimensionId = world.provider.dimensionId;
|
|
}
|
|
}
|
|
|
|
public int getDimensionId() {
|
|
return dimensionId;
|
|
}
|
|
|
|
public void setDimensionId(int dimensionId) {
|
|
this.dimensionId = dimensionId;
|
|
}
|
|
}
|