/* * Decompiled with CFR 0.152. * * Could not load the following classes: * it.unimi.dsi.fastutil.objects.ObjectArrayList * org.jspecify.annotations.Nullable */ 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 net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.EntityTypeTags; import net.minecraft.util.Mth; import net.minecraft.util.Util; 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.entity.projectile.Projectile; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.ClipContext; import net.minecraft.world.level.EntityBasedExplosionDamageCalculator; import net.minecraft.world.level.Explosion; import net.minecraft.world.level.ExplosionDamageCalculator; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.BaseFireBlock; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.gamerules.GameRules; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; import org.jspecify.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 Explosion.BlockInteraction blockInteraction; private final ServerLevel level; private final Vec3 center; private final @Nullable 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, Explosion.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 source) { return source == null ? EXPLOSION_DAMAGE_CALCULATOR : new EntityBasedExplosionDamageCalculator(source); } public static float getSeenPercent(Vec3 center, Entity entity) { AABB bb = entity.getBoundingBox(); double xs = 1.0 / ((bb.maxX - bb.minX) * 2.0 + 1.0); double ys = 1.0 / ((bb.maxY - bb.minY) * 2.0 + 1.0); double zs = 1.0 / ((bb.maxZ - bb.minZ) * 2.0 + 1.0); double xOffset = (1.0 - Math.floor(1.0 / xs) * xs) / 2.0; double zOffset = (1.0 - Math.floor(1.0 / zs) * zs) / 2.0; if (xs < 0.0 || ys < 0.0 || zs < 0.0) { return 0.0f; } int hits = 0; int count = 0; for (double xx = 0.0; xx <= 1.0; xx += xs) { for (double yy = 0.0; yy <= 1.0; yy += ys) { for (double zz = 0.0; zz <= 1.0; zz += zs) { double x = Mth.lerp(xx, bb.minX, bb.maxX); double y = Mth.lerp(yy, bb.minY, bb.maxY); double z = Mth.lerp(zz, bb.minZ, bb.maxZ); Vec3 from = new Vec3(x + xOffset, y, z + zOffset); if (entity.level().clip(new ClipContext(from, center, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, entity)).getType() == HitResult.Type.MISS) { ++hits; } ++count; } } } return (float)hits / (float)count; } @Override public float radius() { return this.radius; } @Override public Vec3 center() { return this.center; } private List calculateExplodedPositions() { HashSet toBlowSet = new HashSet(); int size = 16; for (int xx = 0; xx < 16; ++xx) { for (int yy = 0; yy < 16; ++yy) { block2: for (int zz = 0; zz < 16; ++zz) { if (xx != 0 && xx != 15 && yy != 0 && yy != 15 && zz != 0 && zz != 15) continue; double xd = (float)xx / 15.0f * 2.0f - 1.0f; double yd = (float)yy / 15.0f * 2.0f - 1.0f; double zd = (float)zz / 15.0f * 2.0f - 1.0f; double d = Math.sqrt(xd * xd + yd * yd + zd * zd); xd /= d; yd /= d; zd /= d; double xp = this.center.x; double yp = this.center.y; double zp = this.center.z; float stepSize = 0.3f; for (float remainingPower = this.radius * (0.7f + this.level.random.nextFloat() * 0.6f); remainingPower > 0.0f; remainingPower -= 0.22500001f) { BlockPos pos = BlockPos.containing(xp, yp, zp); BlockState block = this.level.getBlockState(pos); FluidState fluid = this.level.getFluidState(pos); if (!this.level.isInWorldBounds(pos)) continue block2; Optional resistance = this.damageCalculator.getBlockExplosionResistance(this, this.level, pos, block, fluid); if (resistance.isPresent()) { remainingPower -= (resistance.get().floatValue() + 0.3f) * 0.3f; } if (remainingPower > 0.0f && this.damageCalculator.shouldBlockExplode(this, this.level, pos, block, remainingPower)) { toBlowSet.add(pos); } xp += xd * (double)0.3f; yp += yd * (double)0.3f; zp += zd * (double)0.3f; } } } } return new ObjectArrayList(toBlowSet); } private void hurtEntities() { if (this.radius < 1.0E-5f) { return; } float doubleRadius = this.radius * 2.0f; int x0 = Mth.floor(this.center.x - (double)doubleRadius - 1.0); int x1 = Mth.floor(this.center.x + (double)doubleRadius + 1.0); int y0 = Mth.floor(this.center.y - (double)doubleRadius - 1.0); int y1 = Mth.floor(this.center.y + (double)doubleRadius + 1.0); int z0 = Mth.floor(this.center.z - (double)doubleRadius - 1.0); int z1 = Mth.floor(this.center.z + (double)doubleRadius + 1.0); List entities = this.level.getEntities(this.source, new AABB(x0, y0, z0, x1, y1, z1)); for (Entity entity : entities) { Player player; double d; float exposure; double dist; if (entity.ignoreExplosion(this) || (dist = Math.sqrt(entity.distanceToSqr(this.center)) / (double)doubleRadius) > 1.0) continue; Vec3 entityOrigin = entity instanceof PrimedTnt ? entity.position() : entity.getEyePosition(); Vec3 direction = entityOrigin.subtract(this.center).normalize(); boolean shouldDamageEntity = this.damageCalculator.shouldDamageEntity(this, entity); float knockbackMultiplier = this.damageCalculator.getKnockbackMultiplier(entity); float f = exposure = shouldDamageEntity || knockbackMultiplier != 0.0f ? ServerExplosion.getSeenPercent(this.center, entity) : 0.0f; if (shouldDamageEntity) { entity.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, exposure)); } if (entity instanceof LivingEntity) { LivingEntity livingEntity = (LivingEntity)entity; d = livingEntity.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE); } else { d = 0.0; } double knockbackResistance = d; double knockbackPower = (1.0 - dist) * (double)exposure * (double)knockbackMultiplier * (1.0 - knockbackResistance); Vec3 knockback = direction.scale(knockbackPower); entity.push(knockback); if (entity.getType().is(EntityTypeTags.REDIRECTABLE_PROJECTILE) && entity instanceof Projectile) { Projectile projectile = (Projectile)entity; projectile.setOwner(this.damageSource.getEntity()); } else if (!(!(entity instanceof Player) || (player = (Player)entity).isSpectator() || player.isCreative() && player.getAbilities().flying)) { this.hitPlayers.put(player, knockback); } entity.onExplosionHit(this.source); } } private void interactWithBlocks(List targetBlocks) { ArrayList stacks = new ArrayList(); Util.shuffle(targetBlocks, this.level.random); for (BlockPos pos : targetBlocks) { this.level.getBlockState(pos).onExplosionHit(this.level, pos, this, (stack, position) -> ServerExplosion.addOrAppendStack(stacks, stack, position)); } for (StackCollector stack2 : stacks) { Block.popResource((Level)this.level, stack2.pos, stack2.stack); } } private void createFire(List targetBlocks) { for (BlockPos pos : targetBlocks) { if (this.level.random.nextInt(3) != 0 || !this.level.getBlockState(pos).isAir() || !this.level.getBlockState(pos.below()).isSolidRender()) continue; this.level.setBlockAndUpdate(pos, BaseFireBlock.getState(this.level, pos)); } } public int explode() { this.level.gameEvent(this.source, GameEvent.EXPLODE, this.center); List toBlow = this.calculateExplodedPositions(); this.hurtEntities(); if (this.interactsWithBlocks()) { ProfilerFiller profiler = Profiler.get(); profiler.push("explosion_blocks"); this.interactWithBlocks(toBlow); profiler.pop(); } if (this.fire) { this.createFire(toBlow); } return toBlow.size(); } private static void addOrAppendStack(List stacks, ItemStack stack, BlockPos pos) { for (StackCollector stackCollector : stacks) { stackCollector.tryMerge(stack); if (!stack.isEmpty()) continue; return; } stacks.add(new StackCollector(pos, stack)); } private boolean interactsWithBlocks() { return this.blockInteraction != Explosion.BlockInteraction.KEEP; } public Map getHitPlayers() { return this.hitPlayers; } @Override public ServerLevel level() { return this.level; } @Override public @Nullable LivingEntity getIndirectSourceEntity() { return Explosion.getIndirectSourceEntity(this.source); } @Override public @Nullable Entity getDirectSourceEntity() { return this.source; } public DamageSource getDamageSource() { return this.damageSource; } @Override public Explosion.BlockInteraction getBlockInteraction() { return this.blockInteraction; } @Override public boolean canTriggerBlocks() { if (this.blockInteraction != Explosion.BlockInteraction.TRIGGER_BLOCK) { return false; } if (this.source != null && this.source.getType() == EntityType.BREEZE_WIND_CHARGE) { return this.level.getGameRules().get(GameRules.MOB_GRIEFING); } return true; } @Override public boolean shouldAffectBlocklikeEntities() { boolean isNotWindCharge; boolean mobGriefingEnabled = this.level.getGameRules().get(GameRules.MOB_GRIEFING); boolean bl = isNotWindCharge = this.source == null || this.source.getType() != EntityType.BREEZE_WIND_CHARGE && this.source.getType() != EntityType.WIND_CHARGE; if (mobGriefingEnabled) { return isNotWindCharge; } return this.blockInteraction.shouldAffectBlocklikeEntities() && isNotWindCharge; } public boolean isSmall() { return this.radius < 2.0f || !this.interactsWithBlocks(); } private static class StackCollector { private final BlockPos pos; private ItemStack stack; private StackCollector(BlockPos pos, ItemStack stack) { this.pos = pos; this.stack = stack; } public void tryMerge(ItemStack input) { if (ItemEntity.areMergable(this.stack, input)) { this.stack = ItemEntity.merge(this.stack, input, 16); } } } }