package net.minecraft.world.level; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.util.Mth; import net.minecraft.util.profiling.Profiler; import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.world.damagesource.DamageSource; 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.item.ItemEntity; import net.minecraft.world.entity.item.PrimedTnt; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.ClipContext.Block; import net.minecraft.world.level.ClipContext.Fluid; import net.minecraft.world.level.Explosion.BlockInteraction; import net.minecraft.world.level.block.BaseFireBlock; 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.minecraft.world.phys.HitResult.Type; import org.jetbrains.annotations.Nullable; public class ServerExplosion implements Explosion { private static final ExplosionDamageCalculator EXPLOSION_DAMAGE_CALCULATOR = new ExplosionDamageCalculator(); private static final int MAX_DROPS_PER_COMBINED_STACK = 16; private static final float LARGE_EXPLOSION_RADIUS = 2.0F; private final boolean fire; private final BlockInteraction blockInteraction; private final ServerLevel level; private final Vec3 center; @Nullable private final Entity source; private final float radius; private final DamageSource damageSource; private final ExplosionDamageCalculator damageCalculator; private final Map hitPlayers = new HashMap(); public ServerExplosion( ServerLevel level, @Nullable Entity source, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator damageCalculator, Vec3 center, float radius, boolean fire, BlockInteraction blockInteraction ) { this.level = level; this.source = source; this.radius = radius; this.center = center; this.fire = fire; this.blockInteraction = blockInteraction; this.damageSource = damageSource == null ? level.damageSources().explosion(this) : damageSource; this.damageCalculator = damageCalculator == null ? this.makeDamageCalculator(source) : damageCalculator; } private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) { return (ExplosionDamageCalculator)(entity == null ? EXPLOSION_DAMAGE_CALCULATOR : new EntityBasedExplosionDamageCalculator(entity)); } public static float getSeenPercent(Vec3 explosionVector, Entity entity) { AABB aABB = entity.getBoundingBox(); double d = 1.0 / ((aABB.maxX - aABB.minX) * 2.0 + 1.0); double e = 1.0 / ((aABB.maxY - aABB.minY) * 2.0 + 1.0); double f = 1.0 / ((aABB.maxZ - aABB.minZ) * 2.0 + 1.0); double g = (1.0 - Math.floor(1.0 / d) * d) / 2.0; double h = (1.0 - Math.floor(1.0 / f) * f) / 2.0; if (!(d < 0.0) && !(e < 0.0) && !(f < 0.0)) { int i = 0; int j = 0; for (double k = 0.0; k <= 1.0; k += d) { for (double l = 0.0; l <= 1.0; l += e) { for (double m = 0.0; m <= 1.0; m += f) { double n = Mth.lerp(k, aABB.minX, aABB.maxX); double o = Mth.lerp(l, aABB.minY, aABB.maxY); double p = Mth.lerp(m, aABB.minZ, aABB.maxZ); Vec3 vec3 = new Vec3(n + g, o, p + h); if (entity.level().clip(new ClipContext(vec3, explosionVector, Block.COLLIDER, Fluid.NONE, entity)).getType() == Type.MISS) { i++; } j++; } } } return (float)i / j; } else { return 0.0F; } } @Override public float radius() { return this.radius; } @Override public Vec3 center() { return this.center; } private List calculateExplodedPositions() { Set set = new HashSet(); int i = 16; 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 d = j / 15.0F * 2.0F - 1.0F; double e = k / 15.0F * 2.0F - 1.0F; double f = l / 15.0F * 2.0F - 1.0F; double g = Math.sqrt(d * d + e * e + f * f); d /= g; e /= g; f /= g; float h = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F); double m = this.center.x; double n = this.center.y; double o = this.center.z; for (float p = 0.3F; h > 0.0F; h -= 0.22500001F) { BlockPos blockPos = BlockPos.containing(m, n, o); 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()) { h -= (optional.get() + 0.3F) * 0.3F; } if (h > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockPos, blockState, h)) { set.add(blockPos); } m += d * 0.3F; n += e * 0.3F; o += f * 0.3F; } } } } } return new ObjectArrayList<>(set); } private void hurtEntities() { float f = this.radius * 2.0F; int i = Mth.floor(this.center.x - f - 1.0); int j = Mth.floor(this.center.x + f + 1.0); int k = Mth.floor(this.center.y - f - 1.0); int l = Mth.floor(this.center.y + f + 1.0); int m = Mth.floor(this.center.z - f - 1.0); int n = Mth.floor(this.center.z + f + 1.0); for (Entity entity : this.level.getEntities(this.source, new AABB(i, k, m, j, l, n))) { if (!entity.ignoreExplosion(this)) { double d = Math.sqrt(entity.distanceToSqr(this.center)) / f; if (d <= 1.0) { double e = entity.getX() - this.center.x; double g = (entity instanceof PrimedTnt ? entity.getY() : entity.getEyeY()) - this.center.y; double h = entity.getZ() - this.center.z; double o = Math.sqrt(e * e + g * g + h * h); if (o != 0.0) { e /= o; g /= o; h /= o; boolean bl = this.damageCalculator.shouldDamageEntity(this, entity); float p = this.damageCalculator.getKnockbackMultiplier(entity); float q = !bl && p == 0.0F ? 0.0F : getSeenPercent(this.center, entity); if (bl) { entity.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, q)); } double r = (1.0 - d) * q * p; double s; if (entity instanceof LivingEntity livingEntity) { s = r * (1.0 - livingEntity.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE)); } else { s = r; } e *= s; g *= s; h *= s; Vec3 vec3 = new Vec3(e, g, h); entity.push(vec3); if (entity instanceof Player player && !player.isSpectator() && (!player.isCreative() || !player.getAbilities().flying)) { this.hitPlayers.put(player, vec3); } entity.onExplosionHit(this.source); } } } } } private void interactWithBlocks(List blocks) { List list = new ArrayList(); Util.shuffle(blocks, this.level.random); for (BlockPos blockPos : blocks) { this.level.getBlockState(blockPos).onExplosionHit(this.level, blockPos, this, (itemStack, blockPosx) -> addOrAppendStack(list, itemStack, blockPosx)); } for (ServerExplosion.StackCollector stackCollector : list) { net.minecraft.world.level.block.Block.popResource(this.level, stackCollector.pos, stackCollector.stack); } } private void createFire(List blocks) { for (BlockPos blockPos : blocks) { if (this.level.random.nextInt(3) == 0 && this.level.getBlockState(blockPos).isAir() && this.level.getBlockState(blockPos.below()).isSolidRender()) { this.level.setBlockAndUpdate(blockPos, BaseFireBlock.getState(this.level, blockPos)); } } } public void explode() { this.level.gameEvent(this.source, GameEvent.EXPLODE, this.center); List list = this.calculateExplodedPositions(); this.hurtEntities(); if (this.interactsWithBlocks()) { ProfilerFiller profilerFiller = Profiler.get(); profilerFiller.push("explosion_blocks"); this.interactWithBlocks(list); profilerFiller.pop(); } if (this.fire) { this.createFire(list); } } private static void addOrAppendStack(List stackCollectors, ItemStack stack, BlockPos pos) { for (ServerExplosion.StackCollector stackCollector : stackCollectors) { stackCollector.tryMerge(stack); if (stack.isEmpty()) { return; } } stackCollectors.add(new ServerExplosion.StackCollector(pos, stack)); } private boolean interactsWithBlocks() { return this.blockInteraction != BlockInteraction.KEEP; } public Map getHitPlayers() { return this.hitPlayers; } @Override public ServerLevel level() { return this.level; } @Nullable @Override public LivingEntity getIndirectSourceEntity() { return Explosion.getIndirectSourceEntity(this.source); } @Nullable @Override public Entity getDirectSourceEntity() { return this.source; } public DamageSource getDamageSource() { return this.damageSource; } @Override public BlockInteraction getBlockInteraction() { return this.blockInteraction; } @Override public boolean canTriggerBlocks() { if (this.blockInteraction != BlockInteraction.TRIGGER_BLOCK) { return false; } else { return this.source != null && this.source.getType() == EntityType.BREEZE_WIND_CHARGE ? this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) : true; } } @Override public boolean shouldAffectBlocklikeEntities() { boolean bl = this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); boolean bl2 = this.source == null || !this.source.isInWater(); boolean bl3 = this.source == null || this.source.getType() != EntityType.BREEZE_WIND_CHARGE && this.source.getType() != EntityType.WIND_CHARGE; return bl ? bl2 && bl3 : this.blockInteraction.shouldAffectBlocklikeEntities() && bl2 && bl3; } public boolean isSmall() { return this.radius < 2.0F || !this.interactsWithBlocks(); } static class StackCollector { final BlockPos pos; ItemStack stack; StackCollector(BlockPos pos, ItemStack stack) { this.pos = pos; this.stack = stack; } public void tryMerge(ItemStack stack) { if (ItemEntity.areMergable(this.stack, stack)) { this.stack = ItemEntity.merge(this.stack, stack, 16); } } } }