mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 22:27:32 +00:00
Fix dome melee for all radii with server-side tick detection
MC's processUseEntity uses player.getDistanceSqToEntity(dome) for reach validation, which computes center-to-center distance. For domes with radius > 5, this always exceeds the 36-block threshold, making melee impossible regardless of how close the player is to the surface. Fix: server-side processMeleeHits() in onUpdate detects swinging players near the dome surface via ray-sphere intersection, then calls attackTargetEntityWithCurrentItem directly — bypassing MC's broken reach check while preserving full damage calculation (enchantments, crits, etc). Also adds adjustMeleeBoundingBox for client-side entity picking (places a small 2x2x2 BB at the nearest sphere surface point toward the player), and allows melee targeting from both inside and outside the dome. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
e711a9012b
commit
b3357a2751
1 file changed
+138
-13
@@ -11,9 +11,11 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -29,6 +31,10 @@ public class EntityEnergyDome extends EntityEnergyBarrier {
|
||||
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 EntityEnergyDome(World world) {
|
||||
super(world);
|
||||
this.noClip = true;
|
||||
@@ -96,12 +102,25 @@ public class EntityEnergyDome extends EntityEnergyBarrier {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== INCOMING CHECK ====================
|
||||
@@ -349,29 +368,74 @@ 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.
|
||||
* 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 (worldObj.isRemote) {
|
||||
EntityPlayer player = CustomNpcs.proxy.getPlayer();
|
||||
if (player != null && isEntityInside(player)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isCharging()) 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.
|
||||
* 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;
|
||||
@@ -379,12 +443,73 @@ public class EntityEnergyDome extends EntityEnergyBarrier {
|
||||
double dz = attacker.posZ - this.posZ;
|
||||
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
double surfaceDist = Math.abs(dist - domeRadius);
|
||||
// Allow hit only if attacker is within melee reach of the sphere surface
|
||||
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 ====================
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user