Files
CustomNPC-Plus/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java
T

870 lines
33 KiB
Java
Raw Normal View History

2026-02-12 13:55:56 -05:00
package kamkeel.npcs.entity;
2026-02-21 04:56:53 -05:00
import kamkeel.npcs.util.ByteBufUtils;
2026-02-12 13:55:56 -05:00
import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
2026-03-03 00:36:45 -05:00
import kamkeel.npcs.controllers.AbilityController;
2026-02-12 13:55:56 -05:00
import kamkeel.npcs.controllers.data.ability.Ability;
2026-02-22 03:28:56 -05:00
import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect;
import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper;
import kamkeel.npcs.controllers.data.ability.enums.TargetFilter;
2026-02-12 13:55:56 -05:00
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.DamageSource;
import net.minecraft.world.World;
import noppes.npcs.NpcDamageSource;
import noppes.npcs.entity.EntityNPCInterface;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Shared zone entity for Trap and Hazard abilities.
2026-02-15 19:19:57 -05:00
* ZoneType distinguishes behavior: TRAP (proximity-triggered burst) vs HAZARD (persistent AoE).
2026-02-12 13:55:56 -05:00
*/
public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnData {
// ═══════════════════════════════════════════════════════════════════
// ENUMS
// ═══════════════════════════════════════════════════════════════════
2026-02-18 05:13:51 -05:00
public enum ZoneType {TRAP, HAZARD}
2026-02-12 13:55:56 -05:00
public enum ZoneShape {
CIRCLE, SQUARE;
@Override
public String toString() {
switch (this) {
2026-02-18 05:13:51 -05:00
case CIRCLE:
return "ability.shape.circle";
case SQUARE:
return "ability.shape.square";
default:
return name();
2026-02-12 13:55:56 -05:00
}
}
}
public enum AccentStyle {
STATIC, SWAYING, FLICKERING;
@Override
public String toString() {
switch (this) {
2026-02-18 05:13:51 -05:00
case STATIC:
return "ability.accentStyle.static";
case SWAYING:
return "ability.accentStyle.swaying";
case FLICKERING:
return "ability.accentStyle.flickering";
default:
return name();
2026-02-12 13:55:56 -05:00
}
}
}
public enum ParticleMotion {
RISING, DRIFTING, SPARKS;
@Override
public String toString() {
switch (this) {
2026-02-18 05:13:51 -05:00
case RISING:
return "ability.particleMotion.rising";
case DRIFTING:
return "ability.particleMotion.drifting";
case SPARKS:
return "ability.particleMotion.sparks";
default:
return name();
2026-02-12 13:55:56 -05:00
}
}
}
private static final double GROUND_OFFSET = 0.005;
2026-02-15 19:19:57 -05:00
private static final byte TRIGGER_FLASH_STATUS = 60;
2026-02-12 13:55:56 -05:00
// ═══════════════════════════════════════════════════════════════════
2026-02-15 19:19:57 -05:00
// COMMON PROPERTIES
2026-02-12 13:55:56 -05:00
// ═══════════════════════════════════════════════════════════════════
private ZoneType zoneType = ZoneType.TRAP;
private ZoneShape shape = ZoneShape.CIRCLE;
private int ownerEntityId = -1;
private float radius = 4.0f;
private int durationTicks = 200;
private int maxTicks = 200;
private int innerColor = 0xFF6600;
private int outerColor = 0xFF0000;
private boolean outerColorEnabled = true;
private float zoneHeight = 2.0f;
private float particleDensity = 1.0f;
private float particleScale = 1.0f;
private float animSpeed = 1.0f;
private float lightningDensity = 1.0f;
2026-02-15 19:19:57 -05:00
// Visual layer fields
2026-02-12 13:55:56 -05:00
private boolean groundFill = true;
private float groundAlpha = 0.25f;
private boolean rings = true;
private int ringCount = 3;
private boolean border = true;
private float borderSpeed = 1.0f;
private boolean accents = true;
2026-02-15 19:19:57 -05:00
private int accentStyle = 0;
2026-02-12 13:55:56 -05:00
private boolean lightning = false;
private boolean particles = true;
2026-02-15 19:19:57 -05:00
private int particleMotion = 0;
2026-02-12 13:55:56 -05:00
private String particleDir = "";
private int particleSize = 32;
private boolean particleGlow = true;
private long deathWorldTime = -1;
2026-02-17 17:37:51 -05:00
private List<AbilityPotionEffect> effects = new ArrayList<>();
2026-02-12 13:55:56 -05:00
// ═══════════════════════════════════════════════════════════════════
// TRAP-SPECIFIC PROPERTIES
// ═══════════════════════════════════════════════════════════════════
private int armTime = 20;
private float triggerRadius = 2.0f;
private int maxTriggers = 1;
private int triggerCooldown = 20;
private float damage = 6.0f;
private float damageRadius = 0.0f;
private float knockback = 0.5f;
2026-02-15 19:19:57 -05:00
private boolean visible = true;
2026-02-12 13:55:56 -05:00
// ═══════════════════════════════════════════════════════════════════
// HAZARD-SPECIFIC PROPERTIES
// ═══════════════════════════════════════════════════════════════════
private float damagePerSecond = 1.0f;
private int damageInterval = 20;
2026-03-02 03:58:18 -05:00
private boolean ignoreIFrames = false;
2026-02-12 13:55:56 -05:00
private boolean affectsCaster = false;
// ═══════════════════════════════════════════════════════════════════
2026-02-15 19:19:57 -05:00
// RUNTIME STATE
2026-02-12 13:55:56 -05:00
// ═══════════════════════════════════════════════════════════════════
private transient boolean armed = false;
private transient int triggerCount = 0;
private transient int ticksSinceLastTrigger = 0;
2026-03-02 18:29:39 -05:00
private transient Set<Integer> triggeredEntities = new HashSet<>();
2026-02-15 19:19:57 -05:00
private transient int triggerFlashTick = -1;
2026-02-12 13:55:56 -05:00
private transient int ticksSinceDamage = 0;
private transient Set<Integer> damagedThisTick = new HashSet<>();
2026-03-03 00:36:45 -05:00
private transient Ability sourceAbility = null;
2026-02-12 13:55:56 -05:00
private boolean previewMode = false;
private EntityLivingBase previewOwner = null;
// ═══════════════════════════════════════════════════════════════════
// CONSTRUCTORS
// ═══════════════════════════════════════════════════════════════════
public EntityAbilityZone(World world) {
super(world);
this.setSize(0.1f, 0.1f);
this.noClip = true;
this.isImmuneToFire = true;
this.ignoreFrustumCheck = true;
}
private EntityAbilityZone(World world, ZoneType type, EntityLivingBase owner,
2026-02-18 05:13:51 -05:00
double x, double y, double z) {
2026-02-12 13:55:56 -05:00
this(world);
this.zoneType = type;
this.ownerEntityId = owner.getEntityId();
// Snap to ground with tiny offset to prevent z-clipping
double groundY = Ability.findGroundLevel(world, x, y, z) + GROUND_OFFSET;
this.setPosition(x, groundY, z);
}
public static EntityAbilityZone createTrap(World world, EntityLivingBase owner,
2026-02-18 05:13:51 -05:00
double x, double y, double z,
ZoneShape shape,
float triggerRadius, int armTime, int maxTriggers,
int triggerCooldown, float damage, float damageRadius,
float knockback, int durationTicks,
2026-03-02 03:58:18 -05:00
boolean ignoreIFrames,
2026-02-18 05:13:51 -05:00
int innerColor, int outerColor, boolean outerColorEnabled,
float zoneHeight,
float particleDensity, float particleScale,
float animSpeed, float lightningDensity,
boolean visible,
List<AbilityPotionEffect> effects) {
2026-02-12 13:55:56 -05:00
EntityAbilityZone zone = new EntityAbilityZone(world, ZoneType.TRAP, owner, x, y, z);
zone.shape = shape;
zone.radius = triggerRadius;
zone.triggerRadius = triggerRadius;
zone.armTime = armTime;
zone.maxTriggers = maxTriggers;
zone.triggerCooldown = triggerCooldown;
zone.damage = damage;
zone.damageRadius = damageRadius;
zone.knockback = knockback;
zone.durationTicks = durationTicks;
zone.maxTicks = durationTicks;
2026-03-02 03:58:18 -05:00
zone.ignoreIFrames = ignoreIFrames;
2026-02-12 13:55:56 -05:00
zone.innerColor = innerColor;
zone.outerColor = outerColor;
zone.outerColorEnabled = outerColorEnabled;
zone.zoneHeight = zoneHeight;
zone.particleDensity = particleDensity;
zone.particleScale = particleScale;
zone.animSpeed = animSpeed;
zone.lightningDensity = lightningDensity;
2026-02-15 19:19:57 -05:00
zone.visible = visible;
2026-02-12 13:55:56 -05:00
if (effects != null) {
2026-02-17 17:37:51 -05:00
for (AbilityPotionEffect e : effects) {
2026-02-12 13:55:56 -05:00
zone.effects.add(e.copy());
}
}
zone.ticksSinceLastTrigger = triggerCooldown;
return zone;
}
public static EntityAbilityZone createHazard(World world, EntityLivingBase owner,
2026-02-18 05:13:51 -05:00
double x, double y, double z,
ZoneShape shape,
float radius,
float damagePerSecond, int damageInterval,
2026-03-02 03:58:18 -05:00
boolean ignoreIFrames, boolean affectsCaster,
2026-02-18 05:13:51 -05:00
int durationTicks,
int innerColor, int outerColor, boolean outerColorEnabled,
float zoneHeight,
float particleDensity, float particleScale,
float animSpeed, float lightningDensity,
List<AbilityPotionEffect> effects) {
2026-02-12 13:55:56 -05:00
EntityAbilityZone zone = new EntityAbilityZone(world, ZoneType.HAZARD, owner, x, y, z);
zone.shape = shape;
zone.radius = radius;
zone.damagePerSecond = damagePerSecond;
zone.damageInterval = damageInterval;
2026-03-02 03:58:18 -05:00
zone.ignoreIFrames = ignoreIFrames;
2026-02-12 13:55:56 -05:00
zone.affectsCaster = affectsCaster;
zone.durationTicks = durationTicks;
zone.maxTicks = durationTicks;
zone.innerColor = innerColor;
zone.outerColor = outerColor;
zone.outerColorEnabled = outerColorEnabled;
zone.zoneHeight = zoneHeight;
zone.particleDensity = particleDensity;
zone.particleScale = particleScale;
zone.animSpeed = animSpeed;
zone.lightningDensity = lightningDensity;
if (effects != null) {
2026-02-17 17:37:51 -05:00
for (AbilityPotionEffect e : effects) {
2026-02-12 13:55:56 -05:00
zone.effects.add(e.copy());
}
}
zone.ticksSinceDamage = damageInterval;
return zone;
}
// ═══════════════════════════════════════════════════════════════════
// ENTITY LIFECYCLE
// ═══════════════════════════════════════════════════════════════════
@Override
2026-02-18 05:13:51 -05:00
protected void entityInit() {
}
2026-02-15 19:19:57 -05:00
@Override
public void handleHealthUpdate(byte id) {
if (id == TRIGGER_FLASH_STATUS) {
this.triggerFlashTick = this.ticksExisted;
} else {
super.handleHealthUpdate(id);
}
2026-02-12 13:55:56 -05:00
}
@Override
public void onUpdate() {
this.prevPosX = this.posX;
this.prevPosY = this.posY;
this.prevPosZ = this.posZ;
super.onUpdate();
if (deathWorldTime < 0 && worldObj != null) {
deathWorldTime = worldObj.getTotalWorldTime() + maxTicks;
}
if (deathWorldTime > 0 && worldObj.getTotalWorldTime() >= deathWorldTime) {
this.setDead();
return;
}
if (!worldObj.isRemote && !previewMode) {
switch (zoneType) {
case TRAP:
tickTrap();
break;
case HAZARD:
tickHazard();
break;
}
}
}
// ═══════════════════════════════════════════════════════════════════
2026-02-15 19:19:57 -05:00
// TRAP LOGIC
2026-02-12 13:55:56 -05:00
// ═══════════════════════════════════════════════════════════════════
private void tickTrap() {
if (!armed) {
if (ticksExisted >= armTime) {
armed = true;
}
return;
}
ticksSinceLastTrigger++;
if (maxTriggers > 0 && triggerCount >= maxTriggers) {
this.setDead();
return;
}
if (ticksSinceLastTrigger < triggerCooldown) {
return;
}
AxisAlignedBB box = AxisAlignedBB.getBoundingBox(
posX - triggerRadius, posY - 0.5, posZ - triggerRadius,
posX + triggerRadius, posY + zoneHeight, posZ + triggerRadius
);
Entity owner = getOwner();
@SuppressWarnings("unchecked")
List<EntityLivingBase> entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, box);
for (EntityLivingBase entity : entities) {
if (owner != null && entity == owner) continue;
if (entity.isDead) continue;
2026-03-02 18:29:39 -05:00
if (maxTriggers == 1 && triggeredEntities.contains(entity.getEntityId())) continue;
if (owner instanceof EntityLivingBase && !AbilityTargetHelper.shouldAffect((EntityLivingBase) owner, entity, TargetFilter.ENEMIES, false)) continue;
2026-02-12 13:55:56 -05:00
double dx = entity.posX - posX;
double dz = entity.posZ - posZ;
boolean inRange;
if (shape == ZoneShape.SQUARE) {
inRange = Math.abs(dx) <= triggerRadius && Math.abs(dz) <= triggerRadius;
} else {
double dist = Math.sqrt(dx * dx + dz * dz);
inRange = dist <= triggerRadius;
}
if (inRange) {
triggerTrap(entity);
return;
}
}
}
private void triggerTrap(EntityLivingBase triggerer) {
Entity owner = getOwner();
Set<EntityLivingBase> affected = new HashSet<>();
if (damageRadius > 0) {
AxisAlignedBB box = AxisAlignedBB.getBoundingBox(
posX - damageRadius, posY - 0.5, posZ - damageRadius,
posX + damageRadius, posY + zoneHeight, posZ + damageRadius
);
@SuppressWarnings("unchecked")
List<EntityLivingBase> entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, box);
for (EntityLivingBase entity : entities) {
if (owner != null && entity == owner) continue;
if (owner instanceof EntityLivingBase && !AbilityTargetHelper.shouldAffect((EntityLivingBase) owner, entity, TargetFilter.ENEMIES, false)) continue;
2026-02-12 13:55:56 -05:00
double dx = entity.posX - posX;
double dz = entity.posZ - posZ;
if (shape == ZoneShape.SQUARE) {
if (Math.abs(dx) <= damageRadius && Math.abs(dz) <= damageRadius) {
affected.add(entity);
}
} else {
double dist = Math.sqrt(dx * dx + dz * dz);
if (dist <= damageRadius) {
affected.add(entity);
}
}
}
} else {
affected.add(triggerer);
}
boolean anyDamaged = false;
for (EntityLivingBase entity : affected) {
boolean hit = applyDamage(entity, owner, damage);
if (hit) {
anyDamaged = true;
}
if (hit && knockback > 0) {
double dx = entity.posX - posX;
double dz = entity.posZ - posZ;
double len = Math.sqrt(dx * dx + dz * dz);
if (len > 0) {
entity.addVelocity((dx / len) * knockback * 0.5, 0.1, (dz / len) * knockback * 0.5);
entity.velocityChanged = true;
}
}
if (hit) {
applyEffects(entity);
}
}
if (anyDamaged) {
triggerCount++;
ticksSinceLastTrigger = 0;
2026-03-02 18:29:39 -05:00
triggeredEntities.add(triggerer.getEntityId());
2026-02-12 13:55:56 -05:00
triggerFlashTick = ticksExisted;
2026-02-15 19:19:57 -05:00
worldObj.setEntityState(this, TRIGGER_FLASH_STATUS);
2026-02-12 13:55:56 -05:00
}
}
// ═══════════════════════════════════════════════════════════════════
2026-02-15 19:19:57 -05:00
// HAZARD LOGIC
2026-02-12 13:55:56 -05:00
// ═══════════════════════════════════════════════════════════════════
private void tickHazard() {
damagedThisTick.clear();
ticksSinceDamage++;
if (ticksSinceDamage < damageInterval) {
return;
}
ticksSinceDamage = 0;
AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox(
posX - radius, posY - 0.5, posZ - radius,
posX + radius, posY + zoneHeight, posZ + radius
);
Entity owner = getOwner();
@SuppressWarnings("unchecked")
List<EntityLivingBase> entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, searchBox);
for (EntityLivingBase entity : entities) {
if (owner != null && entity == owner && !affectsCaster) continue;
if (damagedThisTick.contains(entity.getEntityId())) continue;
if (entity != owner && owner instanceof EntityLivingBase && !AbilityTargetHelper.shouldAffect((EntityLivingBase) owner, entity, TargetFilter.ENEMIES, false)) continue;
2026-02-12 13:55:56 -05:00
if (!isInZone(entity)) continue;
if (damagePerSecond > 0) {
applyDamage(entity, owner, damagePerSecond);
}
applyEffects(entity);
damagedThisTick.add(entity.getEntityId());
}
}
private boolean isInZone(EntityLivingBase entity) {
double dx = entity.posX - posX;
double dz = entity.posZ - posZ;
switch (shape) {
case CIRCLE:
return Math.sqrt(dx * dx + dz * dz) <= radius;
case SQUARE:
return Math.abs(dx) <= radius && Math.abs(dz) <= radius;
default:
return Math.sqrt(dx * dx + dz * dz) <= radius;
}
}
// ═══════════════════════════════════════════════════════════════════
// DAMAGE
// ═══════════════════════════════════════════════════════════════════
private boolean applyDamage(EntityLivingBase target, Entity owner, float dmg) {
if (dmg <= 0) return false;
2026-03-02 03:58:18 -05:00
int previousHurtResistantTime = Ability.clearHurtResistanceIfNeeded(target, ignoreIFrames);
try {
2026-03-03 00:36:45 -05:00
// Route through ability extender (e.g. DBC Addon damage scaling)
boolean handled = false;
if (sourceAbility != null && owner instanceof EntityLivingBase) {
double dx = target.posX - posX;
double dz = target.posZ - posZ;
handled = AbilityController.Instance.fireOnAbilityDamage(
sourceAbility, (EntityLivingBase) owner, target,
dmg, 0.0f, 0.0f, dx, dz, 1.0f);
}
if (!handled) {
if (owner instanceof EntityNPCInterface) {
return target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), dmg);
} else if (owner instanceof EntityPlayer) {
return target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), dmg);
} else if (owner instanceof EntityLivingBase) {
return target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), dmg);
} else {
return target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmg);
}
2026-03-02 03:58:18 -05:00
}
2026-03-03 00:36:45 -05:00
return true;
2026-03-02 03:58:18 -05:00
} finally {
Ability.restoreHurtResistanceIfNeeded(target, ignoreIFrames, previousHurtResistantTime);
2026-02-12 13:55:56 -05:00
}
}
private void applyEffects(EntityLivingBase entity) {
if (entity == null || effects.isEmpty()) return;
2026-02-17 17:37:51 -05:00
for (AbilityPotionEffect effect : effects) {
2026-02-12 13:55:56 -05:00
effect.apply(entity);
}
}
// ═══════════════════════════════════════════════════════════════════
// ENTITY HELPERS
// ═══════════════════════════════════════════════════════════════════
private Entity getOwner() {
if (previewMode && previewOwner != null) return previewOwner;
if (ownerEntityId == -1) return null;
return worldObj.getEntityByID(ownerEntityId);
}
public void setupPreview(EntityLivingBase owner) {
this.previewMode = true;
this.previewOwner = owner;
}
2026-03-03 00:36:45 -05:00
public Ability getSourceAbility() {
return sourceAbility;
}
public void setSourceAbility(Ability ability) {
this.sourceAbility = ability;
}
2026-02-12 13:55:56 -05:00
// ═══════════════════════════════════════════════════════════════════
// RENDERING SUPPORT
// ═══════════════════════════════════════════════════════════════════
@Override
@SideOnly(Side.CLIENT)
public boolean isInRangeToRenderDist(double distance) {
2026-02-15 19:19:57 -05:00
return distance < 16384.0D;
2026-02-12 13:55:56 -05:00
}
@Override
public boolean shouldRenderInPass(int pass) {
2026-02-15 19:19:57 -05:00
return pass == 1;
2026-02-12 13:55:56 -05:00
}
@Override
public float getBrightness(float partialTicks) {
return 1.0f;
}
@Override
@SideOnly(Side.CLIENT)
public int getBrightnessForRender(float partialTicks) {
return 0xF000F0;
}
// ═══════════════════════════════════════════════════════════════════
// GETTERS FOR RENDERER
// ═══════════════════════════════════════════════════════════════════
2026-02-18 05:13:51 -05:00
public ZoneType getZoneType() {
return zoneType;
}
public ZoneShape getShape() {
return shape;
}
public float getRadius() {
return radius;
}
public int getInnerColor() {
return innerColor;
}
public int getOuterColor() {
return outerColor;
}
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;
}
public float getParticleDensity() {
return particleDensity;
}
public float getParticleScale() {
return particleScale;
}
public float getAnimSpeed() {
return animSpeed;
}
public float getLightningDensity() {
return lightningDensity;
}
public boolean isGroundFill() {
return groundFill;
}
public float getGroundAlpha() {
return groundAlpha;
}
public boolean isRings() {
return rings;
}
public int getRingCount() {
return ringCount;
}
public boolean isBorder() {
return border;
}
public float getBorderSpeed() {
return borderSpeed;
}
public boolean isAccents() {
return accents;
}
public int getAccentStyle() {
return accentStyle;
}
public boolean isLightning() {
return lightning;
}
public boolean isParticles() {
return particles;
}
public int getParticleMotion() {
return particleMotion;
}
public String getParticleDir() {
return particleDir;
}
public int getParticleSize() {
return particleSize;
}
public boolean isParticleGlow() {
return particleGlow;
}
2026-02-12 13:55:56 -05:00
public void applyVisual(boolean groundFill, float groundAlpha,
2026-02-18 05:13:51 -05:00
boolean rings, int ringCount,
boolean border, float borderSpeed,
boolean accents, int accentStyle,
boolean lightning,
boolean particles, int particleMotion,
String particleDir, int particleSize, boolean particleGlow) {
2026-02-12 13:55:56 -05:00
this.groundFill = groundFill;
this.groundAlpha = groundAlpha;
this.rings = rings;
this.ringCount = ringCount;
this.border = border;
this.borderSpeed = borderSpeed;
this.accents = accents;
this.accentStyle = accentStyle;
this.lightning = lightning;
this.particles = particles;
this.particleMotion = particleMotion;
this.particleDir = particleDir != null ? particleDir : "";
this.particleSize = particleSize;
this.particleGlow = particleGlow;
}
// ═══════════════════════════════════════════════════════════════════
// NBT SERIALIZATION
// ═══════════════════════════════════════════════════════════════════
@Override
protected void readEntityFromNBT(NBTTagCompound nbt) {
2026-02-21 04:11:28 -05:00
// Intentionally empty — ability entities are transient (not saved to world)
2026-02-12 13:55:56 -05:00
}
@Override
protected void writeEntityToNBT(NBTTagCompound nbt) {
2026-02-21 04:11:28 -05:00
// Intentionally empty — ability entities are transient (not saved to world)
2026-02-12 13:55:56 -05:00
}
// ═══════════════════════════════════════════════════════════════════
// SPAWN DATA (ByteBuf for client sync)
// ═══════════════════════════════════════════════════════════════════
@Override
public void writeSpawnData(ByteBuf buffer) {
buffer.writeInt(zoneType.ordinal());
2026-02-21 04:56:53 -05:00
ByteBufUtils.writeString(buffer, shape.name());
2026-02-12 13:55:56 -05:00
buffer.writeInt(ownerEntityId);
buffer.writeFloat(radius);
buffer.writeInt(durationTicks);
buffer.writeInt(maxTicks);
buffer.writeInt(innerColor);
buffer.writeInt(outerColor);
buffer.writeBoolean(outerColorEnabled);
buffer.writeFloat(zoneHeight);
buffer.writeFloat(particleDensity);
buffer.writeFloat(particleScale);
buffer.writeFloat(animSpeed);
buffer.writeFloat(lightningDensity);
2026-02-15 19:19:57 -05:00
// Visual layer fields
2026-02-12 13:55:56 -05:00
buffer.writeBoolean(groundFill);
buffer.writeFloat(groundAlpha);
buffer.writeBoolean(rings);
buffer.writeInt(ringCount);
buffer.writeBoolean(border);
buffer.writeFloat(borderSpeed);
buffer.writeBoolean(accents);
buffer.writeInt(accentStyle);
buffer.writeBoolean(lightning);
buffer.writeBoolean(particles);
buffer.writeInt(particleMotion);
2026-02-21 04:56:53 -05:00
ByteBufUtils.writeString(buffer, particleDir);
2026-02-12 13:55:56 -05:00
buffer.writeInt(particleSize);
buffer.writeBoolean(particleGlow);
2026-02-15 19:19:57 -05:00
// Trap-specific
2026-02-12 13:55:56 -05:00
buffer.writeInt(armTime);
2026-02-15 19:19:57 -05:00
buffer.writeBoolean(visible);
2026-02-12 13:55:56 -05:00
2026-02-15 19:19:57 -05:00
// Effects
2026-02-12 13:55:56 -05:00
NBTTagCompound effectsNbt = new NBTTagCompound();
NBTTagList effectList = new NBTTagList();
2026-02-17 17:37:51 -05:00
for (AbilityPotionEffect effect : effects) {
2026-02-12 13:55:56 -05:00
effectList.appendTag(effect.writeNBT());
}
effectsNbt.setTag("Effects", effectList);
2026-02-21 05:09:00 -05:00
try {
ByteBufUtils.writeNBT(buffer, effectsNbt);
} catch (java.io.IOException e) {
noppes.npcs.LogWriter.error("Error writing zone effects spawn data", e);
}
2026-02-12 13:55:56 -05:00
}
@Override
public void readSpawnData(ByteBuf buffer) {
int zoneTypeOrd = buffer.readInt();
this.zoneType = (zoneTypeOrd >= 0 && zoneTypeOrd < ZoneType.values().length) ? ZoneType.values()[zoneTypeOrd] : ZoneType.TRAP;
2026-02-21 04:56:53 -05:00
String shapeName = ByteBufUtils.readString(buffer);
2026-02-12 13:55:56 -05:00
try {
this.shape = ZoneShape.valueOf(shapeName);
} catch (Exception e) {
this.shape = ZoneShape.CIRCLE;
}
this.ownerEntityId = buffer.readInt();
2026-03-01 16:19:50 -05:00
this.radius = Math.max(0.1f, Math.min(100.0f, buffer.readFloat()));
if (Float.isNaN(radius) || Float.isInfinite(radius)) radius = 5.0f;
2026-02-12 13:55:56 -05:00
this.durationTicks = buffer.readInt();
this.maxTicks = buffer.readInt();
this.innerColor = buffer.readInt();
this.outerColor = buffer.readInt();
this.outerColorEnabled = buffer.readBoolean();
this.zoneHeight = buffer.readFloat();
this.particleDensity = buffer.readFloat();
this.particleScale = buffer.readFloat();
this.animSpeed = buffer.readFloat();
this.lightningDensity = buffer.readFloat();
2026-02-15 19:19:57 -05:00
// Visual layer fields
2026-02-12 13:55:56 -05:00
this.groundFill = buffer.readBoolean();
this.groundAlpha = buffer.readFloat();
this.rings = buffer.readBoolean();
this.ringCount = buffer.readInt();
this.border = buffer.readBoolean();
this.borderSpeed = buffer.readFloat();
this.accents = buffer.readBoolean();
this.accentStyle = buffer.readInt();
this.lightning = buffer.readBoolean();
this.particles = buffer.readBoolean();
this.particleMotion = buffer.readInt();
2026-02-21 04:56:53 -05:00
this.particleDir = ByteBufUtils.readString(buffer);
2026-02-12 13:55:56 -05:00
this.particleSize = buffer.readInt();
this.particleGlow = buffer.readBoolean();
this.armTime = buffer.readInt();
2026-02-15 19:19:57 -05:00
this.visible = buffer.readBoolean();
2026-02-12 13:55:56 -05:00
2026-02-15 19:19:57 -05:00
// Effects
2026-02-21 05:09:00 -05:00
NBTTagCompound effectsNbt = null;
try {
effectsNbt = ByteBufUtils.readNBT(buffer);
} catch (java.io.IOException e) {
noppes.npcs.LogWriter.error("Error reading zone effects spawn data", e);
}
2026-02-12 13:55:56 -05:00
effects.clear();
if (effectsNbt != null && effectsNbt.hasKey("Effects")) {
NBTTagList effectList = effectsNbt.getTagList("Effects", 10);
for (int i = 0; i < effectList.tagCount(); i++) {
2026-02-17 17:37:51 -05:00
AbilityPotionEffect effect = AbilityPotionEffect.fromNBT(effectList.getCompoundTagAt(i));
2026-02-12 13:55:56 -05:00
if (effect != null && effect.isValid()) {
effects.add(effect);
}
}
}
}
}