package kamkeel.npcs.entity; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.EnergyController; import kamkeel.npcs.controllers.data.ability.Ability; 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 kamkeel.npcs.controllers.data.ability.data.energy.EnergyPanelData; 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.world.World; import noppes.npcs.NpcDamageSource; import noppes.npcs.entity.EntityNPCInterface; import java.util.List; /** * Energy Panel entity - a flat rectangular barrier used by Wall and Shield abilities. * Supports three modes: PLACED (stationary), HELD (tracks caster), LAUNCHED (moves forward). * Extends EntityEnergyBarrier for shared barrier logic. */ public class EntityEnergyPanel extends EntityEnergyBarrier { public enum PanelMode { PLACED, // Stationary wall HELD, // Follows caster, rotates with caster look LAUNCHED // Moves forward, deals damage/knockback on contact } // ==================== PANEL-SPECIFIC PROPERTIES ==================== protected EnergyPanelData panelData = new EnergyPanelData(); protected PanelMode mode = PanelMode.PLACED; protected float panelYaw = 0.0f; // Rotation of the panel face (degrees) protected float prevPanelYaw = 0.0f; // Previous tick yaw for smooth interpolation // ==================== CHARGING (panel-specific targets) ==================== protected float targetPanelWidth; protected float targetPanelHeight; public EntityEnergyPanel(World world) { super(world); this.setSize(0.5f, 0.5f); } public EntityEnergyPanel(World world, EntityLivingBase owner, double x, double y, double z, float yaw, PanelMode mode, EnergyDisplayData display, EnergyLightningData lightning, EnergyBarrierData barrier, EnergyPanelData panel) { this(world); this.setPosition(x, y + panel.heightOffset, z); this.ownerEntityId = owner.getEntityId(); this.panelYaw = yaw; this.prevPanelYaw = yaw; this.mode = mode; // 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.panelData = panel != null ? panel.copy() : new EnergyPanelData(); this.currentHealth = this.barrierData.maxHealth; // For launched panels, set initial velocity if (mode == PanelMode.LAUNCHED) { float yawRad = (float) Math.toRadians(yaw); this.motionX = -Math.sin(yawRad) * panel.launchSpeed; this.motionY = 0; this.motionZ = Math.cos(yawRad) * panel.launchSpeed; } } // ==================== POSITION / BOUNDING BOX ==================== /** * Override setPosition to maintain panel-sized bounding box. * MC's default setPosition() resets BB based on width/height fields. * Network sync calls setPosition(), which would shrink the BB. * This ensures melee targeting always works against the full panel surface. */ @Override public void setPosition(double x, double y, double z) { this.posX = x; this.posY = y; this.posZ = z; if (panelData != null) { float halfW = panelData.panelWidth * 0.5f; float halfH = panelData.panelHeight * 0.5f; float extent = Math.max(halfW, 0.5f); this.boundingBox.setBounds(x - extent, y - halfH, z - extent, x + extent, y + halfH, z + extent); } else { float f = this.width / 2.0F; this.boundingBox.setBounds(x - f, y, z - f, x + f, y + this.height, z + f); } } @Override public void onUpdate() { this.prevPosX = this.posX; this.prevPosY = this.posY; this.prevPosZ = this.posZ; this.prevPanelYaw = this.panelYaw; super.onUpdate(); // Handle charging animation (both sides for smooth rendering) if (isCharging()) { chargeTick++; float progress = getChargeProgress(); panelData.panelWidth = targetPanelWidth * progress; panelData.panelHeight = targetPanelHeight * progress; // During charging, held panels still track owner if (mode == PanelMode.HELD) { updateHeld(); } return; // Don't tick duration/death during charging } ticksAlive++; if (updateBarrierTick()) return; // Process entity physics every tick (not during launched mode) // Server: solid + knockback for all entities // Client: solid prediction for local player only (smooth movement blocking) if ((barrierData.solid || barrierData.knockbackEnabled) && mode != PanelMode.LAUNCHED) { processEntityPhysics(); } // Mode-specific updates switch (mode) { case HELD: updateHeld(); break; case LAUNCHED: updateLaunched(); break; case PLACED: default: // Stationary, no update needed break; } debugLogBarrierTick(); } private void updateHeld() { Entity owner = getOwnerEntity(); if (owner == null) return; // Update yaw BEFORE position calc to avoid 1-tick lag this.panelYaw = owner.rotationYaw; // Follow owner position float frontDist = 1.5f; float yawRad = (float) Math.toRadians(panelYaw); double newX = owner.posX + (-Math.sin(yawRad) * frontDist) + panelData.offsetX; double newY = owner.posY + panelData.heightOffset + (owner.height * 0.5f); double newZ = owner.posZ + (Math.cos(yawRad) * frontDist) + panelData.offsetZ; this.setPosition(newX, newY, newZ); // Sync prevPos with owner's prevPos for smooth interpolation float prevYawRad = (float) Math.toRadians(owner.prevRotationYaw); this.prevPosX = owner.prevPosX + (-Math.sin(prevYawRad) * frontDist) + panelData.offsetX; this.prevPosY = owner.prevPosY + panelData.heightOffset + (owner.height * 0.5f); this.prevPosZ = owner.prevPosZ + (Math.cos(prevYawRad) * frontDist) + panelData.offsetZ; } @Override public void setPositionAndRotation2(double x, double y, double z, float yaw, float pitch, int posRotationIncrements) { // HELD panels are fully owner-driven each tick via updateHeld(). // Applying network position lerp on top of owner-following causes visible jitter/stutter // when the caster is moving, so ignore remote corrections in this mode. if (worldObj != null && worldObj.isRemote && mode == PanelMode.HELD && getOwnerEntity() != null) { return; } // Bypass vanilla's lerp + push-out-of-blocks logic which causes // large-BB entities (PLACED/LAUNCHED panels) to shift into the sky. this.setPosition(x, y, z); this.setRotation(yaw, pitch); } private void updateLaunched() { // Move forward on both sides for smooth client rendering this.posX += motionX; this.posY += motionY; this.posZ += motionZ; this.setPosition(posX, posY, posZ); // Damage and knockback are server-only if (!worldObj.isRemote) { // Check entity collision for damage float halfW = panelData.panelWidth * 0.5f; float halfH = panelData.panelHeight * 0.5f; float halfD = 0.5f; // Panel collision depth float searchRadius = Math.max(halfW, halfD); AxisAlignedBB hitBox = AxisAlignedBB.getBoundingBox( posX - searchRadius, posY - halfH, posZ - searchRadius, posX + searchRadius, posY + halfH, posZ + searchRadius ); @SuppressWarnings("unchecked") List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); Entity owner = getOwnerEntity(); boolean ignoreIFrames = sourceAbility != null && sourceAbility.isIgnoreIFrames(); for (EntityLivingBase target : entities) { if (target.getEntityId() == ownerEntityId) continue; if (isAllyOfOwner(target)) continue; // Apply damage if (panelData.launchDamage > 0) { 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, panelData.launchDamage, panelData.launchKnockback, 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, panelData.launchDamage, panelData.launchKnockback, 0.0f, dx, dz, 1.0f, customDamageData); } if (!handled) { if (owner instanceof EntityNPCInterface) { target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), panelData.launchDamage); } else if (owner instanceof EntityPlayer) { target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), panelData.launchDamage); } else { target.attackEntityFrom(DamageSource.generic, panelData.launchDamage); } } } finally { Ability.restoreHurtResistanceIfNeeded(target, ignoreIFrames, previousHurtResistantTime); } } // Apply knockback if (panelData.launchKnockback > 0) { double dx = target.posX - posX; double dz = target.posZ - posZ; double len = Math.sqrt(dx * dx + dz * dz); if (len > 0) { double kbX = (dx / len) * panelData.launchKnockback * 0.5; double kbZ = (dz / len) * panelData.launchKnockback * 0.5; if (canPushInDirection(target, kbX, 0.1, kbZ)) { target.addVelocity(kbX, 0.1, kbZ); target.velocityChanged = true; } } } } // Max lifetime check if (ticksAlive > 200) { onBarrierDestroyed(); this.setDead(); } } } // ==================== INCOMING CHECK ==================== /** * Swept ray-plane intersection test for the panel. * Tests if the line segment from prevPos to currPos crosses the panel plane * within its width/height bounds. Handles fast projectiles that skip through * the thin panel in a single tick. * * @return true if the segment crosses the panel from either side */ 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; float halfW = panelData.panelWidth * 0.5f; float halfH = panelData.panelHeight * 0.5f; float panelThickness = 0.5f; float yawRad = (float) Math.toRadians(panelYaw); double normalX = -Math.sin(yawRad); double normalZ = Math.cos(yawRad); double cos = Math.cos(yawRad); double sin = Math.sin(yawRad); // Signed distance from prevPos and currPos to panel plane double relPrevX = prevX - this.posX; double relPrevY = prevY - this.posY; double relPrevZ = prevZ - this.posZ; double prevDist = relPrevX * normalX + relPrevZ * normalZ; double relCurrX = currX - this.posX; double relCurrY = currY - this.posY; double relCurrZ = currZ - this.posZ; double currDist = relCurrX * normalX + relCurrZ * normalZ; // Check if segment crosses the plane (sign change) if (prevDist * currDist > 0) { // Both on same side — check if within thickness (slow projectile fallback) if (Math.abs(currDist) <= panelThickness) { double localRight = relCurrX * cos + relCurrZ * sin; if (Math.abs(localRight) > halfW) return false; if (Math.abs(relCurrY) > halfH) return false; // Check incoming direction double motX = currX - prevX; double motZ = currZ - prevZ; double dot = motX * normalX + motZ * normalZ; if (currDist > 0 && dot >= 0) return false; if (currDist < 0 && dot <= 0) return false; return true; } return false; } // Segment crosses plane — compute intersection parameter t double rayDirX = currX - prevX; double rayDirZ = currZ - prevZ; double denom = rayDirX * normalX + rayDirZ * normalZ; if (Math.abs(denom) < 1e-10) return false; // Parallel to plane double t = -prevDist / denom; if (t < 0.0 || t > 1.0) return false; // Compute intersection point double hitX = prevX + (currX - prevX) * t; double hitY = prevY + (currY - prevY) * t; double hitZ = prevZ + (currZ - prevZ) * t; // Transform hit point to panel-local space and check bounds double relHitX = hitX - this.posX; double relHitY = hitY - this.posY; double relHitZ = hitZ - this.posZ; double localRight = relHitX * cos + relHitZ * sin; if (Math.abs(localRight) > halfW) return false; if (Math.abs(relHitY) > halfH) return false; return true; } @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 EntityNPCInterface && projOwner instanceof EntityNPCInterface) { if (((EntityNPCInterface) owner).faction.id == ((EntityNPCInterface) projOwner).faction.id) { return false; } } if (mode == PanelMode.LAUNCHED) { // For moving panels, use RELATIVE motion in the panel's reference frame. // This correctly captures the combined closing speed when both objects // are moving toward each other, preventing phase-through at high speeds. double relMotionX = projectile.motionX - this.motionX; double relMotionY = projectile.motionY - this.motionY; double relMotionZ = projectile.motionZ - this.motionZ; double nextX = projectile.posX + relMotionX; double nextY = projectile.posY + relMotionY; double nextZ = projectile.posZ + relMotionZ; return isIncomingRay( nextX, nextY, nextZ, projectile.posX, projectile.posY, projectile.posZ, projectile.getOwnerEntityId()); } // Non-moving panels: standard test using projectile's absolute motion double nextX = projectile.posX + projectile.motionX; double nextY = projectile.posY + projectile.motionY; double nextZ = projectile.posZ + projectile.motionZ; return isIncomingRay( nextX, nextY, nextZ, projectile.posX, projectile.posY, projectile.posZ, 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) { if (mode == PanelMode.LAUNCHED) { // Use relative motion for moving panels double relMotionX = motionX - this.motionX; double relMotionY = motionY - this.motionY; double relMotionZ = motionZ - this.motionZ; double adjX = prevPosX + relMotionX; double adjY = prevPosY + relMotionY; double adjZ = prevPosZ + relMotionZ; return isIncomingRay(adjX, adjY, adjZ, prevPosX, prevPosY, prevPosZ, ownerEntityId); } return isIncomingRay(posX, posY, posZ, prevPosX, prevPosY, prevPosZ, ownerEntityId); } @Override public double[] getSurfaceNormal(double hitX, double hitY, double hitZ, double velX, double velY, double velZ) { float yawRad = (float) Math.toRadians(panelYaw); double nx = -Math.sin(yawRad); double nz = Math.cos(yawRad); // Determine which side the hit is on double relX = hitX - this.posX; double relZ = hitZ - this.posZ; double side = relX * nx + relZ * nz; if (Math.abs(side) < 1.0e-5) { // Near the plane: use incoming velocity to determine side side = velX * nx + velZ * nz; } if (side < 0.0) { nx = -nx; nz = -nz; } return new double[]{nx, 0.0, nz}; } @Override public double[] getOutsideSurfacePoint(double px, double py, double pz, double velX, double velY, double velZ, float bias) { float yawRad = (float) Math.toRadians(panelYaw); double normalX = -Math.sin(yawRad); double normalZ = Math.cos(yawRad); double relX = px - this.posX; double relZ = pz - this.posZ; double signedDist = relX * normalX + relZ * normalZ; double halfThickness = 0.25; double side; if (Math.abs(signedDist) > 0.035) { side = signedDist >= 0 ? 1.0 : -1.0; } else { double vDot = velX * normalX + velZ * normalZ; if (Math.abs(vDot) > 1.0e-5) { side = vDot < 0 ? 1.0 : -1.0; } else { side = signedDist >= 0 ? 1.0 : -1.0; } } double targetDist = side * (halfThickness + bias); double delta = targetDist - signedDist; return new double[]{ px + normalX * delta, py, pz + normalZ * delta }; } @Override public float getMaxExtent() { return Math.max(panelData.panelWidth, panelData.panelHeight) * 0.5f + 1.0f; } // ==================== CHARGING ==================== @Override public void setupCharging(int duration) { this.targetPanelWidth = panelData.panelWidth; this.targetPanelHeight = panelData.panelHeight; panelData.panelWidth = 0.01f; panelData.panelHeight = 0.01f; this.chargeDuration = duration; this.chargeTick = 0; setCharging(true); } @Override public void finishCharging() { panelData.panelWidth = targetPanelWidth; panelData.panelHeight = targetPanelHeight; setCharging(false); } // ==================== ENTITY PHYSICS (Solid + Knockback) ==================== /** * Processes entity physics for the panel barrier. Two independent systems: *

* SOLID: Hard wall — entities cannot cross the panel plane within its bounds. * Uses reactive crossing detection: compares current side to previous tick * to detect plane crossings, then teleports back and applies velocity correction. * Also preemptively cancels normal velocity when near the plane. *

* KNOCKBACK: Repulsion force — entities near the panel 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. */ @Override @SuppressWarnings("unchecked") protected void processEntityPhysics() { float halfW = panelData.panelWidth * 0.5f; float halfH = panelData.panelHeight * 0.5f; float searchExtension = 3.0f; boolean solid = barrierData.solid; boolean knockback = barrierData.knockbackEnabled; float strength = barrierData.knockbackStrength; AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox( posX - halfW - searchExtension, posY - halfH - searchExtension, posZ - halfW - searchExtension, posX + halfW + searchExtension, posY + halfH + searchExtension, posZ + halfW + searchExtension ); EntityPlayer localPlayer = getClientPredictionPlayer(); List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, searchBox); float yawRad = (float) Math.toRadians(panelYaw); float normalX = -(float) Math.sin(yawRad); float normalZ = (float) Math.cos(yawRad); for (EntityLivingBase ent : entities) { if (shouldSkipBarrierPhysicsTarget(ent, localPlayer)) continue; // Current position relative to panel center double dx = ent.posX - posX; double dy = (ent.posY + ent.height * 0.5) - posY; double dz = ent.posZ - posZ; // Panel-local coordinates float localForward = (float) (dx * normalX + dz * normalZ); float localRight = (float) (dx * (-normalZ) + dz * normalX); // Skip if outside panel bounds (with margin) if (Math.abs(localRight) > halfW + 1.0f) continue; if (Math.abs(dy) > halfH + 1.0f) continue; if (Math.abs(localForward) > searchExtension) continue; // Determine which side the entity is on float side = localForward >= 0 ? 1.0f : -1.0f; // Check if within panel width/height bounds boolean inBounds = Math.abs(localRight) <= halfW && Math.abs(dy) <= halfH; // --- SOLID: hard wall preventing plane crossing --- if (solid && inBounds) { // Previous tick: entity position relative to panel double prevDx = ent.prevPosX - prevPosX; double prevDz = ent.prevPosZ - prevPosZ; float prevLocalForward = (float) (prevDx * normalX + prevDz * normalZ); float prevSide = prevLocalForward >= 0 ? 1.0f : -1.0f; // Normal velocity component (positive = moving in normal direction) double normalVel = ent.motionX * normalX + ent.motionZ * normalZ; if (prevSide != side) { // Entity crossed the panel plane this tick — push back to original side double pushDist = 0.3; double newX = posX + normalX * prevSide * pushDist + (localRight * (-normalZ)); double newZ = posZ + normalZ * prevSide * pushDist + (localRight * normalX); teleportEntity(ent, newX, ent.posY, newZ); // Cancel normal velocity and add corrective push ent.motionX -= normalVel * normalX; ent.motionZ -= normalVel * normalZ; double pushX = normalX * prevSide * 0.15; double pushZ = normalZ * prevSide * 0.15; if (canPushInDirection(ent, pushX, 0, pushZ)) { ent.motionX += pushX; ent.motionZ += pushZ; } ent.velocityChanged = true; } else { // Preemptive: near the plane and moving toward it — cancel normal velocity double absDist = Math.abs(localForward); if (absDist < 1.5) { boolean movingToward = (side > 0 && normalVel < -0.01) || (side < 0 && normalVel > 0.01); if (movingToward) { ent.motionX -= normalVel * normalX; ent.motionZ -= normalVel * normalZ; double pushX = normalX * side * 0.05; double pushZ = normalZ * side * 0.05; if (canPushInDirection(ent, pushX, 0, pushZ)) { ent.motionX += pushX; ent.motionZ += pushZ; } ent.velocityChanged = true; } } } } // --- KNOCKBACK: repulsion push (server only, synced via velocityChanged) --- if (!worldObj.isRemote && knockback && inBounds) { double absDist = Math.abs(localForward); if (absDist < searchExtension) { double proximity = 1.0 - (absDist / searchExtension); double force = proximity * strength * 0.06; double pushX = normalX * side * force; double pushZ = normalZ * side * force; if (canPushInDirection(ent, pushX, 0, pushZ)) { ent.motionX += pushX; ent.motionZ += pushZ; } ent.velocityChanged = true; } } } } // ==================== RENDER ==================== @Override @SideOnly(Side.CLIENT) public boolean isInRangeToRenderDist(double distance) { double d = Math.max(panelData.panelWidth, panelData.panelHeight) * 8.0D; d *= 64.0D; return distance < d * d; } // ==================== DISTANCE (for render sorting) ==================== /** * Returns squared distance from the given point to the closest point on the panel surface. * Accounts for panel rotation (panelYaw), width, height, and thickness. * This ensures correct transparency render ordering when the camera is near or behind the panel. */ @Override public double getDistanceSq(double x, double y, double z) { double dx = x - this.posX; double dy = y - this.posY; double dz = z - this.posZ; float yawRad = (float) Math.toRadians(panelYaw); double cos = Math.cos(yawRad); double sin = Math.sin(yawRad); // Transform to panel-local space // Panel normal (forward): (-sin, 0, cos) // Panel right (width axis): (cos, 0, sin) double localForward = dx * (-sin) + dz * cos; double localRight = dx * cos + dz * sin; double localUp = dy; float halfW = panelData.panelWidth * 0.5f; float halfH = panelData.panelHeight * 0.5f; float halfThickness = 0.25f; // Clamp to panel bounds to find closest point on the panel volume double clampedForward = Math.max(-halfThickness, Math.min(halfThickness, localForward)); double clampedRight = Math.max(-halfW, Math.min(halfW, localRight)); double clampedUp = Math.max(-halfH, Math.min(halfH, localUp)); // Distance from point to closest point on panel double df = localForward - clampedForward; double dr = localRight - clampedRight; double du = localUp - clampedUp; return df * df + dr * dr + du * du; } // ==================== DEBUG ==================== @Override protected String debugLogBarrierExtra() { return String.format("mode=%s yaw=%.1f w=%.2f h=%.2f", mode.name(), panelYaw, panelData.panelWidth, panelData.panelHeight); } // ==================== GETTERS ==================== public PanelMode getMode() { return mode; } public float getPanelYaw() { return panelYaw; } public float getPrevPanelYaw() { return prevPanelYaw; } public EnergyPanelData getPanelData() { return panelData; } public void setPanelYaw(float yaw) { this.panelYaw = yaw; this.prevPanelYaw = yaw; } public void setMode(PanelMode mode) { this.mode = mode; } // ==================== CLIENT SYNC ==================== @Override protected void writeBarrierClientSyncData(NBTTagCompound nbt) { nbt.setFloat("PanelWidth", panelData.panelWidth); nbt.setFloat("PanelHeight", panelData.panelHeight); nbt.setFloat("PanelYaw", panelYaw); } @Override protected void applyBarrierClientSyncData(NBTTagCompound nbt) { panelData.panelWidth = nbt.getFloat("PanelWidth"); panelData.panelHeight = nbt.getFloat("PanelHeight"); panelYaw = nbt.getFloat("PanelYaw"); } // ==================== 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("PanelYaw", panelYaw); nbt.setInteger("PanelMode", mode.ordinal()); nbt.setFloat("TargetPanelWidth", targetPanelWidth); nbt.setFloat("TargetPanelHeight", targetPanelHeight); panelData.writeNBT(nbt); } @Override protected void readSpawnNBT(NBTTagCompound nbt) { readBarrierBaseNBT(nbt); this.panelYaw = nbt.getFloat("PanelYaw"); int modeOrdinal = nbt.getInteger("PanelMode"); this.mode = (modeOrdinal >= 0 && modeOrdinal < PanelMode.values().length) ? PanelMode.values()[modeOrdinal] : PanelMode.PLACED; panelData.readNBT(nbt); panelData.panelWidth = sanitize(panelData.panelWidth, 3.0f, MAX_ENTITY_SIZE); panelData.panelHeight = sanitize(panelData.panelHeight, 3.0f, MAX_ENTITY_SIZE); this.targetPanelWidth = sanitize(nbt.hasKey("TargetPanelWidth") ? nbt.getFloat("TargetPanelWidth") : panelData.panelWidth, 3.0f, MAX_ENTITY_SIZE); this.targetPanelHeight = sanitize(nbt.hasKey("TargetPanelHeight") ? nbt.getFloat("TargetPanelHeight") : panelData.panelHeight, 3.0f, MAX_ENTITY_SIZE); } }