mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 17:47:32 +00:00
640 lines
25 KiB
Java
640 lines
25 KiB
Java
package kamkeel.npcs.entity;
|
|
|
|
import cpw.mods.fml.relauncher.Side;
|
|
import cpw.mods.fml.relauncher.SideOnly;
|
|
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData;
|
|
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
|
|
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData;
|
|
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.AxisAlignedBB;
|
|
import net.minecraft.util.DamageSource;
|
|
import net.minecraft.util.Vec3;
|
|
import net.minecraft.world.World;
|
|
import noppes.npcs.CustomNpcs;
|
|
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* Energy Dome entity - a spherical barrier that blocks incoming energy projectiles.
|
|
* Centered on the caster's position at time of casting.
|
|
* Only blocks incoming attacks (not outgoing from allies inside).
|
|
* Extends EntityAbilityBarrier for shared barrier logic.
|
|
*/
|
|
public class EntityAbilityDome extends EntityAbilityBarrier {
|
|
|
|
// ==================== DOME-SPECIFIC PROPERTIES ====================
|
|
protected float domeRadius = 5.0f;
|
|
protected float targetDomeRadius = 5.0f;
|
|
protected boolean followCaster = false;
|
|
|
|
// Server-side melee detection state
|
|
private boolean inTickMelee = false;
|
|
private final HashSet<Integer> processedMeleeSwings = new HashSet<>();
|
|
|
|
public EntityAbilityDome(World world) {
|
|
super(world);
|
|
this.noClip = true;
|
|
this.stepHeight = 0.0F;
|
|
this.setSize(1.0f, 1.0f);
|
|
}
|
|
|
|
public EntityAbilityDome(World world, EntityLivingBase owner, double x, double y, double z,
|
|
float domeRadius, EnergyDisplayData display, EnergyLightningData lightning,
|
|
EnergyBarrierData barrier) {
|
|
this(world);
|
|
this.noClip = true;
|
|
this.stepHeight = 0.0F;
|
|
this.ownerEntityId = owner.getEntityId();
|
|
this.setDomeRadius(domeRadius);
|
|
// Defensive copy: entities must never share data objects with the source ability.
|
|
this.displayData = display != null ? display.copy() : new EnergyDisplayData();
|
|
this.lightningData = lightning != null ? lightning.copy() : new EnergyLightningData();
|
|
this.barrierData = barrier != null ? barrier.copy() : new EnergyBarrierData();
|
|
this.currentHealth = this.barrierData.maxHealth;
|
|
this.setPosition(x, y, z);
|
|
}
|
|
|
|
// ==================== POSITION / BOUNDING BOX ====================
|
|
|
|
@Override
|
|
protected void setSize(float width, float height) {
|
|
super.setSize(width, height);
|
|
}
|
|
|
|
@Override
|
|
public void applyEntityCollision(Entity entityIn) {
|
|
}
|
|
|
|
@Override
|
|
public void onUpdate() {
|
|
this.prevPosX = this.posX;
|
|
this.prevPosY = this.posY;
|
|
this.prevPosZ = this.posZ;
|
|
|
|
super.onUpdate();
|
|
|
|
// Follow caster: both sides for smooth interpolated rendering
|
|
if (followCaster) {
|
|
Entity owner = ownerEntityId >= 0 ? worldObj.getEntityByID(ownerEntityId) : null;
|
|
if (owner != null) {
|
|
this.setPosition(owner.posX, owner.posY, owner.posZ);
|
|
// Sync prevPos with owner's prevPos for smooth interpolation
|
|
this.prevPosX = owner.prevPosX;
|
|
this.prevPosY = owner.prevPosY;
|
|
this.prevPosZ = owner.prevPosZ;
|
|
}
|
|
}
|
|
|
|
// Handle charging animation (both sides for smooth rendering)
|
|
if (isCharging()) {
|
|
chargeTick++;
|
|
float progress = getChargeProgress();
|
|
this.setDomeRadius(targetDomeRadius * progress);
|
|
// Refresh BB for current charge radius
|
|
this.setPosition(posX, posY, posZ);
|
|
return; // Don't tick duration/death during charging
|
|
}
|
|
|
|
ticksAlive++;
|
|
|
|
if (updateBarrierTick()) return;
|
|
|
|
// Server-side: detect and apply melee hits from nearby players.
|
|
// Bypasses MC's processUseEntity reach check (which uses center-to-center
|
|
// distance and fails for domes with radius > 5).
|
|
if (!worldObj.isRemote && barrierData.meleeEnabled) {
|
|
processMeleeHits();
|
|
}
|
|
|
|
// Process entity physics every tick
|
|
// Server: solid + knockback for all entities
|
|
// Client: solid prediction for local player only (smooth movement blocking)
|
|
if (barrierData.solid || barrierData.knockbackEnabled) {
|
|
processEntityPhysics();
|
|
}
|
|
|
|
// Client-side: adjust BB for accurate melee targeting at all approach angles.
|
|
// Must run AFTER all position/BB updates and BEFORE getMouseOver() in the render phase.
|
|
if (worldObj.isRemote && barrierData.meleeEnabled && !isCharging()) {
|
|
adjustMeleeBoundingBox();
|
|
}
|
|
|
|
debugLogBarrierTick();
|
|
}
|
|
|
|
// ==================== INCOMING CHECK ====================
|
|
|
|
/**
|
|
* Swept ray-sphere intersection test.
|
|
* Tests if the line segment from prevPos to currPos crosses the dome sphere boundary
|
|
* from outside. Handles fast projectiles that skip through in a single tick.
|
|
*
|
|
* @return true if the segment enters the sphere from outside
|
|
*/
|
|
private boolean isIncomingRay(
|
|
double currX, double currY, double currZ,
|
|
double prevX, double prevY, double prevZ,
|
|
int projOwnerEntityId)
|
|
{
|
|
if (isCharging()) return false;
|
|
if (projOwnerEntityId == this.ownerEntityId) return false;
|
|
|
|
// Ray: P(t) = prev + t * (curr - prev), t in [0, 1]
|
|
double rayDirX = currX - prevX;
|
|
double rayDirY = currY - prevY;
|
|
double rayDirZ = currZ - prevZ;
|
|
|
|
// Vector from dome center to ray origin (prevPos)
|
|
double ocX = prevX - this.posX;
|
|
double ocY = prevY - this.posY;
|
|
double ocZ = prevZ - this.posZ;
|
|
|
|
double r = domeRadius;
|
|
|
|
// Quadratic: a*t^2 + b*t + c = 0
|
|
double a = rayDirX * rayDirX + rayDirY * rayDirY + rayDirZ * rayDirZ;
|
|
double b = 2.0 * (ocX * rayDirX + ocY * rayDirY + ocZ * rayDirZ);
|
|
double c = (ocX * ocX + ocY * ocY + ocZ * ocZ) - r * r;
|
|
|
|
// c > 0: prevPos is outside sphere; c <= 0: inside (don't block)
|
|
if (c <= 0) return false;
|
|
|
|
if (a < 1e-10) return false; // No movement
|
|
|
|
double discriminant = b * b - 4.0 * a * c;
|
|
if (discriminant < 0) return false; // Ray misses sphere entirely
|
|
|
|
double sqrtDisc = Math.sqrt(discriminant);
|
|
double t1 = (-b - sqrtDisc) / (2.0 * a); // Entry point (first intersection)
|
|
|
|
// Entry must be within this tick's movement segment [0, 1]
|
|
return t1 >= 0.0 && t1 <= 1.0;
|
|
}
|
|
|
|
@Override
|
|
public boolean isIncomingProjectile(EntityEnergyProjectile projectile) {
|
|
// Faction check: don't block same-faction NPC projectiles
|
|
Entity owner = getOwnerEntity();
|
|
Entity projOwner = projectile.getOwnerEntity();
|
|
if (owner instanceof noppes.npcs.entity.EntityNPCInterface && projOwner instanceof noppes.npcs.entity.EntityNPCInterface) {
|
|
if (((noppes.npcs.entity.EntityNPCInterface) owner).faction.id == ((noppes.npcs.entity.EntityNPCInterface) projOwner).faction.id) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
double prevX = projectile.posX - projectile.motionX;
|
|
double prevY = projectile.posY - projectile.motionY;
|
|
double prevZ = projectile.posZ - projectile.motionZ;
|
|
|
|
return isIncomingRay(
|
|
projectile.posX, projectile.posY, projectile.posZ,
|
|
prevX, prevY, prevZ,
|
|
projectile.getOwnerEntityId());
|
|
}
|
|
|
|
@Override
|
|
public boolean isIncomingGenericProjectile(
|
|
double posX, double posY, double posZ,
|
|
double motionX, double motionY, double motionZ,
|
|
double prevPosX, double prevPosY, double prevPosZ,
|
|
int ownerEntityId)
|
|
{
|
|
return isIncomingRay(posX, posY, posZ, prevPosX, prevPosY, prevPosZ, ownerEntityId);
|
|
}
|
|
|
|
@Override
|
|
public float getMaxExtent() {
|
|
return domeRadius;
|
|
}
|
|
|
|
// ==================== CHARGING ====================
|
|
|
|
@Override
|
|
public void setupCharging(int duration) {
|
|
this.targetDomeRadius = this.domeRadius;
|
|
this.setDomeRadius(0.01f);
|
|
this.chargeDuration = duration;
|
|
this.chargeTick = 0;
|
|
setCharging(true);
|
|
}
|
|
|
|
@Override
|
|
public void finishCharging() {
|
|
this.setDomeRadius(this.targetDomeRadius);
|
|
setCharging(false);
|
|
}
|
|
|
|
// ==================== ENTITY PHYSICS (Solid + Knockback) ====================
|
|
|
|
/**
|
|
* Processes entity physics for the dome barrier. Two independent systems:
|
|
* <p>
|
|
* SOLID: Hard wall — entities cannot cross the dome boundary in either direction.
|
|
* Uses reactive crossing detection: compares current position to previous tick
|
|
* to detect boundary crossings, then teleports back and applies velocity correction.
|
|
* Also preemptively cancels inward velocity when near the surface.
|
|
* <p>
|
|
* KNOCKBACK: Repulsion force — entities near the dome surface get pushed away.
|
|
* This is a push effect only; entities can still pass through with enough effort.
|
|
* Does NOT prevent passthrough — use solid for that.
|
|
* <p>
|
|
* For moving domes (followCaster), dome velocity is accounted for by using
|
|
* the dome's previous position for previous-tick distance calculations.
|
|
*/
|
|
@Override
|
|
@SuppressWarnings("unchecked")
|
|
protected void processEntityPhysics() {
|
|
float r = domeRadius;
|
|
boolean solid = barrierData.solid;
|
|
boolean knockback = barrierData.knockbackEnabled;
|
|
float strength = barrierData.knockbackStrength;
|
|
|
|
float solidMargin = 1.5f; // Zone around surface for preemptive velocity correction
|
|
float knockbackMargin = 2.0f; // Zone around surface for knockback push
|
|
|
|
AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox(
|
|
posX - r - knockbackMargin, posY - r - knockbackMargin, posZ - r - knockbackMargin,
|
|
posX + r + knockbackMargin, posY + r + knockbackMargin, posZ + r + knockbackMargin
|
|
);
|
|
|
|
EntityPlayer localPlayer = getClientPredictionPlayer();
|
|
|
|
List<EntityLivingBase> entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, searchBox);
|
|
for (EntityLivingBase ent : entities) {
|
|
if (shouldSkipBarrierPhysicsTarget(ent, localPlayer)) continue;
|
|
|
|
// Current position relative to dome center
|
|
double dx = ent.posX - posX;
|
|
double dy = (ent.posY + ent.height * 0.5) - posY;
|
|
double dz = ent.posZ - posZ;
|
|
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
if (dist < 0.01) continue;
|
|
|
|
// Normalized direction from dome center toward entity (outward)
|
|
double nx = dx / dist;
|
|
double ny = dy / dist;
|
|
double nz = dz / dist;
|
|
|
|
boolean isInside = dist < r;
|
|
|
|
// --- SOLID: hard wall preventing boundary crossing ---
|
|
if (solid) {
|
|
// Previous tick: entity position relative to dome's previous position
|
|
double prevDx = ent.prevPosX - prevPosX;
|
|
double prevDy = (ent.prevPosY + ent.height * 0.5) - prevPosY;
|
|
double prevDz = ent.prevPosZ - prevPosZ;
|
|
double prevDist = Math.sqrt(prevDx * prevDx + prevDy * prevDy + prevDz * prevDz);
|
|
boolean wasInside = prevDist < r;
|
|
|
|
// Radial velocity (positive = moving outward from dome center)
|
|
double radialVel = ent.motionX * nx + ent.motionY * ny + ent.motionZ * nz;
|
|
|
|
if (wasInside != isInside) {
|
|
// Entity crossed the boundary this tick — push back to original side
|
|
double pushDist = wasInside ? (r - 0.3) : (r + 0.3);
|
|
double surfaceX = posX + nx * pushDist;
|
|
double surfaceY = posY + ny * pushDist - ent.height * 0.5;
|
|
double surfaceZ = posZ + nz * pushDist;
|
|
|
|
// Prevent grounded entities from being teleported below the floor.
|
|
// The spherical surface calculation can produce a Y below ground level
|
|
// near the dome's equator, and setPosition bypasses collision detection.
|
|
if (ent.onGround && surfaceY < ent.posY) {
|
|
surfaceY = ent.posY;
|
|
}
|
|
|
|
teleportEntity(ent, surfaceX, surfaceY, surfaceZ);
|
|
|
|
// Cancel radial velocity and add corrective push
|
|
ent.motionX -= radialVel * nx;
|
|
ent.motionY -= radialVel * ny;
|
|
ent.motionZ -= radialVel * nz;
|
|
double pushForce = wasInside ? -0.15 : 0.15;
|
|
ent.motionX += nx * pushForce;
|
|
ent.motionZ += nz * pushForce;
|
|
ent.velocityChanged = true;
|
|
} else {
|
|
// Preemptive: near surface and moving toward it — cancel radial velocity
|
|
double surfaceDist = Math.abs(dist - r);
|
|
if (surfaceDist < solidMargin) {
|
|
boolean movingTowardSurface = (!isInside && radialVel < -0.01)
|
|
|| (isInside && radialVel > 0.01);
|
|
if (movingTowardSurface) {
|
|
ent.motionX -= radialVel * nx;
|
|
ent.motionY -= radialVel * ny;
|
|
ent.motionZ -= radialVel * nz;
|
|
double pushForce = isInside ? -0.05 : 0.05;
|
|
ent.motionX += nx * pushForce;
|
|
ent.motionZ += nz * pushForce;
|
|
ent.velocityChanged = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- KNOCKBACK: repulsion push (server only, synced via velocityChanged) ---
|
|
if (!worldObj.isRemote && knockback) {
|
|
double surfaceDist = Math.abs(dist - r);
|
|
if (surfaceDist < knockbackMargin) {
|
|
double proximity = 1.0 - (surfaceDist / knockbackMargin);
|
|
double force = proximity * strength * 0.06;
|
|
|
|
if (isInside) {
|
|
// Push toward center (away from surface)
|
|
ent.motionX += -nx * force;
|
|
ent.motionY += -ny * force * 0.5;
|
|
ent.motionZ += -nz * force;
|
|
} else {
|
|
// Push outward (away from surface)
|
|
ent.motionX += nx * force;
|
|
ent.motionY += ny * force * 0.5;
|
|
ent.motionZ += nz * force;
|
|
}
|
|
ent.velocityChanged = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ==================== CONTAINMENT CHECK ====================
|
|
|
|
@Override
|
|
public boolean isEntityInside(Entity entity) {
|
|
if (entity == null) return false;
|
|
double dx = entity.posX - posX;
|
|
double dy = (entity.posY + entity.height * 0.5) - posY;
|
|
double dz = entity.posZ - posZ;
|
|
return dx * dx + dy * dy + dz * dz < domeRadius * domeRadius;
|
|
}
|
|
|
|
// ==================== MELEE (spherical check) ====================
|
|
|
|
/**
|
|
* Controls whether MC's entity picking (crosshair raycast) can target this dome.
|
|
* Returns false when melee is disabled or the dome is still charging.
|
|
* <p>
|
|
* Inside players CAN target the dome because adjustMeleeBoundingBox places a small BB
|
|
* at the nearest surface point, allowing mobs in other directions to still be targeted.
|
|
*/
|
|
@Override
|
|
public boolean canBeCollidedWith() {
|
|
if (!barrierData.meleeEnabled) return false;
|
|
if (isCharging()) return false;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Client-side: Repositions the bounding box to a small cube at the nearest sphere surface
|
|
* point toward the player. This fixes melee targeting at diagonal approach angles where the
|
|
* player would otherwise be inside the dome-sized cubic BB but outside the actual sphere,
|
|
* causing MC's AABB raycast to return the far exit point (beyond reach distance).
|
|
* <p>
|
|
* With a small BB (2x2x2) at the surface point, even if the player is inside it at close
|
|
* range, the exit point is at most ~1.7 blocks away — well within melee reach.
|
|
*/
|
|
private void adjustMeleeBoundingBox() {
|
|
EntityPlayer player = CustomNpcs.proxy.getPlayer();
|
|
if (player == null) {
|
|
updateBoundingBox();
|
|
return;
|
|
}
|
|
|
|
double dx = player.posX - posX;
|
|
double dy = (player.posY + player.getEyeHeight()) - posY;
|
|
double dz = player.posZ - posZ;
|
|
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
|
|
// Only adjust when player is within reasonable melee range of dome surface
|
|
if (dist < 0.01 || dist > domeRadius + 6.0) {
|
|
updateBoundingBox();
|
|
return;
|
|
}
|
|
|
|
// Nearest point on sphere surface toward player
|
|
double nx = dx / dist;
|
|
double ny = dy / dist;
|
|
double nz = dz / dist;
|
|
double surfX = posX + nx * domeRadius;
|
|
double surfY = posY + ny * domeRadius;
|
|
double surfZ = posZ + nz * domeRadius;
|
|
|
|
float s = 1.0f;
|
|
this.boundingBox.setBounds(
|
|
surfX - s, surfY - s, surfZ - s,
|
|
surfX + s, surfY + s, surfZ + s
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Player melee is handled by processMeleeHits (server-side tick detection) to bypass
|
|
* MC's processUseEntity reach check which uses center-to-center distance.
|
|
* Direct player melee ("player" damage type) is rejected unless inTickMelee is set,
|
|
* preventing double-hits when both the C02 path and tick detection fire.
|
|
* Non-player melee (mobs) and all other damage sources pass through with sphere validation.
|
|
*/
|
|
@Override
|
|
public boolean attackEntityFrom(DamageSource source, float amount) {
|
|
// Player melee: only allow from tick-based detection
|
|
if (!inTickMelee && "player".equals(source.damageType)) {
|
|
return false;
|
|
}
|
|
if (source.getEntity() != null) {
|
|
Entity attacker = source.getEntity();
|
|
double dx = attacker.posX - this.posX;
|
|
double dy = (attacker.posY + attacker.height * 0.5) - this.posY;
|
|
double dz = attacker.posZ - this.posZ;
|
|
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
double surfaceDist = Math.abs(dist - domeRadius);
|
|
if (surfaceDist > 5.0) return false;
|
|
}
|
|
return super.attackEntityFrom(source, amount);
|
|
}
|
|
|
|
/**
|
|
* Server-side melee detection. Each tick, finds players near the dome surface who are
|
|
* swinging and looking at the dome, then applies the attack via attackTargetEntityWithCurrentItem
|
|
* (preserving enchantments, crits, fire aspect, etc.). This bypasses MC's processUseEntity
|
|
* reach check which uses center-to-center distance and fails for large domes.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
private void processMeleeHits() {
|
|
float r = domeRadius;
|
|
float maxReach = 6.0f;
|
|
|
|
AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox(
|
|
posX - r - maxReach, posY - r - maxReach, posZ - r - maxReach,
|
|
posX + r + maxReach, posY + r + maxReach, posZ + r + maxReach
|
|
);
|
|
|
|
List<EntityPlayer> players = worldObj.getEntitiesWithinAABB(EntityPlayer.class, searchBox);
|
|
for (EntityPlayer player : players) {
|
|
if (player.getEntityId() == ownerEntityId) continue;
|
|
if (isAllyOfOwner(player)) continue;
|
|
|
|
int playerId = player.getEntityId();
|
|
|
|
if (!player.isSwingInProgress) {
|
|
processedMeleeSwings.remove(playerId);
|
|
continue;
|
|
}
|
|
|
|
if (processedMeleeSwings.contains(playerId)) continue;
|
|
|
|
// Check surface distance
|
|
double dx = player.posX - posX;
|
|
double dy = (player.posY + 1.62) - posY;
|
|
double dz = player.posZ - posZ;
|
|
double distSq = dx * dx + dy * dy + dz * dz;
|
|
double dist = Math.sqrt(distSq);
|
|
double surfaceDist = Math.abs(dist - r);
|
|
if (surfaceDist > maxReach) continue;
|
|
|
|
// Ray-sphere intersection: check if look direction hits the dome
|
|
Vec3 look = player.getLook(1.0f);
|
|
double b = 2.0 * (dx * look.xCoord + dy * look.yCoord + dz * look.zCoord);
|
|
double c = distSq - (double)(r * r);
|
|
double discriminant = b * b - 4.0 * c;
|
|
if (discriminant < 0) continue;
|
|
|
|
double sqrtDisc = Math.sqrt(discriminant);
|
|
double t1 = (-b - sqrtDisc) / 2.0;
|
|
double t2 = (-b + sqrtDisc) / 2.0;
|
|
|
|
// Outside: t1 is entry distance. Inside: t1 < 0, t2 is exit distance.
|
|
double hitDist = t1 >= 0 ? t1 : t2;
|
|
if (hitDist < 0 || hitDist > maxReach) continue;
|
|
|
|
// Apply the attack with full MC damage calculation
|
|
processedMeleeSwings.add(playerId);
|
|
inTickMelee = true;
|
|
player.attackTargetEntityWithCurrentItem(this);
|
|
inTickMelee = false;
|
|
}
|
|
}
|
|
|
|
// ==================== BOUNDING BOX ====================
|
|
|
|
/**
|
|
* No expansion needed — the bounding box is already dome-sized.
|
|
* Returning 0 ensures players outside the dome sphere are also outside the
|
|
* ray-testing BB, so MC's raycast returns the near-face hit (within reach)
|
|
* rather than the far exit point (beyond reach).
|
|
*/
|
|
@Override
|
|
public float getCollisionBorderSize() {
|
|
return 0.0f;
|
|
}
|
|
|
|
@Override
|
|
@SideOnly(Side.CLIENT)
|
|
public boolean isInRangeToRenderDist(double distance) {
|
|
double d = domeRadius * 8.0D;
|
|
d *= 64.0D;
|
|
return distance < d * d;
|
|
}
|
|
|
|
// ==================== DISTANCE (for render sorting) ====================
|
|
|
|
/**
|
|
* Returns squared distance from the given point to the nearest point on the dome sphere surface.
|
|
* Uses absolute distance to surface: |centerDist - radius|.
|
|
* <p>
|
|
* Inside: nearest surface is (radius - centerDist) away.
|
|
* Outside: nearest surface is (centerDist - radius) away.
|
|
* This ensures entities inside the dome sort correctly relative to the dome shell.
|
|
*/
|
|
@Override
|
|
public double getDistanceSq(double x, double y, double z) {
|
|
double dx = this.posX - x;
|
|
double dy = this.posY - y;
|
|
double dz = this.posZ - z;
|
|
double centerDist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
|
|
double surfaceDist = Math.abs(centerDist - domeRadius);
|
|
return surfaceDist * surfaceDist;
|
|
}
|
|
|
|
// ==================== DEBUG ====================
|
|
|
|
@Override
|
|
protected String debugLogBarrierExtra() {
|
|
return String.format("radius=%.2f targetRadius=%.2f follow=%b",
|
|
domeRadius, targetDomeRadius, followCaster);
|
|
}
|
|
|
|
// ==================== GETTERS ====================
|
|
|
|
public float getDomeRadius() {
|
|
return domeRadius;
|
|
}
|
|
|
|
public void setDomeRadius(float radius) {
|
|
this.domeRadius = Math.max(0.1f, radius);
|
|
this.setPosition(this.posX, this.posY, this.posZ);
|
|
}
|
|
|
|
public boolean isFollowCaster() {
|
|
return followCaster;
|
|
}
|
|
|
|
public void setFollowCaster(boolean follow) {
|
|
this.followCaster = follow;
|
|
}
|
|
|
|
@Override
|
|
@SideOnly(Side.CLIENT)
|
|
public void setPositionAndRotation2(double x, double y, double z, float yaw, float pitch, int increments) {
|
|
// Don't do the vanilla "push up out of blocks" logic.
|
|
this.setPosition(x, y, z);
|
|
this.setRotation(yaw, pitch);
|
|
}
|
|
|
|
@Override
|
|
public void setPosition(double x, double y, double z) {
|
|
this.posX = x;
|
|
this.posY = y;
|
|
this.posZ = z;
|
|
updateBoundingBox();
|
|
}
|
|
|
|
protected void updateBoundingBox() {
|
|
float r = this.domeRadius;
|
|
this.boundingBox.setBounds(
|
|
this.posX - r, this.posY - r, this.posZ - r,
|
|
this.posX + r, this.posY + r, this.posZ + r
|
|
);
|
|
}
|
|
|
|
// ==================== NBT ====================
|
|
|
|
@Override
|
|
protected void readEntityFromNBT(NBTTagCompound nbt) {
|
|
// Intentionally empty — ability entities are transient (not saved to world)
|
|
}
|
|
|
|
@Override
|
|
protected void writeEntityToNBT(NBTTagCompound nbt) {
|
|
// Intentionally empty — ability entities are transient (not saved to world)
|
|
}
|
|
|
|
@Override
|
|
protected void writeSpawnNBT(NBTTagCompound nbt) {
|
|
writeBarrierBaseNBT(nbt);
|
|
nbt.setFloat("DomeRadius", domeRadius);
|
|
nbt.setFloat("TargetDomeRadius", targetDomeRadius);
|
|
nbt.setBoolean("FollowCaster", followCaster);
|
|
}
|
|
|
|
@Override
|
|
protected void readSpawnNBT(NBTTagCompound nbt) {
|
|
readBarrierBaseNBT(nbt);
|
|
this.setDomeRadius(sanitize(nbt.getFloat("DomeRadius"), 5.0f, MAX_ENTITY_RADIUS));
|
|
this.targetDomeRadius = sanitize(nbt.hasKey("TargetDomeRadius") ? nbt.getFloat("TargetDomeRadius") : domeRadius, 5.0f, MAX_ENTITY_RADIUS);
|
|
this.followCaster = nbt.hasKey("FollowCaster") && nbt.getBoolean("FollowCaster");
|
|
}
|
|
}
|