package slimeknights.tconstruct.gadgets; import com.google.common.base.Predicate; import com.google.common.collect.Lists; import gnu.trove.map.hash.TObjectIntHashMap; import gnu.trove.procedure.TObjectIntProcedure; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.init.SoundEvents; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.DamageSource; import net.minecraft.util.EntitySelectors; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.SoundCategory; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.world.Explosion; import net.minecraft.world.World; import net.minecraft.world.WorldServer; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.ForgeEventFactory; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; import org.apache.commons.lang3.tuple.Pair; import java.util.List; import java.util.Random; import javax.annotation.Nullable; import slimeknights.tconstruct.common.TinkerNetwork; import slimeknights.tconstruct.tools.common.network.EntityMovementChangePacket; public class Exploder { public final double r; public final double rr; public final int dist; public final double explosionStrength; public final int blocksPerIteration; public final int x, y, z; public final World world; public final Entity exploder; public final Explosion explosion; protected int currentRadius; private int curX, curY, curZ; protected List<ItemStack> droppedItems; // map containing all items dropped by the explosion and their amounts public Exploder(World world, Explosion explosion, Entity exploder, BlockPos location, double r, double explosionStrength, int blocksPerIteration) { this.r = r; this.world = world; this.explosion = explosion; this.exploder = exploder; this.rr = r * r; this.dist = (int) r + 1; this.explosionStrength = explosionStrength; this.blocksPerIteration = blocksPerIteration; this.currentRadius = 0; this.x = location.getX(); this.y = location.getY(); this.z = location.getZ(); this.curX = 0; this.curY = 0; this.curZ = 0; this.droppedItems = Lists.newArrayList(); } public static void startExplosion(World world, Explosion explosion, Entity entity, BlockPos location, double r, double explosionStrength) { Exploder exploder = new Exploder(world, explosion, entity, location, r, explosionStrength, Math.max(50, (int) (r * r * r / 10d))); exploder.handleEntities(); world.playSound(null, location, SoundEvents.ENTITY_GENERIC_EXPLODE, SoundCategory.BLOCKS, 4.0F, (1.0F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.2F) * 0.7F); MinecraftForge.EVENT_BUS.register(exploder); } @SubscribeEvent public void onTick(TickEvent.WorldTickEvent event) { if(event.world == world && event.phase == TickEvent.Phase.END) { if(!iteration()) { // goodbye world, we're done exploding finish(); } } } void handleEntities() { final Predicate<Entity> predicate = new Predicate<Entity>() { @Override public boolean apply(@Nullable Entity entity) { return entity != null && !entity.isImmuneToExplosions() && EntitySelectors.NOT_SPECTATING.apply(entity) && EntitySelectors.IS_ALIVE.apply(entity) && entity.getPositionVector().squareDistanceTo(x, y, z) <= r * r; } }; // damage and blast back entities List<Entity> list = world.getEntitiesInAABBexcluding(this.exploder, new AxisAlignedBB(x - r - 1, y - r - 1, z - r - 1, x + r + 1, y + r + 1, z + r + 1), predicate ); net.minecraftforge.event.ForgeEventFactory.onExplosionDetonate(world, explosion, list, r * 2); for(Entity entity : list) { //double str = 1f - (double)currentRadius/r; //str *= str; // move it away from the center depending on distance and explosion strength Vec3d dir = entity.getPositionVector().subtract(exploder.getPositionVector().addVector(0, -r / 2, 0)); double str = (r - dir.lengthVector()) / r; str = Math.max(0.3, str); dir = dir.normalize(); dir = dir.scale(explosionStrength * str * 0.3); entity.addVelocity(dir.xCoord, dir.yCoord + 0.5, dir.zCoord); entity.attackEntityFrom(DamageSource.causeExplosionDamage(explosion), (float) (str * explosionStrength)); if(entity instanceof EntityPlayerMP) { TinkerNetwork.sendTo(new EntityMovementChangePacket(entity), (EntityPlayerMP) entity); } } } private void finish() { final int d = (int) r / 2; final BlockPos pos = new BlockPos(x - d, y - d, z - d); final Random random = new Random(); List<ItemStack> aggregatedDrops = Lists.newArrayList(); for(ItemStack drop : droppedItems) { boolean notInList = true; // check if it's already in our list for(ItemStack stack : aggregatedDrops) { if(ItemStack.areItemsEqual(drop, stack) && ItemStack.areItemStackTagsEqual(drop, stack)) { stack.stackSize += drop.stackSize; notInList = false; break; } } if(notInList) { aggregatedDrops.add(drop); } } // actually drop the aggregated items for(ItemStack drop : aggregatedDrops) { int stacksize = drop.stackSize; do { BlockPos spawnPos = pos.add(random.nextInt((int) r), random.nextInt((int) r), random.nextInt((int) r)); ItemStack dropItemstack = drop.copy(); dropItemstack.stackSize = Math.min(stacksize, 64); Block.spawnAsEntity(world, spawnPos, dropItemstack); stacksize -= dropItemstack.stackSize; } while(stacksize > 0); } MinecraftForge.EVENT_BUS.unregister(this); } /** * Explodes away all blocks for the current iteration */ private boolean iteration() { int count = 0; while(count < blocksPerIteration && currentRadius < (int) r + 1) { double d = curX * curX + curY * curY + curZ * curZ; // inside the explosion? if(d <= rr) { BlockPos pos = new BlockPos(x + curX, y + curY, z + curZ); IBlockState state = world.getBlockState(pos); // no air blocks if(!state.getBlock().isAir(state, world, pos)) { // explosion "strength" at the current position double f = explosionStrength * (1f - d / rr); float f2 = exploder != null ? exploder.getExplosionResistance(explosion, world, pos, state) : state.getBlock().getExplosionResistance(world, pos, null, explosion); f -= (f2 + 0.3F) * 0.3F; if(f > 0.0F && (exploder == null || exploder.verifyExplosion(explosion, world, pos, state, (float) f))) { // block should be exploded count++; explodeBlock(state, pos); } } } // get next coordinate; step(); } return count == blocksPerIteration; // can lead to 1 more call where nothing is done, but that's ok } // get the next coordinate private void step() { // we go X/Z plane wise from top to bottom if(++curX > currentRadius) { curX = -currentRadius; if(++curZ > currentRadius) { curZ = -currentRadius; if(--curY < -currentRadius) { currentRadius++; curX = curZ = -currentRadius; curY = currentRadius; } } } // we skip the internals if(curY != -currentRadius && curY != currentRadius) { // we're not in the top or bottom plane if(curZ != -currentRadius && curZ != currentRadius) { // we're not in the X/Y planes of the cube, we can therefore skip the x to the end if we're inside if(curX > -currentRadius) { curX = currentRadius; } } } } private void explodeBlock(IBlockState state, BlockPos pos) { Block block = state.getBlock(); if(!world.isRemote && block.canDropFromExplosion(explosion)) { List<ItemStack> drops = block.getDrops(world, pos, state, 0); ForgeEventFactory.fireBlockHarvesting(drops, world, pos, state, 0, 1f, false, null); droppedItems.addAll(drops); } if(world instanceof WorldServer) { ((WorldServer) world).spawnParticle(EnumParticleTypes.EXPLOSION_NORMAL, true, pos.getX(), pos.getY(), pos.getZ(), 2, 0, 0, 0, 0d); ((WorldServer) world).spawnParticle(EnumParticleTypes.SMOKE_LARGE, true, pos.getX(), pos.getY(), pos.getZ(), 1, 0, 0, 0, 0d); } block.onBlockExploded(world, pos, explosion); } }