numerous fixes to pillars (now they work!)

This commit is contained in:
Vidal committed 2026-03-14 20:22:57 -03:00
1 parent 0222a4984e
commit a2beef0549
4 files changed
+260 -149

No files matched your search

@@ -2,12 +2,13 @@ package kamkeel.npcs.client.renderer;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.client.renderer.lightning.AttachedLightningRenderer;
import kamkeel.npcs.entity.EntityAbilityPillar;
import kamkeel.npcs.entity.EntityAbilityPillar.PillarOrigin;
import kamkeel.npcs.entity.EntityAbilityPillar.PillarShape;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.entity.Entity;
import noppes.npcs.LogWriter;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
/**
@@ -17,12 +18,13 @@ import org.lwjgl.opengl.GL11;
* - CIRCLE: cylindrical pillar rendered with triangulated segments.
* - SQUARE: rectangular prism pillar.
*
* Both modes render three color layers (outer glow, mid, inner core)
* following the same pattern as RenderEnergyLaser.
* Both modes render three color layers (outer glow, mid, inner core).
*
* Origin modes:
* - FROM_GROUND: base at posY, tip grows upward.
* - FROM_ABOVE: base at posY + height, tip grows downward.
* - FROM_GROUND: base at posY, pillar grows upward toward posY + height.
* - FROM_ABOVE: pillar falls from above, tip at posY, grows downward.
* The entity posY is always the ground anchor point.
* Visually: base = posY + targetHeight, tip grows down toward posY.
*/
@SideOnly(Side.CLIENT)
public class RenderEnergyPillar extends RenderEnergy {
@@ -34,12 +36,6 @@ public class RenderEnergyPillar extends RenderEnergy {
public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) {
EntityAbilityPillar pillar = (EntityAbilityPillar) entity;
noppes.npcs.LogWriter.info("RenderEnergyPillar: tick=" + pillar.ticksExisted
+ " charging=" + pillar.isCharging()
+ " growing=" + pillar.isGrowing()
+ " radius=" + pillar.getPillarRadius()
+ " height=" + pillar.getPillarHeight());
if (shouldSkipInitialActiveRender(pillar)) return;
setupRenderState();
@@ -60,17 +56,22 @@ public class RenderEnergyPillar extends RenderEnergy {
return;
}
// Resolve Y extents based on origin
// FROM_GROUND: base at y, tip at y+height (grows up).
// FROM_ABOVE: pillar descends from above. The entity's posY is the ground anchor.
// The full column height is targetHeight. The current height is how far
// it has grown. So the visible column spans from:
// tip = posY + targetHeight - height (starts high, descends)
// base = posY + targetHeight (fixed ceiling)
float baseY, tipY;
float targetHeight = pillar.getPillarData().targetHeight;
if (pillar.getPillarOrigin() == PillarOrigin.FROM_ABOVE) {
baseY = (float) y + height;
tipY = (float) y;
baseY = (float) y + targetHeight;
tipY = (float) y + targetHeight - height;
} else {
baseY = (float) y;
tipY = (float) y + height;
}
// Rotation around Y axis
float rotationAngle = 0f;
float rotationSpeed = pillar.getRotationSpeed();
if (rotationSpeed != 0f) {
@@ -88,15 +89,12 @@ public class RenderEnergyPillar extends RenderEnergy {
float midRadius = innerRadius + (outerRadius - innerRadius) * 0.5f;
GL11.glDepthMask(false);
renderPillarShape(pillar.getPillarShape(), x, baseY, tipY, z,
outerRadius, outerColor, outerAlpha * 0.4f, rotationAngle);
renderPillarShape(pillar.getPillarShape(), x, baseY, tipY, z,
midRadius, outerColor, outerAlpha * 0.7f, rotationAngle);
renderPillarShape(pillar.getPillarShape(), x, baseY, tipY, z, outerRadius, outerColor, outerAlpha * 0.4f, rotationAngle);
renderPillarShape(pillar.getPillarShape(), x, baseY, tipY, z, midRadius, outerColor, outerAlpha * 0.7f, rotationAngle);
GL11.glDepthMask(true);
}
renderPillarShape(pillar.getPillarShape(), x, baseY, tipY, z,
innerRadius, innerColor, innerAlpha, rotationAngle);
renderPillarShape(pillar.getPillarShape(), x, baseY, tipY, z, innerRadius, innerColor, innerAlpha, rotationAngle);
if (pillar.hasLightningEffect()) {
float midWorldY = (baseY + tipY) * 0.5f;
@@ -119,14 +117,13 @@ public class RenderEnergyPillar extends RenderEnergy {
if (renderRadius <= 0.01f) return;
// Pulse effect
float pulseTime = pillar.ticksExisted + partialTicks;
float pulse = (float) Math.sin(pulseTime * 0.2f) * 0.08f;
renderRadius *= (1.0f + pulse);
float height = 0.05f; // MIN_HEIGHT — flat disc during charging
// During charging, render a flat disc at ground level
float baseY = (float) y;
float tipY = baseY + height;
float tipY = baseY + 0.05f;
float rotationAngle = pillar.getRotationSpeed() != 0f
? (pillar.ticksExisted + partialTicks) * pillar.getRotationSpeed()
@@ -139,7 +136,7 @@ public class RenderEnergyPillar extends RenderEnergy {
if (pillar.hasLightningEffect()) {
GL11.glPushMatrix();
GL11.glTranslated(x, y + height * 0.5f, z);
GL11.glTranslated(x, y + 0.025f, z);
renderAttachedLightning(pillar, INNER_SCALE, renderRadius);
GL11.glPopMatrix();
}
@@ -150,15 +147,12 @@ public class RenderEnergyPillar extends RenderEnergy {
float midRadius = innerRadius + (outerRadius - innerRadius) * 0.5f;
GL11.glDepthMask(false);
renderPillarShape(pillar.getPillarShape(), x, baseY, tipY, z,
outerRadius, outerColor, outerAlpha * 0.4f, rotationAngle);
renderPillarShape(pillar.getPillarShape(), x, baseY, tipY, z,
midRadius, outerColor, outerAlpha * 0.7f, rotationAngle);
renderPillarShape(pillar.getPillarShape(), x, baseY, tipY, z, outerRadius, outerColor, outerAlpha * 0.4f, rotationAngle);
renderPillarShape(pillar.getPillarShape(), x, baseY, tipY, z, midRadius, outerColor, outerAlpha * 0.7f, rotationAngle);
GL11.glDepthMask(true);
}
renderPillarShape(pillar.getPillarShape(), x, baseY, tipY, z,
innerRadius, innerColor, innerAlpha, rotationAngle);
renderPillarShape(pillar.getPillarShape(), x, baseY, tipY, z, innerRadius, innerColor, innerAlpha, rotationAngle);
}
// ==================== SHAPE DISPATCH ====================
@@ -175,10 +169,6 @@ public class RenderEnergyPillar extends RenderEnergy {
// ==================== CYLINDER ====================
/**
* Render a cylindrical pillar with lateral faces and top/bottom caps.
* Rotation is applied around Y axis.
*/
private void renderCylinderPillar(double cx, float baseY, float tipY, double cz,
float radius, int color, float alpha, float rotationAngle) {
float[] rgb = extractRGB(color);
@@ -203,11 +193,9 @@ public class RenderEnergyPillar extends RenderEnergy {
float z1 = (float) cz + sinTable[i] * radius;
float x2 = (float) cx + cosTable[i + 1] * radius;
float z2 = (float) cz + sinTable[i + 1] * radius;
float nx = (cosTable[i] + cosTable[i + 1]) * 0.5f;
float nz = (sinTable[i] + sinTable[i + 1]) * 0.5f;
tess.setNormal(nx, 0, nz);
tess.addVertex(x1, baseY, z1);
tess.addVertex(x2, baseY, z2);
tess.addVertex(x2, tipY, z2);
@@ -250,24 +238,18 @@ public class RenderEnergyPillar extends RenderEnergy {
// ==================== SQUARE ====================
/**
* Render a rectangular prism pillar with four lateral faces and top/bottom caps.
* Rotation is applied around Y axis.
*/
private void renderSquarePillar(double cx, float baseY, float tipY, double cz,
float radius, int color, float alpha, float rotationAngle) {
float[] rgb = extractRGB(color);
float r = rgb[0], g = rgb[1], b = rgb[2];
// Four corners of the square, rotated around Y
double rotRad = Math.toRadians(rotationAngle);
float cosA = (float) Math.cos(rotRad);
float sinA = (float) Math.sin(rotRad);
// Local corners before rotation: (±radius, ±radius)
float[][] corners = new float[4][2];
float[] lx = {-radius, radius, radius, -radius};
float[] lz = {-radius, -radius, radius, radius};
float[][] corners = new float[4][2];
for (int i = 0; i < 4; i++) {
corners[i][0] = (float) cx + lx[i] * cosA - lz[i] * sinA;
corners[i][1] = (float) cz + lx[i] * sinA + lz[i] * cosA;
@@ -277,19 +259,15 @@ public class RenderEnergyPillar extends RenderEnergy {
tess.startDrawingQuads();
tess.setColorRGBA_F(r, g, b, alpha);
// Four lateral faces
for (int i = 0; i < 4; i++) {
int next = (i + 1) % 4;
float x1 = corners[i][0], z1 = corners[i][1];
float x2 = corners[next][0], z2 = corners[next][1];
// Outward normal (midpoint of edge, normalized)
float nx = (x1 + x2) * 0.5f - (float) cx;
float nz = (z1 + z2) * 0.5f - (float) cz;
float nlen = (float) Math.sqrt(nx * nx + nz * nz);
if (nlen > 0) { nx /= nlen; nz /= nlen; }
tess.setNormal(nx, 0, nz);
tess.addVertex(x1, baseY, z1);
tess.addVertex(x2, baseY, z2);
tess.addVertex(x2, tipY, z2);
@@ -314,7 +292,7 @@ public class RenderEnergyPillar extends RenderEnergy {
}
@Override
protected net.minecraft.util.ResourceLocation getEntityTexture(Entity entity) {
protected ResourceLocation getEntityTexture(Entity entity) {
return WHITE_TEXTURE;
}
}
@@ -6,9 +6,9 @@ import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyPillarData;
import kamkeel.npcs.controllers.data.ability.enums.LockMode;
import kamkeel.npcs.controllers.data.ability.enums.TargetingMode;
import kamkeel.npcs.controllers.data.ability.enums.HitType;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
@@ -27,10 +27,16 @@ import java.util.List;
* or fall from the sky.
*
* ANCHORED: pillar spawns at the target's position and stays there.
* MOVING: pillar spawns in front of the caster and travels forward,
* optionally homing toward a target.
* When count > 1, each subsequent pillar recalculates a random position
* near the target at spawn time.
*
* MOVING: pillar spawns in front of the caster and travels forward.
* With homing: steers toward target.
* Without homing: straight line along caster look vector or toward target.
* When count > 1, pillarOffset spaces pillars apart along the travel direction.
*
* Telegraph shape matches PillarShape (CIRCLE or SQUARE).
* In MOVING mode, telegraph appears at the spawn point in front of the caster.
*/
public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
@@ -39,6 +45,12 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
private EnergyPillarData pillarData = new EnergyPillarData();
private EnergyHomingData homingData = new EnergyHomingData();
/**
* Spacing between pillars in MOVING mode when count > 1.
* 0 = all spawn at the same position.
*/
private float pillarOffset = 0f;
// ==================== CONSTRUCTOR ====================
public AbilityPillar() {
@@ -47,16 +59,8 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
new EnergyCombatData(),
new EnergyLifespanData()
);
this.typeId = "ability.cnpc.pillar";
this.name = "Pillar";
this.targetingMode = TargetingMode.AGGRO_TARGET;
this.maxRange = 25.0f;
this.minRange = 5.0f;
this.cooldownTicks = 0;
this.windUpTicks = 30;
this.lockMovement = LockMode.WINDUP;
this.telegraphType = pillarData.shape.getTelegraphType();
this.showTelegraph = true;
combatData.hitType = HitType.MULTI;
combatData.multiHitDelayTicks = 20;
}
// ==================== ABSTRACT IMPLEMENTATIONS ====================
@@ -77,6 +81,8 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
@Override
protected void setupEntityCharging(EntityAbilityPillar entity, int index) {
// The charge visual should appear at ground level at the spawn position,
// not following the caster anchor. Position is already set by createEntity.
entity.setupCharging(windUpTicks);
}
@@ -101,42 +107,81 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
@Override
protected double[] getSpawnPosition(EntityLivingBase caster, EntityLivingBase target, int index) {
if (pillarData.mode == PillarMode.MOVING) {
// Spawn in front of caster along look vector
Vec3 look = caster.getLookVec();
if (look != null) {
float dist = Math.max(1.0f, pillarData.targetRadius);
return new double[]{
caster.posX + look.xCoord * dist,
caster.posY,
caster.posZ + look.zCoord * dist
};
if (look == null) {
return new double[]{caster.posX, caster.posY, caster.posZ};
}
return new double[]{caster.posX, caster.posY, caster.posZ};
float baseDist = Math.max(1.0f, pillarData.targetRadius);
float offsetDist = baseDist + pillarOffset * index;
return new double[]{
caster.posX + look.xCoord * offsetDist,
caster.posY,
caster.posZ + look.zCoord * offsetDist
};
}
// ANCHORED: use pre-calculated position or target position
return super.getSpawnPosition(caster, target, index);
// ANCHORED: index 0 uses pre-calculated telegraph position.
// Subsequent indices recalculate a random nearby position at spawn time.
if (index == 0) {
return super.getSpawnPosition(caster, target, index);
}
// For index > 0, find a new random position near the target
double baseX = target != null ? target.posX : caster.posX;
double baseZ = target != null ? target.posZ : caster.posZ;
double baseY = target != null ? target.posY : caster.posY;
float spawnRadius = Math.max(pillarData.targetRadius * 2.0f, 3.0f);
double angle = Math.random() * Math.PI * 2;
double dist = Math.sqrt(Math.random()) * spawnRadius;
return new double[]{
baseX + Math.cos(angle) * dist,
baseY,
baseZ + Math.sin(angle) * dist
};
}
// ==================== EXECUTE — startGrowing ====================
// ==================== EXECUTE ====================
@Override
protected void spawnZoneAt(EntityLivingBase caster, EntityLivingBase target, int index) {
super.spawnZoneAt(caster, target, index);
EntityAbilityPillar entity = entities[index];
if (entity != null && !entity.isDead) {
if (target != null) {
entity.setTarget(target);
}
entity.startGrowing();
if (entity == null || entity.isDead) return;
if (target != null) {
entity.setTarget(target);
}
// Set straight-line motion for MOVING mode without homing
if (pillarData.mode == PillarMode.MOVING && !homingData.isHoming()) {
double mx = 0, mz = 0;
if (target != null) {
double dx = target.posX - entity.posX;
double dz = target.posZ - entity.posZ;
double len = Math.sqrt(dx * dx + dz * dz);
if (len > 0) {
mx = (dx / len) * homingData.getSpeed();
mz = (dz / len) * homingData.getSpeed();
}
} else {
Vec3 look = caster.getLookVec();
if (look != null) {
mx = look.xCoord * homingData.getSpeed();
mz = look.zCoord * homingData.getSpeed();
}
}
entity.setInitialMotion(mx, mz);
}
entity.startGrowing();
}
// ==================== TELEGRAPH ====================
@Override
public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) {
if (!showTelegraph || telegraphType == TelegraphType.NONE || isPlayerCaster(caster) || target == null) {
if (!showTelegraph || telegraphType == TelegraphType.NONE || isPlayerCaster(caster)) {
return null;
}
@@ -153,17 +198,44 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
telegraph.setWarningStartTick(Math.max(5, windUpTicks / 4));
telegraph.setHeightOffset(telegraphHeightOffset);
double groundY = findGroundLevel(caster.worldObj, target.posX, target.posY, target.posZ);
double telegraphX, telegraphZ, telegraphY;
// Store position for use in getSpawnPosition (ANCHORED mode)
preCalculatedPositions.clear();
for (int i = 0; i < zoneCount; i++) {
preCalculatedPositions.add(new double[]{target.posX, groundY, target.posZ});
if (pillarData.mode == PillarMode.MOVING) {
// Telegraph appears at the spawn point in front of the caster
Vec3 look = caster.getLookVec();
if (look != null) {
float dist = Math.max(1.0f, pillarData.targetRadius);
telegraphX = caster.posX + look.xCoord * dist;
telegraphZ = caster.posZ + look.zCoord * dist;
} else {
telegraphX = caster.posX;
telegraphZ = caster.posZ;
}
telegraphY = findGroundLevel(caster.worldObj, telegraphX, caster.posY, telegraphZ);
preCalculatedPositions.clear();
preCalculatedPositions.add(new double[]{telegraphX, telegraphY, telegraphZ});
} else {
// ANCHORED: telegraph at target position
if (target == null) return null;
telegraphX = target.posX;
telegraphZ = target.posZ;
telegraphY = findGroundLevel(caster.worldObj, telegraphX, target.posY, telegraphZ);
preCalculatedPositions.clear();
preCalculatedPositions.add(new double[]{telegraphX, telegraphY, telegraphZ});
}
TelegraphInstance instance = new TelegraphInstance(telegraph, target.posX, groundY, target.posZ, caster.rotationYaw);
TelegraphInstance instance = new TelegraphInstance(
telegraph, telegraphX, telegraphY, telegraphZ, caster.rotationYaw);
instance.setCasterEntityId(caster.getEntityId());
instance.setEntityIdToFollow(homingData.isHoming() ? target.getEntityId() : -1);
// In ANCHORED+homing, telegraph follows the target
if (pillarData.mode == PillarMode.ANCHORED && homingData.isHoming() && target != null) {
instance.setEntityIdToFollow(target.getEntityId());
} else {
instance.setEntityIdToFollow(-1);
}
return instance;
}
@@ -179,12 +251,14 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
protected void writeTypeSpecificNBT(NBTTagCompound nbt) {
pillarData.writeNBT(nbt);
homingData.writeNBT(nbt);
nbt.setFloat("PillarOffset", pillarOffset);
}
@Override
protected void readTypeSpecificNBT(NBTTagCompound nbt) {
pillarData.readNBT(nbt);
homingData.readNBT(nbt);
this.pillarOffset = nbt.hasKey("PillarOffset") ? nbt.getFloat("PillarOffset") : 0f;
}
// ==================== GUI ====================
@@ -195,16 +269,13 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
defs.add(FieldDef.section("ability.section.pillar"));
defs.add(FieldDef.enumField("ability.pillarMode", PillarMode.class,
() -> pillarData.mode,
v -> pillarData.mode = v));
() -> pillarData.mode, v -> pillarData.mode = v));
defs.add(FieldDef.enumField("ability.pillarOrigin", EntityAbilityPillar.PillarOrigin.class,
() -> pillarData.origin,
v -> pillarData.origin = v));
() -> pillarData.origin, v -> pillarData.origin = v));
defs.add(FieldDef.enumField("ability.pillarShape", PillarShape.class,
() -> pillarData.shape,
v -> pillarData.shape = v));
() -> pillarData.shape, v -> pillarData.shape = v));
defs.add(FieldDef.row(
FieldDef.floatField("ability.pillar.targetRadius", () -> pillarData.targetRadius, v -> pillarData.targetRadius = v).range(0.1f, 64f),
@@ -219,9 +290,17 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
defs.add(FieldDef.intField("ability.pillar.spawnDelay", () -> pillarData.spawnDelay, v -> pillarData.spawnDelay = Math.max(0, v))
.range(0, 200));
defs.add(FieldDef.section("ability.section.homing")
defs.add(FieldDef.floatField("ability.pillar.offset", () -> pillarOffset, v -> pillarOffset = Math.max(0f, v))
.visibleWhen(() -> zoneCount > 1)
.range(0f, 20f));
defs.add(FieldDef.section("ability.section.movement")
.visibleWhen(() -> pillarData.mode == PillarMode.MOVING));
defs.add(FieldDef.floatField("ability.speed", () -> homingData.getSpeed(), v -> homingData.setSpeed(v))
.visibleWhen(() -> pillarData.mode == PillarMode.MOVING)
.range(0.01f, 10f));
defs.add(FieldDef.boolField("ability.homing", () -> homingData.isHoming(), v -> homingData.setHoming(v))
.visibleWhen(() -> pillarData.mode == PillarMode.MOVING));
@@ -230,10 +309,6 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
FieldDef.floatField("ability.homingRange", () -> homingData.getHomingRange(), v -> homingData.setHomingRange(v))
).visibleWhen(() -> pillarData.mode == PillarMode.MOVING && homingData.isHoming()));
defs.add(FieldDef.floatField("ability.speed", () -> homingData.getSpeed(), v -> homingData.setSpeed(v))
.visibleWhen(() -> pillarData.mode == PillarMode.MOVING)
.range(0.01f, 10f));
defs.add(FieldDef.section("ability.section.combat"));
defs.add(FieldDef.floatField("ability.damage", this::getDamage, this::setDamage).range(0f, 1000f));
@@ -248,4 +323,7 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
public EnergyHomingData getHomingData() { return homingData; }
public void setHomingData(EnergyHomingData data) { this.homingData = data != null ? data : new EnergyHomingData(); }
public float getPillarOffset() { return pillarOffset; }
public void setPillarOffset(float offset) { this.pillarOffset = Math.max(0f, offset); }
}
@@ -1,14 +1,17 @@
package kamkeel.npcs.entity;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyPillarData;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData;
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyPillarData;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
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;
import java.util.List;
@@ -19,7 +22,8 @@ import java.util.List;
* Behavior modes:
* - ANCHORED: spawns at a fixed position and stays there.
* - MOVING: spawns in front of the caster and travels forward,
* optionally homing toward a target.
* optionally homing toward a target. Without homing, travels in a straight
* line along the initial direction set by setInitialMotion().
*
* Lifecycle:
* 1. Charging — pillarRadius grows from 0 to targetRadius; pillarHeight stays at MIN_HEIGHT.
@@ -35,6 +39,10 @@ public class EntityAbilityPillar extends EntityEnergyZone {
private static final float MIN_HEIGHT = 0.05f;
// DataWatcher slots (base uses 20 for charging)
private static final int DW_PILLAR_RADIUS = 21;
private static final int DW_PILLAR_HEIGHT = 22;
// ==================== ENUMS ====================
public enum PillarMode {
@@ -52,11 +60,8 @@ public class EntityAbilityPillar extends EntityEnergyZone {
SQUARE;
public TelegraphType getTelegraphType() {
if (this == CIRCLE) {
return TelegraphType.CIRCLE;
} else {
return TelegraphType.SQUARE;
}
if (this == CIRCLE) return TelegraphType.CIRCLE;
else return TelegraphType.SQUARE;
}
}
@@ -64,11 +69,13 @@ public class EntityAbilityPillar extends EntityEnergyZone {
private EnergyPillarData pillarData = new EnergyPillarData();
// ==================== PILLAR DIMENSIONS ====================
// ==================== PILLAR DIMENSIONS — LOCAL ====================
private float pillarRadius = 0.01f;
private float pillarHeight = MIN_HEIGHT;
// ==================== PILLAR DIMENSIONS — RENDER INTERPOLATION ====================
private float renderPillarRadius = 0.01f;
private float renderPillarHeight = MIN_HEIGHT;
@@ -107,26 +114,52 @@ public class EntityAbilityPillar extends EntityEnergyZone {
snapToGround(x, y, z);
this.pillarRadius = 0.01f;
this.pillarHeight = MIN_HEIGHT;
setVisualRadius(0.01f);
setVisualHeight(MIN_HEIGHT);
setPillarRadiusInternal(0.01f);
setPillarHeightInternal(MIN_HEIGHT);
syncHitbox();
}
// ==================== ENTITY INIT ====================
@Override
protected void entityInit() {
super.entityInit();
this.dataWatcher.addObject(DW_PILLAR_RADIUS, 0.01f);
this.dataWatcher.addObject(DW_PILLAR_HEIGHT, MIN_HEIGHT);
}
// ==================== INTERNAL DIMENSION SETTERS ====================
private void setPillarRadiusInternal(float value) {
this.pillarRadius = value;
if (!previewMode && worldObj != null && !worldObj.isRemote) {
this.dataWatcher.updateObject(DW_PILLAR_RADIUS, value);
}
}
private void setPillarHeightInternal(float value) {
this.pillarHeight = value;
if (!previewMode && worldObj != null && !worldObj.isRemote) {
this.dataWatcher.updateObject(DW_PILLAR_HEIGHT, value);
}
}
// ==================== SETUP ====================
/**
* Set the entity this pillar tracks in MOVING+homing mode.
*/
public void setTarget(EntityLivingBase target) {
this.targetEntityId = target != null ? target.getEntityId() : -1;
}
/**
* Set up for GUI preview rendering.
* Set straight-line motion for MOVING mode without homing.
* Called by the ability before spawning, using the caster's look vector or
* direction toward target.
*/
public void setInitialMotion(double mx, double mz) {
this.motionX = mx;
this.motionZ = mz;
}
public void setupPreview(EntityLivingBase owner, EnergyPillarData pillarData,
EnergyDisplayData display, EnergyLightningData lightning,
int chargeDuration) {
@@ -135,10 +168,12 @@ public class EntityAbilityPillar extends EntityEnergyZone {
this.displayData = display != null ? display.copy() : new EnergyDisplayData();
this.lightningData = lightning != null ? lightning.copy() : new EnergyLightningData();
setupCharging(chargeDuration);
this.pillarRadius = 0.01f;
this.pillarHeight = MIN_HEIGHT;
setVisualRadius(0.01f);
setVisualHeight(MIN_HEIGHT);
setPillarRadiusInternal(0.01f);
setPillarHeightInternal(MIN_HEIGHT);
this.renderPillarRadius = 0.01f;
this.prevRenderPillarRadius = 0.01f;
this.renderPillarHeight = MIN_HEIGHT;
this.prevRenderPillarHeight = MIN_HEIGHT;
}
/**
@@ -149,6 +184,9 @@ public class EntityAbilityPillar extends EntityEnergyZone {
setCharging(false);
this.spawnDelayTick = 0;
this.growing = pillarData.spawnDelay <= 0;
setPillarRadiusInternal(pillarData.targetRadius);
setPillarHeightInternal(MIN_HEIGHT);
syncHitbox();
}
// ==================== CHARGING ====================
@@ -157,8 +195,8 @@ public class EntityAbilityPillar extends EntityEnergyZone {
protected void updateCharging() {
chargeTick++;
float progress = getChargeProgress();
this.pillarRadius = pillarData.targetRadius * progress;
this.pillarHeight = MIN_HEIGHT;
setPillarRadiusInternal(pillarData.targetRadius * progress);
setPillarHeightInternal(MIN_HEIGHT);
syncHitbox();
}
@@ -169,27 +207,29 @@ public class EntityAbilityPillar extends EntityEnergyZone {
prevRenderPillarRadius = renderPillarRadius;
prevRenderPillarHeight = renderPillarHeight;
if (worldObj.isRemote) {
float dwRadius = this.dataWatcher.getWatchableObjectFloat(DW_PILLAR_RADIUS);
float dwHeight = this.dataWatcher.getWatchableObjectFloat(DW_PILLAR_HEIGHT);
this.pillarRadius = dwRadius;
this.pillarHeight = dwHeight;
renderPillarRadius += (pillarRadius - renderPillarRadius) * 0.15f;
renderPillarHeight += (pillarHeight - renderPillarHeight) * 0.15f;
return;
}
if (!growing) {
spawnDelayTick++;
if (spawnDelayTick >= pillarData.spawnDelay) {
growing = true;
}
renderPillarRadius += (pillarRadius - renderPillarRadius) * 0.15f;
renderPillarHeight += (pillarHeight - renderPillarHeight) * 0.15f;
return;
}
if (worldObj.isRemote) {
renderPillarRadius += (pillarRadius - renderPillarRadius) * 0.15f;
renderPillarHeight += (pillarHeight - renderPillarHeight) * 0.15f;
return;
}
if (pillarRadius < pillarData.targetRadius) {
pillarRadius = Math.min(pillarData.targetRadius, pillarRadius + pillarData.radiusGrowSpeed);
setPillarRadiusInternal(Math.min(pillarData.targetRadius, pillarRadius + pillarData.radiusGrowSpeed));
}
if (pillarHeight < pillarData.targetHeight) {
pillarHeight = Math.min(pillarData.targetHeight, pillarHeight + pillarData.heightGrowSpeed);
setPillarHeightInternal(Math.min(pillarData.targetHeight, pillarHeight + pillarData.heightGrowSpeed));
}
syncHitbox();
@@ -207,6 +247,7 @@ public class EntityAbilityPillar extends EntityEnergyZone {
private void updateMovement() {
if (!homingData.isHoming()) {
// Straight-line: motion was set by setInitialMotion() at spawn time
this.posX += motionX;
this.posZ += motionZ;
snapToGround(posX, posY, posZ);
@@ -272,10 +313,6 @@ public class EntityAbilityPillar extends EntityEnergyZone {
}
}
/**
* Narrow-phase XZ check using PillarShape.
* AABB broad phase already filtered by Y; this checks the XZ footprint.
*/
private boolean isInPillar(EntityLivingBase entity) {
double dx = entity.posX - posX;
double dz = entity.posZ - posZ;
@@ -288,16 +325,20 @@ public class EntityAbilityPillar extends EntityEnergyZone {
}
}
/**
* Sync entity bounding box to current pillar dimensions.
* Called after any dimension change so Minecraft chunk tracking stays accurate.
*/
private void syncHitbox() {
float w = Math.max(0.1f, pillarRadius * 2.0f);
float h = Math.max(0.1f, pillarHeight);
this.setSize(w, h);
}
@Override
@SideOnly(Side.CLIENT)
public boolean isInRangeToRenderDist(double distance) {
double extent = Math.max(pillarRadius, pillarHeight) * 2.0D;
double range = Math.max(128.0D, extent * 4.0D + 64.0D);
return distance < range * range;
}
// ==================== ENTITY HELPERS ====================
public Entity getTargetEntity() {
@@ -348,19 +389,28 @@ public class EntityAbilityPillar extends EntityEnergyZone {
pillarData.readNBT(nbt);
homingData.readNBT(nbt);
this.pillarRadius = Math.max(0.01f, nbt.getFloat("PillarRadius"));
this.pillarHeight = Math.max(MIN_HEIGHT, nbt.getFloat("PillarHeight"));
float radius = Math.max(0.01f, nbt.getFloat("PillarRadius"));
float height = Math.max(MIN_HEIGHT, nbt.getFloat("PillarHeight"));
this.pillarRadius = radius;
this.pillarHeight = height;
if (this.dataWatcher != null) {
this.dataWatcher.updateObject(DW_PILLAR_RADIUS, radius);
this.dataWatcher.updateObject(DW_PILLAR_HEIGHT, height);
}
this.renderPillarRadius = radius;
this.prevRenderPillarRadius = radius;
this.renderPillarHeight = height;
this.prevRenderPillarHeight = height;
this.spawnDelayTick = nbt.getInteger("SpawnDelayTick");
this.growing = nbt.getBoolean("Growing");
this.targetEntityId = nbt.hasKey("TargetEntityId") ? nbt.getInteger("TargetEntityId") : -1;
this.motionX = nbt.getDouble("MotionX");
this.motionZ = nbt.getDouble("MotionZ");
this.renderPillarRadius = this.pillarRadius;
this.prevRenderPillarRadius = this.pillarRadius;
this.renderPillarHeight = this.pillarHeight;
this.prevRenderPillarHeight = this.pillarHeight;
syncHitbox();
}
}
@@ -20,7 +20,12 @@ import net.minecraft.world.World;
import noppes.npcs.NpcDamageSource;
import noppes.npcs.entity.EntityNPCInterface;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Abstract base class for all energy zone entities.
@@ -50,7 +55,7 @@ public abstract class EntityEnergyZone extends EntityEnergyAbility {
protected static final float MAX_ZONE_RADIUS = 64.0f;
protected static final float MAX_ZONE_HEIGHT = 64.0f;
protected static final double GROUND_OFFSET = 0.005;
protected static final double GROUND_OFFSET = 0.05;
// ==================== LIFESPAN ====================