diff --git a/src/api b/src/api index 092b18fe9..456bb2cbf 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit 092b18fe95d57353d16960411e5289ac3bbfa6ef +Subproject commit 456bb2cbfa7456d114bfc22caf49020353282e07 diff --git a/src/main/java/kamkeel/npcs/client/renderer/TelegraphRenderer.java b/src/main/java/kamkeel/npcs/client/renderer/TelegraphRenderer.java index 3b5e49e2d..78ba58aed 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/TelegraphRenderer.java +++ b/src/main/java/kamkeel/npcs/client/renderer/TelegraphRenderer.java @@ -110,6 +110,9 @@ public class TelegraphRenderer { case POINT: renderPoint(red, green, blue, alpha); break; + case SQUARE: + renderSquare(telegraph.getRadius(), red, green, blue, alpha); + break; default: break; } @@ -258,6 +261,30 @@ public class TelegraphRenderer { tessellator.draw(); } + private void renderSquare(float radius, float r, float g, float b, float a) { + Tessellator tessellator = Tessellator.instance; + tessellator.startDrawingQuads(); + tessellator.setColorRGBA_F(r, g, b, a); + + tessellator.addVertex(-radius, 0, -radius); + tessellator.addVertex(radius, 0, -radius); + tessellator.addVertex(radius, 0, radius); + tessellator.addVertex(-radius, 0, radius); + + tessellator.draw(); + + // Draw border + tessellator.startDrawing(GL11.GL_LINE_LOOP); + tessellator.setColorRGBA_F(r, g, b, Math.min(1.0f, a * 2)); + + tessellator.addVertex(-radius, 0.01, -radius); + tessellator.addVertex(radius, 0.01, -radius); + tessellator.addVertex(radius, 0.01, radius); + tessellator.addVertex(-radius, 0.01, radius); + + tessellator.draw(); + } + // ==================== GUI PREVIEW RENDERING ==================== /** @@ -322,6 +349,9 @@ public class TelegraphRenderer { case POINT: renderPoint(red, green, blue, alpha); break; + case SQUARE: + renderSquare(telegraph.getRadius(), red, green, blue, alpha); + break; default: break; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index b95879c3b..2dd86a4b4 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -117,7 +117,7 @@ public abstract class Ability implements IAbility { protected transient int currentTick = 0; protected transient EntityLivingBase currentTarget; protected transient long executionStartTime; - protected transient TelegraphInstance telegraphInstance; + protected transient List telegraphInstances = new ArrayList<>(); // Burst execution state protected transient int burstIndex = 0; @@ -682,6 +682,9 @@ public abstract class Ability implements IAbility { case POINT: telegraph = new Telegraph("", TelegraphType.POINT); break; + case SQUARE: + telegraph = Telegraph.square(getTelegraphRadius()); + break; default: return null; } @@ -716,6 +719,17 @@ public abstract class Ability implements IAbility { return instance; } + /** + * Create all telegraph instances for this ability. + * Override for abilities that need multiple telegraphs (e.g., zone abilities). + * Default wraps createTelegraph() in a singleton list. + */ + public List createTelegraphs(EntityLivingBase caster, EntityLivingBase target) { + TelegraphInstance instance = createTelegraph(caster, target); + if (instance == null) return new ArrayList<>(); + return new ArrayList<>(Collections.singletonList(instance)); + } + /** * Find the ground level at a given position. * Searches downward from the given Y to find a solid block. @@ -784,17 +798,17 @@ public abstract class Ability implements IAbility { } /** - * Get the current telegraph instance. + * Get the current telegraph instances. */ - public TelegraphInstance getTelegraphInstance() { - return telegraphInstance; + public List getTelegraphInstances() { + return telegraphInstances; } /** - * Set the telegraph instance (called by DataAbilities). + * Set the telegraph instances (called by DataAbilities). */ - public void setTelegraphInstance(TelegraphInstance instance) { - this.telegraphInstance = instance; + public void setTelegraphInstances(List instances) { + this.telegraphInstances = instances != null ? instances : new ArrayList<>(); } // ═══════════════════════════════════════════════════════════════════ @@ -918,7 +932,7 @@ public abstract class Ability implements IAbility { } burstIndex = 0; currentTarget = null; - telegraphInstance = null; + telegraphInstances.clear(); } /** @@ -970,7 +984,7 @@ public abstract class Ability implements IAbility { currentTick = 0; burstIndex = 0; currentTarget = null; - telegraphInstance = null; + telegraphInstances.clear(); previewMode = false; previewEntityHandler = null; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityBeam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityBeam.java index f99a03528..8bf2341f5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityBeam.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityBeam.java @@ -118,8 +118,9 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam { this.fireDelay = Math.max(0, delay); } - // Legacy compat + /** @deprecated Use {@link #getFireDelay()} */ public int getDualFireDelay() { return fireDelay; } + /** @deprecated Use {@link #setFireDelay(int)} */ public void setDualFireDelay(int delay) { setFireDelay(delay); } @Override @@ -225,8 +226,13 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam { for (EntityAbilityBeam beam : beamEntities) { spawnAbilityEntity(world, beam); } - for (EntityAbilityBeam beam : beamEntities) { - fireBeamEntity(beam, target); + + // Fire first projectile immediately, let onActiveTick handle the rest with fireDelay + fireBeamEntity(beamEntities[0], target); + if (fireDelay <= 0) { + for (int i = 1; i < projectileCount; i++) { + fireBeamEntity(beamEntities[i], target); + } } } @@ -356,55 +362,21 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam { int count = nbt.hasKey("projectileCount") ? nbt.getInteger("projectileCount") : 1; initProjectiles(count); - if (nbt.hasKey("fireDelay")) { - this.fireDelay = nbt.getInteger("fireDelay"); - } else if (nbt.hasKey("dualFireDelay")) { - this.fireDelay = nbt.getInteger("dualFireDelay"); - } else { - this.fireDelay = 0; - } + this.fireDelay = nbt.hasKey("fireDelay") ? nbt.getInteger("fireDelay") : 0; combatData.readNBT(nbt); homingData.readNBT(nbt); lifespanData.readNBT(nbt); - if (nbt.hasKey("Projectile_0")) { - NBTTagCompound proj0 = nbt.getCompoundTag("Projectile_0"); - if (proj0.hasKey("colorOverride")) { - // NEW format - displayData.readNBT(nbt); - lightningData.readNBT(nbt); - for (int i = 0; i < projectileCount; i++) { - if (nbt.hasKey("Projectile_" + i)) { - projectiles[i].readNBT(nbt.getCompoundTag("Projectile_" + i)); - } - } - } else { - // OLD per-projectile format - displayData.readNBT(proj0); - lightningData.readNBT(proj0); - projectiles[0].anchor.readNBT(proj0); + // Shared visual data + displayData.readNBT(nbt); + lightningData.readNBT(nbt); - for (int i = 1; i < projectileCount; i++) { - if (nbt.hasKey("Projectile_" + i)) { - NBTTagCompound projNbt = nbt.getCompoundTag("Projectile_" + i); - projectiles[i].anchor.readNBT(projNbt); - - EnergyDisplayData tempDisplay = new EnergyDisplayData(); - tempDisplay.readNBT(projNbt); - if (tempDisplay.innerColor != displayData.innerColor || tempDisplay.outerColor != displayData.outerColor) { - projectiles[i].colorOverride = true; - projectiles[i].innerColor = tempDisplay.innerColor; - projectiles[i].outerColor = tempDisplay.outerColor; - } - } - } + // Per-projectile data + for (int i = 0; i < projectileCount; i++) { + if (nbt.hasKey("Projectile_" + i)) { + projectiles[i].readNBT(nbt.getCompoundTag("Projectile_" + i)); } - } else { - // LEGACY flat format - displayData.readNBT(nbt); - lightningData.readNBT(nbt); - projectiles[0].anchor.readNBT(nbt); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDisc.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDisc.java index f890b7d7b..886a269e2 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDisc.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDisc.java @@ -124,8 +124,9 @@ public class AbilityDisc extends Ability implements IAbilityDisc { this.fireDelay = Math.max(0, delay); } - // Legacy compat + /** @deprecated Use {@link #getFireDelay()} */ public int getDualFireDelay() { return fireDelay; } + /** @deprecated Use {@link #setFireDelay(int)} */ public void setDualFireDelay(int delay) { setFireDelay(delay); } @Override @@ -228,8 +229,13 @@ public class AbilityDisc extends Ability implements IAbilityDisc { for (EntityAbilityDisc disc : discEntities) { spawnAbilityEntity(world, disc); } - for (EntityAbilityDisc disc : discEntities) { - fireDiscEntity(disc, target); + + // Fire first projectile immediately, let onActiveTick handle the rest with fireDelay + fireDiscEntity(discEntities[0], target); + if (fireDelay <= 0) { + for (int i = 1; i < projectileCount; i++) { + fireDiscEntity(discEntities[i], target); + } } } @@ -371,61 +377,22 @@ public class AbilityDisc extends Ability implements IAbilityDisc { int count = nbt.hasKey("projectileCount") ? nbt.getInteger("projectileCount") : 1; initProjectiles(count); - // Read fireDelay with fallback to old dualFireDelay key - if (nbt.hasKey("fireDelay")) { - this.fireDelay = nbt.getInteger("fireDelay"); - } else if (nbt.hasKey("dualFireDelay")) { - this.fireDelay = nbt.getInteger("dualFireDelay"); - } else { - this.fireDelay = 0; - } + this.fireDelay = nbt.hasKey("fireDelay") ? nbt.getInteger("fireDelay") : 0; // Shared combat/movement data combatData.readNBT(nbt); homingData.readNBT(nbt); lifespanData.readNBT(nbt); - // Determine format and read accordingly - if (nbt.hasKey("Projectile_0")) { - NBTTagCompound proj0 = nbt.getCompoundTag("Projectile_0"); - if (proj0.hasKey("colorOverride")) { - // NEW format: shared visuals at root + ProjectileData per projectile - displayData.readNBT(nbt); - lightningData.readNBT(nbt); - for (int i = 0; i < projectileCount; i++) { - if (nbt.hasKey("Projectile_" + i)) { - projectiles[i].readNBT(nbt.getCompoundTag("Projectile_" + i)); - } - } - } else { - // OLD format: full per-projectile arrays (displayData, lightningData, etc. in each Projectile_i) - // Read primary visuals from Projectile_0 - displayData.readNBT(proj0); - lightningData.readNBT(proj0); - projectiles[0].anchor.readNBT(proj0); + // Shared visual data + displayData.readNBT(nbt); + lightningData.readNBT(nbt); - // Read remaining projectiles, set color override if colors differ - for (int i = 1; i < projectileCount; i++) { - if (nbt.hasKey("Projectile_" + i)) { - NBTTagCompound projNbt = nbt.getCompoundTag("Projectile_" + i); - projectiles[i].anchor.readNBT(projNbt); - - // Check if this projectile had different colors - EnergyDisplayData tempDisplay = new EnergyDisplayData(); - tempDisplay.readNBT(projNbt); - if (tempDisplay.innerColor != displayData.innerColor || tempDisplay.outerColor != displayData.outerColor) { - projectiles[i].colorOverride = true; - projectiles[i].innerColor = tempDisplay.innerColor; - projectiles[i].outerColor = tempDisplay.outerColor; - } - } - } + // Per-projectile data + for (int i = 0; i < projectileCount; i++) { + if (nbt.hasKey("Projectile_" + i)) { + projectiles[i].readNBT(nbt.getCompoundTag("Projectile_" + i)); } - } else { - // LEGACY flat format: all data at root level - displayData.readNBT(nbt); - lightningData.readNBT(nbt); - projectiles[0].anchor.readNBT(nbt); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java index 35397371b..51538da55 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java @@ -27,23 +27,23 @@ import java.util.List; public class AbilityHazard extends AbilityZone implements IAbilityHazard { // Hazard-specific fields - private float radius = 4.0f; + private float radius = 2.0f; private float damagePerSecond = 1.0f; private int damageInterval = 20; private boolean ignoreInvulnFrames = false; private boolean affectsCaster = false; public AbilityHazard() { - super(100, new EnergyDisplayData(0x00CC00, 0x006600, true, 1.0f, 0.5f, 1.5f)); + super(300, new EnergyDisplayData(0x00CC00, 0x006600, true, 1.0f, 0.5f, 1.5f)); this.typeId = "ability.cnpc.hazard"; this.name = "Hazard"; this.windUpTicks = 30; - this.windUpAnimationName = "Ability_Hazard_Windup"; - this.activeAnimationName = "Ability_Hazard_Active"; + this.windUpAnimationName = "Ability_Zone_Windup"; + this.activeAnimationName = "Ability_Zone_Active"; } @Override - public float getTelegraphRadius() { + public float getZoneRadius() { return radius; } @@ -56,12 +56,24 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard { activeEntities.clear(); - List placedPositions = new ArrayList<>(); - float minSeparation = radius * 2.0f; + // Use pre-calculated positions from telegraph phase, or generate new ones + List positions; + if (!preCalculatedPositions.isEmpty() && preCalculatedPositions.size() == zoneCount) { + positions = new ArrayList<>(preCalculatedPositions); + preCalculatedPositions.clear(); + } else { + positions = new ArrayList<>(); + List placedPositions = new ArrayList<>(); + float minSeparation = radius * 2.0f; + for (int i = 0; i < zoneCount; i++) { + double[] pos = findSpawnPosition(caster, placedPositions, minSeparation); + placedPositions.add(pos); + positions.add(pos); + } + } for (int i = 0; i < zoneCount; i++) { - double[] pos = findSpawnPosition(caster, placedPositions, minSeparation); - placedPositions.add(pos); + double[] pos = positions.get(i); EntityAbilityZone entity = EntityAbilityZone.createHazard(world, caster, pos[0], caster.posY, pos[1], @@ -102,26 +114,9 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard { @Override public void readTypeNBT(NBTTagCompound nbt) { - readZoneNBT(nbt, 100); - - // Backward compat: old "shape" key with RING/CONE → ignore (base defaults to CIRCLE) - // The old HazardShape enum had CIRCLE/RING/CONE — only CIRCLE maps to new ZoneShape - if (nbt.hasKey("shape")) { - String oldShape = nbt.getString("shape"); - if ("RING".equals(oldShape) || "CONE".equals(oldShape)) { - this.zoneShape = ZoneShape.CIRCLE; - } - } - - this.radius = nbt.hasKey("radius") ? nbt.getFloat("radius") : 4.0f; - // Support legacy "damagePerTick" key for backwards compatibility - if (nbt.hasKey("damagePerSecond")) { - this.damagePerSecond = nbt.getFloat("damagePerSecond"); - } else if (nbt.hasKey("damagePerTick")) { - this.damagePerSecond = nbt.getFloat("damagePerTick"); - } else { - this.damagePerSecond = 1.0f; - } + readZoneNBT(nbt, 300); + this.radius = nbt.hasKey("radius") ? nbt.getFloat("radius") : 2.0f; + this.damagePerSecond = nbt.hasKey("damagePerSecond") ? nbt.getFloat("damagePerSecond") : 1.0f; this.damageInterval = nbt.hasKey("damageInterval") ? nbt.getInteger("damageInterval") : 20; this.ignoreInvulnFrames = nbt.hasKey("ignoreInvulnFrames") && nbt.getBoolean("ignoreInvulnFrames"); this.affectsCaster = nbt.hasKey("affectsCaster") && nbt.getBoolean("affectsCaster"); @@ -178,6 +173,5 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard { )); addVisualFieldDefs(defs); - addTelegraphSizeField(defs); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java index c4a4a1ebb..8b4783f7c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java @@ -115,8 +115,9 @@ public class AbilityOrb extends Ability implements IAbilityOrb { this.fireDelay = Math.max(0, delay); } - // Legacy compat + /** @deprecated Use {@link #getFireDelay()} */ public int getDualFireDelay() { return fireDelay; } + /** @deprecated Use {@link #setFireDelay(int)} */ public void setDualFireDelay(int delay) { setFireDelay(delay); } @Override @@ -229,8 +230,13 @@ public class AbilityOrb extends Ability implements IAbilityOrb { for (EntityAbilityOrb orb : orbEntities) { spawnAbilityEntity(world, orb); } - for (EntityAbilityOrb orb : orbEntities) { - fireOrbEntity(orb, target); + + // Fire first projectile immediately, let onActiveTick handle the rest with fireDelay + fireOrbEntity(orbEntities[0], target); + if (fireDelay <= 0) { + for (int i = 1; i < projectileCount; i++) { + fireOrbEntity(orbEntities[i], target); + } } } @@ -354,8 +360,6 @@ public class AbilityOrb extends Ability implements IAbilityOrb { nbt.setTag("Projectile_" + i, projNbt); } - // Backward compat write - nbt.setFloat("orbSpeed", homingData.speed); } @Override @@ -365,66 +369,22 @@ public class AbilityOrb extends Ability implements IAbilityOrb { int count = nbt.hasKey("projectileCount") ? nbt.getInteger("projectileCount") : 1; initProjectiles(count); - // Read fireDelay with fallback to old dualFireDelay key - if (nbt.hasKey("fireDelay")) { - this.fireDelay = nbt.getInteger("fireDelay"); - } else if (nbt.hasKey("dualFireDelay")) { - this.fireDelay = nbt.getInteger("dualFireDelay"); - } else { - this.fireDelay = 0; - } + this.fireDelay = nbt.hasKey("fireDelay") ? nbt.getInteger("fireDelay") : 0; // Shared combat/movement data combatData.readNBT(nbt); homingData.readNBT(nbt); lifespanData.readNBT(nbt); - // Determine format and read accordingly - if (nbt.hasKey("Projectile_0")) { - NBTTagCompound proj0 = nbt.getCompoundTag("Projectile_0"); - if (proj0.hasKey("colorOverride")) { - // NEW format: shared visuals at root + ProjectileData per projectile - displayData.readNBT(nbt); - lightningData.readNBT(nbt); - for (int i = 0; i < projectileCount; i++) { - if (nbt.hasKey("Projectile_" + i)) { - projectiles[i].readNBT(nbt.getCompoundTag("Projectile_" + i)); - } - } - } else { - // OLD format: full per-projectile arrays (displayData, lightningData, etc. in each Projectile_i) - // Read primary visuals from Projectile_0 - displayData.readNBT(proj0); - lightningData.readNBT(proj0); - projectiles[0].anchor.readNBT(proj0); + // Shared visual data + displayData.readNBT(nbt); + lightningData.readNBT(nbt); - // Read remaining projectiles, set color override if colors differ - for (int i = 1; i < projectileCount; i++) { - if (nbt.hasKey("Projectile_" + i)) { - NBTTagCompound projNbt = nbt.getCompoundTag("Projectile_" + i); - projectiles[i].anchor.readNBT(projNbt); - - // Check if this projectile had different colors - EnergyDisplayData tempDisplay = new EnergyDisplayData(); - tempDisplay.readNBT(projNbt); - if (tempDisplay.innerColor != displayData.innerColor || tempDisplay.outerColor != displayData.outerColor) { - projectiles[i].colorOverride = true; - projectiles[i].innerColor = tempDisplay.innerColor; - projectiles[i].outerColor = tempDisplay.outerColor; - } - } - } + // Per-projectile data + for (int i = 0; i < projectileCount; i++) { + if (nbt.hasKey("Projectile_" + i)) { + projectiles[i].readNBT(nbt.getCompoundTag("Projectile_" + i)); } - } else { - // LEGACY flat format: all data at root level - displayData.readNBT(nbt); - lightningData.readNBT(nbt); - projectiles[0].anchor.readNBT(nbt); - } - - // Backward compat - if (nbt.hasKey("orbSpeed")) { - homingData.speed = nbt.getFloat("orbSpeed"); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java index 8e09c43e7..4b19bc91d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java @@ -441,15 +441,6 @@ public class AbilityTeleport extends Ability implements IAbilityTeleport { } catch (Exception e) { this.mode = TeleportMode.BLINK; } - } else if (nbt.hasKey("pattern")) { - String legacy = nbt.getString("pattern"); - if ("BEHIND_TARGET".equals(legacy)) { - this.mode = TeleportMode.BEHIND; - } else if ("RANDOM".equals(legacy)) { - this.mode = TeleportMode.BLINK; - } else { - this.mode = TeleportMode.SINGLE; - } } else { this.mode = TeleportMode.BLINK; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java index 173efa0de..d55a4060c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java @@ -37,10 +37,17 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap { private boolean visible = true; public AbilityTrap() { - super(200, new EnergyDisplayData(0xFF6600, 0xFF0000, true, 1.0f, 0.5f, 1.5f)); + super(300, new EnergyDisplayData(0xFF6600, 0xFF0000, true, 1.0f, 0.5f, 1.5f)); this.typeId = "ability.cnpc.trap"; this.name = "Trap"; - this.windUpTicks = 20; + this.windUpTicks = 30; + this.windUpAnimationName = "Ability_Zone_Windup"; + this.activeAnimationName = "Ability_Zone_Active"; + } + + @Override + public float getZoneRadius() { + return triggerRadius; } @Override @@ -52,12 +59,24 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap { activeEntities.clear(); - List placedPositions = new ArrayList<>(); - float minSeparation = triggerRadius * 2.0f; + // Use pre-calculated positions from telegraph phase, or generate new ones + List positions; + if (!preCalculatedPositions.isEmpty() && preCalculatedPositions.size() == zoneCount) { + positions = new ArrayList<>(preCalculatedPositions); + preCalculatedPositions.clear(); + } else { + positions = new ArrayList<>(); + List placedPositions = new ArrayList<>(); + float minSeparation = triggerRadius * 2.0f; + for (int i = 0; i < zoneCount; i++) { + double[] pos = findSpawnPosition(caster, placedPositions, minSeparation); + placedPositions.add(pos); + positions.add(pos); + } + } for (int i = 0; i < zoneCount; i++) { - double[] pos = findSpawnPosition(caster, placedPositions, minSeparation); - placedPositions.add(pos); + double[] pos = positions.get(i); EntityAbilityZone entity = EntityAbilityZone.createTrap(world, caster, pos[0], caster.posY, pos[1], @@ -99,7 +118,7 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap { @Override public void readTypeNBT(NBTTagCompound nbt) { - readZoneNBT(nbt, 200); + readZoneNBT(nbt, 300); this.triggerRadius = nbt.hasKey("triggerRadius") ? nbt.getFloat("triggerRadius") : 2.0f; this.armTime = nbt.hasKey("armTime") ? nbt.getInteger("armTime") : 20; this.maxTriggers = nbt.hasKey("maxTriggers") ? nbt.getInteger("maxTriggers") : 1; @@ -171,6 +190,5 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap { )); addVisualFieldDefs(defs); - addTelegraphSizeField(defs); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java index 94ceea56d..5f8c33499 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java @@ -5,6 +5,7 @@ import kamkeel.npcs.controllers.data.ability.LockMovementType; import kamkeel.npcs.controllers.data.ability.TargetingMode; import kamkeel.npcs.controllers.data.ability.UserType; import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilityZone; @@ -36,9 +37,8 @@ public abstract class AbilityZone extends Ability { // Zone properties protected int durationTicks; protected ZoneShape zoneShape = ZoneShape.CIRCLE; - protected float spawnRadius = 5.0f; - protected float telegraphSize = 5.0f; - protected int zoneCount = 1; + protected float spawnRadius = 10.0f; + protected int zoneCount = 3; protected float zoneHeight = 2.0f; // Visual parameters @@ -67,15 +67,15 @@ public abstract class AbilityZone extends Ability { // Runtime state protected static final Random RANDOM = new Random(); protected transient List activeEntities = new ArrayList<>(); + protected transient List preCalculatedPositions = new ArrayList<>(); protected AbilityZone(int defaultDuration, EnergyDisplayData defaultColors) { this.durationTicks = defaultDuration; this.colorData = defaultColors; this.targetingMode = TargetingMode.AGGRO_TARGET; - this.maxRange = 15.0f; + this.maxRange = 20.0f; this.lockMovement = LockMovementType.WINDUP; this.cooldownTicks = 0; - this.telegraphType = TelegraphType.CIRCLE; this.allowedBy = UserType.NPC_ONLY; } @@ -98,22 +98,53 @@ public abstract class AbilityZone extends Ability { return new TargetingMode[]{TargetingMode.AGGRO_TARGET}; } - @Override - public float getTelegraphRadius() { - return telegraphSize; - } + /** + * Returns the actual zone effect radius for telegraph sizing. + * AbilityHazard returns its damage radius, AbilityTrap returns triggerRadius. + */ + public abstract float getZoneRadius(); @Override - public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) { - TelegraphInstance instance = super.createTelegraph(caster, target); - if (instance == null) return null; + public List createTelegraphs(EntityLivingBase caster, EntityLivingBase target) { + if (!showTelegraph || windUpTicks <= 0) return new ArrayList<>(); - // Always show telegraph at caster position - instance.setEntityIdToFollow(-1); - instance.setX(caster.posX); - instance.setY(caster.posY); - instance.setZ(caster.posZ); - return instance; + // Pre-calculate zone positions + preCalculatedPositions.clear(); + List placedPositions = new ArrayList<>(); + float minSeparation = getZoneRadius() * 2.0f; + + for (int i = 0; i < zoneCount; i++) { + double[] pos = findSpawnPosition(caster, placedPositions, minSeparation); + placedPositions.add(pos); + preCalculatedPositions.add(pos); + } + + // Create a telegraph for each zone position + List telegraphs = new ArrayList<>(); + float zoneRadius = getZoneRadius(); + + for (double[] pos : preCalculatedPositions) { + Telegraph telegraph; + if (zoneShape == ZoneShape.SQUARE) { + telegraph = Telegraph.square(zoneRadius); + } else { + telegraph = Telegraph.circle(zoneRadius); + } + + telegraph.setDurationTicks(windUpTicks); + telegraph.setColor(windUpColor); + telegraph.setWarningColor(activeColor); + telegraph.setWarningStartTick(Math.max(5, windUpTicks / 4)); + telegraph.setHeightOffset(telegraphHeightOffset); + + double groundY = findGroundLevel(caster.worldObj, pos[0], caster.posY, pos[1]); + TelegraphInstance instance = new TelegraphInstance(telegraph, pos[0], groundY, pos[1], 0); + instance.setCasterEntityId(caster.getEntityId()); + instance.setEntityIdToFollow(-1); + telegraphs.add(instance); + } + + return telegraphs; } @Override @@ -150,6 +181,7 @@ public abstract class AbilityZone extends Ability { } } activeEntities.clear(); + preCalculatedPositions.clear(); } @Override @@ -208,7 +240,7 @@ public abstract class AbilityZone extends Ability { // ═════════════════════════════════════════════════════════════════ /** - * Apply preset defaults by old style name (for backward compatibility). + * Apply preset defaults by style name. */ private void applyPresetDefaults(String styleName) { switch (styleName) { @@ -307,7 +339,6 @@ public abstract class AbilityZone extends Ability { nbt.setInteger("durationTicks", durationTicks); nbt.setString("zoneShape", zoneShape.name()); nbt.setFloat("spawnRadius", spawnRadius); - nbt.setFloat("telegraphSize", telegraphSize); nbt.setInteger("zoneCount", zoneCount); nbt.setFloat("zoneHeight", zoneHeight); nbt.setFloat("particleDensity", particleDensity); @@ -341,9 +372,8 @@ public abstract class AbilityZone extends Ability { } else { this.zoneShape = ZoneShape.CIRCLE; } - this.spawnRadius = nbt.hasKey("spawnRadius") ? nbt.getFloat("spawnRadius") : 5.0f; - this.telegraphSize = nbt.hasKey("telegraphSize") ? nbt.getFloat("telegraphSize") : spawnRadius; - this.zoneCount = nbt.hasKey("zoneCount") ? nbt.getInteger("zoneCount") : 1; + this.spawnRadius = nbt.hasKey("spawnRadius") ? nbt.getFloat("spawnRadius") : 10.0f; + this.zoneCount = nbt.hasKey("zoneCount") ? nbt.getInteger("zoneCount") : 3; 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; @@ -351,44 +381,21 @@ public abstract class AbilityZone extends Ability { this.lightningDensity = nbt.hasKey("lightningDensity") ? nbt.getFloat("lightningDensity") : 1.0f; colorData.readNBT(nbt); - // Visual fields — backward compat with old zoneStyle format - if (nbt.hasKey("zoneStyle")) { - // OLD FORMAT: apply preset defaults, then overlay CustomVisual - applyPresetDefaults(nbt.getString("zoneStyle")); - if (nbt.hasKey("CustomVisual")) { - NBTTagCompound custom = nbt.getCompoundTag("CustomVisual"); - this.groundFill = !custom.hasKey("groundFill") || custom.getBoolean("groundFill"); - this.groundAlpha = custom.hasKey("groundAlpha") ? custom.getFloat("groundAlpha") : 0.25f; - this.rings = !custom.hasKey("rings") || custom.getBoolean("rings"); - this.ringCount = custom.hasKey("ringCount") ? custom.getInteger("ringCount") : 3; - this.border = !custom.hasKey("border") || custom.getBoolean("border"); - this.borderSpeed = custom.hasKey("borderSpeed") ? custom.getFloat("borderSpeed") : 1.0f; - this.accents = !custom.hasKey("accents") || custom.getBoolean("accents"); - this.accentStyle = custom.hasKey("accentStyle") ? custom.getInteger("accentStyle") : 0; - this.lightning = custom.hasKey("lightning") && custom.getBoolean("lightning"); - this.particles = !custom.hasKey("particles") || custom.getBoolean("particles"); - this.particleMotion = custom.hasKey("particleMotion") ? custom.getInteger("particleMotion") : 0; - this.particleDir = custom.hasKey("particleDir") ? custom.getString("particleDir") : ""; - this.particleSize = custom.hasKey("particleSize") ? custom.getInteger("particleSize") : 32; - this.particleGlow = !custom.hasKey("particleGlow") || custom.getBoolean("particleGlow"); - } - } else { - // NEW FORMAT: read individual top-level keys - this.groundFill = !nbt.hasKey("groundFill") || nbt.getBoolean("groundFill"); - this.groundAlpha = nbt.hasKey("groundAlpha") ? nbt.getFloat("groundAlpha") : 0.25f; - this.rings = !nbt.hasKey("rings") || nbt.getBoolean("rings"); - this.ringCount = nbt.hasKey("ringCount") ? nbt.getInteger("ringCount") : 3; - this.border = !nbt.hasKey("border") || nbt.getBoolean("border"); - this.borderSpeed = nbt.hasKey("borderSpeed") ? nbt.getFloat("borderSpeed") : 1.0f; - this.accents = !nbt.hasKey("accents") || nbt.getBoolean("accents"); - this.accentStyle = nbt.hasKey("accentStyle") ? nbt.getInteger("accentStyle") : 0; - this.lightning = nbt.hasKey("lightning") && nbt.getBoolean("lightning"); - this.particles = !nbt.hasKey("particles") || nbt.getBoolean("particles"); - this.particleMotion = nbt.hasKey("particleMotion") ? nbt.getInteger("particleMotion") : 0; - this.particleDir = nbt.hasKey("particleDir") ? nbt.getString("particleDir") : ""; - this.particleSize = nbt.hasKey("particleSize") ? nbt.getInteger("particleSize") : 32; - this.particleGlow = !nbt.hasKey("particleGlow") || nbt.getBoolean("particleGlow"); - } + // Visual layer fields + this.groundFill = !nbt.hasKey("groundFill") || nbt.getBoolean("groundFill"); + this.groundAlpha = nbt.hasKey("groundAlpha") ? nbt.getFloat("groundAlpha") : 0.25f; + this.rings = !nbt.hasKey("rings") || nbt.getBoolean("rings"); + this.ringCount = nbt.hasKey("ringCount") ? nbt.getInteger("ringCount") : 3; + this.border = !nbt.hasKey("border") || nbt.getBoolean("border"); + this.borderSpeed = nbt.hasKey("borderSpeed") ? nbt.getFloat("borderSpeed") : 1.0f; + this.accents = !nbt.hasKey("accents") || nbt.getBoolean("accents"); + this.accentStyle = nbt.hasKey("accentStyle") ? nbt.getInteger("accentStyle") : 0; + this.lightning = nbt.hasKey("lightning") && nbt.getBoolean("lightning"); + this.particles = !nbt.hasKey("particles") || nbt.getBoolean("particles"); + this.particleMotion = nbt.hasKey("particleMotion") ? nbt.getInteger("particleMotion") : 0; + this.particleDir = nbt.hasKey("particleDir") ? nbt.getString("particleDir") : ""; + this.particleSize = nbt.hasKey("particleSize") ? nbt.getInteger("particleSize") : 32; + this.particleGlow = !nbt.hasKey("particleGlow") || nbt.getBoolean("particleGlow"); } // ═════════════════════════════════════════════════════════════════ @@ -409,8 +416,6 @@ public abstract class AbilityZone extends Ability { public float getSpawnRadius() { return spawnRadius; } public void setSpawnRadius(float spawnRadius) { this.spawnRadius = Math.max(0, spawnRadius); } - public float getTelegraphSize() { return telegraphSize; } - public void setTelegraphSize(float telegraphSize) { this.telegraphSize = Math.max(0, telegraphSize); } public int getZoneCount() { return zoneCount; } public void setZoneCount(int zoneCount) { this.zoneCount = Math.max(1, zoneCount); } public float getZoneHeight() { return zoneHeight; } @@ -546,9 +551,4 @@ public abstract class AbilityZone extends Ability { )); } - @SideOnly(Side.CLIENT) - protected void addTelegraphSizeField(List defs) { - defs.add(FieldDef.floatField("gui.size", this::getTelegraphSize, this::setTelegraphSize) - .tab("ability.tab.effects")); - } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/telegraph/Telegraph.java b/src/main/java/kamkeel/npcs/controllers/data/telegraph/Telegraph.java index 3ba858acc..bcb7a7958 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/telegraph/Telegraph.java +++ b/src/main/java/kamkeel/npcs/controllers/data/telegraph/Telegraph.java @@ -150,6 +150,12 @@ public class Telegraph { return new Telegraph("", TelegraphType.POINT); } + public static Telegraph square(float radius) { + Telegraph t = new Telegraph("", TelegraphType.SQUARE); + t.radius = radius; + return t; + } + // ═══════════════════════════════════════════════════════════════════ // GETTERS & SETTERS // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/java/kamkeel/npcs/controllers/data/telegraph/TelegraphType.java b/src/main/java/kamkeel/npcs/controllers/data/telegraph/TelegraphType.java index 4a2fe4b12..dd080f093 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/telegraph/TelegraphType.java +++ b/src/main/java/kamkeel/npcs/controllers/data/telegraph/TelegraphType.java @@ -29,6 +29,11 @@ public enum TelegraphType { */ POINT, + /** + * Filled square (uses radius as half-size) + */ + SQUARE, + /** * No telegraph */ diff --git a/src/main/java/noppes/npcs/DataAbilities.java b/src/main/java/noppes/npcs/DataAbilities.java index 5e1ff694f..b5c23c756 100644 --- a/src/main/java/noppes/npcs/DataAbilities.java +++ b/src/main/java/noppes/npcs/DataAbilities.java @@ -191,10 +191,9 @@ public class DataAbilities { case ACTIVE: if (phaseChanged && (oldPhase == AbilityPhase.WINDUP || oldPhase == AbilityPhase.BURST_DELAY)) { - // Just entered ACTIVE phase - lock telegraph position if it was following + // Just entered ACTIVE phase - lock telegraph positions if they were following // This commits the ability to its current target position - TelegraphInstance telegraph = currentAbility.getTelegraphInstance(); - if (telegraph != null) { + for (TelegraphInstance telegraph : currentAbility.getTelegraphInstances()) { telegraph.lockPosition(); } @@ -603,26 +602,27 @@ public class DataAbilities { } /** - * Spawn and send telegraph for an ability. + * Spawn and send telegraphs for an ability. */ private void spawnTelegraph(Ability ability, EntityLivingBase target) { - TelegraphInstance telegraph = ability.createTelegraph(npc, target); - if (telegraph != null) { - ability.setTelegraphInstance(telegraph); - // Send to all nearby players - TelegraphSpawnPacket.sendToTracking(telegraph, npc); + List telegraphs = ability.createTelegraphs(npc, target); + if (!telegraphs.isEmpty()) { + ability.setTelegraphInstances(telegraphs); + for (TelegraphInstance telegraph : telegraphs) { + TelegraphSpawnPacket.sendToTracking(telegraph, npc); + } } } /** - * Remove telegraph when ability ends or is interrupted. + * Remove telegraphs when ability ends or is interrupted. */ private void removeTelegraph(Ability ability) { - TelegraphInstance telegraph = ability.getTelegraphInstance(); - if (telegraph != null) { + List telegraphs = ability.getTelegraphInstances(); + for (TelegraphInstance telegraph : telegraphs) { TelegraphRemovePacket.sendToTracking(telegraph.getInstanceId(), npc); - ability.setTelegraphInstance(null); } + ability.setTelegraphInstances(null); } // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index 6d265de27..a653e2a94 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -118,9 +118,8 @@ public class PlayerAbilityData implements IPlayerAbilityData { case ACTIVE: if (phaseChanged && oldPhase == AbilityPhase.WINDUP) { - // Just entered ACTIVE phase - TelegraphInstance telegraph = currentAbility.getTelegraphInstance(); - if (telegraph != null) { + // Just entered ACTIVE phase - lock all telegraph positions + for (TelegraphInstance telegraph : currentAbility.getTelegraphInstances()) { telegraph.lockPosition(); } removeTelegraph(currentAbility, player); @@ -451,23 +450,25 @@ public class PlayerAbilityData implements IPlayerAbilityData { // ═══════════════════════════════════════════════════════════════════ private void spawnTelegraph(Ability ability, EntityPlayer player, EntityLivingBase target) { - TelegraphInstance telegraph = ability.createTelegraph(player, target); - if (telegraph != null) { - ability.setTelegraphInstance(telegraph); + List telegraphs = ability.createTelegraphs(player, target); + if (!telegraphs.isEmpty()) { + ability.setTelegraphInstances(telegraphs); if (player instanceof EntityPlayerMP) { - TelegraphSpawnPacket.sendToTracking(telegraph, player); + for (TelegraphInstance telegraph : telegraphs) { + TelegraphSpawnPacket.sendToTracking(telegraph, player); + } } } } private void removeTelegraph(Ability ability, EntityPlayer player) { - TelegraphInstance telegraph = ability.getTelegraphInstance(); - if (telegraph != null) { + List telegraphs = ability.getTelegraphInstances(); + for (TelegraphInstance telegraph : telegraphs) { if (player instanceof EntityPlayerMP) { TelegraphRemovePacket.sendToTracking(telegraph.getInstanceId(), player); } - ability.setTelegraphInstance(null); } + ability.setTelegraphInstances(null); } // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/resources/assets/customnpcs/animations/Ability_Hazard_Active.json b/src/main/resources/assets/customnpcs/animations/Ability_Zone_Active.json similarity index 99% rename from src/main/resources/assets/customnpcs/animations/Ability_Hazard_Active.json rename to src/main/resources/assets/customnpcs/animations/Ability_Zone_Active.json index 11cb0ef7a..747f73860 100644 --- a/src/main/resources/assets/customnpcs/animations/Ability_Hazard_Active.json +++ b/src/main/resources/assets/customnpcs/animations/Ability_Zone_Active.json @@ -261,6 +261,6 @@ "ID": 24, "WhileAttacking": 1b, "WhileWalking": 1b, - "Name": "Ability_Hazard_Active", + "Name": "Ability_Zone_Active", "WhileStanding": 1b } diff --git a/src/main/resources/assets/customnpcs/animations/Ability_Hazard_Windup.json b/src/main/resources/assets/customnpcs/animations/Ability_Zone_Windup.json similarity index 99% rename from src/main/resources/assets/customnpcs/animations/Ability_Hazard_Windup.json rename to src/main/resources/assets/customnpcs/animations/Ability_Zone_Windup.json index 8a16135a4..40a6abef7 100644 --- a/src/main/resources/assets/customnpcs/animations/Ability_Hazard_Windup.json +++ b/src/main/resources/assets/customnpcs/animations/Ability_Zone_Windup.json @@ -387,6 +387,6 @@ "ID": 23, "WhileAttacking": 1b, "WhileWalking": 1b, - "Name": "Ability_Hazard_Windup", + "Name": "Ability_Zone_Windup", "WhileStanding": 1b }