From 03276fb3d87bdd254cac0b696d7d27229e8112c6 Mon Sep 17 00:00:00 2001 From: Light_Quanta Date: Thu, 27 Mar 2025 01:18:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0ProjectileEntity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/ICustomKnockback.java | 19 + .../projectile/CustomSyncMotionEntity.java | 5 + .../entity/projectile/ProjectileEntity.java | 910 ++++++++++++++++++ .../superbwarfare/init/ModEntities.java | 5 +- .../network/NetworkRegistry.java | 33 +- .../message/ClientMotionSyncMessage.java | 47 + .../superbwarfare/tools/CustomExplosion.java | 193 ++++ .../tools/ExtendedEntityRayTraceResult.java | 26 + 8 files changed, 1209 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/ICustomKnockback.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/projectile/CustomSyncMotionEntity.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/projectile/ProjectileEntity.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/network/message/ClientMotionSyncMessage.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/tools/CustomExplosion.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/tools/ExtendedEntityRayTraceResult.java diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/ICustomKnockback.java b/src/main/java/com/atsuishio/superbwarfare/entity/ICustomKnockback.java new file mode 100644 index 000000000..a2fb4e274 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/ICustomKnockback.java @@ -0,0 +1,19 @@ +package com.atsuishio.superbwarfare.entity; + +import net.minecraft.world.entity.LivingEntity; + +/** + * Codes Based On @TACZ + */ +public interface ICustomKnockback { + + static ICustomKnockback getInstance(LivingEntity entity) { + return (ICustomKnockback) entity; + } + + void superbWarfare$setKnockbackStrength(double strength); + + void superbWarfare$resetKnockbackStrength(); + + double superbWarfare$getKnockbackStrength(); +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/projectile/CustomSyncMotionEntity.java b/src/main/java/com/atsuishio/superbwarfare/entity/projectile/CustomSyncMotionEntity.java new file mode 100644 index 000000000..ccb14794c --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/projectile/CustomSyncMotionEntity.java @@ -0,0 +1,5 @@ +package com.atsuishio.superbwarfare.entity.projectile; + +public interface CustomSyncMotionEntity { + void syncMotion(); +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/projectile/ProjectileEntity.java b/src/main/java/com/atsuishio/superbwarfare/entity/projectile/ProjectileEntity.java new file mode 100644 index 000000000..0a13a650b --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/projectile/ProjectileEntity.java @@ -0,0 +1,910 @@ +package com.atsuishio.superbwarfare.entity.projectile; + +import com.atsuishio.superbwarfare.block.BarbedWireBlock; +import com.atsuishio.superbwarfare.config.server.MiscConfig; +import com.atsuishio.superbwarfare.config.server.ProjectileConfig; +import com.atsuishio.superbwarfare.entity.ICustomKnockback; +import com.atsuishio.superbwarfare.init.*; +import com.atsuishio.superbwarfare.network.message.ClientMotionSyncMessage; +import com.atsuishio.superbwarfare.tools.*; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Holder; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundSoundPacket; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; +import net.minecraft.tags.EntityTypeTags; +import net.minecraft.util.Mth; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.monster.Monster; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.Projectile; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.gameevent.GameEvent; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.*; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.neoforged.neoforge.common.Tags; +import net.neoforged.neoforge.entity.IEntityWithComplexSpawn; +import net.neoforged.neoforge.entity.PartEntity; +import net.neoforged.neoforge.event.EventHooks; +import net.neoforged.neoforge.network.PacketDistributor; +import org.jetbrains.annotations.NotNull; +import software.bernie.geckolib.animatable.GeoEntity; +import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache; +import software.bernie.geckolib.animation.AnimatableManager; +import software.bernie.geckolib.util.GeckoLibUtil; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; + +@SuppressWarnings({"unused", "UnusedReturnValue", "SuspiciousNameCombination"}) +public class ProjectileEntity extends Projectile implements IEntityWithComplexSpawn, GeoEntity, CustomSyncMotionEntity { + + public static final EntityDataAccessor COLOR_R = SynchedEntityData.defineId(ProjectileEntity.class, EntityDataSerializers.FLOAT); + public static final EntityDataAccessor COLOR_G = SynchedEntityData.defineId(ProjectileEntity.class, EntityDataSerializers.FLOAT); + public static final EntityDataAccessor COLOR_B = SynchedEntityData.defineId(ProjectileEntity.class, EntityDataSerializers.FLOAT); + private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this); + + private static final Predicate PROJECTILE_TARGETS = input -> input != null && input.isPickable() && !input.isSpectator() && input.isAlive(); + private static final Predicate IGNORE_LIST = input -> input != null && (input.getBlock() instanceof LeavesBlock + || input.getBlock() instanceof FenceBlock + || input.is(Blocks.IRON_BARS) + || input.getBlock() instanceof DoorBlock + || input.getBlock() instanceof TrapDoorBlock + || input.getBlock() instanceof BarbedWireBlock); + + @Nullable + protected LivingEntity shooter; + protected int shooterId; + private float damage = 1f; + private float headShot = 1f; + private float monsterMultiple = 0.0f; + private float legShot = 0.5f; + private boolean beast = false; + private boolean zoom = false; + private float bypassArmorRate = 0.0f; + private float undeadMultiple = 1.0f; + private int jhpLevel = 0; + private int heLevel = 0; + private int fireLevel = 0; + private boolean dragonBreath = false; + private float knockback = 0.05f; + private boolean forceKnockback = false; + private final ArrayList mobEffects = new ArrayList<>(); + + public ProjectileEntity(EntityType entityType, Level level) { + super(entityType, level); + this.noCulling = true; + } + + public ProjectileEntity(Level level) { + super(ModEntities.PROJECTILE.get(), level); + } + + @Nullable + protected EntityResult findEntityOnPath(Vec3 startVec, Vec3 endVec) { + if (this.shooter == null) return null; + + Vec3 hitVec = null; + Entity hitEntity = null; + boolean headshot = false; + boolean legShot = false; + List entities = this.level() + .getEntities(this, + this.getBoundingBox() + .expandTowards(this.getDeltaMovement()) + .inflate(this.beast ? 3 : 1), + PROJECTILE_TARGETS + ); + double closestDistance = Double.MAX_VALUE; + + for (Entity entity : entities) { + if (entity.equals(this.shooter)) continue; + if (entity.equals(this.shooter.getVehicle())) continue; + + // TODO target entity +// if (entity instanceof TargetEntity && entity.getEntityData().get(TargetEntity.DOWN_TIME) > 0) continue; + + EntityResult result = this.getHitResult(entity, startVec, endVec); + if (result == null) continue; + + Vec3 hitPos = result.getHitPos(); + if (hitPos == null) continue; + + double distanceToHit = startVec.distanceTo(hitPos); + if (distanceToHit < closestDistance) { + hitVec = hitPos; + hitEntity = entity; + closestDistance = distanceToHit; + headshot = result.isHeadshot(); + legShot = result.isLegShot(); + } + } + return hitEntity != null ? new EntityResult(hitEntity, hitVec, headshot, legShot) : null; + } + + @Nullable + protected List findEntitiesOnPath(Vec3 startVec, Vec3 endVec) { + List hitEntities = new ArrayList<>(); + List entities = this.level().getEntities( + this, + this.getBoundingBox() + .expandTowards(this.getDeltaMovement()) + .inflate(this.beast ? 3 : 1), + PROJECTILE_TARGETS + ); + for (Entity entity : entities) { + if (!entity.equals(this.shooter)) { + EntityResult result = this.getHitResult(entity, startVec, endVec); + if (result == null) continue; + hitEntities.add(result); + } + } + return hitEntities; + } + + /** + * From TaC-Z + */ + @Nullable + private EntityResult getHitResult(Entity entity, Vec3 startVec, Vec3 endVec) { + double expandHeight = entity instanceof Player && !entity.isCrouching() ? 0.0625 : 0.0; + AABB boundingBox = entity.getBoundingBox(); + Vec3 velocity = new Vec3(entity.getX() - entity.xOld, entity.getY() - entity.yOld, entity.getZ() - entity.zOld); + + if (entity instanceof ServerPlayer player && this.shooter instanceof ServerPlayer serverPlayerOwner) { + int ping = Mth.floor((serverPlayerOwner.connection.latency() / 1000.0) * 20.0 + 0.5); + boundingBox = HitboxHelper.getBoundingBox(player, ping); + velocity = HitboxHelper.getVelocity(player, ping); + } + boundingBox = boundingBox.expandTowards(0, expandHeight, 0); + + boundingBox = boundingBox.expandTowards(velocity.x, velocity.y, velocity.z); + + double playerHitboxOffset = 3; + if (entity instanceof ServerPlayer) { + if (entity.getVehicle() != null) { + boundingBox = boundingBox.move(velocity.multiply(playerHitboxOffset / 2, playerHitboxOffset / 2, playerHitboxOffset / 2)); + } + boundingBox = boundingBox.move(velocity.multiply(playerHitboxOffset, playerHitboxOffset, playerHitboxOffset)); + } + + if (entity.getVehicle() != null) { + boundingBox = boundingBox.move(velocity.multiply(-2.5, -2.5, -2.5)); + } + boundingBox = boundingBox.move(velocity.multiply(-5, -5, -5)); + + if (this.beast) { + boundingBox = boundingBox.inflate(3); + } + + Vec3 hitPos = boundingBox.clip(startVec, endVec).orElse(null); + + if (hitPos == null) { + return null; + } + Vec3 hitBoxPos = hitPos.subtract(entity.position()); + boolean headshot = false; + boolean legShot = false; + float eyeHeight = entity.getEyeHeight(); + float bodyHeight = entity.getBbHeight(); + if ((eyeHeight - 0.35) < hitBoxPos.y && hitBoxPos.y < (eyeHeight + 0.4) && entity instanceof LivingEntity) { + headshot = true; + } + if (hitBoxPos.y < (0.33 * bodyHeight) && entity instanceof LivingEntity) { + legShot = true; + } + + return new EntityResult(entity, hitPos, headshot, legShot); + } + + @Override + protected void defineSynchedData(SynchedEntityData.Builder builder) { + builder.define(COLOR_R, 1.0f) + .define(COLOR_G, 222 / 255f) + .define(COLOR_B, 39 / 255f); + + } + + @Override + public void tick() { + super.tick(); + this.updateHeading(); + + Vec3 vec = this.getDeltaMovement(); + + if (!this.level().isClientSide() && this.shooter != null) { + Vec3 startVec = this.position(); + Vec3 endVec = startVec.add(this.getDeltaMovement()); + HitResult result = rayTraceBlocks(this.level(), new ClipContext(startVec, endVec, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this), + ProjectileConfig.ALLOW_PROJECTILE_DESTROY_GLASS.get() ? IGNORE_LIST : IGNORE_LIST.or(input -> input.is(Tags.Blocks.GLASS_PANES))); + if (result.getType() != HitResult.Type.MISS) { + endVec = result.getLocation(); + } + + List entityResults = new ArrayList<>(); + var temp = findEntitiesOnPath(startVec, endVec); + if (temp != null) entityResults.addAll(temp); + entityResults.sort(Comparator.comparingDouble(e -> e.getHitPos().distanceTo(this.shooter.position()))); + + for (EntityResult entityResult : entityResults) { + result = new ExtendedEntityRayTraceResult(entityResult); + if (((EntityHitResult) result).getEntity() instanceof Player player) { + if (this.shooter instanceof Player p && !p.canHarmPlayer(player)) { + result = null; + } + } + if (result != null) { + this.onHit(result); + } + + if (!this.beast) { + this.bypassArmorRate -= 0.2F; + if (this.bypassArmorRate < 0.8F) { + // TODO target +// if (result != null && !(((EntityHitResult) result).getEntity() instanceof TargetEntity target && target.getEntityData().get(TargetEntity.DOWN_TIME) > 0)) { +// break; +// } + } + } + } + if (entityResults.isEmpty()) { + this.onHit(result); + } + + this.setPos(this.getX() + vec.x, this.getY() + vec.y, this.getZ() + vec.z); + } else { + this.setPosRaw(this.getX() + vec.x, this.getY() + vec.y, this.getZ() + vec.z); + } + + this.setDeltaMovement(vec.x, vec.y - 0.02, vec.z); + + if (this.tickCount > (fireLevel > 0 ? 10 : 40)) { + this.discard(); + } + + if (fireLevel > 0 && dragonBreath && this.level() instanceof ServerLevel serverLevel) { + double randomPos = this.tickCount * 0.08 * (Math.random() - 0.5); + ParticleTool.sendParticle(serverLevel, ParticleTypes.FLAME, + (this.xo + this.getX()) / 2 + randomPos, (this.yo + this.getY()) / 2 + randomPos, (this.zo + this.getZ()) / 2 + randomPos, + 0, + this.getDeltaMovement().x, this.getDeltaMovement().y, this.getDeltaMovement().z, + Math.max(this.getDeltaMovement().length() - 1.1 * this.tickCount, 0.2), true + ); + } + + this.syncMotion(); + } + + @Override + public void syncMotion() { + if (!this.level().isClientSide) { + PacketDistributor.sendToAllPlayers(new ClientMotionSyncMessage(this)); + } + } + + @Override + protected void readAdditionalSaveData(CompoundTag tag) { + this.damage = tag.getFloat("Damage"); + this.headShot = tag.getFloat("HeadShot"); + this.monsterMultiple = tag.getFloat("MonsterMultiple"); + this.legShot = tag.getFloat("LegShot"); + this.bypassArmorRate = tag.getFloat("BypassArmorRate"); + this.undeadMultiple = tag.getFloat("UndeadMultiple"); + this.knockback = tag.getFloat("Knockback"); + + this.beast = tag.getBoolean("Beast"); + this.forceKnockback = tag.getBoolean("ForceKnockback"); + } + + @Override + protected void addAdditionalSaveData(CompoundTag tag) { + tag.putFloat("Damage", this.damage); + tag.putFloat("HeadShot", this.headShot); + tag.putFloat("MonsterMultiple", this.monsterMultiple); + tag.putFloat("LegShot", this.legShot); + tag.putFloat("BypassArmorRate", this.bypassArmorRate); + tag.putFloat("UndeadMultiple", this.undeadMultiple); + tag.putFloat("Knockback", this.knockback); + + tag.putBoolean("Beast", this.beast); + tag.putBoolean("ForceKnockback", this.forceKnockback); + } + + @Override + protected void onHit(@NotNull HitResult result) { + if (result instanceof BlockHitResult blockHitResult) { + if (blockHitResult.getType() == HitResult.Type.MISS) { + return; + } + BlockPos resultPos = blockHitResult.getBlockPos(); + BlockState state = this.level().getBlockState(resultPos); + SoundEvent event = state.getBlock().getSoundType(state, this.level(), resultPos, this).getBreakSound(); + this.level().playSound(null, result.getLocation().x, result.getLocation().y, result.getLocation().z, event, SoundSource.AMBIENT, 1.0F, 1.0F); + Vec3 hitVec = result.getLocation(); + + if (state.getBlock() instanceof BellBlock bell) { + bell.attemptToRing(this.level(), resultPos, blockHitResult.getDirection()); + } + + if (ProjectileConfig.ALLOW_PROJECTILE_DESTROY_GLASS.get()) { + if (state.is(Tags.Blocks.GLASS_BLOCKS) || state.is(Tags.Blocks.GLASS_PANES)) { + this.level().destroyBlock(resultPos, false, this.getShooter()); + } + } + + if (state.getBlock() instanceof TargetBlock) { + if (this.shooter == null) return; + + int rings = getRings(blockHitResult, hitVec); + double dis = this.shooter.position().distanceTo(hitVec); + recordHitScore(rings, dis); + } + + this.onHitBlock(hitVec); + if (heLevel > 0) { + explosionBulletBlock(this, this.damage, heLevel, monsterMultiple + 1, hitVec); + } + if (fireLevel > 0 && this.level() instanceof ServerLevel serverLevel) { + ParticleTool.sendParticle(serverLevel, ParticleTypes.LAVA, hitVec.x, hitVec.y, hitVec.z, + 3, 0, 0, 0, 0.5, true); + } + } + + if (result instanceof ExtendedEntityRayTraceResult entityHitResult) { + Entity entity = entityHitResult.getEntity(); + if (entity.getId() == this.shooterId) { + return; + } + + if (this.shooter instanceof Player player) { + if (entity.hasIndirectPassenger(player)) { + return; + } + } + + this.onHitEntity(entity, entityHitResult.isHeadshot(), entityHitResult.isLegShot()); + entity.invulnerableTime = 0; + } + } + + private static int getRings(@NotNull BlockHitResult blockHitResult, @NotNull Vec3 hitVec) { + Direction direction = blockHitResult.getDirection(); + double x = Math.abs(Mth.frac(hitVec.x) - 0.5); + double y = Math.abs(Mth.frac(hitVec.y) - 0.5); + double z = Math.abs(Mth.frac(hitVec.z) - 0.5); + Direction.Axis axis = direction.getAxis(); + double v; + if (axis == Direction.Axis.Y) { + v = Math.max(x, z); + } else if (axis == Direction.Axis.Z) { + v = Math.max(x, y); + } else { + v = Math.max(y, z); + } + + return Math.max(1, Mth.ceil(10.0 * Mth.clamp((0.5 - v) / 0.5, 0.0, 1.0))); + } + + private void recordHitScore(int score, double distance) { + if (!(shooter instanceof Player player)) { + return; + } + + player.displayClientMessage(Component.literal(String.valueOf(score)) + .append(Component.translatable("tips.superbwarfare.shoot.rings")) + .append(Component.literal(" " + FormatTool.format1D(distance, "m"))), false); + + if (!this.shooter.level().isClientSide() && this.shooter instanceof ServerPlayer serverPlayer) { + var holder = score == 10 ? Holder.direct(ModSounds.HEADSHOT.get()) : Holder.direct(ModSounds.INDICATION.get()); + serverPlayer.connection.send(new ClientboundSoundPacket(holder, SoundSource.PLAYERS, player.getX(), player.getY(), player.getZ(), 1f, 1f, player.level().random.nextLong())); + // TODO indicator message + // ModUtils.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new ClientIndicatorMessage(score == 10 ? 1 : 0, 5)); + } + + ItemStack stack = player.getOffhandItem(); + + // TODO transcript +// if (stack.is(ModItems.TRANSCRIPT.get())) { +// final int size = 10; +// +// ListTag tags = stack.getOrCreateTag().getList(Transcript.TAG_SCORES, Tag.TAG_COMPOUND); +// +// Queue queue = new ArrayDeque<>(); +// for (int i = 0; i < tags.size(); i++) { +// queue.add(tags.getCompound(i)); +// } +// +// CompoundTag tag = new CompoundTag(); +// tag.putInt("Score", score); +// tag.putDouble("Distance", distance); +// queue.offer(tag); +// +// while (queue.size() > size) { +// queue.poll(); +// } +// +// ListTag newTags = new ListTag(); +// newTags.addAll(queue); +// +// stack.getOrCreateTag().put(Transcript.TAG_SCORES, newTags); +// } + } + + protected void onHitBlock(Vec3 location) { + if (this.level() instanceof ServerLevel serverLevel) { + if (this.beast) { + ParticleTool.sendParticle(serverLevel, ParticleTypes.END_ROD, location.x, location.y, location.z, 15, 0.1, 0.1, 0.1, 0.05, true); + } else { + ParticleTool.sendParticle(serverLevel, ModParticleTypes.BULLET_HOLE.get(), location.x, location.y, location.z, 1, 0, 0, 0, 0, true); + ParticleTool.sendParticle(serverLevel, ParticleTypes.SMOKE, location.x, location.y, location.z, 3, 0, 0.1, 0, 0.01, true); + this.discard(); + } + serverLevel.playSound(null, new BlockPos((int) location.x, (int) location.y, (int) location.z), ModSounds.LAND.get(), SoundSource.BLOCKS, 1.0F, 1.0F); + } + } + + protected void onHitEntity(Entity entity, boolean headshot, boolean legShot) { + if (this.shooter == null) return; + + float mMultiple = 1 + this.monsterMultiple; + + if (entity == null) return; + + if (entity instanceof PartEntity part) { + entity = part.getParent(); + } + + if (beast && entity instanceof LivingEntity living) { + if (living.isDeadOrDying()) return; + + // TODO target entity + // if (living instanceof TargetEntity) return; + + if (this.shooter instanceof ServerPlayer player) { + // TODO indicator msg + // ModUtils.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> player), new ClientIndicatorMessage(0, 5)); + var holder = Holder.direct(ModSounds.INDICATION.get()); + player.connection.send(new ClientboundSoundPacket(holder, SoundSource.PLAYERS, player.getX(), player.getY(), player.getZ(), 1f, 1f, player.level().random.nextLong())); + ((ServerLevel) this.level()).sendParticles(ParticleTypes.DAMAGE_INDICATOR, living.getX(), living.getY() + .5, living.getZ(), 1000, .4, .7, .4, 0); + + if (MiscConfig.SEND_KILL_FEEDBACK.get()) { + // TODO kill feedback + // ModUtils.PACKET_HANDLER.send(PacketDistributor.ALL.noArg(), new PlayerGunKillMessage(player.getId(), living.getId(), false, ModDamageTypes.BEAST)); + } + } + + if (living instanceof ServerPlayer victim) { + living.setHealth(0); + living.level().players().forEach( + p -> p.sendSystemMessage( + Component.translatable("death.attack.beast_gun", + victim.getDisplayName(), + shooter == null ? "" : shooter.getDisplayName() + ) + ) + ); + } else { + living.setHealth(0); + living.level().broadcastEntityEvent(living, (byte) 60); + living.remove(RemovalReason.KILLED); + living.gameEvent(GameEvent.ENTITY_DIE); + } + + level().playSound(living, new BlockPos((int) living.getX(), (int) living.getY(), (int) living.getZ()), ModSounds.OUCH.get(), SoundSource.PLAYERS, 2.0F, 1.0F); + return; + } + + if (entity instanceof Monster) { + this.damage *= mMultiple; + } + + if (entity instanceof LivingEntity living && living.getType().is(EntityTypeTags.UNDEAD)) { + this.damage *= this.undeadMultiple; + } + + if (entity instanceof LivingEntity living && jhpLevel > 0) { + this.damage *= (1.0f + 0.12f * jhpLevel) * ((float) (10 / (living.getAttributeValue(Attributes.ARMOR) + 10)) + 0.25f); + } + + if (heLevel > 0) { + explosionBulletEntity(this, entity, this.damage, heLevel, mMultiple); + } + + if (fireLevel > 0) { + if (!entity.level().isClientSide() && entity instanceof LivingEntity living) { + living.addEffect(new MobEffectInstance(ModMobEffects.BURN, 60 + fireLevel * 20, fireLevel, false, false), this.shooter); + } + } + + if (headshot) { + if (!this.shooter.level().isClientSide() && this.shooter instanceof ServerPlayer player) { + var holder = Holder.direct(ModSounds.HEADSHOT.get()); + player.connection.send(new ClientboundSoundPacket(holder, SoundSource.PLAYERS, player.getX(), player.getY(), player.getZ(), 1f, 1f, player.level().random.nextLong())); + + // TODO indicator msg + // ModUtils.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> player), new ClientIndicatorMessage(1, 5)); + } + performOnHit(entity, this.damage, true, this.knockback); + } else { + if (!this.shooter.level().isClientSide() && this.shooter instanceof ServerPlayer player) { + var holder = Holder.direct(ModSounds.INDICATION.get()); + player.connection.send(new ClientboundSoundPacket(holder, SoundSource.PLAYERS, player.getX(), player.getY(), player.getZ(), 1f, 1f, player.level().random.nextLong())); + // TODO indicator msg + // ModUtils.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> player), new ClientIndicatorMessage(0, 5)); + } + + if (legShot) { + if (entity instanceof LivingEntity living) { + if (living instanceof Player player && player.isCreative()) { + return; + } + if (!living.level().isClientSide()) { + living.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 20, 2, false, false)); + } + } + this.damage *= this.legShot; + } + + performOnHit(entity, this.damage, false, this.knockback); + } + + if (!this.mobEffects.isEmpty() && entity instanceof LivingEntity living) { + for (MobEffectInstance instance : this.mobEffects) { + living.addEffect(instance, this.shooter); + } + } + + this.discard(); + } + + public void performOnHit(Entity entity, float damage, boolean headshot, double knockback) { + if (entity instanceof LivingEntity living) { + if (this.forceKnockback) { + Vec3 vec3 = this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D).normalize(); + living.addDeltaMovement(vec3.scale(knockback)); + performDamage(entity, damage, headshot); + } else { + ICustomKnockback iCustomKnockback = ICustomKnockback.getInstance(living); + iCustomKnockback.superbWarfare$setKnockbackStrength(knockback); + performDamage(entity, damage, headshot); + iCustomKnockback.superbWarfare$resetKnockbackStrength(); + } + } else { + performDamage(entity, damage, headshot); + } + } + + protected void explosionBulletBlock(Entity projectile, float damage, int heLevel, float monsterMultiple, Vec3 hitVec) { + CustomExplosion explosion = new CustomExplosion(projectile.level(), projectile, + ModDamageTypes.causeProjectileBoomDamage(projectile.level().registryAccess(), projectile, this.getShooter()), (float) ((0.9 * damage) * (1 + 0.1 * heLevel)), + hitVec.x, hitVec.y, hitVec.z, (float) ((1.5 + 0.02 * damage) * (1 + 0.05 * heLevel))).setDamageMultiplier(monsterMultiple).bulletExplode(); + explosion.explode(); + EventHooks.onExplosionStart(projectile.level(), explosion); + explosion.finalizeExplosion(false); + ParticleTool.spawnMiniExplosionParticles(this.level(), hitVec); + } + + protected void explosionBulletEntity(Entity projectile, Entity target, float damage, int heLevel, float monsterMultiple) { + CustomExplosion explosion = new CustomExplosion(projectile.level(), projectile, + ModDamageTypes.causeProjectileBoomDamage(projectile.level().registryAccess(), projectile, this.getShooter()), (float) ((0.8 * damage) * (1 + 0.1 * heLevel)), + target.getX(), target.getY(), target.getZ(), (float) ((1.5 + 0.02 * damage) * (1 + 0.05 * heLevel))).setDamageMultiplier(monsterMultiple).bulletExplode(); + explosion.explode(); + EventHooks.onExplosionStart(projectile.level(), explosion); + explosion.finalizeExplosion(false); + ParticleTool.spawnMiniExplosionParticles(target.level(), target.position()); + } + + public void setDamage(float damage) { + this.damage = damage; + } + + public float getDamage() { + return this.damage; + } + + public void shoot(Player player, double vecX, double vecY, double vecZ, float velocity, float spread) { + Vec3 vec3 = (new Vec3(vecX, vecY, vecZ)).normalize(). + add(this.random.triangle(0.0D, 0.0172275D * (double) spread), this.random.triangle(0.0D, 0.0172275D * (double) spread), this.random.triangle(0.0D, 0.0172275D * (double) spread)). + scale(velocity); + this.setDeltaMovement(vec3); + double d0 = vec3.horizontalDistance(); + this.setYRot((float) (Mth.atan2(vec3.x, vec3.z) * (double) (180F / (float) Math.PI))); + this.setXRot((float) (Mth.atan2(vec3.y, d0) * (double) (180F / (float) Math.PI))); + this.yRotO = this.getYRot(); + this.xRotO = this.getXRot(); + } + + @SuppressWarnings("SameParameterValue") + private static BlockHitResult rayTraceBlocks(Level world, ClipContext context, Predicate ignorePredicate) { + return performRayTrace(context, (rayTraceContext, blockPos) -> { + BlockState blockState = world.getBlockState(blockPos); + if (ignorePredicate.test(blockState)) return null; + FluidState fluidState = world.getFluidState(blockPos); + Vec3 startVec = rayTraceContext.getFrom(); + Vec3 endVec = rayTraceContext.getTo(); + VoxelShape blockShape = rayTraceContext.getBlockShape(blockState, world, blockPos); + BlockHitResult blockResult = world.clipWithInteractionOverride(startVec, endVec, blockPos, blockShape, blockState); + VoxelShape fluidShape = rayTraceContext.getFluidShape(fluidState, world, blockPos); + BlockHitResult fluidResult = fluidShape.clip(startVec, endVec, blockPos); + double blockDistance = blockResult == null ? Double.MAX_VALUE : rayTraceContext.getFrom().distanceToSqr(blockResult.getLocation()); + double fluidDistance = fluidResult == null ? Double.MAX_VALUE : rayTraceContext.getFrom().distanceToSqr(fluidResult.getLocation()); + return blockDistance <= fluidDistance ? blockResult : fluidResult; + }, (rayTraceContext) -> { + Vec3 Vector3d = rayTraceContext.getFrom().subtract(rayTraceContext.getTo()); + return BlockHitResult.miss(rayTraceContext.getTo(), Direction.getNearest(Vector3d.x, Vector3d.y, Vector3d.z), BlockPos.containing(rayTraceContext.getTo())); + }); + } + + private static T performRayTrace(ClipContext context, BiFunction hitFunction, Function p_217300_2_) { + Vec3 startVec = context.getFrom(); + Vec3 endVec = context.getTo(); + if (!startVec.equals(endVec)) { + double startX = Mth.lerp(-0.0000001, endVec.x, startVec.x); + double startY = Mth.lerp(-0.0000001, endVec.y, startVec.y); + double startZ = Mth.lerp(-0.0000001, endVec.z, startVec.z); + double endX = Mth.lerp(-0.0000001, startVec.x, endVec.x); + double endY = Mth.lerp(-0.0000001, startVec.y, endVec.y); + double endZ = Mth.lerp(-0.0000001, startVec.z, endVec.z); + int blockX = Mth.floor(endX); + int blockY = Mth.floor(endY); + int blockZ = Mth.floor(endZ); + BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(blockX, blockY, blockZ); + T t = hitFunction.apply(context, mutablePos); + if (t != null) { + return t; + } + + double deltaX = startX - endX; + double deltaY = startY - endY; + double deltaZ = startZ - endZ; + int signX = Mth.sign(deltaX); + int signY = Mth.sign(deltaY); + int signZ = Mth.sign(deltaZ); + double d9 = signX == 0 ? Double.MAX_VALUE : (double) signX / deltaX; + double d10 = signY == 0 ? Double.MAX_VALUE : (double) signY / deltaY; + double d11 = signZ == 0 ? Double.MAX_VALUE : (double) signZ / deltaZ; + double d12 = d9 * (signX > 0 ? 1.0D - Mth.frac(endX) : Mth.frac(endX)); + double d13 = d10 * (signY > 0 ? 1.0D - Mth.frac(endY) : Mth.frac(endY)); + double d14 = d11 * (signZ > 0 ? 1.0D - Mth.frac(endZ) : Mth.frac(endZ)); + + while (d12 <= 1.0D || d13 <= 1.0D || d14 <= 1.0D) { + if (d12 < d13) { + if (d12 < d14) { + blockX += signX; + d12 += d9; + } else { + blockZ += signZ; + d14 += d11; + } + } else if (d13 < d14) { + blockY += signY; + d13 += d10; + } else { + blockZ += signZ; + d14 += d11; + } + + T t1 = hitFunction.apply(context, mutablePos.set(blockX, blockY, blockZ)); + if (t1 != null) { + return t1; + } + } + } + return p_217300_2_.apply(context); + } + + public @Nullable LivingEntity getShooter() { + return this.shooter; + } + + public int getShooterId() { + return this.shooterId; + } + + public float getBypassArmorRate() { + return this.bypassArmorRate; + } + + public void updateHeading() { + double horizontalDistance = this.getDeltaMovement().horizontalDistance(); + this.setYRot((float) (Mth.atan2(this.getDeltaMovement().x(), this.getDeltaMovement().z()) * (180D / Math.PI))); + this.setXRot((float) (Mth.atan2(this.getDeltaMovement().y(), horizontalDistance) * (180D / Math.PI))); + this.yRotO = this.getYRot(); + this.xRotO = this.getXRot(); + } + + private void performDamage(Entity entity, float damage, boolean isHeadshot) { + float rate = Mth.clamp(this.bypassArmorRate, 0, 1); + + float normalDamage = damage * Mth.clamp(1 - rate, 0, 1); + float absoluteDamage = damage * Mth.clamp(rate, 0, 1); + + entity.invulnerableTime = 0; + + float headShotModifier = isHeadshot ? this.headShot : 1; + if (normalDamage > 0) { + entity.hurt(ModDamageTypes.causeGunFireHeadshotDamage(this.level().registryAccess(), this, this.shooter), normalDamage * headShotModifier); + entity.invulnerableTime = 0; + } + if (absoluteDamage > 0) { + entity.hurt(ModDamageTypes.causeGunFireHeadshotAbsoluteDamage(this.level().registryAccess(), this, this.shooter), absoluteDamage * headShotModifier); + entity.invulnerableTime = 0; + + // TODO 大于1的穿甲对载具造成额外伤害 +// if (entity instanceof VehicleEntity vehicle && this.bypassArmorRate > 1) { +// vehicle.hurt(ModDamageTypes.causeGunFireAbsoluteDamage(this.level().registryAccess(), this, this.shooter), absoluteDamage * (this.bypassArmorRate - 1) * 0.5f); +// } + } + } + + + @Override + public void readSpawnData(@NotNull RegistryFriendlyByteBuf additionalData) { + } + + @Override + public void writeSpawnData(@NotNull RegistryFriendlyByteBuf buffer) { + } + + public static class EntityResult { + private final Entity entity; + private final Vec3 hitVec; + private final boolean headshot; + private final boolean legShot; + + public EntityResult(Entity entity, Vec3 hitVec, boolean headshot, boolean legShot) { + this.entity = entity; + this.hitVec = hitVec; + this.headshot = headshot; + this.legShot = legShot; + } + + /** + * Gets the entity that was hit by the projectile + */ + public Entity getEntity() { + return this.entity; + } + + /** + * Gets the position the projectile hit + */ + public Vec3 getHitPos() { + return this.hitVec; + } + + /** + * Gets if this was a headshot + */ + public boolean isHeadshot() { + return this.headshot; + } + + public boolean isLegShot() { + return this.legShot; + } + } + + @Override + public void registerControllers(AnimatableManager.ControllerRegistrar data) { + } + + @Override + public AnimatableInstanceCache getAnimatableInstanceCache() { + return this.cache; + } + + public boolean isZoom() { + return this.zoom; + } + + /** + * Builders + */ + public ProjectileEntity shooter(LivingEntity shooter) { + this.shooter = shooter; + return this; + } + + public ProjectileEntity damage(float damage) { + this.damage = damage; + return this; + } + + public ProjectileEntity headShot(float headShot) { + this.headShot = headShot; + return this; + } + + public ProjectileEntity monsterMultiple(float monsterMultiple) { + this.monsterMultiple = monsterMultiple; + return this; + } + + public ProjectileEntity legShot(float legShot) { + this.legShot = legShot; + return this; + } + + public ProjectileEntity beast() { + this.beast = true; + return this; + } + + public ProjectileEntity jhpBullet(int jhpLevel) { + this.jhpLevel = jhpLevel; + return this; + } + + public ProjectileEntity heBullet(int heLevel) { + this.heLevel = heLevel; + return this; + } + + public ProjectileEntity fireBullet(int fireLevel, boolean dragonBreath) { + this.fireLevel = fireLevel; + this.dragonBreath = dragonBreath; + return this; + } + + public ProjectileEntity zoom(boolean zoom) { + this.zoom = zoom; + return this; + } + + public ProjectileEntity bypassArmorRate(float bypassArmorRate) { + this.bypassArmorRate = bypassArmorRate; + return this; + } + + public ProjectileEntity undeadMultiple(float undeadMultiple) { + this.undeadMultiple = undeadMultiple; + return this; + } + + public ProjectileEntity effect(ArrayList mobEffectInstances) { + this.mobEffects.addAll(mobEffectInstances); + return this; + } + + public void setRGB(float[] rgb) { + this.entityData.set(COLOR_R, rgb[0]); + this.entityData.set(COLOR_G, rgb[1]); + this.entityData.set(COLOR_B, rgb[2]); + } + + public ProjectileEntity knockback(float knockback) { + this.knockback = knockback; + return this; + } + + public ProjectileEntity forceKnockback() { + this.forceKnockback = true; + return this; + } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/init/ModEntities.java b/src/main/java/com/atsuishio/superbwarfare/init/ModEntities.java index 122f596f9..42b3fd2df 100644 --- a/src/main/java/com/atsuishio/superbwarfare/init/ModEntities.java +++ b/src/main/java/com/atsuishio/superbwarfare/init/ModEntities.java @@ -2,6 +2,7 @@ package com.atsuishio.superbwarfare.init; import com.atsuishio.superbwarfare.ModUtils; import com.atsuishio.superbwarfare.entity.projectile.LaserEntity; +import com.atsuishio.superbwarfare.entity.projectile.ProjectileEntity; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; @@ -48,8 +49,8 @@ public class ModEntities { // EntityType.Builder.of(RpgRocketEntity::new, MobCategory.MISC).setShouldReceiveVelocityUpdates(false).setTrackingRange(64).setUpdateInterval(1).setCustomClientFactory(RpgRocketEntity::new).sized(0.5f, 0.5f)); // public static final DeferredHolder, EntityType> MORTAR_SHELL = register("mortar_shell", // EntityType.Builder.of(MortarShellEntity::new, MobCategory.MISC).setShouldReceiveVelocityUpdates(false).setTrackingRange(64).setUpdateInterval(1).setCustomClientFactory(MortarShellEntity::new).sized(0.5f, 0.5f)); -// public static final DeferredHolder, EntityType> PROJECTILE = register("projectile", -// EntityType.Builder.of(ProjectileEntity::new, MobCategory.MISC).setShouldReceiveVelocityUpdates(false).setCustomClientFactory(ProjectileEntity::new).setTrackingRange(64).noSave().noSummon().sized(0.25f, 0.25f)); +public static final DeferredHolder, EntityType> PROJECTILE = register("projectile", + EntityType.Builder.of(ProjectileEntity::new, MobCategory.MISC).setShouldReceiveVelocityUpdates(false).setTrackingRange(64).noSave().noSummon().sized(0.25f, 0.25f)); // public static final DeferredHolder, EntityType> CANNON_SHELL = register("cannon_shell", // EntityType.Builder.of(CannonShellEntity::new, MobCategory.MISC).setShouldReceiveVelocityUpdates(false).setTrackingRange(64).setUpdateInterval(1).setCustomClientFactory(CannonShellEntity::new).sized(0.5f, 0.5f)); // public static final DeferredHolder, EntityType> GUN_GRENADE = register("gun_grenade", diff --git a/src/main/java/com/atsuishio/superbwarfare/network/NetworkRegistry.java b/src/main/java/com/atsuishio/superbwarfare/network/NetworkRegistry.java index 30bdef64e..f5acfd669 100644 --- a/src/main/java/com/atsuishio/superbwarfare/network/NetworkRegistry.java +++ b/src/main/java/com/atsuishio/superbwarfare/network/NetworkRegistry.java @@ -1,9 +1,6 @@ package com.atsuishio.superbwarfare.network; -import com.atsuishio.superbwarfare.network.message.BreathMessage; -import com.atsuishio.superbwarfare.network.message.LaserShootMessage; -import com.atsuishio.superbwarfare.network.message.PlayerVariablesSyncMessage; -import com.atsuishio.superbwarfare.network.message.ShakeClientMessage; +import com.atsuishio.superbwarfare.network.message.*; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; import net.neoforged.neoforge.network.registration.PayloadRegistrar; @@ -11,29 +8,11 @@ public class NetworkRegistry { public static void register(final RegisterPayloadHandlersEvent event) { final PayloadRegistrar registrar = event.registrar("1"); - registrar.playToClient( - PlayerVariablesSyncMessage.TYPE, - PlayerVariablesSyncMessage.STREAM_CODEC, - PlayerVariablesSyncMessage::handler - ); - - registrar.playToClient( - ShakeClientMessage.TYPE, - ShakeClientMessage.STREAM_CODEC, - ShakeClientMessage::handler - ); - - registrar.playToServer( - LaserShootMessage.TYPE, - LaserShootMessage.STREAM_CODEC, - LaserShootMessage::handler - ); - - registrar.playToServer( - BreathMessage.TYPE, - BreathMessage.STREAM_CODEC, - BreathMessage::handler - ); + registrar.playToClient(PlayerVariablesSyncMessage.TYPE, PlayerVariablesSyncMessage.STREAM_CODEC, PlayerVariablesSyncMessage::handler); + registrar.playToClient(ShakeClientMessage.TYPE, ShakeClientMessage.STREAM_CODEC, ShakeClientMessage::handler); + registrar.playToClient(ClientMotionSyncMessage.TYPE, ClientMotionSyncMessage.STREAM_CODEC, ClientMotionSyncMessage::handler); + registrar.playToServer(LaserShootMessage.TYPE, LaserShootMessage.STREAM_CODEC, LaserShootMessage::handler); + registrar.playToServer(BreathMessage.TYPE, BreathMessage.STREAM_CODEC, BreathMessage::handler); } } diff --git a/src/main/java/com/atsuishio/superbwarfare/network/message/ClientMotionSyncMessage.java b/src/main/java/com/atsuishio/superbwarfare/network/message/ClientMotionSyncMessage.java new file mode 100644 index 000000000..ba332a496 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/network/message/ClientMotionSyncMessage.java @@ -0,0 +1,47 @@ +package com.atsuishio.superbwarfare.network.message; + +import com.atsuishio.superbwarfare.ModUtils; +import io.netty.buffer.ByteBuf; +import net.minecraft.client.Minecraft; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.world.entity.Entity; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import org.jetbrains.annotations.NotNull; + +public record ClientMotionSyncMessage(int id, float x, float y, float z) implements CustomPacketPayload { + + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ModUtils.loc("breath")); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.INT, + ClientMotionSyncMessage::id, + ByteBufCodecs.FLOAT, + ClientMotionSyncMessage::x, + ByteBufCodecs.FLOAT, + ClientMotionSyncMessage::y, + ByteBufCodecs.FLOAT, + ClientMotionSyncMessage::z, + ClientMotionSyncMessage::new + ); + + public ClientMotionSyncMessage(Entity entity) { + this(entity.getId(), (float) entity.getDeltaMovement().x, (float) entity.getDeltaMovement().y, (float) entity.getDeltaMovement().z); + } + + + public static void handler(final ClientMotionSyncMessage message, final IPayloadContext context) { + var level = Minecraft.getInstance().level; + if (level == null) return; + Entity entity = level.getEntity(message.id); + if (entity != null) { + entity.lerpMotion(message.x, message.y, message.z); + } + } + + @Override + public @NotNull Type type() { + return TYPE; + } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/tools/CustomExplosion.java b/src/main/java/com/atsuishio/superbwarfare/tools/CustomExplosion.java new file mode 100644 index 000000000..0e2d63d96 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/tools/CustomExplosion.java @@ -0,0 +1,193 @@ +package com.atsuishio.superbwarfare.tools; + +import com.atsuishio.superbwarfare.config.server.ExplosionConfig; +import com.atsuishio.superbwarfare.network.message.ShakeClientMessage; +import com.google.common.collect.Sets; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.item.PrimedTnt; +import net.minecraft.world.entity.monster.Monster; +import net.minecraft.world.level.Explosion; +import net.minecraft.world.level.ExplosionDamageCalculator; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.gameevent.GameEvent; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.event.EventHooks; +import net.neoforged.neoforge.network.PacketDistributor; + +import javax.annotation.Nullable; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +public class CustomExplosion extends Explosion { + + private final Level level; + private final double x; + private final double y; + private final double z; + @Nullable + private final Entity source; + private final float radius; + private final DamageSource damageSource; + private final ExplosionDamageCalculator damageCalculator; + private final float damage; + private int fireTime; + private float damageMultiplier; + + public CustomExplosion(Level pLevel, @Nullable Entity pSource, @Nullable DamageSource source, + @Nullable ExplosionDamageCalculator pDamageCalculator, + float damage, double pToBlowX, double pToBlowY, double pToBlowZ, float pRadius, + BlockInteraction pBlockInteraction) { + + // TODO what are these options? + super(pLevel, pSource, source, null, pToBlowX, pToBlowY, pToBlowZ, pRadius, false, pBlockInteraction, null, null, null); + // super(pLevel, pSource, source, null, pToBlowX, pToBlowY, pToBlowZ, pRadius, false, pBlockInteraction +// , ?,?,? ); + + this.level = pLevel; + this.source = pSource; + this.radius = pRadius; + this.damageSource = source == null ? pLevel.damageSources().explosion(this) : source; + this.damageCalculator = pDamageCalculator == null ? new ExplosionDamageCalculator() : pDamageCalculator; + this.x = pToBlowX; + this.y = pToBlowY; + this.z = pToBlowZ; + this.damage = damage; + } + + public CustomExplosion(Level pLevel, @Nullable Entity pSource, float damage, double pToBlowX, double pToBlowY, double pToBlowZ, float pRadius, BlockInteraction pBlockInteraction) { + this(pLevel, pSource, null, null, damage, pToBlowX, pToBlowY, pToBlowZ, pRadius, pBlockInteraction); + } + + public CustomExplosion(Level pLevel, @Nullable Entity pSource, @Nullable DamageSource source, float damage, double pToBlowX, double pToBlowY, double pToBlowZ, float pRadius, BlockInteraction pBlockInteraction) { + this(pLevel, pSource, source, null, damage, pToBlowX, pToBlowY, pToBlowZ, pRadius, pBlockInteraction); + final Vec3 center = new Vec3(pToBlowX, pToBlowY, pToBlowZ); + for (Entity target : level.getEntitiesOfClass(Entity.class, new AABB(center, center).inflate(4 * radius), e -> true).stream().sorted(Comparator.comparingDouble(e -> e.distanceToSqr(center))).toList()) { + if (target instanceof ServerPlayer serverPlayer) { + PacketDistributor.sendToPlayer(serverPlayer, new ShakeClientMessage(20 + 0.02 * damage, 3 * pRadius, 50 + 0.05 * damage, pToBlowX, pToBlowY, pToBlowZ)); + } + } + } + + public CustomExplosion(Level pLevel, @Nullable Entity pSource, @Nullable DamageSource source, float damage, double pToBlowX, double pToBlowY, double pToBlowZ, float pRadius) { + this(pLevel, pSource, source, null, damage, pToBlowX, pToBlowY, pToBlowZ, pRadius, BlockInteraction.KEEP); + + final Vec3 center = new Vec3(pToBlowX, pToBlowY, pToBlowZ); + for (Entity target : level.getEntitiesOfClass(Entity.class, new AABB(center, center).inflate(radius), e -> true).stream().sorted(Comparator.comparingDouble(e -> e.distanceToSqr(center))).toList()) { + if (target instanceof ServerPlayer serverPlayer && !(target == pSource && pSource.getVehicle() != null)) { + PacketDistributor.sendToPlayer(serverPlayer, new ShakeClientMessage(20 + 0.02 * damage, pRadius, 10 + 0.03 * damage, pToBlowX, pToBlowY, pToBlowZ)); + } + } + } + + public CustomExplosion setFireTime(int fireTime) { + this.fireTime = fireTime; + return this; + } + + public CustomExplosion setDamageMultiplier(float damageMultiplier) { + this.damageMultiplier = damageMultiplier; + return this; + } + + public CustomExplosion bulletExplode() { + return this; + } + + @Override + public void explode() { + this.level.gameEvent(this.source, GameEvent.EXPLODE, new Vec3(this.x, this.y, this.z)); + Set set = Sets.newHashSet(); + + for (int j = 0; j < 16; ++j) { + for (int k = 0; k < 16; ++k) { + for (int l = 0; l < 16; ++l) { + if (j == 0 || j == 15 || k == 0 || k == 15 || l == 0 || l == 15) { + double d0 = (float) j / 15.0F * 2.0F - 1.0F; + double d1 = (float) k / 15.0F * 2.0F - 1.0F; + double d2 = (float) l / 15.0F * 2.0F - 1.0F; + double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2); + d0 /= d3; + d1 /= d3; + d2 /= d3; + float f = this.radius * (0.4F + this.level.random.nextFloat() * 0.3F); + double d4 = this.x; + double d6 = this.y; + double d8 = this.z; + + for (; f > 0.0F; f -= 0.22500001F) { + BlockPos blockpos = BlockPos.containing(d4, d6, d8); + BlockState blockstate = this.level.getBlockState(blockpos); + FluidState fluidstate = this.level.getFluidState(blockpos); + if (!this.level.isInWorldBounds(blockpos)) { + break; + } + + Optional optional = this.damageCalculator.getBlockExplosionResistance(this, this.level, blockpos, blockstate, fluidstate); + if (optional.isPresent()) { + f -= (optional.get() + 1F) * 0.3F; + } + + if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockpos, blockstate, f)) { + set.add(blockpos); + } + + d4 += d0 * (double) 0.3F; + d6 += d1 * (double) 0.3F; + d8 += d2 * (double) 0.3F; + } + } + } + } + } + + this.getToBlow().addAll(set); + + float diameter = this.radius * 2.0F; + int x0 = Mth.floor(this.x - (double) diameter - 1.0D); + int x1 = Mth.floor(this.x + (double) diameter + 1.0D); + int y0 = Mth.floor(this.y - (double) diameter - 1.0D); + int y1 = Mth.floor(this.y + (double) diameter + 1.0D); + int z0 = Mth.floor(this.z - (double) diameter - 1.0D); + int z1 = Mth.floor(this.z + (double) diameter + 1.0D); + List list = this.level.getEntities(this.source, new AABB(x0, y0, z0, x1, y1, z1)); + EventHooks.onExplosionDetonate(this.level, this, list, diameter); + Vec3 position = new Vec3(this.x, this.y, this.z); + + for (Entity entity : list) { + if (!entity.ignoreExplosion(this)) { + double distanceRate = Math.sqrt(entity.distanceToSqr(position)) / (double) diameter; + if (distanceRate <= 1.0D) { + double xDistance = entity.getX() - this.x; + double yDistance = (entity instanceof PrimedTnt ? entity.getY() : entity.getEyeY()) - this.y; + double zDistance = entity.getZ() - this.z; + double distance = Math.sqrt(xDistance * xDistance + yDistance * yDistance + zDistance * zDistance); + + if (distance != 0.0D) { + double seenPercent = Mth.clamp(getSeenPercent(position, entity), 0.01 * ExplosionConfig.EXPLOSION_PENETRATION_RATIO.get(), Double.POSITIVE_INFINITY); + double damagePercent = (1.0D - distanceRate) * seenPercent; + double damageFinal = (damagePercent * damagePercent + damagePercent) / 2.0D * damage; + + if (entity instanceof Monster monster) { + monster.hurt(this.damageSource, (float) damageFinal * (1 + 0.2f * this.damageMultiplier)); + } else { + entity.hurt(this.damageSource, (float) damageFinal); + } + + if (fireTime > 0) { + entity.setRemainingFireTicks(fireTime); + } + } + } + } + } + } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/tools/ExtendedEntityRayTraceResult.java b/src/main/java/com/atsuishio/superbwarfare/tools/ExtendedEntityRayTraceResult.java new file mode 100644 index 000000000..2b20fe358 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/tools/ExtendedEntityRayTraceResult.java @@ -0,0 +1,26 @@ +package com.atsuishio.superbwarfare.tools; + +import com.atsuishio.superbwarfare.entity.projectile.ProjectileEntity; +import net.minecraft.world.phys.EntityHitResult; + +/** + * Author: MrCrayFish + */ +public class ExtendedEntityRayTraceResult extends EntityHitResult { + private final boolean headshot; + private final boolean legShot; + + public ExtendedEntityRayTraceResult(ProjectileEntity.EntityResult result) { + super(result.getEntity(), result.getHitPos()); + this.headshot = result.isHeadshot(); + this.legShot = result.isLegShot(); + } + + public boolean isHeadshot() { + return this.headshot; + } + + public boolean isLegShot() { + return this.legShot; + } +}