package com.rwtema.funkylocomotion.movers; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import org.apache.commons.lang3.Validate; import com.rwtema.funkylocomotion.api.IMoveFactory; import com.rwtema.funkylocomotion.blocks.BlockMoving; import com.rwtema.funkylocomotion.blocks.TileMovingServer; import com.rwtema.funkylocomotion.description.Describer; import com.rwtema.funkylocomotion.factory.FactoryRegistry; import com.rwtema.funkylocomotion.helper.BlockHelper; import com.rwtema.funkylocomotion.helper.BlockStates; import com.rwtema.funkylocomotion.network.FLNetwork; import com.rwtema.funkylocomotion.network.MessageClearTile; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReportCategory; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.inventory.IInventory; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.play.server.SPacketBlockChange; import net.minecraft.network.play.server.SPacketUpdateTileEntity; import net.minecraft.server.management.PlayerChunkMapEntry; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.ReportedException; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.world.NextTickListEntry; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; public class MoveManager { public static final NBTTagCompound airBlockTag; public static final NBTTagCompound airDescTag; final static LinkedHashMap<String, Object> vars = new LinkedHashMap<>(10, 0.2F); private static final Object BLANK = new Object(); static { airBlockTag = new NBTTagCompound(); airDescTag = new NBTTagCompound(); if (Block.getIdFromBlock(Blocks.AIR) != 0) airDescTag.setInteger("Block", Block.getIdFromBlock(Blocks.AIR)); } public static void startMoving(World world, List<BlockPos> posList, EnumFacing dir, int maxTime) { ArrayList<BlockLink> links = new ArrayList<>(posList.size()); for (BlockPos blockPos : posList) { links.add(new BlockLink(blockPos, blockPos.offset(dir))); } startMoving(world, world, links, dir, maxTime); } public synchronized static void startMoving(World srcWorld, World dstWorld, List<BlockLink> links, @Nullable EnumFacing dir, int maxTime) { String section = "Start"; clearVars(); try { vars.put("srcWorld", srcWorld); vars.put("dstWorld", dstWorld); vars.put("links", links); vars.put("dir", dir); vars.put("maxTime", maxTime); if (dir != null && srcWorld != dstWorld) { throw new IllegalArgumentException("Trying to regular shift between worlds"); } HashSet<BlockPos> srcBlocks = new HashSet<>(); HashSet<BlockPos> dstBlocks = new HashSet<>(); vars.put("srcBlocks", srcBlocks); vars.put("dstBlocks", dstBlocks); for (BlockLink link : links) { srcBlocks.add(link.srcPos); dstBlocks.add(link.dstPos); } HashSet<BlockPos> dstBlocksToBeDestroyed = new HashSet<>(); HashSet<BlockPos> srcBlocksToBecomeAir = new HashSet<>(); vars.put("dstBlocksToBeDestroyed", dstBlocksToBeDestroyed); vars.put("srcBlocksToBecomeAir", srcBlocksToBecomeAir); srcBlocksToBecomeAir.addAll(srcBlocks); dstBlocksToBeDestroyed.addAll(dstBlocks); if (srcWorld == dstWorld) { srcBlocksToBecomeAir.removeAll(dstBlocks); dstBlocksToBeDestroyed.removeAll(srcBlocks); } Map<BlockPos, Entry> dstTileEntries = new HashMap<>(); vars.put("dstTileEntries", dstTileEntries); section = "BreakBlockWithDrop"; for (BlockPos pos : dstBlocksToBeDestroyed) { BlockHelper.breakBlockWithDrop(dstWorld, pos); } Set<Chunk> srcChunks = new HashSet<>(); // HashSet<EntityPlayer> srcWatchingPlayers = new HashSet<>(); HashSet<Object> inventories = new HashSet<>(); vars.put("srcChunks", srcChunks); // vars.put("srcWatchingPlayers", srcWatchingPlayers); vars.put("inventories", inventories); section = "Read Data"; for (BlockLink link : links) { vars.put("Iterator", link); BlockPos dstPos = link.dstPos; BlockPos srcPos = link.srcPos; Entry e = new Entry(dstPos); IBlockState state = srcWorld.getBlockState(srcPos); e.block = state.getBlock(); e.meta = e.block.getMetaFromState(state); e.lightopacity = state.getLightOpacity(srcWorld, srcPos); e.lightlevel = state.getLightValue(srcWorld, srcPos); List<AxisAlignedBB> axes = new ArrayList<>(); state.addCollisionBoxToList(srcWorld, srcPos, TileEntity.INFINITE_EXTENT_AABB, axes, null, false); if (axes.size() > 0) { e.bb = new ArrayList<>(); for (AxisAlignedBB bb : axes) { e.bb.add(new AxisAlignedBB( bb.minX - srcPos.getX(), bb.minY - srcPos.getY(), bb.minZ - srcPos.getZ(), bb.maxX - srcPos.getX(), bb.maxY - srcPos.getY(), bb.maxZ - srcPos.getZ() )); } } NBTTagCompound descriptor = new NBTTagCompound(); descriptor.setInteger("Block", Block.getIdFromBlock(e.block)); if (e.meta != 0) descriptor.setByte("Meta", (byte) e.meta); TileEntity tile = srcWorld.getTileEntity(srcPos); if (tile != null) { Describer.addDescriptionToTags(descriptor, tile); if (tile instanceof IInventory) { inventories.add(tile); } } e.description = descriptor; srcChunks.add(srcWorld.getChunkFromBlockCoords(srcPos)); dstTileEntries.put(dstPos, e); } vars.put("Iterator", BLANK); section = "LoadTicks"; for (Chunk chunk : srcChunks) { vars.put("Iterator", chunk); List<NextTickListEntry> ticks = srcWorld.getPendingBlockUpdates(chunk, false); if (ticks != null) { long k = srcWorld.getTotalWorldTime(); for (NextTickListEntry tick : ticks) { vars.put("Iterator2", tick); BlockPos pos = tick.position; if (chunk.getBlockState(pos).getBlock() != tick.getBlock()) continue; if (dir != null) pos = pos.offset(dir); if (!dstTileEntries.containsKey(pos)) continue; Entry e = dstTileEntries.get(pos); e.scheduledTickTime = (int) (tick.scheduledTime - k); e.scheduledTickPriority = tick.priority; } } // PlayerChunkMapEntry chunkWatcher = FLNetwork.getChunkWatcher(chunk, srcWorld); // if (chunkWatcher != null) { // srcWatchingPlayers.addAll(chunkWatcher.addPlayer();); // } } vars.put("Iterator", BLANK); vars.put("Iterator2", BLANK); // from now on - NO BLOCK UPDATES section = "destroyBlock"; for (BlockLink link : links) { vars.put("Iterator", link); IMoveFactory factory = FactoryRegistry.getFactory(srcWorld, link.srcPos); dstTileEntries.get(link.dstPos).blockTag = factory.destroyBlock(srcWorld, link.srcPos); } vars.put("Iterator", BLANK); // let there be updates; // section = "closeInventories"; // for (EntityPlayer watchingPlayer : srcWatchingPlayers) { // vars.put("Iterator", watchingPlayer); // if (watchingPlayer.openContainer != watchingPlayer.inventoryContainer && watchingPlayer.openContainer != null) { // for (Object o : watchingPlayer.openContainer.inventorySlots) { // Slot s = (Slot) o; // if (inventories.contains(s.inventory)) { // watchingPlayer.closeScreen(); // break; // } // } // } // } vars.put("Iterator", BLANK); section = "clearTilesSilent"; for (BlockLink link : links) { vars.put("Iterator", link); BlockPos dstPos = link.dstPos; BlockPos srcPos = link.srcPos; BlockHelper.silentClear(dstWorld.getChunkFromBlockCoords(dstPos), dstPos); // if (dir != null) FLNetwork.sendToAllWatchingChunk(srcWorld, srcPos, new MessageClearTile(srcPos)); dstWorld.removeTileEntity(dstPos); } vars.put("Iterator", BLANK); section = "postUpdateBlock"; for (BlockPos pos : srcBlocksToBecomeAir) { vars.put("Iterator", pos); BlockHelper.postUpdateBlock(dstWorld, pos); } vars.put("Iterator", BLANK); ArrayList<TileMovingServer> tiles = new ArrayList<>(); vars.put("tiles", tiles); section = "createTiles"; for (Entry e : dstTileEntries.values()) { vars.put("Iterator", e); dstWorld.setBlockState(e.pos, BlockMoving.instance.getDefaultState(), 1); TileMovingServer tile = (TileMovingServer) Validate.notNull(dstWorld.getTileEntity(e.pos)); vars.put("Iterator2", tile); tile.block = e.blockTag; tile.desc = e.description; tile.dir = dir == null ? 6 : dir.ordinal(); tile.maxTime = maxTime; tile.lightLevel = e.lightlevel; tile.lightOpacity = e.lightopacity; tile.scheduledTickTime = e.scheduledTickTime; tile.scheduledTickPriority = e.scheduledTickPriority; if (e.bb != null) tile.collisions = e.bb.toArray(new AxisAlignedBB[e.bb.size()]); tile.isAir = false; tiles.add(tile); } vars.put("Iterator", BLANK); vars.put("Iterator2", BLANK); section = "createBlankTiles"; for (BlockLink link : links) { vars.put("Iterator", BLANK); BlockPos srcPos = link.srcPos; if (srcWorld != dstWorld || !dstTileEntries.containsKey(srcPos)) { srcWorld.setBlockState(srcPos, BlockMoving.instance.getDefaultState(), 1); TileMovingServer tile = (TileMovingServer) Validate.notNull(srcWorld.getTileEntity(srcPos)); vars.put("Iterator2", tile); if (dir != null) { tile.block = airBlockTag.copy(); tile.desc = airDescTag.copy(); tile.dir = dir.ordinal(); tile.maxTime = maxTime; tile.lightLevel = 0; tile.lightOpacity = 0; tile.isAir = true; } else { FLNetwork.sendToAllWatchingChunk(srcWorld, srcPos, new MessageClearTile(srcPos)); Entry e = dstTileEntries.get(link.dstPos); tile.block = airBlockTag.copy(); if (e.description != null) tile.desc = e.description.copy(); else tile.desc = airDescTag.copy(); tile.dir = 7; tile.maxTime = maxTime; tile.lightLevel = e.lightlevel; tile.lightOpacity = e.lightopacity; if (e.bb != null) tile.collisions = e.bb.toArray(new AxisAlignedBB[e.bb.size()]); tile.isAir = true; } tiles.add(tile); } } vars.put("Iterator", BLANK); section = "networkUpdateBlocks"; for (TileMovingServer tile : tiles) { vars.put("Iterator", tile); PlayerChunkMapEntry watcher = FLNetwork.getChunkWatcher(tile.getWorld(), tile.getPos()); if (watcher != null) { SPacketBlockChange pkt = new SPacketBlockChange(tile.getWorld(), tile.getPos()); watcher.sendPacket(pkt); } } vars.put("Iterator", BLANK); section = "networkUpdateTile"; for (TileMovingServer tile : tiles) { vars.put("Iterator", tile); PlayerChunkMapEntry watcher = FLNetwork.getChunkWatcher(tile.getWorld(), tile.getPos()); if (watcher != null) { SPacketUpdateTileEntity packet = tile.getUpdatePacket(); vars.put("Iterator2", packet); if (packet != null) watcher.sendPacket(packet); } } vars.put("Iterator", BLANK); vars.put("Iterator2", BLANK); section = "DoneIfWeCrashNowThenWhatTheHell"; clearVars(); } catch (Throwable err) { try { CrashReport crashReport = buildCrashReport(section, err); throw new ReportedException(crashReport); } finally { clearVars(); } } } private static CrashReport buildCrashReport(String section, Throwable err) { CrashReport crashReport = CrashReport.makeCrashReport(err, "FunkyLocomotionMoveCrash"); CrashReportCategory moveCode = crashReport.makeCategory("MoveCode"); moveCode.addCrashSection("Section", "\"" + section + "\""); for (Map.Entry<String, Object> e : vars.entrySet()) { Object value = e.getValue(); if (value != BLANK) moveCode.addCrashSection("var_" + e.getKey(), makeString(value, 0)); } return crashReport; } private static void clearVars() { for (Map.Entry<String, Object> entry : vars.entrySet()) { entry.setValue(BLANK); } } public static void finishMoving() { clearVars(); String section = "start"; try { List<TileMovingServer> tiles = MovingTileRegistry.getTilesFinishedMoving(); if (tiles.isEmpty()) return; HashSet<Chunk> chunks = new HashSet<>(); vars.put("tiles", tiles); vars.put("chunks", tiles); // Clear Tiles section = "Clear Tiles"; for (TileMovingServer tile : tiles) { vars.put("tile", tile); World worldObj = tile.getWorld(); BlockPos pos = tile.getPos(); chunks.add(worldObj.getChunkFromBlockCoords(pos)); worldObj.setBlockState(pos, BlockStates.AIR, 0); worldObj.setBlockState(pos, BlockStates.STONE, 0); } vars.put("tile", BLANK); // Set Block/Tile section = "Set Block/Tile"; for (TileMovingServer tile : tiles) { BlockPos pos = tile.getPos(); vars.put("tile", tile); if (tile.block != null) { BlockHelper.silentClear(tile.getWorld().getChunkFromBlockCoords(pos), pos); Block block = Block.getBlockFromName(tile.block.getString("Block")); vars.put("block", block); if (block == null) block = Blocks.AIR; IMoveFactory factory = FactoryRegistry.getFactory(block); vars.put("factory", factory); factory.recreateBlock(tile.getWorld(), pos, tile.block); } } vars.put("tile", BLANK); vars.put("block", BLANK); vars.put("factory", BLANK); // Update Blocks section = "Update Blocks"; for (TileMovingServer tile : tiles) { vars.put("tile", tile); BlockPos pos = tile.getPos(); BlockHelper.postUpdateBlock(tile.getWorld(), pos); if (tile.scheduledTickTime != -1) { int time = tile.scheduledTickTime - 1; if (time <= 0) { World world = tile.getWorld(); IBlockState state = world.getBlockState(pos); state.getBlock().updateTick(tile.getWorld(), tile.getPos(), state, world.rand); } else { tile.getWorld().scheduleBlockUpdate( tile.getPos(), tile.getWorld().getBlockState(pos).getBlock(), time, tile.scheduledTickPriority); } } } vars.put("tile", BLANK); // Send Update Packets section = "Send Update Packets"; for (Chunk chunk : chunks) { vars.put("chunk", chunk); chunk.setModified(true); FLNetwork.updateChunk(chunk); } vars.put("chunk", BLANK); // Redocached Activation section = "Redo Activation"; for (TileMovingServer tile : tiles) { vars.put("tile", tile); if (tile.activatingPlayer != null) { EntityPlayer player = tile.activatingPlayer.get(); if (player != null) { IBlockState state = tile.getWorld().getBlockState(tile.getPos()); Block b = state.getBlock(); b.onBlockActivated(tile.getWorld(), tile.getPos(), state, player, tile.activatingHand, tile.activatingSide, tile.activatingHitX, tile.activatingHitY, tile.activatingHitZ); } } } vars.put("tile", BLANK); section = "Fin"; clearVars(); } catch (Throwable err) { try { CrashReport crashReport = buildCrashReport(section, err); throw new ReportedException(crashReport); } finally { clearVars(); } } } private static String makeString(Object o, int n) { if (o == null) return "null"; if (o instanceof String) return (String) o; StringBuilder builder = new StringBuilder(); tabs(n, builder); builder.append(o.getClass().getSimpleName()); builder.append("{"); if (o instanceof Block) { String nameForObject = "" + Block.REGISTRY.getNameForObject((Block) o); builder.append(nameForObject); builder.append(","); builder.append(o.toString()); } else if (o instanceof TileEntity) { try { builder.append("tag="); NBTTagCompound tag = new NBTTagCompound(); ((TileEntity) o).writeToNBT(tag); builder.append(tag.toString()); } catch (Exception err) { builder.append("TE WriteToNBT Crash\n"); builder.append(err.toString()); } } else if (o instanceof Collection) { int i = 0; @SuppressWarnings("unchecked") Collection<Object> c = (Collection<Object>) o; Iterator<Object> iterator = c.iterator(); builder.append('\n'); tabs(n, builder); while (iterator.hasNext()) { builder.append("\t\t").append(i).append("="); i++; builder.append(makeString(iterator.next(), n + 1)); builder.append("\n"); tabs(n, builder); } } else { builder.append(o.toString()); } builder.append("}"); return builder.toString(); } private static void tabs(int n, StringBuilder builder) { for (int j = 0; j < n; j++) { builder.append('\t'); } } private static class Entry { final BlockPos pos; public int scheduledTickTime = -1; public int scheduledTickPriority; NBTTagCompound blockTag; NBTTagCompound description; Block block; int meta; List<AxisAlignedBB> bb = null; int lightlevel; int lightopacity; public Entry(BlockPos pos) { this.pos = pos; } @Override public String toString() { return "Entry{" + "scheduledTickTime=" + scheduledTickTime + ", scheduledTickPriority=" + scheduledTickPriority + ", blockTag=" + blockTag + ", description=" + description + ", pos=" + pos + ", block=" + makeString(block, 0) + ", meta=" + meta + ", bb=" + makeString(bb, 0) + ", lightlevel=" + lightlevel + ", lightopacity=" + lightopacity + '}'; } } public static class BlockLink { BlockPos srcPos; BlockPos dstPos; public BlockLink(BlockPos srcPos, BlockPos dstPos) { this.srcPos = srcPos; this.dstPos = dstPos; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BlockLink blockLink = (BlockLink) o; return dstPos.equals(blockLink.dstPos) && srcPos.equals(blockLink.srcPos); } @Override public int hashCode() { int result = srcPos.hashCode(); result = 31 * result + dstPos.hashCode(); return result; } @Override public String toString() { return "BlockLink{" + srcPos.toString() + ", " + dstPos.toString() + '}'; } } }