Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Vidal committed 2026-02-12 18:49:31 -03:00
commit b13919b7b3
16 files changed
+260 -302

No files matched your search

+1 -1
Submodule src/api updated: 092b18fe95...456bb2cbfa.
@@ -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;
}
@@ -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<TelegraphInstance> 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<TelegraphInstance> 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<TelegraphInstance> 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<TelegraphInstance> 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;
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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<double[]> placedPositions = new ArrayList<>();
float minSeparation = radius * 2.0f;
// Use pre-calculated positions from telegraph phase, or generate new ones
List<double[]> positions;
if (!preCalculatedPositions.isEmpty() && preCalculatedPositions.size() == zoneCount) {
positions = new ArrayList<>(preCalculatedPositions);
preCalculatedPositions.clear();
} else {
positions = new ArrayList<>();
List<double[]> 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);
}
}
@@ -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");
}
}
@@ -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;
}
@@ -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<double[]> placedPositions = new ArrayList<>();
float minSeparation = triggerRadius * 2.0f;
// Use pre-calculated positions from telegraph phase, or generate new ones
List<double[]> positions;
if (!preCalculatedPositions.isEmpty() && preCalculatedPositions.size() == zoneCount) {
positions = new ArrayList<>(preCalculatedPositions);
preCalculatedPositions.clear();
} else {
positions = new ArrayList<>();
List<double[]> 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);
}
}
@@ -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<EntityAbilityZone> activeEntities = new ArrayList<>();
protected transient List<double[]> 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<TelegraphInstance> 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<double[]> 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<TelegraphInstance> 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<FieldDef> defs) {
defs.add(FieldDef.floatField("gui.size", this::getTelegraphSize, this::setTelegraphSize)
.tab("ability.tab.effects"));
}
}
@@ -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
// ═══════════════════════════════════════════════════════════════════
@@ -29,6 +29,11 @@ public enum TelegraphType {
*/
POINT,
/**
* Filled square (uses radius as half-size)
*/
SQUARE,
/**
* No telegraph
*/
+13 -13
View File
@@ -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<TelegraphInstance> 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<TelegraphInstance> telegraphs = ability.getTelegraphInstances();
for (TelegraphInstance telegraph : telegraphs) {
TelegraphRemovePacket.sendToTracking(telegraph.getInstanceId(), npc);
ability.setTelegraphInstance(null);
}
ability.setTelegraphInstances(null);
}
// ═══════════════════════════════════════════════════════════════════
@@ -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<TelegraphInstance> 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<TelegraphInstance> telegraphs = ability.getTelegraphInstances();
for (TelegraphInstance telegraph : telegraphs) {
if (player instanceof EntityPlayerMP) {
TelegraphRemovePacket.sendToTracking(telegraph.getInstanceId(), player);
}
ability.setTelegraphInstance(null);
}
ability.setTelegraphInstances(null);
}
// ═══════════════════════════════════════════════════════════════════
@@ -261,6 +261,6 @@
"ID": 24,
"WhileAttacking": 1b,
"WhileWalking": 1b,
"Name": "Ability_Hazard_Active",
"Name": "Ability_Zone_Active",
"WhileStanding": 1b
}
@@ -387,6 +387,6 @@
"ID": 23,
"WhileAttacking": 1b,
"WhileWalking": 1b,
"Name": "Ability_Hazard_Windup",
"Name": "Ability_Zone_Windup",
"WhileStanding": 1b
}