diff --git a/src/main/java/net/mcreator/target/api/entity/ITargetEntity.java b/src/main/java/net/mcreator/target/api/entity/ITargetEntity.java new file mode 100644 index 000000000..6649fb7e2 --- /dev/null +++ b/src/main/java/net/mcreator/target/api/entity/ITargetEntity.java @@ -0,0 +1,19 @@ +package net.mcreator.target.api.entity; + +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.phys.EntityHitResult; + +/** + * 用于进行一些并非 {@link LivingEntity} 但是可被子弹击中的特殊实体的处理 + */ +public interface ITargetEntity { + /** + * @param projectile 弹射物实体 + * @param result 击中实体的位置 + * @param source 伤害源类型 + * @param damage 伤害值 + */ + void onProjectileHit(Entity projectile, EntityHitResult result, DamageSource source, float damage); +} diff --git a/src/main/java/net/mcreator/target/entity/ProjectileEntity.java b/src/main/java/net/mcreator/target/entity/ProjectileEntity.java index e5a7eeb7e..2eeddbae7 100644 --- a/src/main/java/net/mcreator/target/entity/ProjectileEntity.java +++ b/src/main/java/net/mcreator/target/entity/ProjectileEntity.java @@ -1,6 +1,7 @@ package net.mcreator.target.entity; import net.mcreator.target.TargetMod; +import net.mcreator.target.api.entity.ITargetEntity; import net.mcreator.target.headshot.BoundingBoxManager; import net.mcreator.target.headshot.IHeadshotBox; import net.mcreator.target.init.TargetModDamageTypes; @@ -19,6 +20,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ClientboundSoundPacket; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundSource; @@ -39,6 +41,7 @@ import net.minecraftforge.entity.IEntityAdditionalSpawnData; import net.minecraftforge.entity.PartEntity; import net.minecraftforge.network.PacketDistributor; import net.minecraftforge.network.PlayMessages; +import net.minecraftforge.registries.ForgeRegistries; import javax.annotation.Nullable; import java.util.ArrayList; @@ -147,52 +150,44 @@ public class ProjectileEntity extends Entity implements IEntityAdditionalSpawnDa private EntityResult getHitResult(Entity entity, Vec3 startVec, Vec3 endVec) { double expandHeight = entity instanceof Player && !entity.isCrouching() ? 0.0625 : 0.0; AABB boundingBox = entity.getBoundingBox(); - - // 延迟补偿 - if (entity instanceof ServerPlayer && this.shooter != null) { - int ping = (int) Math.floor((((ServerPlayer) this.shooter).latency / 1000.0) * 20.0 + 4.0); - boundingBox = BoundingBoxManager.getBoundingBox((Player) entity, ping); + Vec3 velocity = new Vec3(entity.getX() - entity.xOld, entity.getY() - entity.yOld, entity.getZ() - entity.zOld); + // hitbox 延迟补偿。只有射击者是玩家(且被击中者也是玩家)才进行此类延迟补偿计算 + if (entity instanceof ServerPlayer player && this.shooter instanceof ServerPlayer serverPlayerOwner) { + int ping = Mth.floor((serverPlayerOwner.latency / 1000.0) * 20.0 + 0.5); + boundingBox = BoundingBoxManager.getBoundingBox(player, ping); + velocity = BoundingBoxManager.getVelocity(player, ping); } + // 应用蹲伏导致的 hitbox 变形 boundingBox = boundingBox.expandTowards(0, expandHeight, 0); - - if (this.beast) { - boundingBox = boundingBox.inflate(3); - } - - Vec3 hitPos = boundingBox.clip(startVec, endVec).orElse(null); - Vec3 grownHitPos = boundingBox.inflate(0.35, 0.2, 0.35).clip(startVec, endVec).orElse(null); - if (hitPos == null && grownHitPos != null) { - HitResult result = rayTraceBlocks(this.level(), new ClipContext(startVec, grownHitPos, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this), IGNORE_LEAVES); - if (result.getType() == HitResult.Type.BLOCK) { - return null; + // 根据速度一定程度地扩展 hitbox + boundingBox = boundingBox.expandTowards(velocity.x, velocity.y, velocity.z); + // 玩家 hitbox 修正,可以通过 Config 调整 + double playerHitboxOffset = 3; + if (entity instanceof ServerPlayer) { + if (entity.getVehicle() != null) { + boundingBox = boundingBox.move(velocity.multiply(playerHitboxOffset / 2, playerHitboxOffset / 2, playerHitboxOffset / 2)); } - hitPos = grownHitPos; + boundingBox = boundingBox.move(velocity.multiply(playerHitboxOffset, playerHitboxOffset, playerHitboxOffset)); } + // 给所有实体统一应用的 Hitbox 偏移,其数值为实验得出的定值。 + if (entity.getVehicle() != null || entity instanceof ITargetEntity) { + boundingBox = boundingBox.move(velocity.multiply(-2.5, -2.5, -2.5)); + } + boundingBox = boundingBox.move(velocity.multiply(-5, -5, -5)); + // 计算射线与实体 boundingBox 的交点 + Vec3 hitPos = boundingBox.clip(startVec, endVec).orElse(null); /* Check for headshot */ - boolean headshot = false; - if (entity instanceof LivingEntity) { - IHeadshotBox headshotBox = (IHeadshotBox) BoundingBoxManager.getHeadshotBoxes(entity.getType()); - if (headshotBox != null) { - AABB box = headshotBox.getHeadshotBox((LivingEntity) entity); - if (box != null) { - box = box.move(boundingBox.getCenter().x, boundingBox.minY, boundingBox.getCenter().z); - Optional headshotHitPos = box.clip(startVec, endVec); - if (headshotHitPos.isEmpty()) { - box = box.inflate(0.2, 0.2, 0.2); - headshotHitPos = box.clip(startVec, endVec); - } - if (headshotHitPos.isPresent() && (hitPos == null || headshotHitPos.get().distanceTo(hitPos) < 0.55)) { - hitPos = headshotHitPos.get(); - headshot = true; - } - } - } - } if (hitPos == null) { return null; } + Vec3 hitBoxPos = hitPos.subtract(entity.position()); + boolean headshot = false; + float eyeHeight = entity.getEyeHeight(); + if ((eyeHeight - 0.35) < hitBoxPos.y && hitBoxPos.y < (eyeHeight + 0.35)) { + headshot = true; + } return new EntityResult(entity, hitPos, headshot); } diff --git a/src/main/java/net/mcreator/target/headshot/BoundingBoxManager.java b/src/main/java/net/mcreator/target/headshot/BoundingBoxManager.java index 5342a3cd7..e4010ddc7 100644 --- a/src/main/java/net/mcreator/target/headshot/BoundingBoxManager.java +++ b/src/main/java/net/mcreator/target/headshot/BoundingBoxManager.java @@ -26,6 +26,12 @@ public class BoundingBoxManager { private static final Map, IHeadshotBox> headshotBoxes = new HashMap<>(); private static final WeakHashMap> playerBoxes = new WeakHashMap<>(); + private static final WeakHashMap> PLAYER_POSITION = new WeakHashMap<>(); + // 玩家命中箱缓存表 + private static final WeakHashMap> PLAYER_HITBOXES = new WeakHashMap<>(); + // 玩家速度缓存表 + private static final WeakHashMap> PLAYER_VELOCITY = new WeakHashMap<>(); + static { /* Player */ registerHeadshotBox(EntityType.PLAYER, (entity) -> { @@ -118,6 +124,16 @@ public class BoundingBoxManager { playerBoxes.remove(event.getEntity()); } + public static Vec3 getPlayerVelocity(Player entity) { + LinkedList positions = PLAYER_POSITION.computeIfAbsent(entity, player -> new LinkedList<>()); + if (positions.size() > 1) { + Vec3 currPos = positions.getFirst(); + Vec3 prevPos = positions.getLast(); + return new Vec3(currPos.x - prevPos.x, currPos.y - prevPos.y, currPos.z - prevPos.z); + } + return new Vec3(0, 0, 0); + } + public static AABB getBoundingBox(Player entity, int ping) { if (playerBoxes.containsKey(entity)) { LinkedList boxes = playerBoxes.get(entity); @@ -126,4 +142,13 @@ public class BoundingBoxManager { } return entity.getBoundingBox(); } + + public static Vec3 getVelocity(Player entity, int ping) { + if (PLAYER_VELOCITY.containsKey(entity)) { + LinkedList velocities = PLAYER_VELOCITY.get(entity); + int index = Mth.clamp(ping, 0, velocities.size() - 1); + return velocities.get(index); + } + return getPlayerVelocity(entity); + } } \ No newline at end of file