package ihl.explosion; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.Set; import ihl.IHLMod; import ihl.utils.IHLUtils; import net.minecraft.block.Block; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.network.play.server.S26PacketMapChunkBulk; import net.minecraft.util.DamageSource; import net.minecraft.world.ChunkCoordIntPair; import net.minecraft.world.Explosion; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.world.ExplosionEvent; public class ExplosionVectorBlockV2 { final Set startVectors = new HashSet(); public final int[][] directionMasks = new int[8][3]; public final int bits = IHLMod.config.explosionVectorSizeBits; private final int maxValue = (1 << bits) - 1; public final int halfValue = (1 << bits - 1) - 1; private final int maxArraySize = 1 << bits * 3; // public final int[][] vectors = new int[maxArraySize][2]; public final int[][] vectors = new int[maxArraySize][0]; private final Set chunksToUpdate = new HashSet(64); private final Map cachedDrops = new HashMap(128); final Map blastWaveByDimensionId = new HashMap(); private final Map cachedEntities = new HashMap(); private final Random random = new Random(); public ExplosionVectorBlockV2() { this.precalculateExplosion(); startVectors.add(0); directionMasks[0] = new int[] { 1, 1, 1 }; directionMasks[1] = new int[] { -1, 1, 1 }; directionMasks[2] = new int[] { 1, -1, 1 }; directionMasks[3] = new int[] { 1, 1, -1 }; directionMasks[4] = new int[] { -1, -1, 1 }; directionMasks[5] = new int[] { 1, -1, -1 }; directionMasks[6] = new int[] { -1, 1, -1 }; directionMasks[7] = new int[] { -1, -1, -1 }; } public int encodeXYZ(int x, int y, int z) { return x << bits * 2 | y << bits | z; } public int[] decodeXYZ(int l) { return new int[] { l >> bits * 2, l >> bits & maxValue, l & maxValue }; } public void precalculateExplosion() { for (int levelRadius = 1; levelRadius <= this.maxValue; levelRadius++) for (int ix = 0; ix <= levelRadius; ix++) for (int iy = 0; iy <= levelRadius; iy++) { for (int iz = (ix == levelRadius || iy == levelRadius) ? 0 : levelRadius; iz <= levelRadius; iz++) { int vxyz = encodeXYZ(ix, iy, iz); int[] prevXYZ = new int[] { ix, iy, iz }; reduceCoordinate(prevXYZ, levelRadius); int pvxyz = encodeXYZ(prevXYZ[0], prevXYZ[1], prevXYZ[2]); findFreeSpace(pvxyz, vxyz); } } } private void findFreeSpace(int pvxyz, int vxyz) { int[] nV = new int[vectors[pvxyz].length+1]; for(int i=0;i=pxyz[0] && y>=pxyz[1] && z>=pxyz[2]){ x-=rx; y-=ry; z-=rz; } pxyz[0]=x<0?0:(int)x; pxyz[1]=y<0?0:(int)y; pxyz[2]=z<0?0:(int)z; } public void breakBlocksAndGetDescendants(World world, int sourceX, int sourceY, int sourceZ, Explosion explosion, int ev, int power, int[] directionMask) { power = this.getNewPowerAndProcessBlocks(world, ev, sourceX, sourceY, sourceZ, explosion, power, directionMask); power = (int) (power * 0.94) - 1; if (power > 1) { if (this.vectors[ev].length==0) { int[] xyz = decodeXYZ(ev); int xb = xyz[0] >> bits - 1; int yb = xyz[1] >> bits - 1; int zb = xyz[2] >> bits - 1; int hashb = xb << 2 | yb << 1 | zb; xyz[0] -= xb * halfValue; xyz[1] -= yb * halfValue; xyz[2] -= zb * halfValue; if (hashb == 0 || xb > 1 || yb > 1 || zb > 1) { throw new ArithmeticException("End vectors shall be higher than half value"); } int ev2 = encodeXYZ(xyz[0], xyz[1], xyz[2]); breakBlocksAndGetDescendants(world, sourceX + xb * halfValue * directionMask[0], sourceY + yb * halfValue * directionMask[1], sourceZ + zb * halfValue * directionMask[2], explosion, ev2, power, directionMask); } else { for (int d1 : this.vectors[ev]) { breakBlocksAndGetDescendants(world, sourceX, sourceY, sourceZ, explosion, d1, power, directionMask); } } } } private int getNewPowerAndProcessBlocks(World world, int ev, int sourceX, int sourceY, int sourceZ, Explosion explosion, int power2, int[] directionMask) { int power1 = power2; int[] xyz = decodeXYZ(ev); int absX = xyz[0] * directionMask[0] + sourceX; int absY = xyz[1] * directionMask[1] + sourceY; int absZ = xyz[2] * directionMask[2] + sourceZ; if (absY < 0 || absY >= 256) { return 0; } int absEBSX = absX >> 4; int absEBSZ = absZ >> 4; if (world.getChunkProvider().chunkExists(absEBSX, absEBSZ)) { if (world.getChunkProvider().provideChunk(absEBSX, absEBSZ).getTopFilledSegment() + 24 >= absY) { int remainingPower = this.tryDestroyBlock(world, absX, absY, absZ, sourceX, sourceY, sourceZ, power1, explosion); return remainingPower; } else { return 0; } } else { WorldSavedDataBlastWave blastWave = null; int dimensionId = world.provider.dimensionId; if (this.blastWaveByDimensionId.containsKey(dimensionId)) { blastWave = this.blastWaveByDimensionId.get(dimensionId); } else { blastWave = new WorldSavedDataBlastWave("blastWave"); this.blastWaveByDimensionId.put(dimensionId, blastWave); } long chunkXZKey = ChunkCoordIntPair.chunkXZ2Int(absEBSX, absEBSZ); blastWave.scheduleExplosionEffectsOnChunkLoad(chunkXZKey, ev, sourceX, sourceY, sourceZ, power1, directionMask); return 0; } } public int tryDestroyBlock(World world, int absX, int absY, int absZ, int sourceX, int sourceY, int sourceZ, int power, Explosion explosion) { Chunk chunk = world.getChunkProvider().provideChunk(absX >> 4, absZ >> 4); ExtendedBlockStorage ebs = this.getEBS(chunk, absX, absY, absZ); if (ebs == null) { return power; } else { Block block = ebs.getBlockByExtId(absX & 15, absY & 15, absZ & 15); if (block.getBlockHardness(world, absX, absY, absZ) < 0) { return 0; } else if (absX == sourceX && absY == sourceY && absZ == sourceZ) { int array_index = (absY & 15) << 8 | (absZ & 15) << 4 | (absX & 15); if (ebs.getBlockLSBArray()[array_index] != 0 && ebs.getBlockMSBArray() != null && ebs.getBlockMSBArray().get(absX & 15, absY & 15, absZ & 15) != 0) { ebs.blockRefCount--; } ebs.getBlockLSBArray()[array_index] = 0; if (ebs.getBlockMSBArray() != null) { ebs.getBlockMSBArray().set(absX & 15, absY & 15, absZ & 15, 0); } return power; } else { int remainingPower = power - (int) (block.getExplosionResistance(null, world, absX, absY, absZ, sourceX, sourceY, sourceZ) * 10f + 0.5f); if (remainingPower >= 0) { int array_index = (absY & 15) << 8 | (absZ & 15) << 4 | (absX & 15); if (ebs.getBlockLSBArray()[array_index] != 0 && ebs.getBlockMSBArray() != null && ebs.getBlockMSBArray().get(absX & 15, absY & 15, absZ & 15) != 0) { ebs.blockRefCount--; } ebs.getBlockLSBArray()[array_index] = 0; if (ebs.getBlockMSBArray() != null) { ebs.getBlockMSBArray().set(absX & 15, absY & 15, absZ & 15, 0); } List dropsList = block.getDrops(world, absX, absY, absZ, ebs.getExtBlockMetadata(absX & 15, absY & 15, absZ & 15), 0); Iterator drops = dropsList.iterator(); while (drops.hasNext()) { ItemStack drop = drops.next(); int key = Item.getIdFromItem(drop.getItem()) ^ (drop.getItemDamage() << 16); if (this.cachedDrops.containsKey(key)) { this.cachedDrops.get(key).stackSize += drop.stackSize; } else { this.cachedDrops.put(key, drop); } } Entity[] entities = this.getFromOrCreateCache(world, ebs, absX, absY, absZ); if (entities != null && entities[array_index] != null) { entities[array_index].attackEntityFrom(DamageSource.setExplosionSource(explosion), power / 10f); } } else { block.onNeighborBlockChange(world, absX, absY, absZ, block); if (random.nextInt(8) == 0) { if ((++absY & 15) != 0) { int array_index = (absY & 15) << 8 | (absZ & 15) << 4 | (absX & 15); if (ebs.getBlockLSBArray()[array_index] == 0 && (ebs.getBlockMSBArray() == null || ebs.getBlockMSBArray().get(absX & 15, absY & 15, absZ & 15) == 0)) { this.placeDrops(world, absX, absY, absZ); } } } } return remainingPower; } } } public Entity[] getFromOrCreateCache(World world, ExtendedBlockStorage ebs, int absX, int absY, int absZ) { Entity[] entities = this.cachedEntities.get(ebs); if (entities == null) { Chunk chunk = world.getChunkProvider().provideChunk(absX >> 4, absZ >> 4); List eList = this.getEntityList(chunk, absX, absY, absZ); if (eList != null && !eList.isEmpty()) { entities = new Entity[4096]; Iterator eListI = eList.iterator(); this.cachedEntities.put(ebs, entities); while (eListI.hasNext()) { Entity entity = eListI.next(); int entityX = (int) entity.boundingBox.minX; int entityY = (int) entity.boundingBox.minY; int entityZ = (int) entity.boundingBox.minZ; int rx = entityX & 15; int ry = entityY & 15; int rz = entityZ & 15; int array_index = ry << 8 | rz << 4 | rx; entities[array_index] = entity; } } } return entities; } public ExtendedBlockStorage getEBS(Chunk chunk, int absX, int absY, int absZ) { ExtendedBlockStorage[] ebsA = chunk.getBlockStorageArray(); ExtendedBlockStorage ebs = ebsA[absY >> 4]; if (ebs != null) { this.chunksToUpdate.add(chunk); } return ebs; } @SuppressWarnings("unchecked") public List getEntityList(Chunk chunk, int absX, int absY, int absZ) { return chunk.entityLists[absY >> 4]; } private void placeDrops(World world, int x, int y, int z) { Iterator> di = this.cachedDrops.entrySet().iterator(); if (di.hasNext()) { Entry cde = di.next(); while (di.hasNext()) { cde = di.next(); } ItemStack stack = cde.getValue(); if (stack != null && stack.getItem() != null && stack.stackSize > 0) { if (stack.stackSize <= stack.getMaxStackSize()) { if (stack.stackSize > 0) { PileTileEntity pte = new PileTileEntity(); pte.xCoord = x; pte.yCoord = y; pte.zCoord = z; pte.setWorldObj(world); pte.validate(); pte.setContent(stack); IHLUtils.setBlockAndTileEntityRaw(world, x, y, z, PileBlock.instance, pte); } di.remove(); } else { ItemStack stack1 = stack.copy(); stack1.stackSize = stack.getMaxStackSize(); PileTileEntity pte = new PileTileEntity(); pte.content = stack1; IHLUtils.setBlockAndTileEntityRaw(world, x, y, z, PileBlock.instance, pte); stack.stackSize -= stack.getMaxStackSize(); } } } } public void sendChunkUpdateToPlayersInExplosionAffectedZone(World world, int sourceX, int sourceY, int sourceZ) { Iterator ci = this.chunksToUpdate.iterator(); while (ci.hasNext()) { Chunk chunk = ci.next(); chunk.generateSkylightMap(); Arrays.fill(chunk.updateSkylightColumns, true); chunk.func_150804_b(false); } List chunks = new ArrayList(); chunks.addAll(this.chunksToUpdate); for (Object player : world.playerEntities) { if (player instanceof EntityPlayerMP) { EntityPlayerMP playerMP = (EntityPlayerMP) player; playerMP.playerNetServerHandler.sendPacket(new S26PacketMapChunkBulk(chunks)); } } this.chunksToUpdate.clear(); } public void doExplosion(World world, int sourceX, int sourceY, int sourceZ, final Set startVectors1, int startPower, int[] directionMask, Explosion explosion) { for (int sv : startVectors1) { this.breakBlocksAndGetDescendants(world, sourceX - (directionMask[0] < 0 ? 1 : 0), sourceY - (directionMask[1] < 0 ? 1 : 0), sourceZ - (directionMask[2] < 0 ? 1 : 0), explosion, sv, startPower, directionMask); } // Free and clean resources this.cachedEntities.clear(); } public void doExplosion(World world, int sourceX, int sourceY, int sourceZ, final Set startVectors1, int startPower) { IHLMod.log.info("Starting explosion server"); Explosion explosion = new Explosion(world, null, sourceX, sourceY, sourceZ, 100f); if (!MinecraftForge.EVENT_BUS.post(new ExplosionEvent.Start(world, explosion))) { for (int[] directionMask : directionMasks) { this.doExplosion(world, sourceX, sourceY, sourceZ, startVectors1, startPower, directionMask, explosion); } sendChunkUpdateToPlayersInExplosionAffectedZone(world, sourceX, sourceY, sourceZ); for (Entry entry : this.cachedDrops.entrySet()) { IHLEntityFallingPile fallingPile = new IHLEntityFallingPile(world); fallingPile.setPosition(sourceX + 0.5d, sourceY + 0.5d, sourceZ + 0.5d); fallingPile.setEntityItemStack(entry.getValue()); fallingPile.setVelocity(random.nextDouble() - 0.5d, random.nextDouble() * 2, random.nextDouble() - 0.5d); world.spawnEntityInWorld(fallingPile); } this.cachedDrops.clear(); } } }