package net.minecraft.server; import com.google.common.base.Predicate; import com.google.common.collect.Lists; import com.koloboke.collect.map.hash.HashObjObjMaps; import com.koloboke.collect.set.hash.HashObjSets; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import javax.annotation.Nullable; // CraftBukkit start import org.bukkit.craftbukkit.event.CraftEventFactory; import org.bukkit.event.entity.EntityExplodeEvent; import org.torch.util.random.LightRandom; import org.bukkit.Location; import org.bukkit.event.block.BlockExplodeEvent; // CraftBukkit end public class Explosion { private final boolean a; private final boolean b; private final Random c = new LightRandom(); private final World world; private final double posX; private final double posY; private final double posZ; public final Entity source; private final float size; /** A list of ChunkPositions of blocks affected by this explosion */ private final Set<BlockPosition> blocks = HashObjSets.newMutableSet(); // Torch - ArrayList -> HashSet /** playerKnockbackMap */ private final Map<EntityHuman, Vec3D> k = HashObjObjMaps.newMutableMap(); public boolean wasCanceled = false; // CraftBukkit - add field public Explosion(World world, Entity entity, double d0, double d1, double d2, float f, boolean flag, boolean flag1) { this.world = world; this.source = entity; this.size = (float) Math.max(f, 0.0); // CraftBukkit - clamp bad values this.posX = d0; this.posY = d1; this.posZ = d2; this.a = flag; this.b = flag1; } /** * <b>PAIL: destoryBlocks</b> * <p> * Does the first part of the explosion (destroy blocks) */ public void a() { if (this.size < 0.1F) return; // CraftBukkit for (int k = 0; k < 16; ++k) { for (int i = 0; i < 16; ++i) { for (int j = 0; j < 16; ++j) { if (k == 0 || k == 15 || i == 0 || i == 15 || j == 0 || j == 15) { double d0 = k / 15.0F * 2.0F - 1.0F; double d1 = i / 15.0F * 2.0F - 1.0F; double d2 = j / 15.0F * 2.0F - 1.0F; double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2); d0 /= d3; d1 /= d3; d2 /= d3; double d4 = this.posX; double d5 = this.posY; double d6 = this.posZ; float rate = this.size * (0.7F + this.world.random.nextFloat() * 0.6F); for (; rate > 0.0F; rate -= 0.22500001F) { BlockPosition position = new BlockPosition(d4, d5, d6); IBlockData iblockdata = this.world.getType(position); if (iblockdata.getMaterial() != Material.AIR) { float f2 = this.source != null ? this.source.a(this, this.world, position, iblockdata) : iblockdata.getBlock().a((Entity) null); rate -= (f2 + 0.3F) * 0.3F; } // CraftBukkit - don't wrap explosions if (rate > 0.0F && (this.source == null || this.source.a(this, this.world, position, iblockdata, rate)) && position.getY() < 256 && position.getY() >= 0) { blocks.add(position); } d4 += d0 * 0.30000001192092896D; d5 += d1 * 0.30000001192092896D; d6 += d2 * 0.30000001192092896D; } } } } } float expandSize = this.size * 2.0F; int maxX = MathHelper.floor(this.posX - expandSize - 1.0D); int minX = MathHelper.floor(this.posX + expandSize + 1.0D); int maxY = MathHelper.floor(this.posY - expandSize - 1.0D); int minY = MathHelper.floor(this.posY + expandSize + 1.0D); int minZ = MathHelper.floor(this.posZ - expandSize - 1.0D); int maxZ = MathHelper.floor(this.posZ + expandSize + 1.0D); // Paper start - Fix lag from explosions processing dead entities List<Entity> living = this.world.getEntities(this.source, new AxisAlignedBB(maxX, maxY, minZ, minX, minY, maxZ), new Predicate<Entity>() { @Override public boolean apply(Entity entity) { return IEntitySelector.d.apply(entity) && !entity.dead; } }); // Paper end Vec3D vec3D = new Vec3D(this.posX, this.posY, this.posZ); for (int index = 0, size = living.size(); index < size; index++) { Entity entity = living.get(index); if (entity.bt()) continue; // PAIL: isImmuneToExplosions() double d7 = entity.e(this.posX, this.posY, this.posZ) / expandSize; // PAIL: e -> getDistance if (d7 <= 1.0D) { double offsetX = entity.locX - this.posX; double offsetY = entity.locY + entity.getHeadHeight() - this.posY; double offsetZ = entity.locZ - this.posZ; double norm = MathHelper.sqrt(offsetX * offsetX + offsetY * offsetY + offsetZ * offsetZ); if (norm != 0.0D) { offsetX /= norm; offsetY /= norm; offsetZ /= norm; double blockDensity = this.getBlockDensity(vec3D, entity.getBoundingBox()); // Paper - Optimize explosions double d13 = (1.0D - d7) * blockDensity; // CraftBukkit start CraftEventFactory.entityDamage = source; entity.forceExplosionKnockback = false; boolean wasDamaged = entity.damageEntity(DamageSource.explosion(this), ((int) ((d13 * d13 + d13) / 2.0D * 7.0D * expandSize + 1.0D))); CraftEventFactory.entityDamage = null; if (!wasDamaged && !(entity instanceof EntityTNTPrimed || entity instanceof EntityFallingBlock) && !entity.forceExplosionKnockback) { continue; } // CraftBukkit end double damageReduction = d13; if (entity instanceof EntityLiving) { damageReduction = entity instanceof EntityHuman && world.paperConfig.disableExplosionKnockback ? 0 : EnchantmentProtection.a((EntityLiving) entity, d13); // Paper - Disable explosion knockback } // Paper start - Fix cannons /* entity.motX += d8 * d14; entity.motY += d9 * d14; entity.motZ += d10 * d14; */ // This impulse method sets the dirty flag, so clients will get an immediate velocity update entity.addVelocity(offsetX * damageReduction, offsetY * damageReduction, offsetZ * damageReduction); // Paper end if (entity instanceof EntityHuman) { EntityHuman entityhuman = (EntityHuman) entity; if (!entityhuman.isSpectator() && (!entityhuman.z() && !world.paperConfig.disableExplosionKnockback || !entityhuman.abilities.isFlying)) { // Paper - Disable explosion knockback this.k.put(entityhuman, new Vec3D(offsetX * d13, offsetY * d13, offsetZ * d13)); } } } } } } /** * <b>PAIL: applyPhysics</b> * <p> * Does the second part of the explosion (sound, particles, drop spawn) */ public void a(boolean spawnParticles) { // PAIL: world.a -> world.playSound this.world.a(null, this.posX, this.posY, this.posZ, SoundEffects.bP, SoundCategory.BLOCKS, 4.0F, (1.0F + (this.world.random.nextFloat() - this.world.random.nextFloat()) * 0.2F) * 0.7F); if (this.size >= 2.0F && this.b) { // PAIL: b -> isSmoking this.world.addParticle(EnumParticle.EXPLOSION_HUGE, this.posX, this.posY, this.posZ, 1.0D, 0.0D, 0.0D, new int[0]); } else { this.world.addParticle(EnumParticle.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 1.0D, 0.0D, 0.0D, new int[0]); } if (this.b) { // PAIL: isSmoking // CraftBukkit start org.bukkit.World bukkitWorld = this.world.getWorld(); org.bukkit.entity.Entity explode = this.source == null ? null : this.source.getBukkitEntity(); Location location = new Location(bukkitWorld, this.posX, this.posY, this.posZ); List<org.bukkit.block.Block> blockList = Lists.newArrayList(); for (BlockPosition position : this.blocks) { org.bukkit.block.Block bukkitBlock = bukkitWorld.getBlockAt(position.getX(), position.getY(), position.getZ()); if (bukkitBlock.getType() != org.bukkit.Material.AIR) { blockList.add(bukkitBlock); } } boolean cancelled; List<org.bukkit.block.Block> bukkitBlocks; float yield; if (explode != null) { EntityExplodeEvent event = new EntityExplodeEvent(explode, location, blockList, 1.0F / this.size); this.world.getServer().getPluginManager().callEvent(event); cancelled = event.isCancelled(); bukkitBlocks = event.blockList(); yield = event.getYield(); } else { BlockExplodeEvent event = new BlockExplodeEvent(location.getBlock(), blockList, 1.0F / this.size); this.world.getServer().getPluginManager().callEvent(event); cancelled = event.isCancelled(); bukkitBlocks = event.blockList(); yield = event.getYield(); } if (cancelled) { this.wasCanceled = true; return; } this.blocks.clear(); for (org.bukkit.block.Block bukkitBlock : bukkitBlocks) { BlockPosition coords = new BlockPosition(bukkitBlock.getX(), bukkitBlock.getY(), bukkitBlock.getZ()); blocks.add(coords); // CraftBukkit end // Torch start - moved up from underside, in case duplicate loop Block block = this.world.getType(coords).getBlock(); if (spawnParticles) { double randomX = coords.getX() + this.world.random.nextFloat(); double randomY = coords.getY() + this.world.random.nextFloat(); double randomZ = coords.getZ() + this.world.random.nextFloat(); double d3 = randomX - this.posX; double d4 = randomY - this.posY; double d5 = randomZ - this.posZ; double d6 = MathHelper.sqrt(d3 * d3 + d4 * d4 + d5 * d5); d3 /= d6; d4 /= d6; d5 /= d6; double d7 = 0.5D / (d6 / this.size + 0.1D); d7 *= this.world.random.nextFloat() * this.world.random.nextFloat() + 0.3F; d3 *= d7; d4 *= d7; d5 *= d7; this.world.addParticle(EnumParticle.EXPLOSION_NORMAL, (randomX + this.posX) / 2.0D, (randomY + this.posY) / 2.0D, (randomZ + this.posZ) / 2.0D, d3, d4, d5, new int[0]); this.world.addParticle(EnumParticle.SMOKE_NORMAL, randomX, randomY, randomZ, d3, d4, d5, new int[0]); } if (block.material != Material.AIR) { if (block.a(this)) { // CraftBukkit - add yield block.dropNaturally(this.world, coords, this.world.getType(coords), yield, 0); } this.world.setTypeAndData(coords, Blocks.AIR.getBlockData(), 3); block.wasExploded(this.world, coords, this); } if (this.a) { // Torch - Copied from underside, PAIL: isFlaming if (this.world.getType(coords).getMaterial() == Material.AIR && this.world.getType(coords.down()).b() && this.c.nextInt(3) == 0) { if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.world, coords.getX(), coords.getY(), coords.getZ(), this).isCancelled()) { this.world.setTypeUpdate(coords, Blocks.FIRE.getBlockData()); } } } // Torch end } } else if (this.a) { // PAIL: isFlaming // Flaming only, the concurrent way has been moved up for (BlockPosition position : this.blocks) { if (this.world.getType(position).getMaterial() == Material.AIR && this.world.getType(position.down()).b() && this.c.nextInt(3) == 0) { // CraftBukkit start - Ignition by explosion if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.world, position.getX(), position.getY(), position.getZ(), this).isCancelled()) { this.world.setTypeUpdate(position, Blocks.FIRE.getBlockData()); } // CraftBukkit end } } } } public Map<EntityHuman, Vec3D> b() { return this.k; } @Nullable public EntityLiving getSource() { // CraftBukkit start - obtain Fireball shooter for explosion tracking return this.source == null ? null : (this.source instanceof EntityTNTPrimed ? ((EntityTNTPrimed) this.source).getSource() : (this.source instanceof EntityLiving ? (EntityLiving) this.source : (this.source instanceof EntityFireball ? ((EntityFireball) this.source).shooter : null))); // CraftBukkit end } public void clearBlocks() { this.blocks.clear(); } public Set<BlockPosition> getBlocks() { // Torch return this.blocks; } // Paper start - Optimize explosions private float getBlockDensity(Vec3D vec3d, AxisAlignedBB aabb) { if (!this.world.paperConfig.optimizeExplosions) { return this.world.a(vec3d, aabb); } CacheKey key = new CacheKey(this, aabb); Float blockDensity = this.world.explosionDensityCache.get(key); if (blockDensity == null) { blockDensity = this.world.a(vec3d, aabb); this.world.explosionDensityCache.put(key, blockDensity); } return blockDensity; } static final class CacheKey { private final World world; private final double posX, posY, posZ; private final double minX, minY, minZ; private final double maxX, maxY, maxZ; public CacheKey(Explosion explosion, AxisAlignedBB aabb) { this.world = explosion.world; this.posX = explosion.posX; this.posY = explosion.posY; this.posZ = explosion.posZ; this.minX = aabb.a; this.minY = aabb.b; this.minZ = aabb.c; this.maxX = aabb.d; this.maxY = aabb.e; this.maxZ = aabb.f; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CacheKey cacheKey = (CacheKey) o; if (Double.compare(cacheKey.posX, posX) != 0) return false; if (Double.compare(cacheKey.posY, posY) != 0) return false; if (Double.compare(cacheKey.posZ, posZ) != 0) return false; if (Double.compare(cacheKey.minX, minX) != 0) return false; if (Double.compare(cacheKey.minY, minY) != 0) return false; if (Double.compare(cacheKey.minZ, minZ) != 0) return false; if (Double.compare(cacheKey.maxX, maxX) != 0) return false; if (Double.compare(cacheKey.maxY, maxY) != 0) return false; if (Double.compare(cacheKey.maxZ, maxZ) != 0) return false; return world.equals(cacheKey.world); } @Override public int hashCode() { int result; long temp; result = world.hashCode(); temp = Double.doubleToLongBits(posX); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(posY); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(posZ); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(minX); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(minY); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(minZ); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(maxX); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(maxY); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(maxZ); result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } } // Paper end }