diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java index 0ad5a4844..e4fb4a158 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java @@ -7,10 +7,12 @@ import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.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.world.World; +import noppes.npcs.CustomNpcs; import java.util.List; @@ -53,15 +55,7 @@ public class EntityEnergyDome extends EntityEnergyBarrier { @Override protected void setSize(float width, float height) { - if (width != this.width || height != this.height) { - this.width = width; - this.height = height; - this.boundingBox.maxX = this.boundingBox.minX + (double) this.width; - this.boundingBox.maxZ = this.boundingBox.minZ + (double) this.width; - this.boundingBox.maxY = this.boundingBox.minY + (double) this.height; - } - this.ySize = 0.0F; - this.yOffset = -this.height * 0.5F; + super.setSize(width, height); } @Override @@ -102,9 +96,11 @@ public class EntityEnergyDome extends EntityEnergyBarrier { if (updateBarrierTick()) return; - // Knockback velocity push every tick - if (!worldObj.isRemote && barrierData.knockbackEnabled) { - knockbackEntities(); + // 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(); } } @@ -210,49 +206,50 @@ public class EntityEnergyDome extends EntityEnergyBarrier { setCharging(false); } - // ==================== KNOCKBACK (DBO-style motion prediction) ==================== + // ==================== ENTITY PHYSICS (Solid + Knockback) ==================== /** - * DBO Ki Shield style knockback — unified algorithm with geometric jail: + * Processes entity physics for the dome barrier. Two independent systems: *

- * When jailEnabled = false (default): - * - OUTSIDE entities: predict future position, if it would cross into dome push outward. - * If close and approaching, apply soft outward push. Full motion replacement. - * - INSIDE entities: NOT affected (free movement). Dome only blocks entry. + * 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. *

- * When jailEnabled = true: - * - OUTSIDE entities: pushed outward (same as above, prevents entry). - * - INSIDE entities: pushed inward when near edge (geometric containment). - * Any entity inside stays inside, any entity outside stays outside. + * 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. *

* For moving domes (followCaster), dome velocity is accounted for by using - * relative velocity (entity motion minus dome motion) for predictions. + * the dome's previous position for previous-tick distance calculations. */ @Override @SuppressWarnings("unchecked") - protected void knockbackEntities() { - float margin = 2.0f; + protected void processEntityPhysics() { float r = domeRadius; + boolean solid = barrierData.solid; + boolean knockback = barrierData.knockbackEnabled; float strength = barrierData.knockbackStrength; - boolean jail = barrierData.jailEnabled; - float jailThresh = barrierData.jailThreshold; - // Dome velocity for moving domes (followCaster) - double domeVelX = posX - prevPosX; - double domeVelY = posY - prevPosY; - double domeVelZ = posZ - prevPosZ; + 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 - margin, posY - r - margin, posZ - r - margin, - posX + r + margin, posY + r + margin, posZ + r + margin + posX - r - knockbackMargin, posY - r - knockbackMargin, posZ - r - knockbackMargin, + posX + r + knockbackMargin, posY + r + knockbackMargin, posZ + r + knockbackMargin ); + // On client, identify local player for client-side solid prediction + EntityPlayer localPlayer = worldObj.isRemote ? CustomNpcs.proxy.getPlayer() : null; + List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, searchBox); for (EntityLivingBase ent : entities) { if (ent.getEntityId() == ownerEntityId) continue; - if (!isKnockbackTarget(ent)) continue; if (isAllyOfOwner(ent)) continue; + // Client-side: only process local player for solid prediction + if (worldObj.isRemote && (localPlayer == null || ent != localPlayer)) continue; + // Current position relative to dome center double dx = ent.posX - posX; double dy = (ent.posY + ent.height * 0.5) - posY; @@ -265,71 +262,76 @@ public class EntityEnergyDome extends EntityEnergyBarrier { double ny = dy / dist; double nz = dz / dist; - // Entity velocity relative to dome (accounts for moving domes) - double relVelX = ent.motionX - domeVelX; - double relVelY = ent.motionY - domeVelY; - double relVelZ = ent.motionZ - domeVelZ; - boolean isInside = dist < r; - if (isInside && jail) { - // --- INSIDE + JAIL ENABLED: push toward center when near dome edge --- - if (dist > r * jailThresh) { - double overshoot = dist - r * jailThresh; - double force = (overshoot + 0.1) * strength * 0.2; - // Set motion to dome velocity + inward push (keeps entity moving with dome) - ent.motionX = domeVelX - nx * force; - ent.motionY = domeVelY - ny * force; - ent.motionZ = domeVelZ - nz * force; + // --- 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; + 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 { - // Predict if entity would escape next tick (relative to dome) - double futDx = dx + relVelX; - double futDy = dy + relVelY; - double futDz = dz + relVelZ; - double futDist = Math.sqrt(futDx * futDx + futDy * futDy + futDz * futDz); - if (futDist >= r) { - // Would escape: cancel outward motion, match dome velocity - double radialVel = relVelX * nx + relVelY * ny + relVelZ * nz; - if (radialVel > 0) { + // 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; } } } - } else if (!isInside) { - // --- OUTSIDE: push outward (prevent entry) --- + } - // Predicted future distance from center (relative to dome movement) - double futDx = dx + relVelX; - double futDy = dy + relVelY; - double futDz = dz + relVelZ; - double futDist = Math.sqrt(futDx * futDx + futDy * futDy + futDz * futDz); + // --- 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; - // Radial velocity relative to dome (positive = moving away) - double radialVel = relVelX * nx + relVelY * ny + relVelZ * nz; - - if (futDist < r) { - // Would cross INTO dome: hard push outward proportional to overshoot - double overshoot = r - futDist; - double force = (overshoot * 2.0 + 0.05) * strength * 0.1; - ent.motionX = domeVelX + nx * force; - ent.motionY = domeVelY + ny * force * 0.5; - ent.motionZ = domeVelZ + nz * force; - ent.velocityChanged = true; - } else if (radialVel < 0 && dist < r + margin) { - // Close and approaching: soft push outward proportional to proximity - double proximity = 1.0 - ((dist - r) / margin); - double force = proximity * strength * 0.1; - ent.motionX = domeVelX + nx * force; - ent.motionY = domeVelY + ny * force * 0.5; - ent.motionZ = domeVelZ + nz * force; + 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; } } - // else: INSIDE + no jail → do nothing (free movement) } } @@ -346,6 +348,24 @@ public class EntityEnergyDome extends EntityEnergyBarrier { // ==================== MELEE (spherical check) ==================== + /** + * On the client side, the dome's large bounding box intercepts all raycasts from inside, + * preventing the player from targeting mobs within the dome. To fix this, return false + * when the local player is inside the dome so the raycast passes through to other entities. + * Players outside the dome can still target it for melee normally. + */ + @Override + public boolean canBeCollidedWith() { + if (!barrierData.meleeEnabled) return false; + if (worldObj.isRemote) { + EntityPlayer player = CustomNpcs.proxy.getPlayer(); + if (player != null && isEntityInside(player)) { + return false; + } + } + return true; + } + /** * Reject melee hits that land on the cubic bounding box but are outside the actual sphere. * Uses distance from the sphere surface (not center) for accurate spherical rejection. @@ -368,13 +388,14 @@ public class EntityEnergyDome extends EntityEnergyBarrier { // ==================== BOUNDING BOX ==================== /** - * Small expansion on top of the dome-sized bounding box for comfortable - * melee targeting. The actual BB is already dome-sized (set in onUpdate), - * so this only adds a small reach margin for crosshair ray-testing. + * 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 1.0f; + return 0.0f; } @Override @@ -414,7 +435,7 @@ public class EntityEnergyDome extends EntityEnergyBarrier { public void setDomeRadius(float radius) { this.domeRadius = Math.max(0.1f, radius); - this.setSize(this.domeRadius * 2, this.domeRadius * 2); + this.setPosition(this.posX, this.posY, this.posZ); } public boolean isFollowCaster() { @@ -425,6 +446,30 @@ public class EntityEnergyDome extends EntityEnergyBarrier { 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 diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java index cd33f6460..1ef8dc370 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java @@ -13,6 +13,7 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.DamageSource; import net.minecraft.world.World; +import noppes.npcs.CustomNpcs; import noppes.npcs.NpcDamageSource; import noppes.npcs.entity.EntityNPCInterface; @@ -119,9 +120,11 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { if (updateBarrierTick()) return; - // Knockback velocity push every tick (not during launched mode) - if (!worldObj.isRemote && barrierData.knockbackEnabled && mode != PanelMode.LAUNCHED) { - knockbackEntities(); + // 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 @@ -365,21 +368,28 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { setCharging(false); } - // ==================== KNOCKBACK (DBO-style motion prediction) ==================== + // ==================== ENTITY PHYSICS (Solid + Knockback) ==================== /** - * DBO-inspired planar knockback: - * - Predict future position from current motion - * - Detect crossing or proximity to panel surface - * - Apply counter-force via full motion replacement - * - No separate phases — single unified algorithm + * 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 knockbackEntities() { + protected void processEntityPhysics() { float halfW = panelData.panelWidth * 0.5f; float halfH = panelData.panelHeight * 0.5f; - float searchExtension = 5.0f; + float searchExtension = 3.0f; + boolean solid = barrierData.solid; + boolean knockback = barrierData.knockbackEnabled; float strength = barrierData.knockbackStrength; AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox( @@ -387,6 +397,9 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { posX + halfW + searchExtension, posY + halfH + searchExtension, posZ + halfW + searchExtension ); + // On client, identify local player for client-side solid prediction + EntityPlayer localPlayer = worldObj.isRemote ? CustomNpcs.proxy.getPlayer() : null; + List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, searchBox); float yawRad = (float) Math.toRadians(panelYaw); float normalX = -(float) Math.sin(yawRad); @@ -394,9 +407,11 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { for (EntityLivingBase ent : entities) { if (ent.getEntityId() == ownerEntityId) continue; - if (!isKnockbackTarget(ent)) continue; if (isAllyOfOwner(ent)) continue; + // Client-side: only process local player for solid prediction + if (worldObj.isRemote && (localPlayer == null || ent != localPlayer)) continue; + // Current position relative to panel center double dx = ent.posX - posX; double dy = (ent.posY + ent.height * 0.5) - posY; @@ -407,45 +422,67 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { float localRight = (float) (dx * (-normalZ) + dz * normalX); // Skip if outside panel bounds (with margin) - if (Math.abs(localRight) > halfW + searchExtension) continue; - if (Math.abs(dy) > halfH + searchExtension) continue; - if (Math.abs(localForward) > searchExtension) continue; - - // Skip if outside panel width/height bounds (no wall to push against) if (Math.abs(localRight) > halfW + 1.0f) continue; if (Math.abs(dy) > halfH + 1.0f) continue; - - // Predicted future position - double futDx = (ent.posX + ent.motionX) - posX; - double futDz = (ent.posZ + ent.motionZ) - posZ; - float futLocalForward = (float) (futDx * normalX + futDz * normalZ); - - // Normal velocity component (positive = moving in normal direction) - double normalVel = ent.motionX * normalX + ent.motionZ * normalZ; + if (Math.abs(localForward) > searchExtension) continue; // Determine which side the entity is on float side = localForward >= 0 ? 1.0f : -1.0f; - boolean movingTowardPanel = (localForward >= 0 && normalVel < 0) || (localForward < 0 && normalVel > 0); - // Check if predicted position would cross the plane (sign change) - boolean wouldCross = (localForward > 0 && futLocalForward <= 0) || (localForward < 0 && futLocalForward >= 0); + // Check if within panel width/height bounds + boolean inBounds = Math.abs(localRight) <= halfW && Math.abs(dy) <= halfH; - if (wouldCross && Math.abs(localRight) <= halfW && Math.abs(dy) <= halfH) { - // Hard block: full motion replacement to prevent crossing - // Push back along normal, proportional to how far past the surface they'd go - double overshoot = Math.abs(futLocalForward); - double pushForce = (overshoot + 0.05) * strength * 0.1; - ent.motionX = normalX * side * pushForce; - ent.motionZ = normalZ * side * pushForce; - ent.velocityChanged = true; - } else if (Math.abs(localForward) < 1.0 && movingTowardPanel - && Math.abs(localRight) <= halfW && Math.abs(dy) <= halfH) { - // Soft push: entity is close and approaching, apply proportional force - double proximity = 1.0 - Math.abs(localForward); - double pushForce = proximity * strength * 0.1; - ent.motionX = normalX * side * pushForce; - ent.motionZ = normalZ * side * pushForce; - ent.velocityChanged = true; + // --- 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; + ent.motionX += normalX * prevSide * 0.15; + ent.motionZ += normalZ * prevSide * 0.15; + 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; + ent.motionX += normalX * side * 0.05; + ent.motionZ += normalZ * side * 0.05; + 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; + ent.motionX += normalX * side * force; + ent.motionZ += normalZ * side * force; + ent.velocityChanged = true; + } } } }