Files
CustomNPC-Plus/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java
T
2026-02-18 05:13:51 -05:00

282 lines
9.0 KiB
Java

package kamkeel.npcs.entity;
import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.DamageSource;
import net.minecraft.world.World;
import noppes.npcs.EventHooks;
import noppes.npcs.entity.EntityNPCInterface;
/**
* Base class for energy barrier entities (Dome, Panel).
* Provides shared health, damage, hit flash, knockback target,
* and barrier lifecycle management.
* Extends EntityEnergyAbility for shared visual/owner/charging state.
*/
public abstract class EntityEnergyBarrier extends EntityEnergyAbility {
// ==================== BARRIER PROPERTIES ====================
protected EnergyBarrierData barrierData = new EnergyBarrierData();
protected float currentHealth;
protected int ticksAlive = 0;
// ==================== DATA WATCHER INDICES ====================
protected static final int DW_HEALTH_PERCENT = 21;
protected static final int DW_HIT_FLASH = 22;
public EntityEnergyBarrier(World world) {
super(world);
this.noClip = true;
}
@Override
protected void entityInit() {
super.entityInit(); // DW_CHARGING = 20
this.dataWatcher.addObject(DW_HEALTH_PERCENT, 1.0f);
this.dataWatcher.addObject(DW_HIT_FLASH, (byte) 0);
}
// ==================== ABSTRACT METHODS ====================
/**
* Check if a projectile is incoming toward this barrier.
* Dome uses spherical check, Panel uses planar check.
*/
public abstract boolean isIncomingProjectile(EntityEnergyProjectile projectile);
/**
* Setup charging state. Called during windup phase.
*/
public abstract void setupCharging(int duration);
/**
* Finish charging — snap to full size and become active.
*/
public abstract void finishCharging();
/**
* Push entities away from the barrier surface.
*/
protected abstract void knockbackEntities();
// ==================== PROJECTILE HIT ====================
/**
* Apply damage to this barrier from a projectile.
* Returns true if the barrier absorbed the hit (projectile should be destroyed).
*/
public boolean onProjectileHit(EntityEnergyProjectile projectile, float baseDamage) {
// Get damage multiplier for this projectile type
String typeId = "";
if (projectile.getSourceAbility() != null) {
typeId = projectile.getSourceAbility().getTypeId();
}
float multiplier = barrierData.getMultiplier(typeId);
float damage = baseDamage * multiplier;
// Fire hit event (may cancel or modify damage)
if (!worldObj.isRemote) {
float eventDamage = EventHooks.onEnergyBarrierHit(this, ownerEntityId, projectile, damage);
if (eventDamage < 0) return false; // Event cancelled — don't block
damage = eventDamage;
}
triggerHitFlash();
if (!barrierData.useHealth) {
return true; // Duration-only mode: block but don't take damage
}
currentHealth -= damage;
syncHealthPercent();
if (currentHealth <= 0) {
onBarrierDestroyed();
this.setDead();
}
return true;
}
// ==================== MELEE DAMAGE ====================
@Override
public boolean canBeCollidedWith() {
return barrierData.meleeEnabled;
}
@Override
public boolean attackEntityFrom(DamageSource source, float amount) {
if (worldObj.isRemote || !barrierData.meleeEnabled) return false;
if (isCharging()) return false;
float damage = amount * barrierData.meleeDamageMultiplier;
// Fire hit event with null projectile (melee hit)
float eventDamage = EventHooks.onEnergyBarrierHit(this, ownerEntityId, null, damage);
if (eventDamage < 0) return false;
damage = eventDamage;
triggerHitFlash();
if (!barrierData.useHealth) return true;
currentHealth -= damage;
syncHealthPercent();
if (currentHealth <= 0) {
onBarrierDestroyed();
this.setDead();
}
return true;
}
// ==================== SHARED UPDATE LOGIC ====================
/**
* Shared barrier tick logic for onUpdate(). Handles spawned event, owner death,
* duration check, hit flash decay, and tick event.
* Call this after incrementing ticksAlive in subclass onUpdate().
*
* @return true if barrier died this tick (caller should return early)
*/
protected boolean updateBarrierTick() {
if (!worldObj.isRemote) {
// Fire spawned event on first tick
if (ticksAlive == 1) {
EventHooks.onEnergyBarrierSpawned(this, ownerEntityId);
}
// Check owner death
if (ownerEntityId >= 0 && ticksAlive > 5) {
Entity owner = worldObj.getEntityByID(ownerEntityId);
if (owner != null) {
if (owner.isDead) {
onBarrierDestroyed();
this.setDead();
return true;
}
if (owner instanceof EntityNPCInterface && ((EntityNPCInterface) owner).isKilled()) {
onBarrierDestroyed();
this.setDead();
return true;
}
}
}
// Duration check
if (barrierData.useDuration && ticksAlive >= barrierData.durationTicks) {
onBarrierDestroyed();
this.setDead();
return true;
}
// Reset hit flash
if (getHitFlash() > 0) {
setHitFlash((byte) (getHitFlash() - 1));
}
// Fire tick event
EventHooks.onEnergyBarrierTick(this, ownerEntityId);
}
return false;
}
// ==================== HELPERS ====================
protected void onBarrierDestroyed() {
EventHooks.onEnergyBarrierDestroyed(this, ownerEntityId);
}
protected void syncHealthPercent() {
float percent = barrierData.useHealth && barrierData.maxHealth > 0
? Math.max(0, currentHealth / barrierData.maxHealth)
: 1.0f;
if (!worldObj.isRemote) {
this.dataWatcher.updateObject(DW_HEALTH_PERCENT, percent);
}
}
protected void triggerHitFlash() {
if (!worldObj.isRemote) {
setHitFlash((byte) 4);
}
}
protected void setHitFlash(byte value) {
this.dataWatcher.updateObject(DW_HIT_FLASH, value);
}
public byte getHitFlash() {
return this.dataWatcher.getWatchableObjectByte(DW_HIT_FLASH);
}
public float getHealthPercent() {
return this.dataWatcher.getWatchableObjectFloat(DW_HEALTH_PERCENT);
}
protected boolean isKnockbackTarget(EntityLivingBase entity) {
switch (barrierData.knockbackTarget) {
case 1:
return entity instanceof EntityPlayer;
case 2:
return entity instanceof EntityNPCInterface;
default:
return entity instanceof EntityPlayer || entity instanceof EntityNPCInterface;
}
}
// ==================== GETTERS ====================
public EnergyBarrierData getBarrierData() {
return barrierData;
}
public float getCurrentHealth() {
return currentHealth;
}
public void setCurrentHealth(float health) {
this.currentHealth = health;
syncHealthPercent();
}
public int getTicksAlive() {
return ticksAlive;
}
// ==================== NBT HELPERS ====================
/**
* Write shared barrier base fields to NBT.
* Call from subclass writeEntityToNBT() before writing type-specific fields.
*/
protected void writeBarrierBaseNBT(NBTTagCompound nbt) {
writeEnergyBaseNBT(nbt);
nbt.setInteger("TicksAlive", ticksAlive);
nbt.setFloat("CurrentHealth", currentHealth);
nbt.setBoolean("Charging", charging);
nbt.setInteger("ChargeTick", chargeTick);
nbt.setInteger("ChargeDuration", chargeDuration);
barrierData.writeNBT(nbt);
}
/**
* Read shared barrier base fields from NBT.
* Call from subclass readEntityFromNBT() before reading type-specific fields.
*/
protected void readBarrierBaseNBT(NBTTagCompound nbt) {
readEnergyBaseNBT(nbt);
this.ticksAlive = nbt.getInteger("TicksAlive");
this.currentHealth = nbt.getFloat("CurrentHealth");
this.charging = nbt.hasKey("Charging") && nbt.getBoolean("Charging");
this.chargeTick = nbt.getInteger("ChargeTick");
this.chargeDuration = nbt.getInteger("ChargeDuration");
this.dataWatcher.updateObject(DW_CHARGING, (byte) (charging ? 1 : 0));
barrierData.readNBT(nbt);
}
}