mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 18:57:32 +00:00
yet more pillar fixes
This commit is contained in:
1 parent
9546500b4b
commit
956d2e27be
6 files changed
+438
-246
No files matched your search
@@ -11,20 +11,13 @@ import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Renders EntityAbilityPillar as a vertical energy column.
|
||||
* Renders EntityAbilityPillar.
|
||||
*
|
||||
* Shape modes:
|
||||
* - CIRCLE: cylindrical pillar with triangulated segments.
|
||||
* - SQUARE: rectangular prism pillar.
|
||||
* Radius: snaps immediately to targetRadius when charging ends — no interpolation.
|
||||
* Height: grows linearly with no easing, at the speed defined in pillarData.
|
||||
*
|
||||
* Both modes render three color layers (outer glow, mid, inner core).
|
||||
*
|
||||
* Origin modes:
|
||||
* - FROM_GROUND: posY = ground base. Pillar grows upward (baseY = posY, tipY = posY + height).
|
||||
* - FROM_ABOVE: posY = top anchor (ceiling). Pillar grows downward
|
||||
* (baseY = posY, tipY = posY - height).
|
||||
*
|
||||
* During charging, a flat disc grows at ground level (FROM_GROUND) or ceiling level (FROM_ABOVE).
|
||||
* FROM_GROUND: baseY = posY, tipY = posY + height (grows up).
|
||||
* FROM_ABOVE: baseY = posY, tipY = posY - height (grows down from ceiling anchor).
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class RenderEnergyPillar extends RenderEnergy {
|
||||
@@ -39,7 +32,6 @@ public class RenderEnergyPillar extends RenderEnergy {
|
||||
if (shouldSkipInitialActiveRender(pillar)) return;
|
||||
|
||||
setupRenderState();
|
||||
|
||||
float proximityAlpha = getProximityAlphaFactor(pillar, x, y, z);
|
||||
|
||||
if (pillar.isCharging()) {
|
||||
@@ -48,7 +40,9 @@ public class RenderEnergyPillar extends RenderEnergy {
|
||||
return;
|
||||
}
|
||||
|
||||
float radius = pillar.getInterpolatedPillarRadius(partialTicks);
|
||||
// Radius: use raw value directly — snaps on charging end, no interpolation needed
|
||||
float radius = pillar.getPillarRadius();
|
||||
// Height: linearly interpolated between prev and current (no easing)
|
||||
float height = pillar.getInterpolatedPillarHeight(partialTicks);
|
||||
|
||||
if (radius <= 0.01f || height <= 0.01f) {
|
||||
@@ -56,8 +50,6 @@ public class RenderEnergyPillar extends RenderEnergy {
|
||||
return;
|
||||
}
|
||||
|
||||
// FROM_GROUND: base at y, tip grows up.
|
||||
// FROM_ABOVE: posY is the top anchor. Base = y, tip = y - height (grows down).
|
||||
float baseY, tipY;
|
||||
if (pillar.getPillarOrigin() == PillarOrigin.FROM_ABOVE) {
|
||||
baseY = (float) y;
|
||||
@@ -103,16 +95,17 @@ public class RenderEnergyPillar extends RenderEnergy {
|
||||
|
||||
private void renderCharging(EntityAbilityPillar pillar, double x, double y, double z,
|
||||
float partialTicks, float proximityAlpha) {
|
||||
float radius = pillar.getTargetRadius() * 2;
|
||||
// Use targetRadius directly — pillarRadius during charging is only 0.01 initially
|
||||
// and grows via DataWatcher which is unavailable on preview entities.
|
||||
float targetRadius = pillar.getPillarData().targetRadius;
|
||||
float chargeProgress = pillar.getInterpolatedChargeProgress(partialTicks);
|
||||
float renderRadius = radius * chargeProgress;
|
||||
float renderRadius = targetRadius * chargeProgress;
|
||||
|
||||
if (renderRadius <= 0.01f) return;
|
||||
|
||||
float pulseTime = pillar.ticksExisted + partialTicks;
|
||||
renderRadius *= (1.0f + (float) Math.sin(pulseTime * 0.2f) * 0.08f);
|
||||
|
||||
// Flat disc at the anchor point
|
||||
float baseY = (float) y;
|
||||
float tipY = baseY + 0.05f;
|
||||
|
||||
@@ -174,7 +167,6 @@ public class RenderEnergyPillar extends RenderEnergy {
|
||||
|
||||
Tessellator tess = Tessellator.instance;
|
||||
|
||||
// Lateral faces
|
||||
tess.startDrawingQuads();
|
||||
tess.setColorRGBA_F(r, g, b, alpha);
|
||||
for (int i = 0; i < CIRCLE_SEGMENTS; i++) {
|
||||
@@ -182,9 +174,7 @@ 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.setNormal((cosTable[i] + cosTable[i + 1]) * 0.5f, 0, (sinTable[i] + sinTable[i + 1]) * 0.5f);
|
||||
tess.addVertex(x1, baseY, z1);
|
||||
tess.addVertex(x2, baseY, z2);
|
||||
tess.addVertex(x2, tipY, z2);
|
||||
@@ -195,7 +185,6 @@ public class RenderEnergyPillar extends RenderEnergy {
|
||||
float capMinY = Math.min(baseY, tipY);
|
||||
float capMaxY = Math.max(baseY, tipY);
|
||||
|
||||
// Bottom cap
|
||||
tess.startDrawingQuads();
|
||||
tess.setColorRGBA_F(r, g, b, alpha);
|
||||
tess.setNormal(0, -1, 0);
|
||||
@@ -211,7 +200,6 @@ public class RenderEnergyPillar extends RenderEnergy {
|
||||
}
|
||||
tess.draw();
|
||||
|
||||
// Top cap
|
||||
tess.startDrawingQuads();
|
||||
tess.setColorRGBA_F(r, g, b, alpha);
|
||||
tess.setNormal(0, 1, 0);
|
||||
|
||||
+66
-2
@@ -13,8 +13,8 @@ public class EnergyPillarData {
|
||||
|
||||
public float targetRadius = 2.0f;
|
||||
public float targetHeight = 10.0f;
|
||||
public float radiusGrowSpeed = 0.1f;
|
||||
public float heightGrowSpeed = 0.2f;
|
||||
public float radiusGrowSpeed = 0.5f;
|
||||
public float heightGrowSpeed = 1.0f;
|
||||
public PillarMode mode = PillarMode.ANCHORED;
|
||||
public PillarOrigin origin = PillarOrigin.FROM_GROUND;
|
||||
public PillarShape shape = PillarShape.CIRCLE;
|
||||
@@ -37,6 +37,70 @@ public class EnergyPillarData {
|
||||
this.spawnDelay = Math.max(0, spawnDelay);
|
||||
}
|
||||
|
||||
public float getTargetRadius() {
|
||||
return targetRadius;
|
||||
}
|
||||
|
||||
public void setTargetRadius(float targetRadius) {
|
||||
this.targetRadius = targetRadius;
|
||||
}
|
||||
|
||||
public float getTargetHeight() {
|
||||
return targetHeight;
|
||||
}
|
||||
|
||||
public void setTargetHeight(float targetHeight) {
|
||||
this.targetHeight = targetHeight;
|
||||
}
|
||||
|
||||
public float getRadiusGrowSpeed() {
|
||||
return radiusGrowSpeed;
|
||||
}
|
||||
|
||||
public void setRadiusGrowSpeed(float radiusGrowSpeed) {
|
||||
this.radiusGrowSpeed = radiusGrowSpeed;
|
||||
}
|
||||
|
||||
public float getHeightGrowSpeed() {
|
||||
return heightGrowSpeed;
|
||||
}
|
||||
|
||||
public void setHeightGrowSpeed(float heightGrowSpeed) {
|
||||
this.heightGrowSpeed = heightGrowSpeed;
|
||||
}
|
||||
|
||||
public PillarMode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(PillarMode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public PillarOrigin getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
public void setOrigin(PillarOrigin origin) {
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
public PillarShape getShape() {
|
||||
return shape;
|
||||
}
|
||||
|
||||
public void setShape(PillarShape shape) {
|
||||
this.shape = shape;
|
||||
}
|
||||
|
||||
public int getSpawnDelay() {
|
||||
return spawnDelay;
|
||||
}
|
||||
|
||||
public void setSpawnDelay(int spawnDelay) {
|
||||
this.spawnDelay = spawnDelay;
|
||||
}
|
||||
|
||||
public void writeNBT(NBTTagCompound nbt) {
|
||||
nbt.setFloat("targetRadius", targetRadius);
|
||||
nbt.setFloat("targetHeight", targetHeight);
|
||||
|
||||
+10
-2
@@ -210,6 +210,14 @@ public abstract class AbilityEnergyZone<E extends EntityEnergyZone> extends Abil
|
||||
return getSpawnPosition(caster, target, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the entity ID the charge visual should follow during charging, or -1 for none.
|
||||
* Default: -1 (static). Override to make the charge visual track a moving entity.
|
||||
*/
|
||||
protected int getChargeVisualFollowEntityId(EntityLivingBase caster, EntityLivingBase target, int index) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected void spawnZoneEntity(E entity, int index) {
|
||||
if (entity == null) return;
|
||||
spawnAbilityEntity(entity);
|
||||
@@ -236,14 +244,14 @@ public abstract class AbilityEnergyZone<E extends EntityEnergyZone> extends Abil
|
||||
if (isPreview() || caster == null || caster.worldObj == null || caster.worldObj.isRemote) return;
|
||||
if (chargeVisualIds == null || index < 0 || index >= chargeVisualIds.length) return;
|
||||
|
||||
|
||||
|
||||
double[] windupPos = getWindupSpawnPosition(caster, target, index);
|
||||
E previewEntity = createZoneEntityAt(caster, target, index, windupPos);
|
||||
previewEntity.setPreviewMode(true);
|
||||
previewEntity.setPreviewOwner(caster);
|
||||
setupEntityCharging(previewEntity, index);
|
||||
previewEntity.setChargeDuration(Math.max(1, chargeDuration));
|
||||
int followId = getChargeVisualFollowEntityId(caster, target, index);
|
||||
previewEntity.setFollowEntityId(followId);
|
||||
|
||||
String id = getChargeVisualId(caster, index);
|
||||
chargeVisualIds[index] = id;
|
||||
|
||||
+252
-127
@@ -2,13 +2,7 @@ package kamkeel.npcs.controllers.data.ability.type.energy;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
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.HitType;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.*;
|
||||
import kamkeel.npcs.controllers.data.ability.enums.LockMode;
|
||||
import kamkeel.npcs.controllers.data.ability.enums.TargetingMode;
|
||||
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
|
||||
@@ -26,44 +20,48 @@ import noppes.npcs.client.gui.builder.FieldDef;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Pillar zone ability — spawns one or more energy pillars that rise from the ground
|
||||
* or fall from the sky.
|
||||
*
|
||||
* ANCHORED: pillar spawns at the target's position at execute time and stays there.
|
||||
* When count > 1, each subsequent pillar gets a random nearby position.
|
||||
*
|
||||
* MOVING: pillar spawns in front of the caster at execute time 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 along the chosen OffsetAxis.
|
||||
*
|
||||
* Charge visual appears at the target/spawn position during windup (tick 1),
|
||||
* growing in radius at ground level — exactly above the telegraph.
|
||||
*/
|
||||
import static kamkeel.npcs.entity.EntityAbilityPillar.MIN_HEIGHT;
|
||||
import static kamkeel.npcs.entity.EntityAbilityPillar.MIN_RADIUS;
|
||||
import static kamkeel.npcs.entity.EntityAbilityPillar.MAX_ZONE_HEIGHT;
|
||||
import static kamkeel.npcs.entity.EntityAbilityPillar.MAX_ZONE_RADIUS;
|
||||
|
||||
// TODO 1. add another mode, static, 2. fix charging radius, rotation, etc
|
||||
/**
|
||||
* Pillar zone ability.
|
||||
*
|
||||
* ANCHORED: spawns at target position at execute time.
|
||||
* - trackTarget: predicts where target will be based on current velocity, spawns there.
|
||||
* - followTarget (requires homing): pillar chases target after spawn.
|
||||
* When count > 1: each pillar after index 0 gets a random nearby position.
|
||||
*
|
||||
* MOVING: spawns in front of caster and travels forward.
|
||||
* - Without homing: straight line.
|
||||
* - With homing: steers toward target.
|
||||
* When count > 1: pillarOffset spaces pillars along offsetAxis relative to look vector.
|
||||
* OffsetAxis.Z = along look direction (one behind the other).
|
||||
* OffsetAxis.X = perpendicular to look direction (side by side).
|
||||
*
|
||||
* TODO 1. fix charging visual for ABOVE origin; 2. fix hitbox for ABOVE origin; 3. fix trackTarget to actually track the player
|
||||
*/
|
||||
public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
|
||||
|
||||
// ==================== PILLAR CONFIG ====================
|
||||
// ==================== CONFIG ====================
|
||||
|
||||
private EnergyPillarData pillarData = new EnergyPillarData();
|
||||
private EnergyHomingData homingData = new EnergyHomingData();
|
||||
|
||||
/** Spacing between pillars when count > 1. 0 = same position. */
|
||||
private float pillarOffset = 0f;
|
||||
|
||||
/** Axis along which pillarOffset is applied in MOVING mode. */
|
||||
private OffsetAxis offsetAxis = OffsetAxis.Z;
|
||||
|
||||
/** ANCHORED only: follow target after spawn (requires homing). */
|
||||
private boolean followTarget = false;
|
||||
|
||||
/** ANCHORED only: predict target position at spawn time from target velocity. */
|
||||
private boolean trackTarget = false;
|
||||
|
||||
// ==================== CONSTRUCTOR ====================
|
||||
|
||||
public AbilityPillar() {
|
||||
super(
|
||||
new EnergyDisplayData(),
|
||||
new EnergyCombatData(),
|
||||
new EnergyLifespanData()
|
||||
);
|
||||
super(new EnergyDisplayData(), new EnergyCombatData(), new EnergyLifespanData());
|
||||
this.typeId = "ability.cnpc.pillar";
|
||||
this.name = "Pillar";
|
||||
this.targetingMode = TargetingMode.AGGRO_TARGET;
|
||||
@@ -88,8 +86,6 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
|
||||
|
||||
@Override
|
||||
protected void setupEntityCharging(EntityAbilityPillar entity, int index) {
|
||||
// Charge visual: entity is already at the correct ground position.
|
||||
// Just set it up for charging — it will grow in radius at that position.
|
||||
entity.setupCharging(windUpTicks);
|
||||
}
|
||||
|
||||
@@ -111,75 +107,99 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
|
||||
|
||||
// ==================== SPAWN POSITIONS ====================
|
||||
|
||||
/**
|
||||
* Spawn position at execute time.
|
||||
* MOVING: in front of caster with offset per index along offsetAxis.
|
||||
* ANCHORED index 0: pre-calculated telegraph position.
|
||||
* ANCHORED index > 0: random position near target.
|
||||
*/
|
||||
@Override
|
||||
protected double[] getSpawnPosition(EntityLivingBase caster, EntityLivingBase target, int index) {
|
||||
if (pillarData.mode == PillarMode.MOVING) {
|
||||
Vec3 look = caster.getLookVec();
|
||||
if (look == null) return new double[]{caster.posX, caster.posY, caster.posZ};
|
||||
float baseDist = Math.max(1.0f, pillarData.targetRadius);
|
||||
double ox = offsetAxis == OffsetAxis.X ? pillarOffset * index : 0;
|
||||
double oz = offsetAxis == OffsetAxis.Z ? pillarOffset * index : 0;
|
||||
return new double[]{
|
||||
caster.posX + look.xCoord * baseDist + ox,
|
||||
caster.posY,
|
||||
caster.posZ + look.zCoord * baseDist + oz
|
||||
};
|
||||
return getMovingSpawnPosition(caster, index);
|
||||
}
|
||||
|
||||
// ANCHORED: spawn at target's current position at execute time
|
||||
if (index == 0) {
|
||||
return super.getSpawnPosition(caster, target, index);
|
||||
if (target == null) return new double[]{caster.posX, caster.posY, caster.posZ};
|
||||
double spawnX = target.posX;
|
||||
double spawnZ = target.posZ;
|
||||
if (trackTarget) {
|
||||
// Predict where target will be based on current motion and spawnDelay
|
||||
double prediction = pillarData.spawnDelay * homingData.getHomingStrength();
|
||||
spawnX += target.motionX * prediction;
|
||||
spawnZ += target.motionZ * prediction;
|
||||
}
|
||||
return new double[]{spawnX, target.posY, spawnZ};
|
||||
}
|
||||
|
||||
// index > 0: random nearby position
|
||||
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 spread = Math.max(pillarData.targetRadius * 2.0f, 3.0f);
|
||||
double angle = Math.random() * Math.PI * 2;
|
||||
double dist = Math.sqrt(Math.random()) * spread;
|
||||
return new double[]{
|
||||
baseX + Math.cos(angle) * dist,
|
||||
baseY,
|
||||
baseZ + Math.sin(angle) * dist
|
||||
};
|
||||
return new double[]{baseX + Math.cos(angle) * dist, baseY, baseZ + Math.sin(angle) * dist};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getChargeVisualFollowEntityId(EntityLivingBase caster, EntityLivingBase target, int index) {
|
||||
// ANCHORED: charge visual follows target during charging
|
||||
// MOVING: charge visual is static at spawn point
|
||||
if (pillarData.mode == PillarMode.ANCHORED && target != null) {
|
||||
return target.getEntityId();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Windup spawn position for charge visual.
|
||||
* Always uses the target/telegraph position so the charge visual
|
||||
* appears at ground level exactly above the telegraph.
|
||||
*/
|
||||
@Override
|
||||
protected double[] getWindupSpawnPosition(EntityLivingBase caster, EntityLivingBase target, int index) {
|
||||
if (pillarData.mode == PillarMode.MOVING) {
|
||||
// Charge visual appears at the same spot the pillar will spawn
|
||||
Vec3 look = caster.getLookVec();
|
||||
if (look == null) return new double[]{caster.posX, caster.posY, caster.posZ};
|
||||
float baseDist = Math.max(1.0f, pillarData.targetRadius);
|
||||
double ox = offsetAxis == OffsetAxis.X ? pillarOffset * index : 0;
|
||||
double oz = offsetAxis == OffsetAxis.Z ? pillarOffset * index : 0;
|
||||
return new double[]{
|
||||
caster.posX + look.xCoord * baseDist + ox,
|
||||
caster.posY,
|
||||
caster.posZ + look.zCoord * baseDist + oz
|
||||
};
|
||||
return getMovingSpawnPosition(caster, index);
|
||||
}
|
||||
// ANCHORED: use pre-calculated telegraph position for index 0,
|
||||
// target position for the rest
|
||||
if (index == 0 && !preCalculatedPositions.isEmpty()) {
|
||||
return preCalculatedPositions.get(0);
|
||||
}
|
||||
if (target != null) {
|
||||
return new double[]{target.posX, target.posY, target.posZ};
|
||||
}
|
||||
if (target != null) return new double[]{target.posX, target.posY, target.posZ};
|
||||
return new double[]{caster.posX, caster.posY, caster.posZ};
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn position for MOVING mode.
|
||||
* OffsetAxis.Z = offset along look direction (one behind the other).
|
||||
* OffsetAxis.X = offset perpendicular to look direction (side by side).
|
||||
*/
|
||||
private double[] getMovingSpawnPosition(EntityLivingBase caster, int index) {
|
||||
Vec3 look = caster.getLookVec();
|
||||
if (look == null) return new double[]{caster.posX, caster.posY, caster.posZ};
|
||||
|
||||
float baseDist = Math.max(1.0f, pillarData.targetRadius);
|
||||
|
||||
// Forward direction (normalized XZ)
|
||||
double fwdX = look.xCoord;
|
||||
double fwdZ = look.zCoord;
|
||||
double fwdLen = Math.sqrt(fwdX * fwdX + fwdZ * fwdZ);
|
||||
if (fwdLen > 0) { fwdX /= fwdLen; fwdZ /= fwdLen; }
|
||||
|
||||
// Right direction = perpendicular to forward in XZ plane
|
||||
double rightX = fwdZ;
|
||||
double rightZ = -fwdX;
|
||||
|
||||
double ox, oz;
|
||||
if (offsetAxis == OffsetAxis.Z) {
|
||||
// Along look direction
|
||||
ox = fwdX * pillarOffset * index;
|
||||
oz = fwdZ * pillarOffset * index;
|
||||
} else {
|
||||
// Perpendicular to look direction
|
||||
// Center the group: offset by -(count-1)/2 * spacing + index * spacing
|
||||
double centerOffset = -(zoneCount - 1) * 0.5 * pillarOffset + index * pillarOffset;
|
||||
ox = rightX * centerOffset;
|
||||
oz = rightZ * centerOffset;
|
||||
}
|
||||
|
||||
return new double[]{
|
||||
caster.posX + fwdX * baseDist + ox,
|
||||
caster.posY,
|
||||
caster.posZ + fwdZ * baseDist + oz
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== EXECUTE ====================
|
||||
|
||||
@Override
|
||||
@@ -192,22 +212,21 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
|
||||
entity.setTarget(target);
|
||||
}
|
||||
|
||||
if (pillarData.mode == PillarMode.ANCHORED) {
|
||||
entity.setFollowTarget(followTarget && homingData.isHoming());
|
||||
}
|
||||
|
||||
if (pillarData.mode == PillarMode.MOVING && !homingData.isHoming()) {
|
||||
Vec3 look = caster.getLookVec();
|
||||
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();
|
||||
}
|
||||
if (len > 0) { mx = (dx / len) * homingData.getSpeed(); mz = (dz / len) * homingData.getSpeed(); }
|
||||
} else if (look != null) {
|
||||
mx = look.xCoord * homingData.getSpeed();
|
||||
mz = look.zCoord * homingData.getSpeed();
|
||||
}
|
||||
entity.setInitialMotion(mx, mz);
|
||||
}
|
||||
@@ -219,16 +238,11 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
|
||||
|
||||
@Override
|
||||
public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) {
|
||||
if (!showTelegraph || telegraphType == TelegraphType.NONE || isPlayerCaster(caster)) {
|
||||
return null;
|
||||
}
|
||||
if (!showTelegraph || telegraphType == TelegraphType.NONE || isPlayerCaster(caster)) return null;
|
||||
|
||||
Telegraph telegraph;
|
||||
if (pillarData.shape == PillarShape.SQUARE) {
|
||||
telegraph = Telegraph.square(pillarData.targetRadius);
|
||||
} else {
|
||||
telegraph = Telegraph.circle(pillarData.targetRadius);
|
||||
}
|
||||
Telegraph telegraph = pillarData.shape == PillarShape.SQUARE
|
||||
? Telegraph.square(pillarData.targetRadius)
|
||||
: Telegraph.circle(pillarData.targetRadius);
|
||||
|
||||
telegraph.setDurationTicks(windUpTicks);
|
||||
telegraph.setColor(windUpColor);
|
||||
@@ -239,18 +253,13 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
|
||||
double telegraphX, telegraphZ, telegraphY;
|
||||
|
||||
if (pillarData.mode == PillarMode.MOVING) {
|
||||
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;
|
||||
}
|
||||
double[] pos = getMovingSpawnPosition(caster, 0);
|
||||
telegraphX = pos[0];
|
||||
telegraphZ = pos[2];
|
||||
telegraphY = findGroundLevel(caster.worldObj, telegraphX, caster.posY, telegraphZ);
|
||||
} else {
|
||||
if (target == null) return null;
|
||||
// Regular: start at target's current position, telegraph follows target
|
||||
telegraphX = target.posX;
|
||||
telegraphZ = target.posZ;
|
||||
telegraphY = findGroundLevel(caster.worldObj, telegraphX, target.posY, telegraphZ);
|
||||
@@ -262,8 +271,14 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
|
||||
TelegraphInstance instance = new TelegraphInstance(
|
||||
telegraph, telegraphX, telegraphY, telegraphZ, caster.rotationYaw);
|
||||
instance.setCasterEntityId(caster.getEntityId());
|
||||
instance.setEntityIdToFollow(-1);
|
||||
|
||||
// ANCHORED: telegraph follows target in real time (regular and trackTarget)
|
||||
// MOVING: telegraph is static at spawn point
|
||||
if (pillarData.mode == PillarMode.ANCHORED && target != null) {
|
||||
instance.setEntityIdToFollow(target.getEntityId());
|
||||
} else {
|
||||
instance.setEntityIdToFollow(-1);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -280,6 +295,8 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
|
||||
homingData.writeNBT(nbt);
|
||||
nbt.setFloat("pillarOffset", pillarOffset);
|
||||
nbt.setInteger("offsetAxis", offsetAxis.ordinal());
|
||||
nbt.setBoolean("followTarget", followTarget);
|
||||
nbt.setBoolean("trackTarget", trackTarget);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -290,6 +307,8 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
|
||||
int axisOrd = nbt.hasKey("offsetAxis") ? nbt.getInteger("offsetAxis") : 1;
|
||||
this.offsetAxis = (axisOrd >= 0 && axisOrd < OffsetAxis.values().length)
|
||||
? OffsetAxis.values()[axisOrd] : OffsetAxis.Z;
|
||||
this.followTarget = nbt.hasKey("followTarget") && nbt.getBoolean("followTarget");
|
||||
this.trackTarget = nbt.hasKey("trackTarget") && nbt.getBoolean("trackTarget");
|
||||
}
|
||||
|
||||
// ==================== GUI ====================
|
||||
@@ -298,47 +317,54 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
|
||||
@Override
|
||||
protected void addTypeDefinitions(List<FieldDef> defs) {
|
||||
FieldDef.insertAfter(defs, "ability.fireDelay", FieldDef.row(
|
||||
FieldDef.floatField("ability.pillar.offset", () -> pillarOffset, v -> pillarOffset = Math.max(0f, v)).range(0f, 20f),
|
||||
FieldDef.enumField("ability.pillar.offsetAxis", OffsetAxis.class, () -> offsetAxis, v -> offsetAxis = v)
|
||||
).visibleWhen(() -> zoneCount > 1));
|
||||
FieldDef.floatField("ability.pillar.offset", this::getPillarOffset, this::setPillarOffset).range(0f, 20f),
|
||||
FieldDef.enumField("ability.pillar.offsetAxis", OffsetAxis.class, this::getOffsetAxis, this::setOffsetAxis)
|
||||
).visibleWhen(() -> zoneCount > 1 && pillarData.mode == PillarMode.MOVING));
|
||||
|
||||
defs.add(FieldDef.section("ability.section.pillar"));
|
||||
|
||||
defs.add(FieldDef.enumField("ability.pillarMode", PillarMode.class,
|
||||
() -> pillarData.mode, v -> pillarData.mode = v));
|
||||
this::getMode, this::setMode));
|
||||
|
||||
defs.add(FieldDef.enumField("ability.pillarOrigin", EntityAbilityPillar.PillarOrigin.class,
|
||||
() -> pillarData.origin, v -> pillarData.origin = v));
|
||||
this::getOrigin, this::setOrigin));
|
||||
|
||||
defs.add(FieldDef.enumField("ability.pillarShape", PillarShape.class,
|
||||
() -> pillarData.shape, v -> pillarData.shape = v));
|
||||
this::getShape, this::setShape));
|
||||
|
||||
defs.add(FieldDef.intField("ability.pillar.spawnDelay", () -> pillarData.spawnDelay, v -> pillarData.spawnDelay = Math.max(0, v))
|
||||
.range(0, 200));
|
||||
defs.add(FieldDef.intField("ability.pillar.spawnDelay",
|
||||
this::getSpawnDelay, this::setSpawnDelay).range(0, getMaxLifetime()));
|
||||
|
||||
defs.add(FieldDef.section("ability.section.size"));
|
||||
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.pillar.targetRadius", () -> pillarData.targetRadius, v -> pillarData.targetRadius = v).range(0.1f, 64f),
|
||||
FieldDef.floatField("ability.pillar.targetHeight", () -> pillarData.targetHeight, v -> pillarData.targetHeight = v).range(0.1f, 64f)
|
||||
FieldDef.floatField("ability.pillar.targetRadius", this::getTargetRadius, this::setTargetRadius).range(0.1f, 64f),
|
||||
FieldDef.floatField("ability.pillar.targetHeight", this::getTargetHeight, this::setTargetHeight).range(0.1f, 64f)
|
||||
));
|
||||
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.pillar.radiusGrowSpeed", () -> pillarData.radiusGrowSpeed, v -> pillarData.radiusGrowSpeed = Math.max(0.01f, v)),
|
||||
FieldDef.floatField("ability.pillar.heightGrowSpeed", () -> pillarData.heightGrowSpeed, v -> pillarData.heightGrowSpeed = Math.max(0.01f, v))
|
||||
FieldDef.floatField("ability.pillar.radiusGrowSpeed", this::getRadiusGrowSpeed, this::setRadiusGrowSpeed),
|
||||
FieldDef.floatField("ability.pillar.heightGrowSpeed", this::getHeightGrowSpeed, this::setRadiusGrowSpeed)
|
||||
));
|
||||
|
||||
defs.add(FieldDef.section("ability.section.movement"));
|
||||
|
||||
defs.add(FieldDef.floatField("ability.speed", () -> homingData.getSpeed(), v -> homingData.setSpeed(v))
|
||||
defs.add(FieldDef.floatField("ability.speed", this::getPillarSpeed, this::setPillarSpeed)
|
||||
.range(0.01f, 10f));
|
||||
|
||||
defs.add(FieldDef.boolField("ability.homing", () -> homingData.isHoming(), v -> homingData.setHoming(v)));
|
||||
defs.add(FieldDef.boolField("ability.homing", this::isHoming, this::setHoming));
|
||||
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.floatField("ability.homingStrength", () -> homingData.getHomingStrength(), v -> homingData.setHomingStrength(v)).range(0f, 1f),
|
||||
FieldDef.floatField("ability.homingRange", () -> homingData.getHomingRange(), v -> homingData.setHomingRange(v))
|
||||
).visibleWhen(() -> homingData.isHoming()));
|
||||
FieldDef.floatField("ability.homingStrength", this::getHomingStrength, this::setHomingStrength),
|
||||
FieldDef.floatField("ability.homingRange", this::getHomingRange, this::setHomingRange)
|
||||
).visibleWhen(this::isHoming));
|
||||
|
||||
// ANCHORED-only options
|
||||
defs.add(FieldDef.boolField("ability.pillar.trackTarget", this::isTrackTarget, this::setTrackTarget)
|
||||
.visibleWhen(() -> pillarData.mode == PillarMode.ANCHORED));
|
||||
|
||||
defs.add(FieldDef.boolField("ability.pillar.followTarget", this::isFollowTarget, this::setFollowTarget)
|
||||
.visibleWhen(() -> pillarData.mode == PillarMode.ANCHORED && homingData.isHoming()));
|
||||
|
||||
defs.add(FieldDef.section("ability.section.combat"));
|
||||
|
||||
@@ -354,12 +380,111 @@ public class AbilityPillar extends AbilityEnergyZone<EntityAbilityPillar> {
|
||||
public EnergyPillarData getPillarData() { return pillarData; }
|
||||
public void setPillarData(EnergyPillarData data) { this.pillarData = data != null ? data : new EnergyPillarData(); }
|
||||
|
||||
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); }
|
||||
public void setPillarOffset(float offset) { this.pillarOffset = Math.max(0f, Math.min(20f, offset)); }
|
||||
|
||||
public OffsetAxis getOffsetAxis() { return offsetAxis; }
|
||||
public void setOffsetAxis(OffsetAxis axis) { this.offsetAxis = axis != null ? axis : OffsetAxis.Z; }
|
||||
|
||||
public boolean isFollowTarget() { return followTarget; }
|
||||
public void setFollowTarget(boolean v) { this.followTarget = v; }
|
||||
|
||||
public boolean isTrackTarget() { return trackTarget; }
|
||||
public void setTrackTarget(boolean v) { this.trackTarget = v; }
|
||||
|
||||
public float getTargetRadius() {
|
||||
return pillarData.targetRadius;
|
||||
}
|
||||
|
||||
public void setTargetRadius(float targetRadius) {
|
||||
pillarData.targetRadius = Math.max(MIN_RADIUS, Math.min(MAX_ZONE_RADIUS, targetRadius));
|
||||
}
|
||||
|
||||
public float getTargetHeight() {
|
||||
return pillarData.targetHeight;
|
||||
}
|
||||
|
||||
public void setTargetHeight(float targetHeight) {
|
||||
pillarData.targetHeight = Math.max(MIN_HEIGHT, Math.min(MAX_ZONE_HEIGHT, targetHeight));
|
||||
}
|
||||
|
||||
public float getRadiusGrowSpeed() {
|
||||
return pillarData.radiusGrowSpeed;
|
||||
}
|
||||
|
||||
public void setRadiusGrowSpeed(float radiusGrowSpeed) {
|
||||
pillarData.radiusGrowSpeed = Math.max(0.01f, radiusGrowSpeed);
|
||||
}
|
||||
|
||||
public float getHeightGrowSpeed() {
|
||||
return pillarData.heightGrowSpeed;
|
||||
}
|
||||
|
||||
public void setHeightGrowSpeed(float heightGrowSpeed) {
|
||||
pillarData.heightGrowSpeed = Math.max(0.01f, heightGrowSpeed);
|
||||
}
|
||||
|
||||
public PillarMode getMode() {
|
||||
return pillarData.mode;
|
||||
}
|
||||
|
||||
public void setMode(PillarMode mode) {
|
||||
pillarData.mode = mode;
|
||||
}
|
||||
|
||||
public EntityAbilityPillar.PillarOrigin getOrigin() {
|
||||
return pillarData.origin;
|
||||
}
|
||||
|
||||
public void setOrigin(EntityAbilityPillar.PillarOrigin origin) {
|
||||
pillarData.origin = origin;
|
||||
}
|
||||
|
||||
public PillarShape getShape() {
|
||||
return pillarData.shape;
|
||||
}
|
||||
|
||||
public void setShape(PillarShape shape) {
|
||||
pillarData.shape = shape;
|
||||
}
|
||||
|
||||
public int getSpawnDelay() {
|
||||
return pillarData.spawnDelay;
|
||||
}
|
||||
|
||||
public void setSpawnDelay(int spawnDelay) {
|
||||
pillarData.spawnDelay = Math.max(0, Math.min(getMaxLifetime(), spawnDelay));
|
||||
}
|
||||
|
||||
public float getPillarSpeed() {
|
||||
return homingData.speed;
|
||||
}
|
||||
|
||||
public void setPillarSpeed(float speed) {
|
||||
homingData.speed = speed;
|
||||
}
|
||||
|
||||
public boolean isHoming() {
|
||||
return homingData.homing;
|
||||
}
|
||||
|
||||
public void setHoming(boolean homing) {
|
||||
homingData.homing = homing;
|
||||
}
|
||||
|
||||
public float getHomingStrength() {
|
||||
return homingData.homingStrength;
|
||||
}
|
||||
|
||||
public void setHomingStrength(float strength) {
|
||||
homingData.homingStrength = strength;
|
||||
}
|
||||
|
||||
public float getHomingRange() {
|
||||
return homingData.homingRange;
|
||||
}
|
||||
|
||||
public void setHomingRange(float range) {
|
||||
homingData.homingRange = range;
|
||||
}
|
||||
}
|
||||
@@ -8,79 +8,62 @@ 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;
|
||||
|
||||
/**
|
||||
* Pillar zone entity — a column of energy that rises from the ground or falls from the sky.
|
||||
*
|
||||
* 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. Without homing, travels in a straight
|
||||
* line along the initial direction set by setInitialMotion().
|
||||
* Pillar zone entity.
|
||||
*
|
||||
* Lifecycle:
|
||||
* 1. Charging — charge visual grows in radius at ground level during windup.
|
||||
* 2. spawnDelay — entity is alive but does not grow and does not deal damage.
|
||||
* 3. Active — pillarHeight grows toward targetHeight; damage checks begin.
|
||||
* 2. spawnDelay — entity is alive, not growing, no damage.
|
||||
* 3. Active — pillarHeight grows linearly at heightGrowSpeed; damage checks begin.
|
||||
*
|
||||
* FROM_ABOVE: posY is always the top anchor. The pillar grows downward.
|
||||
* Hitbox spans from posY - height to posY.
|
||||
* FROM_GROUND: posY is the ground anchor. The pillar grows upward.
|
||||
* Hitbox spans from posY to posY + height.
|
||||
* Radius snaps to targetRadius immediately when charging ends — no interpolation.
|
||||
* Height grows linearly with no easing.
|
||||
*
|
||||
* FROM_ABOVE: posY = top anchor. Hitbox: posY - height to posY.
|
||||
* FROM_GROUND: posY = ground anchor. Hitbox: posY to posY + height.
|
||||
*
|
||||
* ANCHORED modes:
|
||||
* - followTarget: pillar moves directly toward target each tick (requires homing=true).
|
||||
* - trackTarget: spawn position predicted once from target velocity at spawn time.
|
||||
*/
|
||||
public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
|
||||
// ==================== CONSTANTS ====================
|
||||
|
||||
private static final float MIN_HEIGHT = 0.05f;
|
||||
|
||||
public static final float MIN_HEIGHT = 0.05f;
|
||||
public static final float MIN_RADIUS = 0.01f;
|
||||
private static final int DW_PILLAR_RADIUS = 21;
|
||||
private static final int DW_PILLAR_HEIGHT = 22;
|
||||
|
||||
// ==================== ENUMS ====================
|
||||
|
||||
public enum PillarMode {
|
||||
ANCHORED,
|
||||
MOVING
|
||||
}
|
||||
public enum PillarMode { ANCHORED, MOVING }
|
||||
|
||||
public enum PillarOrigin {
|
||||
FROM_GROUND,
|
||||
FROM_ABOVE
|
||||
}
|
||||
public enum PillarOrigin { FROM_GROUND, FROM_ABOVE }
|
||||
|
||||
public enum PillarShape {
|
||||
CIRCLE,
|
||||
SQUARE;
|
||||
CIRCLE, SQUARE;
|
||||
|
||||
public TelegraphType getTelegraphType() {
|
||||
if (this == CIRCLE) return TelegraphType.CIRCLE;
|
||||
else return TelegraphType.SQUARE;
|
||||
return this == CIRCLE ? TelegraphType.CIRCLE : TelegraphType.SQUARE;
|
||||
}
|
||||
}
|
||||
|
||||
public enum OffsetAxis {
|
||||
X,
|
||||
Z
|
||||
}
|
||||
public enum OffsetAxis { X, Z }
|
||||
|
||||
// ==================== PILLAR CONFIG ====================
|
||||
// ==================== CONFIG ====================
|
||||
|
||||
private EnergyPillarData pillarData = new EnergyPillarData();
|
||||
|
||||
// ==================== PILLAR DIMENSIONS ====================
|
||||
// ==================== DIMENSIONS ====================
|
||||
|
||||
private float pillarRadius = 0.01f;
|
||||
private float pillarHeight = MIN_HEIGHT;
|
||||
|
||||
private float renderPillarRadius = 0.01f;
|
||||
// Height interpolation only — radius snaps immediately
|
||||
private float renderPillarHeight = MIN_HEIGHT;
|
||||
|
||||
private float prevRenderPillarRadius = 0.01f;
|
||||
private float prevRenderPillarHeight = MIN_HEIGHT;
|
||||
|
||||
// ==================== HOMING ====================
|
||||
@@ -88,6 +71,11 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
protected EnergyHomingData homingData = new EnergyHomingData();
|
||||
private int targetEntityId = -1;
|
||||
|
||||
// ==================== ANCHORED MODES ====================
|
||||
|
||||
/** If true, pillar follows target directly each tick (ANCHORED + homing only). */
|
||||
private boolean followTarget = false;
|
||||
|
||||
// ==================== RUNTIME STATE ====================
|
||||
|
||||
private boolean growing = false;
|
||||
@@ -105,7 +93,6 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
EnergyCombatData combat, EnergyHomingData homing,
|
||||
EnergyLightningData lightning, EnergyLifespanData lifespan) {
|
||||
super(world);
|
||||
|
||||
this.ownerEntityId = owner != null ? owner.getEntityId() : -1;
|
||||
this.pillarData = pillarData != null ? pillarData.copy() : new EnergyPillarData();
|
||||
this.displayData = display != null ? display.copy() : new EnergyDisplayData();
|
||||
@@ -114,9 +101,8 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
this.lightningData = lightning != null ? lightning.copy() : new EnergyLightningData();
|
||||
this.lifespanData = lifespan != null ? lifespan.copy() : new EnergyLifespanData();
|
||||
|
||||
// FROM_ABOVE: posY is the top anchor — position at target height, not ground
|
||||
if (pillarData != null && pillarData.origin == PillarOrigin.FROM_ABOVE) {
|
||||
this.setPosition(x, y + pillarData.targetHeight, z);
|
||||
if (this.pillarData.origin == PillarOrigin.FROM_ABOVE) {
|
||||
this.setPosition(x, y + this.pillarData.targetHeight, z);
|
||||
} else {
|
||||
snapToGround(x, y, z);
|
||||
}
|
||||
@@ -135,7 +121,7 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
this.dataWatcher.addObject(DW_PILLAR_HEIGHT, MIN_HEIGHT);
|
||||
}
|
||||
|
||||
// ==================== INTERNAL DIMENSION SETTERS ====================
|
||||
// ==================== INTERNAL SETTERS ====================
|
||||
|
||||
private void setPillarRadiusInternal(float value) {
|
||||
this.pillarRadius = value;
|
||||
@@ -157,9 +143,10 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
this.targetEntityId = target != null ? target.getEntityId() : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set straight-line motion for MOVING mode without homing.
|
||||
*/
|
||||
public void setFollowTarget(boolean follow) {
|
||||
this.followTarget = follow;
|
||||
}
|
||||
|
||||
public void setInitialMotion(double mx, double mz) {
|
||||
this.motionX = mx;
|
||||
this.motionZ = mz;
|
||||
@@ -175,22 +162,19 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
setupCharging(chargeDuration);
|
||||
setPillarRadiusInternal(0.01f);
|
||||
setPillarHeightInternal(MIN_HEIGHT);
|
||||
this.renderPillarRadius = 0.01f;
|
||||
this.prevRenderPillarRadius = 0.01f;
|
||||
this.renderPillarHeight = MIN_HEIGHT;
|
||||
this.prevRenderPillarHeight = MIN_HEIGHT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the ability when charging ends.
|
||||
* Starts the spawnDelay countdown. No damage is dealt during spawnDelay.
|
||||
*/
|
||||
public void startGrowing() {
|
||||
setCharging(false);
|
||||
this.spawnDelayTick = 0;
|
||||
this.growing = pillarData.spawnDelay <= 0;
|
||||
// Radius snaps immediately to target — no interpolation
|
||||
setPillarRadiusInternal(pillarData.targetRadius);
|
||||
setPillarHeightInternal(MIN_HEIGHT);
|
||||
this.renderPillarHeight = MIN_HEIGHT;
|
||||
this.prevRenderPillarHeight = MIN_HEIGHT;
|
||||
syncHitbox();
|
||||
}
|
||||
|
||||
@@ -199,6 +183,7 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
@Override
|
||||
protected void updateCharging() {
|
||||
chargeTick++;
|
||||
// Radius grows during charging for the visual disc effect
|
||||
float progress = getChargeProgress();
|
||||
setPillarRadiusInternal(pillarData.targetRadius * progress);
|
||||
setPillarHeightInternal(MIN_HEIGHT);
|
||||
@@ -209,20 +194,20 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
|
||||
@Override
|
||||
protected void updateZone() {
|
||||
prevRenderPillarRadius = renderPillarRadius;
|
||||
prevRenderPillarHeight = renderPillarHeight;
|
||||
|
||||
if (worldObj.isRemote) {
|
||||
float dwRadius = this.dataWatcher.getWatchableObjectFloat(DW_PILLAR_RADIUS);
|
||||
float dwHeight = this.dataWatcher.getWatchableObjectFloat(DW_PILLAR_HEIGHT);
|
||||
// Radius: snap directly, no interpolation
|
||||
this.pillarRadius = dwRadius;
|
||||
// Height: linear tracking, no easing
|
||||
this.pillarHeight = dwHeight;
|
||||
renderPillarRadius += (pillarRadius - renderPillarRadius) * 0.15f;
|
||||
renderPillarHeight += (pillarHeight - renderPillarHeight) * 0.15f;
|
||||
renderPillarHeight = pillarHeight;
|
||||
return;
|
||||
}
|
||||
|
||||
// spawnDelay: entity is alive but not growing and not dealing damage
|
||||
// spawnDelay: no growth, no damage
|
||||
if (!growing) {
|
||||
spawnDelayTick++;
|
||||
if (spawnDelayTick >= pillarData.spawnDelay) {
|
||||
@@ -231,26 +216,27 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pillarRadius < pillarData.targetRadius) {
|
||||
setPillarRadiusInternal(Math.min(pillarData.targetRadius, pillarRadius + pillarData.radiusGrowSpeed));
|
||||
}
|
||||
// Linear height growth — no easing, exact speed per tick
|
||||
if (pillarHeight < pillarData.targetHeight) {
|
||||
setPillarHeightInternal(Math.min(pillarData.targetHeight, pillarHeight + pillarData.heightGrowSpeed));
|
||||
}
|
||||
// Radius stays at targetRadius (set in startGrowing)
|
||||
|
||||
syncHitbox();
|
||||
|
||||
if (pillarData.mode == PillarMode.MOVING) {
|
||||
updateMovement();
|
||||
} else if (pillarData.mode == PillarMode.ANCHORED && followTarget) {
|
||||
updateFollowTarget();
|
||||
}
|
||||
|
||||
// Only deal damage once growing has started and height is meaningful
|
||||
// No damage during spawnDelay — only when growing
|
||||
if (pillarHeight > MIN_HEIGHT) {
|
||||
checkEntityCollision();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== MOVEMENT (MOVING mode) ====================
|
||||
// ==================== MOVEMENT ====================
|
||||
|
||||
private void updateMovement() {
|
||||
if (!homingData.isHoming()) {
|
||||
@@ -292,10 +278,25 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-anchor Y position after horizontal movement.
|
||||
* FROM_GROUND: snap to ground.
|
||||
* FROM_ABOVE: snap to ground + targetHeight (top anchor stays at ceiling height).
|
||||
* ANCHORED followTarget: moves directly toward target each tick.
|
||||
*/
|
||||
private void updateFollowTarget() {
|
||||
Entity target = getTargetEntity();
|
||||
if (target == null || !target.isEntityAlive()) return;
|
||||
|
||||
double dx = target.posX - posX;
|
||||
double dz = target.posZ - posZ;
|
||||
double dist = Math.sqrt(dx * dx + dz * dz);
|
||||
|
||||
if (dist > 0.1) {
|
||||
float speed = homingData.getSpeed();
|
||||
double step = Math.min(dist, speed);
|
||||
this.posX += (dx / dist) * step;
|
||||
this.posZ += (dz / dist) * step;
|
||||
repositionY();
|
||||
}
|
||||
}
|
||||
|
||||
private void repositionY() {
|
||||
if (pillarData.origin == PillarOrigin.FROM_ABOVE) {
|
||||
double groundY = kamkeel.npcs.controllers.data.ability.Ability.findGroundLevel(worldObj, posX, posY, posZ);
|
||||
@@ -331,11 +332,6 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the hit box based on origin.
|
||||
* FROM_GROUND: posY (base) to posY + height (tip).
|
||||
* FROM_ABOVE: posY - height (tip) to posY (base/ceiling).
|
||||
*/
|
||||
private AxisAlignedBB buildHitBox() {
|
||||
float minY, maxY;
|
||||
if (pillarData.origin == PillarOrigin.FROM_ABOVE) {
|
||||
@@ -347,19 +343,15 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
}
|
||||
return AxisAlignedBB.getBoundingBox(
|
||||
posX - pillarRadius, minY, posZ - pillarRadius,
|
||||
posX + pillarRadius, maxY, posZ + pillarRadius
|
||||
);
|
||||
posX + pillarRadius, maxY, posZ + pillarRadius);
|
||||
}
|
||||
|
||||
private boolean isInPillar(EntityLivingBase entity) {
|
||||
double dx = entity.posX - posX;
|
||||
double dz = entity.posZ - posZ;
|
||||
switch (pillarData.shape) {
|
||||
case SQUARE:
|
||||
return Math.abs(dx) <= pillarRadius && Math.abs(dz) <= pillarRadius;
|
||||
case CIRCLE:
|
||||
default:
|
||||
return Math.sqrt(dx * dx + dz * dz) <= pillarRadius;
|
||||
case SQUARE: return Math.abs(dx) <= pillarRadius && Math.abs(dz) <= pillarRadius;
|
||||
case CIRCLE: default: return Math.sqrt(dx * dx + dz * dz) <= pillarRadius;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +369,7 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
return distance < range * range;
|
||||
}
|
||||
|
||||
// ==================== ENTITY HELPERS ====================
|
||||
// ==================== HELPERS ====================
|
||||
|
||||
public Entity getTargetEntity() {
|
||||
if (targetEntityId == -1) return null;
|
||||
@@ -388,20 +380,15 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
|
||||
public float getPillarRadius() { return pillarRadius; }
|
||||
public float getPillarHeight() { return pillarHeight; }
|
||||
public float getInterpolatedPillarHeight(float partialTicks) {
|
||||
return prevRenderPillarHeight + (renderPillarHeight - prevRenderPillarHeight) * partialTicks;
|
||||
}
|
||||
public PillarMode getPillarMode() { return pillarData.mode; }
|
||||
public PillarOrigin getPillarOrigin() { return pillarData.origin; }
|
||||
public PillarShape getPillarShape() { return pillarData.shape; }
|
||||
public boolean isGrowing() { return growing; }
|
||||
public EnergyPillarData getPillarData() { return pillarData; }
|
||||
|
||||
public float getInterpolatedPillarRadius(float partialTicks) {
|
||||
return prevRenderPillarRadius + (renderPillarRadius - prevRenderPillarRadius) * partialTicks;
|
||||
}
|
||||
|
||||
public float getInterpolatedPillarHeight(float partialTicks) {
|
||||
return prevRenderPillarHeight + (renderPillarHeight - prevRenderPillarHeight) * partialTicks;
|
||||
}
|
||||
|
||||
// ==================== SETTERS ====================
|
||||
|
||||
public void setPillarData(EnergyPillarData data) { this.pillarData = data != null ? data.copy() : new EnergyPillarData(); }
|
||||
@@ -417,6 +404,7 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
nbt.setFloat("PillarHeight", pillarHeight);
|
||||
nbt.setInteger("SpawnDelayTick", spawnDelayTick);
|
||||
nbt.setBoolean("Growing", growing);
|
||||
nbt.setBoolean("FollowTarget", followTarget);
|
||||
nbt.setInteger("TargetEntityId", targetEntityId);
|
||||
nbt.setDouble("MotionX", motionX);
|
||||
nbt.setDouble("MotionZ", motionZ);
|
||||
@@ -438,13 +426,12 @@ public class EntityAbilityPillar extends EntityEnergyZone {
|
||||
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.followTarget = nbt.hasKey("FollowTarget") && nbt.getBoolean("FollowTarget");
|
||||
this.targetEntityId = nbt.hasKey("TargetEntityId") ? nbt.getInteger("TargetEntityId") : -1;
|
||||
this.motionX = nbt.getDouble("MotionX");
|
||||
this.motionZ = nbt.getDouble("MotionZ");
|
||||
|
||||
@@ -53,8 +53,8 @@ public abstract class EntityEnergyZone extends EntityEnergyAbility {
|
||||
|
||||
// ==================== SAFETY CONSTANTS ====================
|
||||
|
||||
protected static final float MAX_ZONE_RADIUS = 64.0f;
|
||||
protected static final float MAX_ZONE_HEIGHT = 64.0f;
|
||||
public static final float MAX_ZONE_RADIUS = 64.0f;
|
||||
public static final float MAX_ZONE_HEIGHT = 64.0f;
|
||||
protected static final double GROUND_OFFSET = 0.05;
|
||||
|
||||
// ==================== LIFESPAN ====================
|
||||
@@ -78,6 +78,12 @@ public abstract class EntityEnergyZone extends EntityEnergyAbility {
|
||||
|
||||
protected int hitCount = 0;
|
||||
|
||||
/**
|
||||
* Client-side only: if >= 0, the preview entity updates its position
|
||||
* to match this entity each tick during charging (charge visual follows target).
|
||||
*/
|
||||
protected int followEntityId = -1;
|
||||
|
||||
// ==================== SIZE — LOGICAL ====================
|
||||
|
||||
/** Current radius used for collision and damage checks. */
|
||||
@@ -144,6 +150,15 @@ public abstract class EntityEnergyZone extends EntityEnergyAbility {
|
||||
if (isCharging()) {
|
||||
this.renderRadius = this.radius;
|
||||
this.renderHeight = this.height;
|
||||
|
||||
// Follow entity position during charging preview (e.g. charge visual follows target)
|
||||
if (followEntityId != -1 && worldObj != null) {
|
||||
Entity followed = worldObj.getEntityByID(followEntityId);
|
||||
if (followed instanceof EntityLivingBase && !followed.isDead) {
|
||||
snapToGround(followed.posX, followed.posY, followed.posZ);
|
||||
}
|
||||
}
|
||||
|
||||
updateCharging();
|
||||
return;
|
||||
} else {
|
||||
@@ -415,6 +430,9 @@ public abstract class EntityEnergyZone extends EntityEnergyAbility {
|
||||
public float getTargetHeight() { return targetHeight; }
|
||||
public void setTargetHeight(float height) { this.targetHeight = sanitize(height, 1.0f, MAX_ZONE_HEIGHT); }
|
||||
|
||||
public int getFollowEntityId() { return followEntityId; }
|
||||
public void setFollowEntityId(int id) { this.followEntityId = id; }
|
||||
|
||||
public void setEffects(List<AbilityPotionEffect> effects) {
|
||||
if (effects == null || effects.isEmpty()) {
|
||||
this.effects = new ArrayList<>();
|
||||
@@ -461,6 +479,7 @@ public abstract class EntityEnergyZone extends EntityEnergyAbility {
|
||||
nbt.setBoolean("Charging", isCharging());
|
||||
nbt.setInteger("ChargeDuration", chargeDuration);
|
||||
nbt.setInteger("ChargeTick", chargeTick);
|
||||
nbt.setInteger("ChargeFollowEntityId", followEntityId);
|
||||
|
||||
NBTTagList effectsList = new NBTTagList();
|
||||
for (AbilityPotionEffect effect : effects) {
|
||||
@@ -502,6 +521,7 @@ public abstract class EntityEnergyZone extends EntityEnergyAbility {
|
||||
}
|
||||
this.chargeDuration = nbt.hasKey("ChargeDuration") ? nbt.getInteger("ChargeDuration") : 0;
|
||||
this.chargeTick = nbt.hasKey("ChargeTick") ? nbt.getInteger("ChargeTick") : 0;
|
||||
this.followEntityId = nbt.hasKey("ChargeFollowEntityId") ? nbt.getInteger("ChargeFollowEntityId") : -1;
|
||||
|
||||
this.effects.clear();
|
||||
if (nbt.hasKey("Effects")) {
|
||||
|
||||
Reference in new issue
Block a user