mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 21:17:32 +00:00
985 lines
39 KiB
Java
985 lines
39 KiB
Java
package kamkeel.npcs.entity;
|
|
|
|
import kamkeel.npcs.util.ByteBufUtils;
|
|
import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData;
|
|
import cpw.mods.fml.relauncher.Side;
|
|
import cpw.mods.fml.relauncher.SideOnly;
|
|
import io.netty.buffer.ByteBuf;
|
|
import kamkeel.npcs.controllers.AbilityController;
|
|
import kamkeel.npcs.controllers.EnergyController;
|
|
import kamkeel.npcs.controllers.data.ability.Ability;
|
|
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;
|
|
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.
|
|
* ZoneType distinguishes behavior: TRAP (proximity-triggered burst) vs HAZARD (persistent AoE).
|
|
*/
|
|
public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnData {
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// ENUMS
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
public enum ZoneType {TRAP, HAZARD}
|
|
|
|
public enum ZoneShape {
|
|
CIRCLE, SQUARE;
|
|
|
|
@Override
|
|
public String toString() {
|
|
switch (this) {
|
|
case CIRCLE:
|
|
return "ability.shape.circle";
|
|
case SQUARE:
|
|
return "ability.shape.square";
|
|
default:
|
|
return name();
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum AccentStyle {
|
|
STATIC, SWAYING, FLICKERING;
|
|
|
|
@Override
|
|
public String toString() {
|
|
switch (this) {
|
|
case STATIC:
|
|
return "ability.accentStyle.static";
|
|
case SWAYING:
|
|
return "ability.accentStyle.swaying";
|
|
case FLICKERING:
|
|
return "ability.accentStyle.flickering";
|
|
default:
|
|
return name();
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum ParticleMotion {
|
|
RISING, DRIFTING, SPARKS;
|
|
|
|
@Override
|
|
public String toString() {
|
|
switch (this) {
|
|
case RISING:
|
|
return "ability.particleMotion.rising";
|
|
case DRIFTING:
|
|
return "ability.particleMotion.drifting";
|
|
case SPARKS:
|
|
return "ability.particleMotion.sparks";
|
|
default:
|
|
return name();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static final double GROUND_OFFSET = 0.005;
|
|
private static final byte TRIGGER_FLASH_STATUS = 60;
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// COMMON PROPERTIES
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
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;
|
|
|
|
// Visual layer fields
|
|
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;
|
|
private int accentStyle = 0;
|
|
private boolean lightning = false;
|
|
private boolean particles = true;
|
|
private int particleMotion = 0;
|
|
private String particleDir = "";
|
|
private int particleSize = 32;
|
|
private boolean particleGlow = true;
|
|
|
|
private long deathWorldTime = -1;
|
|
private List<AbilityPotionEffect> effects = new ArrayList<>();
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// 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;
|
|
private boolean visible = true;
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// HAZARD-SPECIFIC PROPERTIES
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
private float damagePerSecond = 1.0f;
|
|
private int damageInterval = 20;
|
|
private boolean ignoreIFrames = false;
|
|
private boolean affectsCaster = false;
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// RUNTIME STATE
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
private transient boolean armed = false;
|
|
private transient int triggerCount = 0;
|
|
private transient int ticksSinceLastTrigger = 0;
|
|
private transient Set<Integer> triggeredEntities = new HashSet<>();
|
|
private transient int triggerFlashTick = -1;
|
|
|
|
private transient int ticksSinceDamage = 0;
|
|
private transient Set<Integer> damagedThisTick = new HashSet<>();
|
|
|
|
private transient Ability sourceAbility = null;
|
|
|
|
/**
|
|
* Custom damage data for script-created zones that don't have a sourceAbility.
|
|
* Used by EnergyController handlers (e.g. DBC Addon) to carry damage configuration
|
|
* directly on the entity. Synced via spawn data for client sync.
|
|
*/
|
|
private NBTTagCompound customDamageData = null;
|
|
|
|
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,
|
|
double x, double y, double z) {
|
|
this(world);
|
|
this.zoneType = type;
|
|
this.ownerEntityId = owner != null ? owner.getEntityId() : -1;
|
|
// 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);
|
|
}
|
|
|
|
/**
|
|
* Initialize as a hazard zone with default values. Used by EnergyController factory.
|
|
* Scripts can then configure properties before calling spawn().
|
|
*/
|
|
public void initAsHazard(Entity owner, double x, double y, double z) {
|
|
this.zoneType = ZoneType.HAZARD;
|
|
this.ownerEntityId = owner != null ? owner.getEntityId() : -1;
|
|
double groundY = Ability.findGroundLevel(worldObj, x, y, z) + GROUND_OFFSET;
|
|
this.setPosition(x, groundY, z);
|
|
this.ticksSinceDamage = damageInterval;
|
|
}
|
|
|
|
/**
|
|
* Initialize as a trap zone with default values. Used by EnergyController factory.
|
|
* Scripts can then configure properties before calling spawn().
|
|
*/
|
|
public void initAsTrap(Entity owner, double x, double y, double z) {
|
|
this.zoneType = ZoneType.TRAP;
|
|
this.ownerEntityId = owner != null ? owner.getEntityId() : -1;
|
|
double groundY = Ability.findGroundLevel(worldObj, x, y, z) + GROUND_OFFSET;
|
|
this.setPosition(x, groundY, z);
|
|
this.ticksSinceLastTrigger = triggerCooldown;
|
|
}
|
|
|
|
public static EntityAbilityZone createTrap(World world, EntityLivingBase owner,
|
|
double x, double y, double z,
|
|
ZoneShape shape,
|
|
float triggerRadius, int armTime, int maxTriggers,
|
|
int triggerCooldown, float damage, float damageRadius,
|
|
float knockback, int durationTicks,
|
|
boolean ignoreIFrames,
|
|
int innerColor, int outerColor, boolean outerColorEnabled,
|
|
float zoneHeight,
|
|
float particleDensity, float particleScale,
|
|
float animSpeed, float lightningDensity,
|
|
boolean visible,
|
|
List<AbilityPotionEffect> effects) {
|
|
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;
|
|
zone.ignoreIFrames = ignoreIFrames;
|
|
zone.innerColor = innerColor;
|
|
zone.outerColor = outerColor;
|
|
zone.outerColorEnabled = outerColorEnabled;
|
|
zone.zoneHeight = zoneHeight;
|
|
zone.particleDensity = particleDensity;
|
|
zone.particleScale = particleScale;
|
|
zone.animSpeed = animSpeed;
|
|
zone.lightningDensity = lightningDensity;
|
|
zone.visible = visible;
|
|
if (effects != null) {
|
|
for (AbilityPotionEffect e : effects) {
|
|
zone.effects.add(e.copy());
|
|
}
|
|
}
|
|
zone.ticksSinceLastTrigger = triggerCooldown;
|
|
return zone;
|
|
}
|
|
|
|
public static EntityAbilityZone createHazard(World world, EntityLivingBase owner,
|
|
double x, double y, double z,
|
|
ZoneShape shape,
|
|
float radius,
|
|
float damagePerSecond, int damageInterval,
|
|
boolean ignoreIFrames, boolean affectsCaster,
|
|
int durationTicks,
|
|
int innerColor, int outerColor, boolean outerColorEnabled,
|
|
float zoneHeight,
|
|
float particleDensity, float particleScale,
|
|
float animSpeed, float lightningDensity,
|
|
List<AbilityPotionEffect> effects) {
|
|
EntityAbilityZone zone = new EntityAbilityZone(world, ZoneType.HAZARD, owner, x, y, z);
|
|
zone.shape = shape;
|
|
zone.radius = radius;
|
|
zone.damagePerSecond = damagePerSecond;
|
|
zone.damageInterval = damageInterval;
|
|
zone.ignoreIFrames = ignoreIFrames;
|
|
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) {
|
|
for (AbilityPotionEffect e : effects) {
|
|
zone.effects.add(e.copy());
|
|
}
|
|
}
|
|
zone.ticksSinceDamage = damageInterval;
|
|
return zone;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// ENTITY LIFECYCLE
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
@Override
|
|
protected void entityInit() {
|
|
}
|
|
|
|
@Override
|
|
protected void readEntityFromNBT(NBTTagCompound nbt) {
|
|
this.setDead();
|
|
}
|
|
|
|
@Override
|
|
protected void writeEntityToNBT(NBTTagCompound nbt) {
|
|
// Intentionally empty — ability entities are transient (not saved to world)
|
|
}
|
|
|
|
@Override
|
|
public void handleHealthUpdate(byte id) {
|
|
if (id == TRIGGER_FLASH_STATUS) {
|
|
this.triggerFlashTick = this.ticksExisted;
|
|
} else {
|
|
super.handleHealthUpdate(id);
|
|
}
|
|
}
|
|
|
|
@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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// TRAP LOGIC
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
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;
|
|
if (maxTriggers == 1 && triggeredEntities.contains(entity.getEntityId())) continue;
|
|
if (owner instanceof EntityLivingBase && !AbilityTargetHelper.shouldAffect((EntityLivingBase) owner, entity, TargetFilter.ENEMIES, false)) continue;
|
|
|
|
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;
|
|
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;
|
|
triggeredEntities.add(triggerer.getEntityId());
|
|
triggerFlashTick = ticksExisted;
|
|
worldObj.setEntityState(this, TRIGGER_FLASH_STATUS);
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// HAZARD LOGIC
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
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;
|
|
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;
|
|
|
|
int previousHurtResistantTime = Ability.clearHurtResistanceIfNeeded(target, ignoreIFrames);
|
|
try {
|
|
// 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);
|
|
}
|
|
// Fallback: route through EnergyController for script-created entities with custom damage data
|
|
if (!handled && customDamageData != null && owner instanceof EntityLivingBase) {
|
|
double dx = target.posX - posX;
|
|
double dz = target.posZ - posZ;
|
|
handled = EnergyController.Instance.fireOnEnergyDamage(
|
|
this, (EntityLivingBase) owner, target,
|
|
dmg, 0.0f, 0.0f, dx, dz, 1.0f, customDamageData);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
return true;
|
|
} finally {
|
|
Ability.restoreHurtResistanceIfNeeded(target, ignoreIFrames, previousHurtResistantTime);
|
|
}
|
|
}
|
|
|
|
private void applyEffects(EntityLivingBase entity) {
|
|
if (entity == null || effects.isEmpty()) return;
|
|
for (AbilityPotionEffect effect : effects) {
|
|
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;
|
|
}
|
|
|
|
public Ability getSourceAbility() {
|
|
return sourceAbility;
|
|
}
|
|
|
|
public void setSourceAbility(Ability ability) {
|
|
this.sourceAbility = ability;
|
|
}
|
|
|
|
public NBTTagCompound getCustomDamageData() {
|
|
return customDamageData;
|
|
}
|
|
|
|
public void setCustomDamageData(NBTTagCompound data) {
|
|
this.customDamageData = data;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// RENDERING SUPPORT
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
@Override
|
|
@SideOnly(Side.CLIENT)
|
|
public boolean isInRangeToRenderDist(double distance) {
|
|
return distance < 16384.0D;
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldRenderInPass(int pass) {
|
|
return pass == 1;
|
|
}
|
|
|
|
@Override
|
|
public float getBrightness(float partialTicks) {
|
|
return 1.0f;
|
|
}
|
|
|
|
@Override
|
|
@SideOnly(Side.CLIENT)
|
|
public int getBrightnessForRender(float partialTicks) {
|
|
return 0xF000F0;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// GETTERS FOR RENDERER
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
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;
|
|
}
|
|
|
|
public void applyVisual(boolean groundFill, float groundAlpha,
|
|
boolean rings, int ringCount,
|
|
boolean border, float borderSpeed,
|
|
boolean accents, int accentStyle,
|
|
boolean lightning,
|
|
boolean particles, int particleMotion,
|
|
String particleDir, int particleSize, boolean particleGlow) {
|
|
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;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// PUBLIC SETTERS (for EnergyController / scripting API)
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
public void setShape(ZoneShape shape) { this.shape = shape; }
|
|
public void setRadius(float radius) { this.radius = radius; }
|
|
public void setZoneHeight(float height) { this.zoneHeight = height; }
|
|
public void setDurationTicks(int ticks) { this.durationTicks = ticks; this.maxTicks = ticks; }
|
|
public void setInnerColor(int color) { this.innerColor = color; }
|
|
public void setOuterColor(int color) { this.outerColor = color; }
|
|
public void setOuterColorEnabled(boolean enabled) { this.outerColorEnabled = enabled; }
|
|
public void setParticleDensity(float density) { this.particleDensity = density; }
|
|
public void setParticleScale(float scale) { this.particleScale = scale; }
|
|
public void setAnimSpeed(float speed) { this.animSpeed = speed; }
|
|
public void setIgnoreIFrames(boolean ignore) { this.ignoreIFrames = ignore; }
|
|
|
|
// Hazard-specific
|
|
public float getDamagePerSecond() { return damagePerSecond; }
|
|
public void setDamagePerSecond(float dps) { this.damagePerSecond = dps; }
|
|
public int getDamageInterval() { return damageInterval; }
|
|
public void setDamageInterval(int ticks) { this.damageInterval = ticks; }
|
|
public boolean isAffectsCaster() { return affectsCaster; }
|
|
public void setAffectsCaster(boolean affects) { this.affectsCaster = affects; }
|
|
|
|
// Trap-specific
|
|
public float getTriggerRadius() { return triggerRadius; }
|
|
public void setTriggerRadius(float radius) { this.triggerRadius = radius; }
|
|
public int getArmTime() { return armTime; }
|
|
public void setArmTime(int ticks) { this.armTime = ticks; }
|
|
public int getMaxTriggers() { return maxTriggers; }
|
|
public void setMaxTriggers(int max) { this.maxTriggers = max; }
|
|
public int getTriggerCooldown() { return triggerCooldown; }
|
|
public void setTriggerCooldown(int ticks) { this.triggerCooldown = ticks; }
|
|
public float getDamage() { return damage; }
|
|
public void setDamage(float damage) { this.damage = damage; }
|
|
public float getDamageRadius() { return damageRadius; }
|
|
public void setDamageRadius(float radius) { this.damageRadius = radius; }
|
|
public float getKnockback() { return knockback; }
|
|
public void setKnockback(float knockback) { this.knockback = knockback; }
|
|
public void setVisible(boolean visible) { this.visible = visible; }
|
|
|
|
public boolean isIgnoreIFrames() { return ignoreIFrames; }
|
|
|
|
public int getOwnerEntityId() { return ownerEntityId; }
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// NBT SERIALIZATION
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
// SPAWN DATA (ByteBuf for client sync)
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
@Override
|
|
public void writeSpawnData(ByteBuf buffer) {
|
|
buffer.writeInt(zoneType.ordinal());
|
|
ByteBufUtils.writeString(buffer, shape.name());
|
|
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);
|
|
|
|
// Visual layer fields
|
|
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);
|
|
ByteBufUtils.writeString(buffer, particleDir);
|
|
buffer.writeInt(particleSize);
|
|
buffer.writeBoolean(particleGlow);
|
|
|
|
// Trap-specific
|
|
buffer.writeInt(armTime);
|
|
buffer.writeBoolean(visible);
|
|
|
|
// Effects
|
|
NBTTagCompound effectsNbt = new NBTTagCompound();
|
|
NBTTagList effectList = new NBTTagList();
|
|
for (AbilityPotionEffect effect : effects) {
|
|
effectList.appendTag(effect.writeNBT());
|
|
}
|
|
effectsNbt.setTag("Effects", effectList);
|
|
try {
|
|
ByteBufUtils.writeNBT(buffer, effectsNbt);
|
|
} catch (java.io.IOException e) {
|
|
noppes.npcs.LogWriter.error("Error writing zone effects spawn data", e);
|
|
}
|
|
|
|
// Custom damage data
|
|
boolean hasDamageData = customDamageData != null;
|
|
buffer.writeBoolean(hasDamageData);
|
|
if (hasDamageData) {
|
|
try {
|
|
ByteBufUtils.writeNBT(buffer, customDamageData);
|
|
} catch (java.io.IOException e) {
|
|
noppes.npcs.LogWriter.error("Error writing zone custom damage data", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void readSpawnData(ByteBuf buffer) {
|
|
int zoneTypeOrd = buffer.readInt();
|
|
this.zoneType = (zoneTypeOrd >= 0 && zoneTypeOrd < ZoneType.values().length) ? ZoneType.values()[zoneTypeOrd] : ZoneType.TRAP;
|
|
String shapeName = ByteBufUtils.readString(buffer);
|
|
try {
|
|
this.shape = ZoneShape.valueOf(shapeName);
|
|
} catch (Exception e) {
|
|
this.shape = ZoneShape.CIRCLE;
|
|
}
|
|
this.ownerEntityId = buffer.readInt();
|
|
this.radius = Math.max(0.1f, Math.min(100.0f, buffer.readFloat()));
|
|
if (Float.isNaN(radius) || Float.isInfinite(radius)) radius = 5.0f;
|
|
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();
|
|
|
|
// Visual layer fields
|
|
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();
|
|
this.particleDir = ByteBufUtils.readString(buffer);
|
|
this.particleSize = buffer.readInt();
|
|
this.particleGlow = buffer.readBoolean();
|
|
|
|
this.armTime = buffer.readInt();
|
|
this.visible = buffer.readBoolean();
|
|
|
|
// Effects
|
|
NBTTagCompound effectsNbt = null;
|
|
try {
|
|
effectsNbt = ByteBufUtils.readNBT(buffer);
|
|
} catch (java.io.IOException e) {
|
|
noppes.npcs.LogWriter.error("Error reading zone effects spawn data", e);
|
|
}
|
|
effects.clear();
|
|
if (effectsNbt != null && effectsNbt.hasKey("Effects")) {
|
|
NBTTagList effectList = effectsNbt.getTagList("Effects", 10);
|
|
for (int i = 0; i < effectList.tagCount(); i++) {
|
|
AbilityPotionEffect effect = AbilityPotionEffect.fromNBT(effectList.getCompoundTagAt(i));
|
|
if (effect != null && effect.isValid()) {
|
|
effects.add(effect);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Custom damage data
|
|
if (buffer.readableBytes() > 0 && buffer.readBoolean()) {
|
|
try {
|
|
this.customDamageData = ByteBufUtils.readNBT(buffer);
|
|
} catch (java.io.IOException e) {
|
|
noppes.npcs.LogWriter.error("Error reading zone custom damage data", e);
|
|
}
|
|
}
|
|
}
|
|
}
|