From a2beef054964d34049dfd58e62701184db6e1a09 Mon Sep 17 00:00:00 2001 From: Vidal Date: Sat, 14 Mar 2026 20:22:57 -0300 Subject: [PATCH] numerous fixes to pillars (now they work!) --- .../client/renderer/RenderEnergyPillar.java | 76 +++----- .../ability/type/energy/AbilityPillar.java | 178 +++++++++++++----- .../npcs/entity/EntityAbilityPillar.java | 146 +++++++++----- .../kamkeel/npcs/entity/EntityEnergyZone.java | 9 +- 4 files changed, 260 insertions(+), 149 deletions(-) diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPillar.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPillar.java index 06c91cf91..c4ae48bb7 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPillar.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPillar.java @@ -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; } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityPillar.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityPillar.java index 57570fb0c..d58967369 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityPillar.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityPillar.java @@ -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 { @@ -39,6 +45,12 @@ public class AbilityPillar extends AbilityEnergyZone { 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 { 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 { @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 { @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 { 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 { 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 { 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 { 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 { 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 { 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); } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityPillar.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityPillar.java index 93196a020..76584f6ca 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityPillar.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityPillar.java @@ -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(); } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyZone.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyZone.java index 909e1218d..82bdd3f7f 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyZone.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyZone.java @@ -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 ====================