/* * This file is part of Applied Energistics 2. * Copyright (c) 2013 - 2015, AlgorithmX2, All rights reserved. * * Applied Energistics 2 is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Applied Energistics 2 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Applied Energistics 2. If not, see . */ package appeng.spatial; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.math.BlockPos; import net.minecraft.world.NextTickListEntry; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import appeng.api.AEApi; import appeng.api.definitions.IBlockDefinition; import appeng.api.movable.IMovableHandler; import appeng.api.movable.IMovableRegistry; import appeng.api.util.AEPartLocation; import appeng.api.util.WorldCoord; import appeng.core.AELog; import appeng.core.worlddata.WorldData; import appeng.util.Platform; public class CachedPlane { private final int x_size; private final int z_size; private final int cx_size; private final int cz_size; private final int x_offset; private final int y_offset; private final int z_offset; private final int y_size; private final Chunk[][] myChunks; private final Column[][] myColumns; private final LinkedList tiles = new LinkedList(); private final LinkedList ticks = new LinkedList(); private final World world; private final IMovableRegistry reg = AEApi.instance().registries().movable(); private final LinkedList updates = new LinkedList(); private final IBlockDefinition matrixFrame = AEApi.instance().definitions().blocks().matrixFrame(); private int verticalBits; private final IBlockState matrixBlockState; public CachedPlane( final World w, final int minX, final int minY, final int minZ, final int maxX, final int maxY, final int maxZ ) { Block matrixFrameBlock = AEApi.instance().definitions().blocks().matrixFrame().maybeBlock().orElse( null ); if( matrixFrameBlock != null ) { this.matrixBlockState = matrixFrameBlock.getDefaultState(); } else { this.matrixBlockState = null; } this.world = w; this.x_size = maxX - minX + 1; this.y_size = maxY - minY + 1; this.z_size = maxZ - minZ + 1; this.x_offset = minX; this.y_offset = minY; this.z_offset = minZ; final int minCX = minX >> 4; final int minCY = minY >> 4; final int minCZ = minZ >> 4; final int maxCX = maxX >> 4; final int maxCY = maxY >> 4; final int maxCZ = maxZ >> 4; this.cx_size = maxCX - minCX + 1; final int cy_size = maxCY - minCY + 1; this.cz_size = maxCZ - minCZ + 1; this.myChunks = new Chunk[this.cx_size][this.cz_size]; this.myColumns = new Column[this.x_size][this.z_size]; this.verticalBits = 0; for( int cy = 0; cy < cy_size; cy++ ) { this.verticalBits |= 1 << ( minCY + cy ); } for( int x = 0; x < this.x_size; x++ ) { for( int z = 0; z < this.z_size; z++ ) { this.myColumns[x][z] = new Column( w.getChunkFromChunkCoords( ( minX + x ) >> 4, ( minZ + z ) >> 4 ), ( minX + x ) & 0xF, ( minZ + z ) & 0xF, minCY, cy_size ); } } final IMovableRegistry mr = AEApi.instance().registries().movable(); for( int cx = 0; cx < this.cx_size; cx++ ) { for( int cz = 0; cz < this.cz_size; cz++ ) { final LinkedList> rawTiles = new LinkedList>(); final LinkedList deadTiles = new LinkedList(); final Chunk c = w.getChunkFromChunkCoords( minCX + cx, minCZ + cz ); this.myChunks[cx][cz] = c; rawTiles.addAll( ( (HashMap) c.getTileEntityMap() ).entrySet() ); for( final Entry tx : rawTiles ) { final BlockPos cp = tx.getKey(); final TileEntity te = tx.getValue(); final BlockPos tePOS = te.getPos(); if( tePOS.getX() >= minX && tePOS.getX() <= maxX && tePOS.getY() >= minY && tePOS.getY() <= maxY && tePOS.getZ() >= minZ && tePOS.getZ() <= maxZ ) { if( mr.askToMove( te ) ) { this.tiles.add( te ); deadTiles.add( cp ); } else { final Object[] details = this.myColumns[tePOS.getX() - minX][tePOS.getZ() - minZ].getDetails( tePOS.getY() ); final Block blk = (Block) details[0]; // don't skip air, just let the code replace it... if( blk != null && blk.isAir( c.getWorld().getBlockState( tePOS ), c.getWorld(), tePOS ) && blk.isReplaceable( c.getWorld(), tePOS ) ) { c.getWorld().setBlockToAir( tePOS ); } else { this.myColumns[tePOS.getX() - minX][tePOS.getZ() - minZ].setSkip( tePOS.getY() ); } } } } for( final BlockPos cp : deadTiles ) { c.getTileEntityMap().remove( cp ); } final long k = this.getWorld().getTotalWorldTime(); final List list = this.getWorld().getPendingBlockUpdates( c, false ); if( list != null ) { for( final Object o : list ) { final NextTickListEntry entry = (NextTickListEntry) o; final BlockPos tePOS = entry.position; if( tePOS.getX() >= minX && tePOS.getX() <= maxX && tePOS.getY() >= minY && tePOS.getY() <= maxY && tePOS.getZ() >= minZ && tePOS.getZ() <= maxZ ) { final NextTickListEntry newEntry = new NextTickListEntry( tePOS, entry.getBlock() ); newEntry.scheduledTime = entry.scheduledTime - k; this.ticks.add( newEntry ); } } } } } for( final TileEntity te : this.tiles ) { try { this.getWorld().loadedTileEntityList.remove( te ); } catch( final Exception e ) { AELog.debug( e ); } } } private IMovableHandler getHandler( final TileEntity te ) { final IMovableRegistry mr = AEApi.instance().registries().movable(); return mr.getHandler( te ); } void swap( final CachedPlane dst ) { final IMovableRegistry mr = AEApi.instance().registries().movable(); if( dst.x_size == this.x_size && dst.y_size == this.y_size && dst.z_size == this.z_size ) { AELog.info( "Block Copy Scale: " + this.x_size + ", " + this.y_size + ", " + this.z_size ); long startTime = System.nanoTime(); for( int x = 0; x < this.x_size; x++ ) { for( int z = 0; z < this.z_size; z++ ) { final Column a = this.myColumns[x][z]; final Column b = dst.myColumns[x][z]; for( int y = 0; y < this.y_size; y++ ) { final int src_y = y + this.y_offset; final int dst_y = y + dst.y_offset; if( a.doNotSkip( src_y ) && b.doNotSkip( dst_y ) ) { final Object[] aD = a.getDetails( src_y ); final Object[] bD = b.getDetails( dst_y ); a.setBlockIDWithMetadata( src_y, bD ); b.setBlockIDWithMetadata( dst_y, aD ); } else { this.markForUpdate( x + this.x_offset, src_y, z + this.z_offset ); dst.markForUpdate( x + dst.x_offset, dst_y, z + dst.z_offset ); } } } } long endTime = System.nanoTime(); long duration = endTime - startTime; AELog.info( "Block Copy Time: " + duration ); for( final TileEntity te : this.tiles ) { final BlockPos tePOS = te.getPos(); dst.addTile( tePOS.getX() - this.x_offset, tePOS.getY() - this.y_offset, tePOS.getZ() - this.z_offset, te, this, mr ); } for( final TileEntity te : dst.tiles ) { final BlockPos tePOS = te.getPos(); this.addTile( tePOS.getX() - dst.x_offset, tePOS.getY() - dst.y_offset, tePOS.getZ() - dst.z_offset, te, dst, mr ); } for( final NextTickListEntry entry : this.ticks ) { final BlockPos tePOS = entry.position; dst.addTick( tePOS.getX() - this.x_offset, tePOS.getY() - this.y_offset, tePOS.getZ() - this.z_offset, entry ); } for( final NextTickListEntry entry : dst.ticks ) { final BlockPos tePOS = entry.position; this.addTick( tePOS.getX() - dst.x_offset, tePOS.getY() - dst.y_offset, tePOS.getZ() - dst.z_offset, entry ); } startTime = System.nanoTime(); this.updateChunks(); dst.updateChunks(); endTime = System.nanoTime(); duration = endTime - startTime; AELog.info( "Update Time: " + duration ); } } private void markForUpdate( final int x, final int y, final int z ) { this.updates.add( new WorldCoord( x, y, z ) ); for( final AEPartLocation d : AEPartLocation.SIDE_LOCATIONS ) { this.updates.add( new WorldCoord( x + d.xOffset, y + d.yOffset, z + d.zOffset ) ); } } private void addTick( final int x, final int y, final int z, final NextTickListEntry entry ) { this.world.scheduleUpdate( new BlockPos( x + this.x_offset, y + this.y_offset, z + this.z_offset ), entry.getBlock(), (int) entry.scheduledTime ); } private void addTile( final int x, final int y, final int z, final TileEntity te, final CachedPlane alternateDestination, final IMovableRegistry mr ) { try { final Column c = this.myColumns[x][z]; if( c.doNotSkip( y + this.y_offset ) || alternateDestination == null ) { final IMovableHandler handler = this.getHandler( te ); try { handler.moveTile( te, this.world, new BlockPos( x + this.x_offset, y + this.y_offset, z + this.z_offset ) ); } catch( final Throwable e ) { AELog.debug( e ); final BlockPos pos = new BlockPos( x, y, z ); // attempt recovery... te.setWorldObj( this.world ); te.setPos( pos ); c.c.addTileEntity( new BlockPos( c.x, y + y, c.z ), te ); // c.c.setChunkTileEntity( c.x, y + y, c.z, te ); if( c.c.isLoaded() ) { this.world.addTileEntity( te ); this.world.notifyBlockUpdate( pos, this.world.getBlockState( pos ), this.world.getBlockState( pos ), z ); } } mr.doneMoving( te ); } else { alternateDestination.addTile( x, y, z, te, null, mr ); } } catch( final Throwable e ) { AELog.debug( e ); } } private void updateChunks() { // update shit.. for( int x = 0; x < this.cx_size; x++ ) { for( int z = 0; z < this.cz_size; z++ ) { final Chunk c = this.myChunks[x][z]; c.resetRelightChecks(); c.generateSkylightMap(); c.setModified( true ); } } // send shit... for( int x = 0; x < this.cx_size; x++ ) { for( int z = 0; z < this.cz_size; z++ ) { final Chunk c = this.myChunks[x][z]; for( int y = 1; y < 255; y += 32 ) { WorldData.instance().compassData().service().updateArea( this.getWorld(), c.xPosition << 4, y, c.zPosition << 4 ); } Platform.sendChunk( c, this.verticalBits ); } } } LinkedList getUpdates() { return this.updates; } World getWorld() { return this.world; } private class Column { private final int x; private final int z; private final Chunk c; private final Object[] ch = { 0, 0 }; private final ExtendedBlockStorage[] storage; private List skipThese = null; public Column( final Chunk chunk, final int x, final int z, final int chunkY, final int chunkHeight ) { this.x = x; this.z = z; this.c = chunk; this.storage = this.c.getBlockStorageArray(); // make sure storage exists before hand... for( int ay = 0; ay < chunkHeight; ay++ ) { final int by = ( ay + chunkY ); ExtendedBlockStorage extendedblockstorage = this.storage[by]; if( extendedblockstorage == null ) { extendedblockstorage = this.storage[by] = new ExtendedBlockStorage( by << 4, !this.c.getWorld().provider.getHasNoSky() ); } } } private void setBlockIDWithMetadata( final int y, final Object[] blk ) { if( blk[0] == matrixBlockState ) { blk[0] = Platform.AIR_BLOCK.getDefaultState(); } final ExtendedBlockStorage extendedBlockStorage = this.storage[y >> 4]; extendedBlockStorage.set( this.x, y & 15, this.z, (IBlockState) blk[0] ); // extendedBlockStorage.setExtBlockID( x, y & 15, z, blk[0] ); extendedBlockStorage.setExtBlocklightValue( this.x, y & 15, this.z, (Integer) blk[1] ); } private Object[] getDetails( final int y ) { final ExtendedBlockStorage extendedblockstorage = this.storage[y >> 4]; this.ch[0] = extendedblockstorage.get( this.x, y & 15, this.z ); this.ch[1] = extendedblockstorage.getExtBlocklightValue( this.x, y & 15, this.z ); return this.ch; } private boolean doNotSkip( final int y ) { final ExtendedBlockStorage extendedblockstorage = this.storage[y >> 4]; if( CachedPlane.this.reg.isBlacklisted( extendedblockstorage.get( this.x, y & 15, this.z ).getBlock() ) ) { return false; } return this.skipThese == null || !this.skipThese.contains( y ); } private void setSkip( final int yCoord ) { if( this.skipThese == null ) { this.skipThese = new LinkedList(); } this.skipThese.add( yCoord ); } } }