Add trap presets & trigger-reveal visuals

Introduce client-side trap presets and a visual "trigger reveal" for traps, plus related entity/renderer changes.

- AbilityTrap: add TrapPreset enum and applyTrapPresetDefaults; wire a SubGuiTrapPresetSelector hook to apply presets from the GUI; pass visibility into created trap entities.
- EntityAbilityZone: add visible flag, TRIGGER_FLASH_STATUS state, setEntityState usage to notify clients of triggers, handleHealthUpdate to start client-side flash, and adjust construction/serialization accordingly.
- RenderAbilityZone: implement trigger-reveal visuals (revealFade, triggerReveal) that briefly show a burst when traps trigger, skip rendering for invisible traps unless revealing, and tweak flash/timing logic.
- Minor cleanup: remove/condense comment blocks and refactor noisy helpers across AbilityZone/AbilityHazard, plus language entry update and new SubGuiTrapPresetSelector GUI class.

These changes enable preset application for traps and improve client-side feedback when traps trigger.
This commit is contained in:
Kam committed 2026-02-15 19:19:57 -05:00
1 parent 89464ce6c0
commit 45d05b9157
7 files changed
+271 -323

No files matched your search

@@ -22,10 +22,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* Renders zone effects for Trap and Hazard abilities.
* Supports CIRCLE and SQUARE shapes with composable visual layers.
*/
@SideOnly(Side.CLIENT)
public class RenderAbilityZone extends Render {
@@ -33,43 +29,35 @@ public class RenderAbilityZone extends Render {
private static final int ACCENT_LINE_COUNT = 12;
private static final Random RANDOM = new Random();
// Track last particle spawn tick per entity to avoid duplicate spawns
private final Map<Integer, Integer> lastParticleTick = new HashMap<>();
// Persistent lightning state
private final Map<Integer, AttachedLightningRenderer.LightningState> lightningStates = new HashMap<>();
// ═══════════════════════════════════════════════════════════════════
// RENDER CONTEXT
// ═══════════════════════════════════════════════════════════════════
private static final int TRIGGER_REVEAL_TICKS = 10;
private static class Ctx {
EntityAbilityZone zone;
ZoneShape shape;
float radius, height;
float ir, ig, ib; // inner color
float ir, ig, ib;
float outerR, outerG, outerB;
boolean outerEnabled;
float time, pulse, rotation, brightness;
boolean flash, newTick;
float particleDensity, particleScale, animSpeed, lightningDensity;
// Visual layer fields (always populated)
boolean groundFill, rings, border, accents, lightning, particles;
float groundAlpha, borderSpeed;
int ringCount, accentStyle, particleMotion, particleSize;
boolean particleGlow;
String particleDir;
boolean trapVisible;
boolean triggerReveal;
float revealFade;
}
// ═══════════════════════════════════════════════════════════════════
// MAIN RENDER
// ═══════════════════════════════════════════════════════════════════
@Override
public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) {
EntityAbilityZone zone = (EntityAbilityZone) entity;
// Build context
Ctx c = new Ctx();
c.zone = zone;
c.shape = zone.getShape();
@@ -92,7 +80,6 @@ public class RenderAbilityZone extends Render {
c.animSpeed = zone.getAnimSpeed();
c.lightningDensity = zone.getLightningDensity();
// Visual layer fields — always populated
c.groundFill = zone.isGroundFill();
c.groundAlpha = zone.getGroundAlpha();
c.rings = zone.isRings();
@@ -113,7 +100,27 @@ public class RenderAbilityZone extends Render {
c.rotation = c.time * 1.5f * c.animSpeed;
c.brightness = 1.0f;
c.trapVisible = true;
c.triggerReveal = false;
c.revealFade = 0.0f;
if (zone.getZoneType() == ZoneType.TRAP) {
c.trapVisible = zone.isVisible();
// Trigger reveal: 10-tick full visual burst that fades out
if (zone.getTriggerFlashTick() >= 0) {
float ticksSinceTrigger = zone.ticksExisted - zone.getTriggerFlashTick() + partialTicks;
if (ticksSinceTrigger < TRIGGER_REVEAL_TICKS) {
c.triggerReveal = true;
c.revealFade = Math.max(0.0f, 1.0f - ticksSinceTrigger / TRIGGER_REVEAL_TICKS);
}
}
// Skip render entirely if invisible and not in reveal
if (!c.trapVisible && !c.triggerReveal) {
return;
}
if (!zone.isArmed()) {
c.brightness = 0.4f;
} else {
@@ -121,16 +128,14 @@ public class RenderAbilityZone extends Render {
}
}
c.flash = zone.getTriggerFlashTick() >= 0
&& zone.ticksExisted - zone.getTriggerFlashTick() < 4;
// White flash during first 2 ticks of reveal
c.flash = c.triggerReveal && c.revealFade > 0.8f;
c.newTick = isNewTick(zone);
// Render
setupRenderState();
GL11.glPushMatrix();
GL11.glTranslated(x, y, z);
GL11.glDepthMask(false);
renderZone(c);
@@ -161,14 +166,21 @@ public class RenderAbilityZone extends Render {
return state;
}
// ═══════════════════════════════════════════════════════════════════
// UNIFIED RENDER — Composable visual layers
// ═══════════════════════════════════════════════════════════════════
private void renderZone(Ctx c) {
// During trigger reveal, override visual layers for dramatic burst
if (c.triggerReveal) {
c.brightness = c.revealFade;
c.groundFill = true;
c.groundAlpha = 0.35f * c.revealFade;
c.rings = true;
c.ringCount = 3;
c.accents = true;
c.accentStyle = 2; // FLICKERING
c.border = true;
}
float rR = ringR(c), rG = ringG(c), rB = ringB(c);
// 1. Ground fill
if (c.groundFill) {
if (c.flash) {
renderShapeFill(c.shape, c.radius, 1, 1, 1, 0.5f);
@@ -178,7 +190,6 @@ public class RenderAbilityZone extends Render {
}
}
// 2. Pulse rings
if (c.rings) {
int count = Math.max(1, Math.min(5, c.ringCount));
for (int i = 0; i < count; i++) {
@@ -188,18 +199,15 @@ public class RenderAbilityZone extends Render {
renderRingLine(c.shape, c.radius * frac,
rR, rG, rB, ra, yOff);
}
// Always include outer edge ring
float edgeA = (0.5f + 0.4f * sin(c.pulse + count * Math.PI / 3)) * c.brightness;
renderRingLine(c.shape, c.radius, rR, rG, rB, edgeA, 0.02f + count * 0.01f);
}
// 3. Rotating border
if (c.border) {
renderRotatingBorder(c.shape, c.radius, rR, rG, rB,
0.7f * c.brightness, c.rotation * c.borderSpeed, 0.05f);
}
// 4. Vertical accents
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)];
@@ -216,7 +224,6 @@ public class RenderAbilityZone extends Render {
}
}
// 5. Lightning
if (c.lightning) {
int innerColor = c.zone.getInnerColor();
int outerColor = c.outerEnabled ? c.zone.getOuterColor() : innerColor;
@@ -246,10 +253,9 @@ public class RenderAbilityZone extends Render {
}
}
state.render();
GL11.glDepthMask(false); // Reset after lightning
GL11.glDepthMask(false);
}
// 6. Particles
if (c.particles && c.newTick && c.particleDensity > 0) {
if (c.particleDir != null && !c.particleDir.isEmpty()) {
if (c.particleDir.startsWith("mc:")) {
@@ -263,7 +269,6 @@ public class RenderAbilityZone extends Render {
}
}
/** Spawn vanilla Minecraft particles using mc: prefix (e.g. "mc:flame"). */
private void spawnVanillaParticle(Ctx c) {
String particleName = c.particleDir.substring(3);
int count = Math.round(3 * c.particleDensity);
@@ -276,7 +281,6 @@ public class RenderAbilityZone extends Render {
}
}
/** Spawn CustomFX particles using a texture directory. */
private void spawnCustomFXParticle(Ctx c) {
int count = Math.round(3 * c.particleDensity);
for (int i = 0; i < count; i++) {
@@ -308,7 +312,6 @@ public class RenderAbilityZone extends Render {
}
}
/** Spawn default ZoneParticleFX with motion style from ParticleMotion enum. */
private void spawnDefaultParticles(Ctx c) {
int count = Math.round(3 * c.particleDensity);
ParticleMotion motion = ParticleMotion.values()[Math.min(c.particleMotion, ParticleMotion.values().length - 1)];
@@ -346,7 +349,6 @@ public class RenderAbilityZone extends Render {
}
}
/** Get motion vector for a CustomFX particle based on ParticleMotion ordinal. */
private double[] getMotionForStyle(int motionOrdinal) {
ParticleMotion motion = ParticleMotion.values()[Math.min(motionOrdinal, ParticleMotion.values().length - 1)];
switch (motion) {
@@ -371,18 +373,12 @@ public class RenderAbilityZone extends Render {
}
}
// ═══════════════════════════════════════════════════════════════════
// STYLE HELPERS
// ═══════════════════════════════════════════════════════════════════
/** Get the ring/outer color (falls back to inner if outer not enabled). */
private float ringR(Ctx c) { return c.outerEnabled ? c.outerR : c.ir; }
private float ringG(Ctx c) { return c.outerEnabled ? c.outerG : c.ig; }
private float ringB(Ctx c) { return c.outerEnabled ? c.outerB : c.ib; }
private static float sin(double v) { return (float) Math.sin(v); }
/** Vertical accents with sinusoidal sway per line. */
private void renderSwayingAccents(Ctx c, float r, float g, float b, float a, float height) {
if (height <= 0.1f) return;
float rotRad = (float) Math.toRadians(c.rotation * 0.2f);
@@ -436,7 +432,6 @@ public class RenderAbilityZone extends Render {
}
}
/** Vertical accents with per-line random alpha flicker. */
private void renderFlickeringAccents(Ctx c, float r, float g, float b, float baseAlpha, float height) {
if (height <= 0.1f) return;
float rotRad = (float) Math.toRadians(c.rotation * 0.4f);
@@ -492,7 +487,6 @@ public class RenderAbilityZone extends Render {
}
}
/** Pick a random position within the zone's area. Returns {x, y, z} offsets from zone center. */
private double[] randomPosInZone(Ctx c) {
if (c.shape == ZoneShape.SQUARE) {
double px = (RANDOM.nextDouble() * 2 - 1) * c.radius;
@@ -507,7 +501,6 @@ public class RenderAbilityZone extends Render {
}
}
/** Pick a random position on the edge of the zone. Returns {x, z}. */
private double[] randomEdgePos(Ctx c) {
if (c.shape == ZoneShape.SQUARE) {
int side = RANDOM.nextInt(4);
@@ -525,7 +518,6 @@ public class RenderAbilityZone extends Render {
}
}
/** Spawn a zone particle at world position. */
private void spawnParticle(EntityAbilityZone zone, double offsetX, double offsetY, double offsetZ,
double mx, double my, double mz,
float r, float g, float b, float alpha,
@@ -537,10 +529,6 @@ public class RenderAbilityZone extends Render {
Minecraft.getMinecraft().effectRenderer.addEffect(particle);
}
// ═══════════════════════════════════════════════════════════════════
// GL STATE
// ═══════════════════════════════════════════════════════════════════
private void setupRenderState() {
GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
GL11.glDisable(GL11.GL_TEXTURE_2D);
@@ -561,10 +549,6 @@ public class RenderAbilityZone extends Render {
GL11.glPopAttrib();
}
// ═══════════════════════════════════════════════════════════════════
// SHAPE FILL (Ground plane)
// ═══════════════════════════════════════════════════════════════════
private void renderShapeFill(ZoneShape shape, float radius, float r, float g, float b, float a) {
GL11.glEnable(GL11.GL_POLYGON_OFFSET_FILL);
GL11.glPolygonOffset(-1.0f, -1.0f);
@@ -602,10 +586,6 @@ public class RenderAbilityZone extends Render {
tess.draw();
}
// ═══════════════════════════════════════════════════════════════════
// RING LINES (Concentric pulse rings)
// ═══════════════════════════════════════════════════════════════════
private void renderRingLine(ZoneShape shape, float radius,
float r, float g, float b, float a, float yOffset) {
switch (shape) {
@@ -640,18 +620,13 @@ public class RenderAbilityZone extends Render {
tess.draw();
}
// ═══════════════════════════════════════════════════════════════════
// ROTATING BORDER
// ═══════════════════════════════════════════════════════════════════
private void renderRotatingBorder(ZoneShape shape, float radius,
float r, float g, float b, float a,
float rotationDeg, float yOffset) {
int dashCount = 16;
if (shape == ZoneShape.SQUARE) {
// Dashes along 4 rectangle edges with offset
float perimeter = radius * 8; // total perimeter = 4 * (2*radius)
float perimeter = radius * 8;
float dashLen = perimeter / dashCount;
float offset = (rotationDeg % 360.0f) / 360.0f * perimeter;
@@ -691,7 +666,6 @@ public class RenderAbilityZone extends Render {
}
}
/** Convert a distance along the square perimeter to an x,z position. */
private double[] perimeterPos(float dist, float radius) {
float sideLen = radius * 2;
if (dist < sideLen) {
@@ -713,10 +687,6 @@ public class RenderAbilityZone extends Render {
return new double[]{-radius, radius - dist};
}
// ═══════════════════════════════════════════════════════════════════
// VERTICAL ACCENT LINES
// ═══════════════════════════════════════════════════════════════════
private void renderVerticalAccents(ZoneShape shape, float radius,
float r, float g, float b, float a,
float height, float rotationDeg) {
@@ -725,7 +695,6 @@ public class RenderAbilityZone extends Render {
Tessellator tess = Tessellator.instance;
if (shape == ZoneShape.SQUARE) {
// Lines at corners + distributed along edges
int perSide = Math.max(1, ACCENT_LINE_COUNT / 4);
for (int side = 0; side < 4; side++) {
for (int j = 0; j < perSide; j++) {
@@ -745,7 +714,6 @@ public class RenderAbilityZone extends Render {
tess.draw();
}
}
// Corner accent lines
float[][] corners = {{-radius, -radius}, {radius, -radius}, {radius, radius}, {-radius, radius}};
for (float[] corner : corners) {
tess.startDrawing(GL11.GL_LINES);
@@ -19,14 +19,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Hazard ability: Creates persistent ground effect zones around the caster.
* Spawns one or more EntityAbilityZone entities in random positions
* within spawnRadius. Damage and zone logic handled by the entity.
*/
public class AbilityHazard extends AbilityZone implements IAbilityHazard {
// Hazard-specific fields
private float radius = 2.0f;
private float damagePerSecond = 1.0f;
private int damageInterval = 20;
@@ -51,7 +45,6 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard {
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
activeEntities.clear();
// Use pre-calculated positions from telegraph phase, or generate new ones
List<double[]> positions;
if (!preCalculatedPositions.isEmpty() && preCalculatedPositions.size() == zoneCount) {
positions = new ArrayList<>(preCalculatedPositions);
@@ -93,10 +86,6 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard {
}
}
// ═══════════════════════════════════════════════════════════════════
// NBT
// ═══════════════════════════════════════════════════════════════════
@Override
public void writeTypeNBT(NBTTagCompound nbt) {
writeZoneNBT(nbt);
@@ -117,10 +106,6 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard {
this.affectsCaster = nbt.getBoolean("affectsCaster");
}
// ═══════════════════════════════════════════════════════════════════
// HAZARD-SPECIFIC GETTERS & SETTERS
// ═══════════════════════════════════════════════════════════════════
public float getRadius() { return radius; }
public void setRadius(float radius) { this.radius = radius; }
@@ -136,10 +121,6 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard {
public boolean isAffectsCaster() { return affectsCaster; }
public void setAffectsCaster(boolean affectsCaster) { this.affectsCaster = affectsCaster; }
// ═══════════════════════════════════════════════════════════════════
// GUI FIELD DEFINITIONS
// ═══════════════════════════════════════════════════════════════════
@SideOnly(Side.CLIENT)
@Override
public void getAbilityDefinitions(List<FieldDef> defs) {
@@ -1,16 +1,16 @@
package kamkeel.npcs.controllers.data.ability.type;
import kamkeel.npcs.controllers.data.ability.AbilityEffect;
import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import kamkeel.npcs.entity.EntityAbilityZone;
import kamkeel.npcs.entity.EntityAbilityZone.ZoneShape;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.api.ability.type.IAbilityTrap;
import noppes.npcs.client.gui.SubGuiTrapPresetSelector;
import noppes.npcs.client.gui.builder.FieldDef;
import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import noppes.npcs.constants.EnumPotionType;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@@ -19,14 +19,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Trap ability: Places proximity-triggered traps around the caster.
* Spawns one or more EntityAbilityZone entities in random positions
* within spawnRadius of the caster. Damage and trigger logic handled by the entity.
*/
public class AbilityTrap extends AbilityZone implements IAbilityTrap {
// Trap-specific fields
private float triggerRadius = 2.0f;
private int armTime = 20;
private int maxTriggers = 1;
@@ -54,7 +48,6 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
public void onExecute(EntityLivingBase caster, EntityLivingBase target) {
activeEntities.clear();
// Use pre-calculated positions from telegraph phase, or generate new ones
List<double[]> positions;
if (!preCalculatedPositions.isEmpty() && preCalculatedPositions.size() == zoneCount) {
positions = new ArrayList<>(preCalculatedPositions);
@@ -81,6 +74,7 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
colorData.innerColor, colorData.outerColor, colorData.outerColorEnabled,
zoneHeight,
particleDensity, particleScale, animSpeed, lightningDensity,
visible,
getEffects());
applyVisualToEntity(entity);
@@ -94,9 +88,81 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
}
}
// ═══════════════════════════════════════════════════════════════════
// NBT
// ═══════════════════════════════════════════════════════════════════
@SideOnly(Side.CLIENT)
public enum TrapPreset {
HIDDEN, VENOM, EXPLOSIVE, CURSED, SHOCK, SNARE;
@Override
public String toString() {
return "ability.preset." + name().toLowerCase();
}
}
private void applyTrapPresetDefaults(String preset) {
effects.clear();
particleDir = "";
groundFill = true;
rings = false;
ringCount = 1;
border = false;
borderSpeed = 1.0f;
accents = false;
accentStyle = 0;
lightning = false;
particleGlow = false;
switch (preset) {
case "VENOM":
groundAlpha = 0.04f;
particles = true; particleDensity = 0.3f; particleScale = 0.5f;
particleMotion = 1; particleDir = "mc:mobSpell";
colorData.innerColor = 0x44DD44; colorData.outerColor = 0x116611;
windUpColor = 0x6044DD44; activeColor = 0xC044FF44;
effects.add(new AbilityEffect(EnumPotionType.Poison, 100, 0));
break;
case "EXPLOSIVE":
groundAlpha = 0.04f;
particles = true; particleDensity = 0.3f; particleScale = 0.6f;
particleMotion = 0; particleDir = "mc:smoke";
colorData.innerColor = 0xFF6611; colorData.outerColor = 0xCC2200;
windUpColor = 0x60FF6611; activeColor = 0xC0FF4400;
effects.add(new AbilityEffect(EnumPotionType.Fire, 60, 0));
break;
case "CURSED":
groundAlpha = 0.04f;
particles = true; particleDensity = 0.3f; particleScale = 0.5f;
particleMotion = 1; particleDir = "mc:portal";
colorData.innerColor = 0xAA44FF; colorData.outerColor = 0x6622BB;
windUpColor = 0x60AA44FF; activeColor = 0xC0CC66FF;
effects.add(new AbilityEffect(EnumPotionType.Weakness, 100, 0));
break;
case "SHOCK":
groundAlpha = 0.04f;
particles = true; particleDensity = 0.3f; particleScale = 0.4f;
particleMotion = 2; particleDir = "mc:enchantmenttable";
colorData.innerColor = 0x4488FF; colorData.outerColor = 0x2244BB;
windUpColor = 0x604488FF; activeColor = 0xC066AAFF;
effects.add(new AbilityEffect(EnumPotionType.MiningFatigue, 80, 1));
break;
case "SNARE":
groundAlpha = 0.04f;
particles = true; particleDensity = 0.3f; particleScale = 0.5f;
particleMotion = 1; particleDir = "mc:snowshovel";
colorData.innerColor = 0x88CCFF; colorData.outerColor = 0x4488CC;
windUpColor = 0x6088CCFF; activeColor = 0xC0AADDFF;
effects.add(new AbilityEffect(EnumPotionType.Slowness, 100, 1));
break;
case "HIDDEN":
default:
groundAlpha = 0.03f;
particles = false; particleDensity = 0.0f; particleScale = 1.0f;
particleMotion = 0;
colorData.innerColor = 0x888888; colorData.outerColor = 0x444444;
windUpColor = 0x60888888; activeColor = 0xC0AAAAAA;
break;
}
}
@Override
public void writeTypeNBT(NBTTagCompound nbt) {
@@ -124,10 +190,6 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
this.visible = nbt.getBoolean("visible");
}
// ═══════════════════════════════════════════════════════════════════
// TRAP-SPECIFIC GETTERS & SETTERS
// ═══════════════════════════════════════════════════════════════════
public float getTriggerRadius() { return triggerRadius; }
public void setTriggerRadius(float triggerRadius) { this.triggerRadius = triggerRadius; }
public int getArmTime() { return armTime; }
@@ -145,9 +207,18 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
public boolean isVisible() { return visible; }
public void setVisible(boolean visible) { this.visible = visible; }
// ═══════════════════════════════════════════════════════════════════
// GUI FIELD DEFINITIONS
// ═══════════════════════════════════════════════════════════════════
@SideOnly(Side.CLIENT)
@Override
protected void addPresetFieldDef(List<FieldDef> defs) {
defs.add(FieldDef.subGuiField("gui.applyPreset",
SubGuiTrapPresetSelector::new,
gui -> {
SubGuiTrapPresetSelector selector = (SubGuiTrapPresetSelector) gui;
if (selector.selectedPreset != null) {
applyTrapPresetDefaults(selector.selectedPreset);
}
}));
}
@SideOnly(Side.CLIENT)
@Override
@@ -31,27 +31,20 @@ import java.util.Iterator;
import java.util.List;
import java.util.Random;
/**
* Abstract base class for zone-based abilities (Trap, Hazard).
* Extracts shared fields: duration, shape, spawn, visual, lifecycle, and FieldDefs.
*/
public abstract class AbilityZone extends Ability {
// Zone properties
protected int durationTicks;
protected ZoneShape zoneShape = ZoneShape.CIRCLE;
protected float spawnRadius = 10.0f;
protected int zoneCount = 3;
protected float zoneHeight = 2.0f;
// Visual parameters
protected float particleDensity = 1.0f;
protected float particleScale = 1.0f;
protected float animSpeed = 1.0f;
protected float lightningDensity = 1.0f;
protected EnergyDisplayData colorData;
// Visual layer fields (always active)
protected boolean groundFill = true;
protected float groundAlpha = 0.25f;
protected boolean rings = true;
@@ -67,7 +60,6 @@ public abstract class AbilityZone extends Ability {
protected int particleSize = 32;
protected boolean particleGlow = true;
// Runtime state
protected static final Random RANDOM = new Random();
protected transient List<EntityAbilityZone> activeEntities = new ArrayList<>();
protected transient List<double[]> preCalculatedPositions = new ArrayList<>();
@@ -82,10 +74,6 @@ public abstract class AbilityZone extends Ability {
this.allowedBy = UserType.NPC_ONLY;
}
// ═════════════════════════════════════════════════════════════════
// SHARED OVERRIDES
// ═════════════════════════════════════════════════════════════════
@Override
public boolean allowBurst() {
return false;
@@ -101,17 +89,12 @@ public abstract class AbilityZone extends Ability {
return new TargetingMode[]{TargetingMode.AGGRO_TARGET};
}
/**
* Returns the actual zone effect radius for telegraph sizing.
* AbilityHazard returns its damage radius, AbilityTrap returns triggerRadius.
*/
public abstract float getZoneRadius();
@Override
public List<TelegraphInstance> createTelegraphs(EntityLivingBase caster, EntityLivingBase target) {
if (!showTelegraph || windUpTicks <= 0) return new ArrayList<>();
// Pre-calculate zone positions
preCalculatedPositions.clear();
List<double[]> placedPositions = new ArrayList<>();
float minSeparation = getZoneRadius() * 2.0f;
@@ -122,7 +105,6 @@ public abstract class AbilityZone extends Ability {
preCalculatedPositions.add(pos);
}
// Create a telegraph for each zone position
List<TelegraphInstance> telegraphs = new ArrayList<>();
float zoneRadius = getZoneRadius();
@@ -152,7 +134,6 @@ public abstract class AbilityZone extends Ability {
@Override
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
// Remove dead entities
Iterator<EntityAbilityZone> it = activeEntities.iterator();
while (it.hasNext()) {
EntityAbilityZone entity = it.next();
@@ -192,13 +173,6 @@ public abstract class AbilityZone extends Ability {
return durationTicks + 10;
}
// ═════════════════════════════════════════════════════════════════
// SHARED SPAWN HELPER
// ═════════════════════════════════════════════════════════════════
/**
* Find a non-overlapping spawn position within spawnRadius of the caster.
*/
protected double[] findSpawnPosition(EntityLivingBase caster, List<double[]> placedPositions, float minSeparation) {
double spawnX = caster.posX;
double spawnZ = caster.posZ;
@@ -227,9 +201,6 @@ public abstract class AbilityZone extends Ability {
return new double[]{spawnX, spawnZ};
}
/**
* Apply visual settings to a zone entity.
*/
protected void applyVisualToEntity(EntityAbilityZone entity) {
entity.applyVisual(groundFill, groundAlpha,
rings, ringCount, border, borderSpeed,
@@ -238,13 +209,6 @@ public abstract class AbilityZone extends Ability {
particleSize, particleGlow);
}
// ═════════════════════════════════════════════════════════════════
// PRESET SUPPORT
// ═════════════════════════════════════════════════════════════════
/**
* Apply preset defaults by style name.
*/
private void applyPresetDefaults(String styleName) {
effects.clear();
particleDir = "";
@@ -307,10 +271,6 @@ public abstract class AbilityZone extends Ability {
}
}
/**
* Client-only preset enum for the GUI dropdown.
* Not persisted — presets simply set field values.
*/
@SideOnly(Side.CLIENT)
public enum ZonePreset {
DEFAULT, TOXIC, INFERNO, ARCANE, ELECTRIC, FROST;
@@ -321,10 +281,6 @@ public abstract class AbilityZone extends Ability {
}
}
// ═════════════════════════════════════════════════════════════════
// SHARED NBT
// ═════════════════════════════════════════════════════════════════
protected void writeZoneNBT(NBTTagCompound nbt) {
nbt.setInteger("durationTicks", durationTicks);
nbt.setString("zoneShape", zoneShape.name());
@@ -337,7 +293,6 @@ public abstract class AbilityZone extends Ability {
nbt.setFloat("lightningDensity", lightningDensity);
colorData.writeNBT(nbt);
// Visual layer fields — always written as top-level keys
nbt.setBoolean("groundFill", groundFill);
nbt.setFloat("groundAlpha", groundAlpha);
nbt.setBoolean("rings", rings);
@@ -367,7 +322,6 @@ public abstract class AbilityZone extends Ability {
this.lightningDensity = nbt.getFloat("lightningDensity");
colorData.readNBT(nbt);
// Visual layer fields
this.groundFill = nbt.getBoolean("groundFill");
this.groundAlpha = nbt.getFloat("groundAlpha");
this.rings = nbt.getBoolean("rings");
@@ -384,10 +338,6 @@ public abstract class AbilityZone extends Ability {
this.particleGlow = nbt.getBoolean("particleGlow");
}
// ═════════════════════════════════════════════════════════════════
// SHARED GETTERS & SETTERS
// ═════════════════════════════════════════════════════════════════
public int getDurationTicks() { return durationTicks; }
public void setDurationTicks(int durationTicks) { this.durationTicks = Math.max(1, durationTicks); }
@@ -416,7 +366,6 @@ public abstract class AbilityZone extends Ability {
public float getLightningDensity() { return lightningDensity; }
public void setLightningDensity(float v) { this.lightningDensity = Math.max(0, Math.min(3, v)); }
// Color getters/setters
public int getInnerColor() { return colorData.innerColor; }
public void setInnerColor(int color) { colorData.innerColor = color; }
public int getOuterColor() { return colorData.outerColor; }
@@ -424,7 +373,6 @@ public abstract class AbilityZone extends Ability {
public boolean isOuterColorEnabled() { return colorData.outerColorEnabled; }
public void setOuterColorEnabled(boolean enabled) { colorData.outerColorEnabled = enabled; }
// Visual layer getters/setters
public boolean isGroundFill() { return groundFill; }
public void setGroundFill(boolean v) { this.groundFill = v; }
public float getGroundAlpha() { return groundAlpha; }
@@ -458,10 +406,6 @@ public abstract class AbilityZone extends Ability {
public boolean isParticleGlow() { return particleGlow; }
public void setParticleGlow(boolean v) { this.particleGlow = v; }
// ═════════════════════════════════════════════════════════════════
// SHARED FIELD DEFINITIONS (client-only)
// ═════════════════════════════════════════════════════════════════
@SideOnly(Side.CLIENT)
protected void addPresetFieldDef(List<FieldDef> defs) {
defs.add(FieldDef.subGuiField("gui.applyPreset",
@@ -477,42 +421,36 @@ public abstract class AbilityZone extends Ability {
@SideOnly(Side.CLIENT)
protected void addVisualFieldDefs(List<FieldDef> defs) {
defs.addAll(Arrays.asList(
// Ground Fill
FieldDef.section("ability.section.ground").tab("ability.tab.visual"),
FieldDef.row(
FieldDef.boolField("gui.enabled", this::isGroundFill, this::setGroundFill),
FieldDef.floatField("gui.alpha", this::getGroundAlpha, this::setGroundAlpha)
).tab("ability.tab.visual"),
// Rings
FieldDef.section("ability.section.rings").tab("ability.tab.visual"),
FieldDef.row(
FieldDef.boolField("gui.enabled", this::isRings, this::setRings),
FieldDef.intField("gui.count", this::getRingCount, this::setRingCount).range(1, 5)
).tab("ability.tab.visual"),
// Border
FieldDef.section("ability.section.border").tab("ability.tab.visual"),
FieldDef.row(
FieldDef.boolField("gui.enabled", this::isBorder, this::setBorder),
FieldDef.floatField("gui.speed", this::getBorderSpeed, this::setBorderSpeed)
).tab("ability.tab.visual"),
// Accents
FieldDef.section("ability.section.accents").tab("ability.tab.visual"),
FieldDef.row(
FieldDef.boolField("gui.enabled", this::isAccents, this::setAccents),
FieldDef.enumField("gui.style", AccentStyle.class, this::getAccentStyleEnum, this::setAccentStyleEnum)
).tab("ability.tab.visual"),
// Lightning
FieldDef.section("ability.section.lightning").tab("ability.tab.visual"),
FieldDef.row(
FieldDef.boolField("gui.enabled", this::isLightning, this::setLightning),
FieldDef.floatField("gui.density", this::getLightningDensity, this::setLightningDensity)
).tab("ability.tab.visual"),
// Particles
FieldDef.section("ability.section.particles").tab("ability.tab.visual"),
FieldDef.boolField("gui.enabled", this::isParticles, this::setParticles)
.tab("ability.tab.visual"),
@@ -527,12 +465,10 @@ public abstract class AbilityZone extends Ability {
FieldDef.stringField("gui.texture", this::getParticleDir, this::setParticleDir)
.tab("ability.tab.visual").visibleWhen(this::isParticles),
// Animation
FieldDef.section("ability.section.animation").tab("ability.tab.visual"),
FieldDef.floatField("gui.speed", this::getAnimSpeed, this::setAnimSpeed)
.tab("ability.tab.visual"),
// Colors
FieldDef.section("ability.section.colors").tab("ability.tab.visual"),
FieldDef.colorSubGui("ability.innerColor", this::getInnerColor, this::setInnerColor)
.tab("ability.tab.visual"),
@@ -26,7 +26,7 @@ import java.util.UUID;
/**
* Shared zone entity for Trap and Hazard abilities.
* Handles rendering, positioning, and damage logic.
* ZoneType distinguishes behavior: TRAP (proximity-triggered burst) vs HAZARD (persistent AoE).
*/
public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnData {
@@ -76,11 +76,11 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
}
}
/** Tiny Y offset above ground to prevent z-clipping */
private static final double GROUND_OFFSET = 0.005;
private static final byte TRIGGER_FLASH_STATUS = 60;
// ═══════════════════════════════════════════════════════════════════
// COMMON PROPERTIES (synced via ByteBuf)
// COMMON PROPERTIES
// ═══════════════════════════════════════════════════════════════════
private ZoneType zoneType = ZoneType.TRAP;
@@ -99,13 +99,12 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
private float zoneHeight = 2.0f;
// Visual parameters (synced for renderer)
private float particleDensity = 1.0f;
private float particleScale = 1.0f;
private float animSpeed = 1.0f;
private float lightningDensity = 1.0f;
// Visual layer parameters (always active)
// Visual layer fields
private boolean groundFill = true;
private float groundAlpha = 0.25f;
private boolean rings = true;
@@ -113,18 +112,15 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
private boolean border = true;
private float borderSpeed = 1.0f;
private boolean accents = true;
private int accentStyle = 0; // AccentStyle ordinal
private int accentStyle = 0;
private boolean lightning = false;
private boolean particles = true;
private int particleMotion = 0; // ParticleMotion ordinal
private int particleMotion = 0;
private String particleDir = "";
private int particleSize = 32;
private boolean particleGlow = true;
// Lifetime
private long deathWorldTime = -1;
// Effects
private List<AbilityEffect> effects = new ArrayList<>();
// ═══════════════════════════════════════════════════════════════════
@@ -138,6 +134,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
private float damage = 6.0f;
private float damageRadius = 0.0f;
private float knockback = 0.5f;
private boolean visible = true;
// ═══════════════════════════════════════════════════════════════════
// HAZARD-SPECIFIC PROPERTIES
@@ -149,21 +146,18 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
private boolean affectsCaster = false;
// ═══════════════════════════════════════════════════════════════════
// RUNTIME STATE (transient)
// RUNTIME STATE
// ═══════════════════════════════════════════════════════════════════
// Trap runtime
private transient boolean armed = false;
private transient int triggerCount = 0;
private transient int ticksSinceLastTrigger = 0;
private transient Set<UUID> triggeredEntities = new HashSet<>();
private transient int triggerFlashTick = -1; // For visual flash on trigger
private transient int triggerFlashTick = -1;
// Hazard runtime
private transient int ticksSinceDamage = 0;
private transient Set<Integer> damagedThisTick = new HashSet<>();
// Preview mode
private boolean previewMode = false;
private EntityLivingBase previewOwner = null;
@@ -179,11 +173,6 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
this.ignoreFrustumCheck = true;
}
/**
* Full constructor for creating a zone entity.
* Use the static factory methods for cleaner creation.
* Position is snapped to ground level with a tiny offset to avoid z-clipping.
*/
private EntityAbilityZone(World world, ZoneType type, EntityLivingBase owner,
double x, double y, double z) {
this(world);
@@ -194,9 +183,6 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
this.setPosition(x, groundY, z);
}
/**
* Create a Trap zone entity.
*/
public static EntityAbilityZone createTrap(World world, EntityLivingBase owner,
double x, double y, double z,
ZoneShape shape,
@@ -207,6 +193,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
float zoneHeight,
float particleDensity, float particleScale,
float animSpeed, float lightningDensity,
boolean visible,
List<AbilityEffect> effects) {
EntityAbilityZone zone = new EntityAbilityZone(world, ZoneType.TRAP, owner, x, y, z);
zone.shape = shape;
@@ -228,19 +215,16 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
zone.particleScale = particleScale;
zone.animSpeed = animSpeed;
zone.lightningDensity = lightningDensity;
zone.visible = visible;
if (effects != null) {
for (AbilityEffect e : effects) {
zone.effects.add(e.copy());
}
}
// Ready to trigger immediately after arm time
zone.ticksSinceLastTrigger = triggerCooldown;
return zone;
}
/**
* Create a Hazard zone entity.
*/
public static EntityAbilityZone createHazard(World world, EntityLivingBase owner,
double x, double y, double z,
ZoneShape shape,
@@ -275,7 +259,6 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
zone.effects.add(e.copy());
}
}
// Ready to deal damage immediately
zone.ticksSinceDamage = damageInterval;
return zone;
}
@@ -285,8 +268,15 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
// ═══════════════════════════════════════════════════════════════════
@Override
protected void entityInit() {
// No DataWatcher needed
protected void entityInit() {}
@Override
public void handleHealthUpdate(byte id) {
if (id == TRIGGER_FLASH_STATUS) {
this.triggerFlashTick = this.ticksExisted;
} else {
super.handleHealthUpdate(id);
}
}
@Override
@@ -297,18 +287,15 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
super.onUpdate();
// Set death time on first tick
if (deathWorldTime < 0 && worldObj != null) {
deathWorldTime = worldObj.getTotalWorldTime() + maxTicks;
}
// Check lifetime
if (deathWorldTime > 0 && worldObj.getTotalWorldTime() >= deathWorldTime) {
this.setDead();
return;
}
// Server handles damage
if (!worldObj.isRemote && !previewMode) {
switch (zoneType) {
case TRAP:
@@ -322,11 +309,10 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
}
// ═══════════════════════════════════════════════════════════════════
// TRAP LOGIC (server-only)
// TRAP LOGIC
// ═══════════════════════════════════════════════════════════════════
private void tickTrap() {
// Arm time countdown
if (!armed) {
if (ticksExisted >= armTime) {
armed = true;
@@ -336,18 +322,15 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
ticksSinceLastTrigger++;
// All triggers used
if (maxTriggers > 0 && triggerCount >= maxTriggers) {
this.setDead();
return;
}
// Cooldown between triggers
if (ticksSinceLastTrigger < triggerCooldown) {
return;
}
// Proximity detection
AxisAlignedBB box = AxisAlignedBB.getBoundingBox(
posX - triggerRadius, posY - 0.5, posZ - triggerRadius,
posX + triggerRadius, posY + zoneHeight, posZ + triggerRadius
@@ -419,7 +402,6 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
anyDamaged = true;
}
// Apply knockback only if damage landed
if (hit && knockback > 0) {
double dx = entity.posX - posX;
double dz = entity.posZ - posZ;
@@ -430,23 +412,22 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
}
}
// Apply effects only if damage landed
if (hit) {
applyEffects(entity);
}
}
// Only count the trigger if at least one entity was actually damaged
if (anyDamaged) {
triggerCount++;
ticksSinceLastTrigger = 0;
triggeredEntities.add(triggerer.getUniqueID());
triggerFlashTick = ticksExisted;
worldObj.setEntityState(this, TRIGGER_FLASH_STATUS);
}
}
// ═══════════════════════════════════════════════════════════════════
// HAZARD LOGIC (server-only)
// HAZARD LOGIC
// ═══════════════════════════════════════════════════════════════════
private void tickHazard() {
@@ -545,12 +526,12 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
@Override
@SideOnly(Side.CLIENT)
public boolean isInRangeToRenderDist(double distance) {
return distance < 16384.0D; // 128 blocks
return distance < 16384.0D;
}
@Override
public boolean shouldRenderInPass(int pass) {
return pass == 1; // Translucent pass
return pass == 1;
}
@Override
@@ -576,6 +557,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
public boolean isOuterColorEnabled() { return outerColorEnabled; }
public float getZoneHeight() { return zoneHeight; }
public boolean isArmed() { return armed || ticksExisted >= armTime; }
public boolean isVisible() { return visible; }
public int getTriggerFlashTick() { return triggerFlashTick; }
public int getDurationTicks() { return durationTicks; }
public int getMaxTicks() { return maxTicks; }
@@ -584,7 +566,6 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
public float getAnimSpeed() { return animSpeed; }
public float getLightningDensity() { return lightningDensity; }
// Visual layer getters
public boolean isGroundFill() { return groundFill; }
public float getGroundAlpha() { return groundAlpha; }
public boolean isRings() { return rings; }
@@ -600,9 +581,6 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
public int getParticleSize() { return particleSize; }
public boolean isParticleGlow() { return particleGlow; }
/**
* Apply visual settings from ability data.
*/
public void applyVisual(boolean groundFill, float groundAlpha,
boolean rings, int ringCount,
boolean border, float borderSpeed,
@@ -626,44 +604,6 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
this.particleGlow = particleGlow;
}
/**
* Apply preset defaults by old style name (for backward compatibility).
*/
private void applyPresetDefaults(String styleName) {
switch (styleName) {
case "TOXIC":
groundFill = true; groundAlpha = 0.30f; rings = true; ringCount = 3;
border = false; borderSpeed = 1.0f; accents = true; accentStyle = 1;
lightning = false; particles = true; particleMotion = 0; particleGlow = true;
break;
case "INFERNO":
groundFill = true; groundAlpha = 0.35f; rings = true; ringCount = 3;
border = true; borderSpeed = 2.0f; accents = true; accentStyle = 2;
lightning = false; particles = true; particleMotion = 0; particleGlow = true;
break;
case "ARCANE":
groundFill = true; groundAlpha = 0.20f; rings = true; ringCount = 1;
border = true; borderSpeed = 0.8f; accents = true; accentStyle = 0;
lightning = false; particles = true; particleMotion = 1; particleGlow = true;
break;
case "ELECTRIC":
groundFill = true; groundAlpha = 0.15f; rings = false; ringCount = 1;
border = false; borderSpeed = 1.0f; accents = false; accentStyle = 0;
lightning = true; particles = true; particleMotion = 2; particleGlow = true;
break;
case "FROST":
groundFill = true; groundAlpha = 0.25f; rings = true; ringCount = 3;
border = true; borderSpeed = 0.3f; accents = true; accentStyle = 0;
lightning = false; particles = true; particleMotion = 1; particleGlow = true;
break;
default: // DEFAULT
groundFill = true; groundAlpha = 0.25f; rings = true; ringCount = 3;
border = true; borderSpeed = 1.0f; accents = true; accentStyle = 0;
lightning = false; particles = false; particleMotion = 0; particleGlow = true;
break;
}
}
// ═══════════════════════════════════════════════════════════════════
// NBT SERIALIZATION
// ═══════════════════════════════════════════════════════════════════
@@ -671,16 +611,9 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
@Override
protected void readEntityFromNBT(NBTTagCompound nbt) {
this.zoneType = ZoneType.values()[nbt.getInteger("ZoneType")];
// Shape: read new string key first, fall back to old ordinal (always CIRCLE for old RING/CONE)
if (nbt.hasKey("ShapeName")) {
try {
this.shape = ZoneShape.valueOf(nbt.getString("ShapeName"));
} catch (Exception e) {
this.shape = ZoneShape.CIRCLE;
}
} else {
// Legacy: old ordinal data — RING(1) and CONE(2) map to CIRCLE
try {
this.shape = ZoneShape.valueOf(nbt.getString("ShapeName"));
} catch (Exception e) {
this.shape = ZoneShape.CIRCLE;
}
@@ -691,51 +624,28 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
this.innerColor = nbt.getInteger("InnerColor");
this.outerColor = nbt.getInteger("OuterColor");
this.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.hasKey("DeathWorldTime") ? nbt.getLong("DeathWorldTime") : -1;
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.deathWorldTime = nbt.getLong("DeathWorldTime");
// 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 cv = nbt.getCompoundTag("CustomVisual");
this.groundFill = !cv.hasKey("GroundFill") || cv.getBoolean("GroundFill");
this.groundAlpha = cv.hasKey("GroundAlpha") ? cv.getFloat("GroundAlpha") : 0.25f;
this.rings = !cv.hasKey("Rings") || cv.getBoolean("Rings");
this.ringCount = cv.hasKey("RingCount") ? cv.getInteger("RingCount") : 3;
this.border = !cv.hasKey("Border") || cv.getBoolean("Border");
this.borderSpeed = cv.hasKey("BorderSpeed") ? cv.getFloat("BorderSpeed") : 1.0f;
this.accents = !cv.hasKey("Accents") || cv.getBoolean("Accents");
this.accentStyle = cv.hasKey("AccentStyle") ? cv.getInteger("AccentStyle") : 0;
this.lightning = cv.hasKey("Lightning") && cv.getBoolean("Lightning");
this.particles = !cv.hasKey("Particles") || cv.getBoolean("Particles");
this.particleMotion = cv.hasKey("ParticleMotion") ? cv.getInteger("ParticleMotion") : 0;
this.particleDir = cv.hasKey("ParticleDir") ? cv.getString("ParticleDir") : "";
this.particleSize = cv.hasKey("ParticleSize") ? cv.getInteger("ParticleSize") : 32;
this.particleGlow = !cv.hasKey("ParticleGlow") || cv.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.getBoolean("GroundFill");
this.groundAlpha = nbt.getFloat("GroundAlpha");
this.rings = nbt.getBoolean("Rings");
this.ringCount = nbt.getInteger("RingCount");
this.border = nbt.getBoolean("Border");
this.borderSpeed = nbt.getFloat("BorderSpeed");
this.accents = nbt.getBoolean("Accents");
this.accentStyle = nbt.getInteger("AccentStyle");
this.lightning = nbt.getBoolean("Lightning");
this.particles = nbt.getBoolean("Particles");
this.particleMotion = nbt.getInteger("ParticleMotion");
this.particleDir = nbt.getString("ParticleDir");
this.particleSize = nbt.getInteger("ParticleSize");
this.particleGlow = nbt.getBoolean("ParticleGlow");
// Trap
this.armTime = nbt.getInteger("ArmTime");
@@ -745,6 +655,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
this.damage = nbt.getFloat("Damage");
this.damageRadius = nbt.getFloat("DamageRadius");
this.knockback = nbt.getFloat("Knockback");
this.visible = nbt.getBoolean("Visible");
// Hazard
this.damagePerSecond = nbt.getFloat("DamagePerSecond");
@@ -783,7 +694,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
nbt.setFloat("LightningDensity", lightningDensity);
nbt.setLong("DeathWorldTime", deathWorldTime);
// Visual fields — always written as top-level keys
// Visual layer fields
nbt.setBoolean("GroundFill", groundFill);
nbt.setFloat("GroundAlpha", groundAlpha);
nbt.setBoolean("Rings", rings);
@@ -807,6 +718,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
nbt.setFloat("Damage", damage);
nbt.setFloat("DamageRadius", damageRadius);
nbt.setFloat("Knockback", knockback);
nbt.setBoolean("Visible", visible);
// Hazard
nbt.setFloat("DamagePerSecond", damagePerSecond);
@@ -843,7 +755,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
buffer.writeFloat(animSpeed);
buffer.writeFloat(lightningDensity);
// Visual fields — always written individually
// Visual layer fields
buffer.writeBoolean(groundFill);
buffer.writeFloat(groundAlpha);
buffer.writeBoolean(rings);
@@ -859,10 +771,11 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
buffer.writeInt(particleSize);
buffer.writeBoolean(particleGlow);
// Trap-specific (client needs armTime for visual state)
// Trap-specific
buffer.writeInt(armTime);
buffer.writeBoolean(visible);
// Effects via NBT in buffer
// Effects
NBTTagCompound effectsNbt = new NBTTagCompound();
NBTTagList effectList = new NBTTagList();
for (AbilityEffect effect : effects) {
@@ -894,7 +807,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
this.animSpeed = buffer.readFloat();
this.lightningDensity = buffer.readFloat();
// Visual fields — always read individually
// Visual layer fields
this.groundFill = buffer.readBoolean();
this.groundAlpha = buffer.readFloat();
this.rings = buffer.readBoolean();
@@ -911,8 +824,9 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
this.particleGlow = buffer.readBoolean();
this.armTime = buffer.readInt();
this.visible = buffer.readBoolean();
// Effects via NBT
// Effects
NBTTagCompound effectsNbt = ByteBufUtils.readTag(buffer);
effects.clear();
if (effectsNbt != null && effectsNbt.hasKey("Effects")) {
@@ -0,0 +1,72 @@
package noppes.npcs.client.gui;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.util.StatCollector;
import noppes.npcs.client.gui.util.GuiNpcButton;
import noppes.npcs.client.gui.util.SubGuiInterface;
@SideOnly(Side.CLIENT)
public class SubGuiTrapPresetSelector extends SubGuiInterface {
public String selectedPreset = null;
private static final String[] PRESET_NAMES = {"HIDDEN", "VENOM", "EXPLOSIVE", "CURSED", "SHOCK", "SNARE"};
private static final String[] PRESET_LABELS = {
"ability.preset.hidden", "ability.preset.venom", "ability.preset.explosive",
"ability.preset.cursed", "ability.preset.shock", "ability.preset.snare"
};
private static final int[] PRESET_COLORS = {0x888888, 0x44DD44, 0xFF6611, 0xAA44FF, 0x4488FF, 0x88CCFF};
public SubGuiTrapPresetSelector() {
xSize = 170;
ySize = 135;
setBackground("menubg.png");
}
@Override
public void initGui() {
super.initGui();
int btnW = 72;
int btnH = 20;
int gapY = 4;
int col1 = guiLeft + 7;
int col2 = col1 + btnW + 6;
int startY = guiTop + 22;
for (int i = 0; i < 6; i++) {
int x = (i % 2 == 0) ? col1 : col2;
int y = startY + (i / 2) * (btnH + gapY);
addButton(new GuiNpcButton(i, x, y, btnW, btnH, PRESET_LABELS[i]));
}
int cancelY = startY + 3 * (btnH + gapY) + 4;
addButton(new GuiNpcButton(10, guiLeft + xSize / 2 - 36, cancelY, 72, btnH, "gui.cancel"));
}
@Override
protected void actionPerformed(GuiButton btn) {
super.actionPerformed(btn);
if (btn.id >= 0 && btn.id < 6) {
selectedPreset = PRESET_NAMES[btn.id];
close();
} else if (btn.id == 10) {
close();
}
}
@Override
public void drawScreen(int mouseX, int mouseY, float f) {
super.drawScreen(mouseX, mouseY, f);
drawCenteredString(fontRendererObj, StatCollector.translateToLocal("gui.applyPreset"), guiLeft + xSize / 2, guiTop + 8, 0xFFFFFF);
for (int i = 0; i < 6; i++) {
GuiNpcButton btn = getButton(i);
if (btn != null && btn.visible) {
int x = btn.xPosition + 4;
int y = btn.yPosition + 5;
drawRect(x, y, x + 10, y + 10, 0xFF000000 | PRESET_COLORS[i]);
}
}
}
}