Rendering Fixes and Logic Fixes for all Energy Abilities

This commit is contained in:
Kam committed 2026-02-19 19:27:56 -05:00
1 parent 024718e7f4
commit f7d0fbcf33
23 files changed
+276 -109

No files matched your search

@@ -262,8 +262,8 @@ public class RenderAbilityBeam extends RenderEnergyAbility {
// Calculate alpha based on age (older = more faded)
float age1 = i < ages.size() ? ages.get(i) : 0;
float age2 = (i + 1) < ages.size() ? ages.get(i + 1) : 0;
float fade1 = 1.0f - Math.min(1.0f, age1 / fadeTime);
float fade2 = 1.0f - Math.min(1.0f, age2 / fadeTime);
float fade1 = fadeTime > 0 ? (1.0f - Math.min(1.0f, age1 / fadeTime)) : 0.0f;
float fade2 = fadeTime > 0 ? (1.0f - Math.min(1.0f, age2 / fadeTime)) : 0.0f;
float alpha1 = baseAlpha * fade1;
float alpha2 = baseAlpha * fade2;
@@ -8,6 +8,9 @@ import net.minecraft.client.renderer.Tessellator;
import net.minecraft.entity.Entity;
import org.lwjgl.opengl.GL11;
import java.util.HashMap;
import java.util.Map;
/**
* Renders the AbilityLaser entity as a rectangular beam with clear inner/outer colors.
* <p>
@@ -16,6 +19,8 @@ import org.lwjgl.opengl.GL11;
@SideOnly(Side.CLIENT)
public class RenderAbilityLaser extends RenderEnergyAbility {
private final Map<Integer, AttachedLightningRenderer.LightningState> tipLightningStates = new HashMap<>();
@Override
public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) {
EntityAbilityLaser laser = (EntityAbilityLaser) entity;
@@ -218,13 +223,16 @@ public class RenderAbilityLaser extends RenderEnergyAbility {
/**
* Get or create the tip lightning state for the laser.
* Stored as a secondary object in lightningState2.
* Persisted per entity ID so arcs accumulate and fade properly across frames.
*/
private AttachedLightningRenderer.LightningState getTipLightningState(EntityAbilityLaser laser) {
// Use a simple approach - store in a transient field
// Since lightningState is already used, we'll create a new state each time
// This works because we're updating and rendering immediately
return new AttachedLightningRenderer.LightningState();
int id = laser.getEntityId();
AttachedLightningRenderer.LightningState state = tipLightningStates.get(id);
if (state == null) {
state = new AttachedLightningRenderer.LightningState();
tipLightningStates.put(id, state);
}
return state;
}
/**
@@ -70,6 +70,7 @@ public class RenderAbilitySweeper extends Render {
/**
* Render beam using the same style as the Beam entity trail.
* Three layers: outer glow, middle, inner core.
* outerColorWidth is an additive offset from inner width (consistent with other renderers).
*/
private void renderBeam(float length, float width, int innerColor, int outerColor,
boolean outerColorEnabled, float outerColorWidth) {
@@ -82,21 +83,28 @@ public class RenderAbilitySweeper extends Render {
float innerG = ((innerColor >> 8) & 0xFF) / 255.0f;
float innerB = (innerColor & 0xFF) / 255.0f;
// Inner scale defines the core width (consistent with other renderers)
float innerScale = 0.6f;
float innerWidth = width * innerScale;
// Render from origin (0,0,0) to (0,0,length) in local coords
// After rotation this extends in the correct world direction
// Outer glow (wider, translucent) - like beam trail - only if enabled
// Outer glow (wider, translucent) - only if enabled
// outerColorWidth is additive offset from inner width
if (outerColorEnabled) {
float outerWidth = innerWidth + outerColorWidth * width;
GL11.glDepthMask(false);
renderBeamSegment(0, 0, 0, 0, 0, length, width * outerColorWidth, outerR, outerG, outerB, 0.3f);
renderBeamSegment(0, 0, 0, 0, 0, length, outerWidth, outerR, outerG, outerB, 0.3f);
GL11.glDepthMask(true);
// Middle layer
renderBeamSegment(0, 0, 0, 0, 0, length, width * 1.3f, outerR, outerG, outerB, 0.6f);
// Middle layer (halfway between inner and outer)
float midWidth = innerWidth + (outerWidth - innerWidth) * 0.5f;
renderBeamSegment(0, 0, 0, 0, 0, length, midWidth, outerR, outerG, outerB, 0.6f);
}
// Inner core (solid) - like beam trail inner
renderBeamSegment(0, 0, 0, 0, 0, length, width * 0.6f, innerR, innerG, innerB, 1.0f);
// Inner core (solid)
renderBeamSegment(0, 0, 0, 0, 0, length, innerWidth, innerR, innerG, innerB, 1.0f);
}
/**
@@ -210,7 +210,7 @@ public class RenderAbilityZone extends Render {
if (c.accents) {
float aa = 0.5f * c.brightness * (0.3f + 0.2f * sin(c.pulse * 0.7f));
AccentStyle style = AccentStyle.values()[Math.min(c.accentStyle, AccentStyle.values().length - 1)];
AccentStyle style = AccentStyle.values()[Math.max(0, Math.min(c.accentStyle, AccentStyle.values().length - 1))];
switch (style) {
case SWAYING:
renderSwayingAccents(c, c.ir, c.ig, c.ib, aa, c.height);
@@ -314,7 +314,7 @@ public class RenderAbilityZone extends Render {
private void spawnDefaultParticles(Ctx c) {
int count = Math.round(3 * c.particleDensity);
ParticleMotion motion = ParticleMotion.values()[Math.min(c.particleMotion, ParticleMotion.values().length - 1)];
ParticleMotion motion = ParticleMotion.values()[Math.max(0, Math.min(c.particleMotion, ParticleMotion.values().length - 1))];
for (int i = 0; i < count; i++) {
double[] pos = randomPosInZone(c);
double mx, my, mz;
@@ -350,7 +350,7 @@ public class RenderAbilityZone extends Render {
}
private double[] getMotionForStyle(int motionOrdinal) {
ParticleMotion motion = ParticleMotion.values()[Math.min(motionOrdinal, ParticleMotion.values().length - 1)];
ParticleMotion motion = ParticleMotion.values()[Math.max(0, Math.min(motionOrdinal, ParticleMotion.values().length - 1))];
switch (motion) {
case DRIFTING:
return new double[]{
@@ -42,7 +42,6 @@ public class RenderEnergyDome extends RenderEnergyBarrier {
float outerAlpha = dome.getOuterColorAlpha() * healthPercent;
GL11.glDepthMask(false);
renderSphere(dome.getOuterColor(), outerAlpha + flashAlpha, radius * outerScale, 8);
GL11.glDepthMask(true);
}
// TODO: Skybox Feature
@@ -50,9 +49,9 @@ public class RenderEnergyDome extends RenderEnergyBarrier {
// renderSkyboxSphere(dome.getSkyboxTexture(), radius, radius * 0.94f, 32, x, y, z);
// }
// Render inner dome
GL11.glDepthMask(true);
// Render inner dome (depth write enabled so inner sphere properly occludes)
renderSphere(dome.getInnerColor(), dome.getInnerAlpha(), radius * innerScale, 8);
GL11.glPopMatrix();
restoreRenderState();
@@ -3,7 +3,6 @@ package kamkeel.npcs.client.renderer.lightning;
import net.minecraft.client.renderer.Tessellator;
import org.lwjgl.opengl.GL11;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -37,6 +36,7 @@ public class AttachedLightningRenderer {
public float getAlphaMultiplier() {
// Fade out over lifetime
if (maxAge <= 0) return 0.0f;
return 1.0f - ((float) age / (float) maxAge);
}
@@ -208,10 +208,9 @@ public class AttachedLightningRenderer {
private static void renderBoltPath(Tessellator tess, List<double[]> points, int color, float width, float alpha) {
if (alpha <= 0.01f) return;
Color c = new Color(color);
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
int r = (color >> 16) & 0xFF;
int g = (color >> 8) & 0xFF;
int b = color & 0xFF;
int a = Math.min(255, Math.max(0, (int) (alpha * 255)));
for (int i = 0; i < points.size() - 1; i++) {
@@ -56,7 +56,7 @@ public class LightningBolt {
this.numSegments0 = 1;
this.length = end.copy().subtract(start).mag();
this.particleMaxAge = FADE_TIME + rand.nextInt(FADE_TIME) - FADE_TIME / 2;
this.particleMaxAge = Math.max(1, FADE_TIME + rand.nextInt(FADE_TIME) - FADE_TIME / 2);
this.particleAge = -(int) (length * speed);
segments.add(new Segment(start, end));
@@ -78,7 +78,7 @@ public class EnergyBarrierData {
}
public void setMaxHealth(float maxHealth) {
this.maxHealth = Math.max(0, maxHealth);
this.maxHealth = Math.max(1.0f, maxHealth);
}
public boolean isUseHealth() {
@@ -75,7 +75,7 @@ public class EnergyCombatData implements IEnergyCombatData {
@Override
public void setExplosionRadius(float explosionRadius) {
this.explosionRadius = explosionRadius;
this.explosionRadius = Math.max(0, explosionRadius);
}
@Override
@@ -98,12 +98,17 @@ public class EnergyCombatData implements IEnergyCombatData {
}
public void readNBT(NBTTagCompound nbt) {
damage = nbt.getFloat("damage");
knockback = nbt.getFloat("knockback");
knockbackUp = nbt.getFloat("knockbackUp");
explosive = nbt.getBoolean("explosive");
explosionRadius = nbt.getFloat("explosionRadius");
explosionDamageFalloff = nbt.getFloat("explosionDamageFalloff");
damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 7.0f;
knockback = nbt.hasKey("knockback") ? nbt.getFloat("knockback") : 1.0f;
knockbackUp = nbt.hasKey("knockbackUp") ? nbt.getFloat("knockbackUp") : 0.1f;
explosive = nbt.hasKey("explosive") && nbt.getBoolean("explosive");
explosionRadius = nbt.hasKey("explosionRadius") ? nbt.getFloat("explosionRadius") : 3.0f;
explosionDamageFalloff = nbt.hasKey("explosionDamageFalloff") ? nbt.getFloat("explosionDamageFalloff") : 0.5f;
// Sanitize
if (Float.isNaN(damage) || Float.isInfinite(damage)) damage = 7.0f;
if (Float.isNaN(knockback) || Float.isInfinite(knockback) || knockback < 0) knockback = 1.0f;
if (Float.isNaN(explosionRadius) || Float.isInfinite(explosionRadius) || explosionRadius < 0) explosionRadius = 3.0f;
}
public EnergyCombatData copy() {
@@ -128,13 +128,13 @@ public class EnergyDisplayData implements IEnergyDisplayData {
}
public void readNBT(NBTTagCompound nbt) {
innerColor = nbt.getInteger("innerColor");
outerColor = nbt.getInteger("outerColor");
outerColorEnabled = nbt.getBoolean("outerColorEnabled");
outerColorWidth = nbt.getFloat("outerColorWidth");
outerColorAlpha = nbt.getFloat("outerColorAlpha");
innerColor = nbt.hasKey("innerColor") ? nbt.getInteger("innerColor") : 0xFFFFFF;
outerColor = nbt.hasKey("outerColor") ? nbt.getInteger("outerColor") : 0x8888FF;
outerColorEnabled = !nbt.hasKey("outerColorEnabled") || nbt.getBoolean("outerColorEnabled");
outerColorWidth = nbt.hasKey("outerColorWidth") ? nbt.getFloat("outerColorWidth") : 0.4f;
outerColorAlpha = nbt.hasKey("outerColorAlpha") ? nbt.getFloat("outerColorAlpha") : 0.5f;
innerAlpha = nbt.hasKey("innerAlpha") ? nbt.getFloat("innerAlpha") : 1.0f;
rotationSpeed = nbt.getFloat("rotationSpeed");
rotationSpeed = nbt.hasKey("rotationSpeed") ? nbt.getFloat("rotationSpeed") : 4.0f;
// TODO: Skybox Feature
// skyboxEnabled = nbt.hasKey("skyboxEnabled") && nbt.getBoolean("skyboxEnabled");
// skyboxTexture = nbt.hasKey("skyboxTexture") ? nbt.getString("skyboxTexture") : "";
@@ -31,7 +31,7 @@ public class EnergyHomingData implements IEnergyHomingData {
@Override
public void setSpeed(float speed) {
this.speed = speed;
this.speed = Math.max(0.01f, speed);
}
@Override
@@ -72,10 +72,13 @@ public class EnergyHomingData implements IEnergyHomingData {
}
public void readNBT(NBTTagCompound nbt) {
speed = nbt.getFloat("speed");
homing = nbt.getBoolean("homing");
homingStrength = nbt.getFloat("homingStrength");
homingRange = nbt.getFloat("homingRange");
speed = nbt.hasKey("speed") ? nbt.getFloat("speed") : 0.5f;
homing = !nbt.hasKey("homing") || nbt.getBoolean("homing");
homingStrength = nbt.hasKey("homingStrength") ? nbt.getFloat("homingStrength") : 0.15f;
homingRange = nbt.hasKey("homingRange") ? nbt.getFloat("homingRange") : 20.0f;
// Sanitize speed to prevent stuck projectiles
if (Float.isNaN(speed) || Float.isInfinite(speed) || speed <= 0) speed = 0.5f;
}
public EnergyHomingData copy() {
@@ -26,7 +26,7 @@ public class EnergyLifespanData implements IEnergyLifespanData {
@Override
public void setMaxDistance(float maxDistance) {
this.maxDistance = maxDistance;
this.maxDistance = Float.isNaN(maxDistance) || maxDistance <= 0 ? 150.0f : maxDistance;
}
@Override
@@ -36,7 +36,7 @@ public class EnergyLifespanData implements IEnergyLifespanData {
@Override
public void setMaxLifetime(int maxLifetime) {
this.maxLifetime = maxLifetime;
this.maxLifetime = maxLifetime <= 0 ? 200 : maxLifetime;
}
public void writeNBT(NBTTagCompound nbt) {
@@ -61,7 +61,7 @@ public class EnergyLightningData implements IEnergyLightningData {
@Override
public void setLightningFadeTime(int lightningFadeTime) {
this.lightningFadeTime = lightningFadeTime;
this.lightningFadeTime = Math.max(1, lightningFadeTime);
}
public void writeNBT(NBTTagCompound nbt) {
@@ -72,10 +72,11 @@ public class EnergyLightningData implements IEnergyLightningData {
}
public void readNBT(NBTTagCompound nbt) {
lightningEffect = nbt.getBoolean("lightningEffect");
lightningDensity = nbt.getFloat("lightningDensity");
lightningRadius = nbt.getFloat("lightningRadius");
lightningFadeTime = nbt.getInteger("lightningFadeTime");
lightningEffect = nbt.hasKey("lightningEffect") && nbt.getBoolean("lightningEffect");
lightningDensity = nbt.hasKey("lightningDensity") ? nbt.getFloat("lightningDensity") : 0.15f;
lightningRadius = nbt.hasKey("lightningRadius") ? nbt.getFloat("lightningRadius") : 0.5f;
lightningFadeTime = nbt.hasKey("lightningFadeTime") ? nbt.getInteger("lightningFadeTime") : 6;
if (lightningFadeTime <= 0) lightningFadeTime = 1;
}
public EnergyLightningData copy() {
@@ -187,6 +187,7 @@ public class EnergyTrajectoryData implements IEnergyTrajectoryData {
public void readNBT(NBTTagCompound nbt) {
this.currentPath = nbt.getInteger("currentPath");
trajectory.clear();
int i = 0;
while (nbt.hasKey("Path_" + i, Constants.NBT.TAG_COMPOUND)) {
@@ -118,6 +118,8 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
this.discRadius = 0.01f;
this.discThickness = 0.01f;
this.size = 0.01f;
this.renderCurrentSize = 0.01f;
this.prevRenderSize = 0.01f;
// Initial position at anchor point
Vec3 pos = AnchorPointHelper.calculateAnchorPosition(owner, anchorData);
@@ -466,7 +466,8 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
*/
public float getLingerAlpha() {
if (!fullyExtended) return 1.0f;
return 1.0f - ((float) ticksSinceFullExtension / lingerTicks);
if (lingerTicks <= 0) return 0.0f;
return Math.max(0.0f, 1.0f - ((float) ticksSinceFullExtension / lingerTicks));
}
// ==================== NBT ====================
@@ -475,8 +476,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
protected void readProjectileNBT(NBTTagCompound nbt) {
this.laserWidth = sanitize(nbt.hasKey("LaserWidth") ? nbt.getFloat("LaserWidth") : 0.2f, 0.2f, MAX_ENTITY_SIZE);
this.expansionSpeed = nbt.hasKey("ExpansionSpeed") ? nbt.getFloat("ExpansionSpeed") : 2.0f;
if (Float.isNaN(expansionSpeed) || Float.isInfinite(expansionSpeed) || expansionSpeed <= 0) expansionSpeed = 2.0f;
this.lingerTicks = nbt.hasKey("LingerTicks") ? nbt.getInteger("LingerTicks") : 10;
if (lingerTicks <= 0) lingerTicks = 1;
this.dieOnImpact = nbt.getBoolean("DieOnImpact");
this.lockVerticalDirection = nbt.getBoolean("LockVerticalDir");
this.ticksSinceFullExtension = nbt.getInteger("TicksSinceExtended");
this.dirX = nbt.getDouble("DirX");
this.dirY = nbt.getDouble("DirY");
this.dirZ = nbt.getDouble("DirZ");
@@ -495,6 +500,8 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
nbt.setFloat("ExpansionSpeed", expansionSpeed);
nbt.setInteger("LingerTicks", lingerTicks);
nbt.setBoolean("DieOnImpact", dieOnImpact);
nbt.setBoolean("LockVerticalDir", lockVerticalDirection);
nbt.setInteger("TicksSinceExtended", ticksSinceFullExtension);
nbt.setDouble("DirX", dirX);
nbt.setDouble("DirY", dirY);
nbt.setDouble("DirZ", dirZ);
@@ -12,6 +12,9 @@ import net.minecraft.util.DamageSource;
import net.minecraft.world.World;
import noppes.npcs.LogWriter;
import noppes.npcs.NpcDamageSource;
import noppes.npcs.controllers.PartyController;
import noppes.npcs.controllers.data.Party;
import noppes.npcs.controllers.data.PlayerData;
import noppes.npcs.entity.EntityNPCInterface;
import java.util.HashSet;
@@ -160,9 +163,7 @@ public class EntityAbilitySweeper extends EntityEnergyAbility {
if (currentAngle >= 360) {
currentAngle -= 360;
completedRotations++;
if (!worldObj.isRemote) {
LogWriter.info("[Sweeper] Completed rotation " + completedRotations + "/" + numberOfRotations);
}
// Rotation completed
}
// Server handles damage
@@ -216,6 +217,7 @@ public class EntityAbilitySweeper extends EntityEnergyAbility {
if (!(entity instanceof EntityLivingBase)) continue;
if (entity == owner) continue;
if (hitThisTick.contains(entity.getEntityId())) continue;
if (shouldIgnoreEntity((EntityLivingBase) entity, owner)) continue;
EntityLivingBase livingEntity = (EntityLivingBase) entity;
@@ -264,6 +266,32 @@ public class EntityAbilitySweeper extends EntityEnergyAbility {
return entityFeetY < beamTopY;
}
/**
* Check if an entity should be ignored for sweep damage.
* Same logic as EntityEnergyProjectile.shouldIgnoreEntity but adapted for sweeper.
*/
private boolean shouldIgnoreEntity(EntityLivingBase entity, Entity owner) {
if (entity instanceof EntityNPCInterface) {
EntityNPCInterface targetNpc = (EntityNPCInterface) entity;
if (targetNpc.faction.isPassive) return true;
if (owner instanceof EntityNPCInterface) {
if (((EntityNPCInterface) owner).faction.id == targetNpc.faction.id) return true;
}
if (owner instanceof EntityPlayer) {
if (targetNpc.faction.isFriendlyToPlayer((EntityPlayer) owner)) return true;
}
}
if (owner instanceof EntityPlayer && entity instanceof EntityPlayer) {
PlayerData ownerData = PlayerData.get((EntityPlayer) owner);
PlayerData targetData = PlayerData.get((EntityPlayer) entity);
if (ownerData.partyUUID != null && ownerData.partyUUID.equals(targetData.partyUUID)) {
Party party = PartyController.Instance().getParty(ownerData.partyUUID);
if (party != null && !party.friendlyFire()) return true;
}
}
return false;
}
private void applyDamage(EntityLivingBase target, Entity owner) {
if (owner instanceof EntityNPCInterface) {
target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), damage);
@@ -339,17 +367,23 @@ public class EntityAbilitySweeper extends EntityEnergyAbility {
this.beamLength = sanitize(nbt.getFloat("BeamLength"), 10.0f, MAX_ENTITY_SIZE);
this.beamWidth = sanitize(nbt.getFloat("BeamWidth"), 0.3f, MAX_ENTITY_SIZE);
this.beamHeight = sanitize(nbt.getFloat("BeamHeight"), 0.8f, MAX_ENTITY_SIZE);
this.sweepSpeed = nbt.getFloat("SweepSpeed");
this.numberOfRotations = nbt.getInteger("NumRotations");
this.sweepSpeed = nbt.hasKey("SweepSpeed") ? nbt.getFloat("SweepSpeed") : 3.0f;
if (Float.isNaN(sweepSpeed) || Float.isInfinite(sweepSpeed) || sweepSpeed <= 0) sweepSpeed = 3.0f;
this.numberOfRotations = nbt.hasKey("NumRotations") ? nbt.getInteger("NumRotations") : 2;
if (numberOfRotations <= 0) numberOfRotations = 1;
this.completedRotations = nbt.getInteger("CompletedRotations");
this.maxTicks = nbt.getInteger("MaxTicks");
this.maxTicks = nbt.hasKey("MaxTicks") ? nbt.getInteger("MaxTicks") : 400;
if (maxTicks <= 0) maxTicks = 400;
this.targetEntityId = nbt.getInteger("TargetId");
this.currentAngle = nbt.getFloat("CurrentAngle");
this.prevAngle = currentAngle;
this.baseYaw = nbt.getFloat("BaseYaw");
this.deathWorldTime = nbt.getLong("DeathWorldTime");
this.damage = nbt.getFloat("Damage");
this.damageInterval = nbt.getInteger("DamageInterval");
this.piercing = nbt.getBoolean("Piercing");
this.damage = nbt.hasKey("Damage") ? nbt.getFloat("Damage") : 5.0f;
if (Float.isNaN(damage) || Float.isInfinite(damage)) damage = 5.0f;
this.damageInterval = nbt.hasKey("DamageInterval") ? nbt.getInteger("DamageInterval") : 5;
if (damageInterval <= 0) damageInterval = 1;
this.piercing = !nbt.hasKey("Piercing") || nbt.getBoolean("Piercing");
this.lockOnTarget = nbt.getBoolean("LockOnTarget");
}
@@ -716,7 +716,8 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
@Override
protected void readEntityFromNBT(NBTTagCompound nbt) {
this.zoneType = ZoneType.values()[nbt.getInteger("ZoneType")];
int zoneTypeOrd = nbt.getInteger("ZoneType");
this.zoneType = (zoneTypeOrd >= 0 && zoneTypeOrd < ZoneType.values().length) ? ZoneType.values()[zoneTypeOrd] : ZoneType.TRAP;
try {
this.shape = ZoneShape.valueOf(nbt.getString("ShapeName"));
} catch (Exception e) {
@@ -724,17 +725,17 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
}
this.ownerEntityId = nbt.getInteger("OwnerId");
this.radius = nbt.getFloat("Radius");
this.durationTicks = nbt.getInteger("Duration");
this.maxTicks = nbt.getInteger("MaxTicks");
this.innerColor = nbt.getInteger("InnerColor");
this.outerColor = nbt.getInteger("OuterColor");
this.outerColorEnabled = nbt.getBoolean("OuterColorEnabled");
this.zoneHeight = nbt.getFloat("ZoneHeight");
this.particleDensity = nbt.getFloat("ParticleDensity");
this.particleScale = nbt.getFloat("ParticleScale");
this.animSpeed = nbt.getFloat("AnimSpeed");
this.lightningDensity = nbt.getFloat("LightningDensity");
this.radius = nbt.hasKey("Radius") ? nbt.getFloat("Radius") : 4.0f;
this.durationTicks = nbt.hasKey("Duration") ? nbt.getInteger("Duration") : 200;
this.maxTicks = nbt.hasKey("MaxTicks") ? nbt.getInteger("MaxTicks") : 200;
this.innerColor = nbt.hasKey("InnerColor") ? nbt.getInteger("InnerColor") : 0xFF6600;
this.outerColor = nbt.hasKey("OuterColor") ? nbt.getInteger("OuterColor") : 0xFF0000;
this.outerColorEnabled = !nbt.hasKey("OuterColorEnabled") || nbt.getBoolean("OuterColorEnabled");
this.zoneHeight = nbt.hasKey("ZoneHeight") ? nbt.getFloat("ZoneHeight") : 2.0f;
this.particleDensity = nbt.hasKey("ParticleDensity") ? nbt.getFloat("ParticleDensity") : 1.0f;
this.particleScale = nbt.hasKey("ParticleScale") ? nbt.getFloat("ParticleScale") : 1.0f;
this.animSpeed = nbt.hasKey("AnimSpeed") ? nbt.getFloat("AnimSpeed") : 1.0f;
this.lightningDensity = nbt.hasKey("LightningDensity") ? nbt.getFloat("LightningDensity") : 1.0f;
this.deathWorldTime = nbt.getLong("DeathWorldTime");
// Visual layer fields
@@ -764,8 +765,9 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
this.visible = nbt.getBoolean("Visible");
// Hazard
this.damagePerSecond = nbt.getFloat("DamagePerSecond");
this.damageInterval = nbt.getInteger("DamageInterval");
this.damagePerSecond = nbt.hasKey("DamagePerSecond") ? nbt.getFloat("DamagePerSecond") : 1.0f;
this.damageInterval = nbt.hasKey("DamageInterval") ? nbt.getInteger("DamageInterval") : 20;
if (damageInterval <= 0) damageInterval = 1;
this.ignoreInvulnFrames = nbt.getBoolean("IgnoreInvuln");
this.affectsCaster = nbt.getBoolean("AffectsCaster");
@@ -893,7 +895,8 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
@Override
public void readSpawnData(ByteBuf buffer) {
this.zoneType = ZoneType.values()[buffer.readInt()];
int zoneTypeOrd = buffer.readInt();
this.zoneType = (zoneTypeOrd >= 0 && zoneTypeOrd < ZoneType.values().length) ? ZoneType.values()[zoneTypeOrd] : ZoneType.TRAP;
String shapeName = ByteBufUtils.readUTF8String(buffer);
try {
this.shape = ZoneShape.valueOf(shapeName);
@@ -279,6 +279,7 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility {
readEnergyBaseNBT(nbt);
this.ticksAlive = nbt.getInteger("TicksAlive");
this.currentHealth = nbt.getFloat("CurrentHealth");
if (Float.isNaN(currentHealth) || Float.isInfinite(currentHealth) || currentHealth < 0) currentHealth = barrierData.maxHealth;
this.charging = nbt.hasKey("Charging") && nbt.getBoolean("Charging");
this.chargeTick = nbt.getInteger("ChargeTick");
this.chargeDuration = nbt.getInteger("ChargeDuration");
@@ -9,6 +9,7 @@ import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.DamageSource;
import net.minecraft.world.World;
import java.util.List;
@@ -28,20 +29,20 @@ public class EntityEnergyDome extends EntityEnergyBarrier {
public EntityEnergyDome(World world) {
super(world);
this.setSize(1.0f, 1.0f); // Hitbox doesn't really matter, we use radius checks
this.setSize(1.0f, 1.0f);
}
public EntityEnergyDome(World world, EntityLivingBase owner, double x, double y, double z,
float domeRadius, EnergyDisplayData display, EnergyLightningData lightning,
EnergyBarrierData barrier) {
this(world);
this.setPosition(x, y, z);
this.ownerEntityId = owner.getEntityId();
this.domeRadius = domeRadius;
this.displayData = display;
this.lightningData = lightning;
this.barrierData = barrier;
this.currentHealth = barrier.maxHealth;
this.setPosition(x, y, z);
}
@Override
@@ -103,14 +104,25 @@ public class EntityEnergyDome extends EntityEnergyBarrier {
double dx = projectile.posX - this.posX;
double dy = projectile.posY - this.posY;
double dz = projectile.posZ - this.posZ;
double distSq = dx * dx + dy * dy + dz * dz;
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
// Check if projectile is within dome radius
if (distSq > domeRadius * domeRadius) return false;
// Check if projectile is outside dome radius
if (dist > domeRadius) return false;
// Check if projectile is moving inward (dot product of velocity and position relative to center)
// Approximate the projectile's position before last tick's movement.
// checkBarrierCollision runs BEFORE updateProjectile, so posX is post-last-movement.
// posX - motionX gives the pre-movement position (works for moveEntity and setPosition).
double prevDx = (projectile.posX - projectile.motionX) - this.posX;
double prevDy = (projectile.posY - projectile.motionY) - this.posY;
double prevDz = (projectile.posZ - projectile.motionZ) - this.posZ;
double prevDist = Math.sqrt(prevDx * prevDx + prevDy * prevDy + prevDz * prevDz);
// Only block projectiles that crossed the dome boundary from outside.
// Projectiles spawned inside (prevDist < radius) pass through freely.
if (prevDist < domeRadius) return false;
// Projectile entered from outside — check it's still moving inward
double dot = dx * projectile.motionX + dy * projectile.motionY + dz * projectile.motionZ;
// Negative dot = moving toward center = incoming
return dot < 0;
}
@@ -172,17 +184,37 @@ public class EntityEnergyDome extends EntityEnergyBarrier {
}
}
// ==================== MELEE (spherical check) ====================
/**
* Reject melee hits that land on the cubic bounding box but are outside the actual sphere.
*/
@Override
public boolean attackEntityFrom(DamageSource source, float amount) {
if (source.getEntity() != null) {
Entity attacker = source.getEntity();
double dx = attacker.posX - this.posX;
double dy = (attacker.posY + attacker.height * 0.5) - this.posY;
double dz = attacker.posZ - this.posZ;
double distSq = dx * dx + dy * dy + dz * dz;
// Allow hit only if attacker is within sphere + melee reach margin
double maxDist = domeRadius + 5.0;
if (distSq > maxDist * maxDist) return false;
}
return super.attackEntityFrom(source, amount);
}
// ==================== BOUNDING BOX ====================
/**
* Expands the ray-trace targeting area so players can melee-hit the dome
* from anywhere near its surface, not just the tiny 1x1 center.
* This is used by EntityRenderer.getMouseOver() to expand the entity BB
* for crosshair targeting without modifying the actual bounding box.
*/
@Override
public AxisAlignedBB getBoundingBox() {
if (barrierData.meleeEnabled) {
return AxisAlignedBB.getBoundingBox(
posX - domeRadius, posY - domeRadius, posZ - domeRadius,
posX + domeRadius, posY + domeRadius, posZ + domeRadius
);
}
return null;
public float getCollisionBorderSize() {
return domeRadius;
}
@Override
@@ -193,6 +225,27 @@ public class EntityEnergyDome extends EntityEnergyBarrier {
return distance < d * d;
}
// ==================== DISTANCE (for render sorting) ====================
/**
* Returns squared distance from the given point to the nearest point on the dome sphere surface.
* Uses absolute distance to surface: |centerDist - radius|.
* <p>
* Inside: nearest surface is (radius - centerDist) away.
* Outside: nearest surface is (centerDist - radius) away.
* This ensures entities inside the dome sort correctly relative to the dome shell.
*/
@Override
public double getDistanceSq(double x, double y, double z) {
double dx = this.posX - x;
double dy = this.posY - y;
double dz = this.posZ - z;
double centerDist = Math.sqrt(dx * dx + dy * dy + dz * dz);
double surfaceDist = Math.abs(centerDist - domeRadius);
return surfaceDist * surfaceDist;
}
// ==================== GETTERS ====================
public float getDomeRadius() {
@@ -167,6 +167,8 @@ public class EntityEnergyPanel extends EntityEnergyBarrier {
target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), panelData.launchDamage);
} else if (owner instanceof EntityPlayer) {
target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), panelData.launchDamage);
} else {
target.attackEntityFrom(DamageSource.generic, panelData.launchDamage);
}
}
@@ -188,6 +190,7 @@ public class EntityEnergyPanel extends EntityEnergyBarrier {
// Max lifetime check
if (ticksAlive > 200) {
onBarrierDestroyed();
this.setDead();
}
}
@@ -341,6 +344,47 @@ public class EntityEnergyPanel extends EntityEnergyBarrier {
return distance < d * d;
}
// ==================== DISTANCE (for render sorting) ====================
/**
* Returns squared distance from the given point to the closest point on the panel surface.
* Accounts for panel rotation (panelYaw), width, height, and thickness.
* This ensures correct transparency render ordering when the camera is near or behind the panel.
*/
@Override
public double getDistanceSq(double x, double y, double z) {
double dx = x - this.posX;
double dy = y - this.posY;
double dz = z - this.posZ;
float yawRad = (float) Math.toRadians(panelYaw);
double cos = Math.cos(yawRad);
double sin = Math.sin(yawRad);
// Transform to panel-local space
// Panel normal (forward): (-sin, 0, cos)
// Panel right (width axis): (cos, 0, sin)
double localForward = dx * (-sin) + dz * cos;
double localRight = dx * cos + dz * sin;
double localUp = dy;
float halfW = panelData.panelWidth * 0.5f;
float halfH = panelData.panelHeight * 0.5f;
float halfThickness = 0.25f;
// Clamp to panel bounds to find closest point on the panel volume
double clampedForward = Math.max(-halfThickness, Math.min(halfThickness, localForward));
double clampedRight = Math.max(-halfW, Math.min(halfW, localRight));
double clampedUp = Math.max(-halfH, Math.min(halfH, localUp));
// Distance from point to closest point on panel
double df = localForward - clampedForward;
double dr = localRight - clampedRight;
double du = localUp - clampedUp;
return df * df + dr * dr + du * du;
}
// ==================== GETTERS ====================
public PanelMode getMode() {
@@ -365,8 +409,8 @@ public class EntityEnergyPanel extends EntityEnergyBarrier {
this.mode = (modeOrdinal >= 0 && modeOrdinal < PanelMode.values().length)
? PanelMode.values()[modeOrdinal] : PanelMode.PLACED;
panelData.readNBT(nbt);
panelData.panelWidth = Math.min(panelData.panelWidth, MAX_ENTITY_SIZE);
panelData.panelHeight = Math.min(panelData.panelHeight, MAX_ENTITY_SIZE);
panelData.panelWidth = sanitize(panelData.panelWidth, 3.0f, MAX_ENTITY_SIZE);
panelData.panelHeight = sanitize(panelData.panelHeight, 3.0f, MAX_ENTITY_SIZE);
this.targetPanelWidth = sanitize(nbt.hasKey("TargetPanelWidth") ? nbt.getFloat("TargetPanelWidth") : panelData.panelWidth, 3.0f, MAX_ENTITY_SIZE);
this.targetPanelHeight = sanitize(nbt.hasKey("TargetPanelHeight") ? nbt.getFloat("TargetPanelHeight") : panelData.panelHeight, 3.0f, MAX_ENTITY_SIZE);
}
@@ -318,8 +318,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
* @return true if projectile was absorbed (caller should stop processing)
*/
protected boolean checkBarrierCollision() {
// Search area for barriers - must cover large domes (up to 30 radius)
double searchRange = 32.0;
// Search range must cover the largest possible barrier (dome radius up to MAX_ENTITY_RADIUS)
double searchRange = MAX_ENTITY_RADIUS + 5;
AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox(
posX - searchRange, posY - searchRange, posZ - searchRange,
posX + searchRange, posY + searchRange, posZ + searchRange
@@ -458,13 +458,15 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
protected void doExplosion() {
if (previewMode) return; // Skip explosion in preview mode
float explosionRad = getExplosionRadius();
if (Float.isNaN(explosionRad) || explosionRad <= 0) return;
worldObj.playSoundEffect(posX, posY, posZ, "random.explode", 1.0f, 1.0f);
Entity owner = getOwnerEntity();
AxisAlignedBB explosionBox = AxisAlignedBB.getBoundingBox(
posX - getExplosionRadius(), posY - getExplosionRadius(), posZ - getExplosionRadius(),
posX + getExplosionRadius(), posY + getExplosionRadius(), posZ + getExplosionRadius()
posX - explosionRad, posY - explosionRad, posZ - explosionRad,
posX + explosionRad, posY + explosionRad, posZ + explosionRad
);
@SuppressWarnings("unchecked")
@@ -479,9 +481,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
Math.pow(target.posZ - posZ, 2)
);
float radius = getExplosionRadius();
if (radius > 0 && dist <= radius) {
float falloff = 1.0f - (float) (dist / radius) * getExplosionDamageFalloff();
if (dist <= explosionRad) {
float falloff = 1.0f - (float) (dist / explosionRad) * getExplosionDamageFalloff();
applyDamage(target, getDamage() * falloff, getKnockback() * falloff);
}
}
@@ -966,14 +967,12 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
nbt.setFloat("Size", size);
// Write effects list
if (!effects.isEmpty()) {
NBTTagList effectsList = new NBTTagList();
for (AbilityPotionEffect effect : effects) {
effectsList.appendTag(effect.writeNBT());
}
nbt.setTag("Effects", effectsList);
// Write effects list (always write, even if empty, so readBaseNBT finds the key)
NBTTagList effectsList = new NBTTagList();
for (AbilityPotionEffect effect : effects) {
effectsList.appendTag(effect.writeNBT());
}
nbt.setTag("Effects", effectsList);
nbt.setLong("DeathWorldTime", deathWorldTime);
@@ -331,8 +331,8 @@ public class EntityEnergySlicer extends EntityEnergyProjectile {
this.sliceWidth = sanitize(nbt.hasKey("SliceWidth") ? nbt.getFloat("SliceWidth") : 3.0f, 3.0f, MAX_ENTITY_SIZE);
this.sliceThickness = sanitize(nbt.hasKey("SliceThickness") ? nbt.getFloat("SliceThickness") : 0.15f, 0.15f, MAX_ENTITY_SIZE);
this.piercing = !nbt.hasKey("Piercing") || nbt.getBoolean("Piercing");
this.targetSliceWidth = nbt.hasKey("TargetSliceWidth") ? nbt.getFloat("TargetSliceWidth") : sliceWidth;
this.targetSliceThickness = nbt.hasKey("TargetSliceThickness") ? nbt.getFloat("TargetSliceThickness") : sliceThickness;
this.targetSliceWidth = sanitize(nbt.hasKey("TargetSliceWidth") ? nbt.getFloat("TargetSliceWidth") : sliceWidth, sliceWidth, MAX_ENTITY_SIZE);
this.targetSliceThickness = sanitize(nbt.hasKey("TargetSliceThickness") ? nbt.getFloat("TargetSliceThickness") : sliceThickness, sliceThickness, MAX_ENTITY_SIZE);
}
@Override