package slimeknights.tconstruct.smeltery.tileentity; import com.google.common.collect.ImmutableList; import net.minecraft.block.state.IBlockState; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.NetworkManager; import net.minecraft.network.play.server.SPacketUpdateTileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; import javax.annotation.Nonnull; import slimeknights.mantle.multiblock.IMasterLogic; import slimeknights.mantle.multiblock.IServantLogic; import slimeknights.mantle.tileentity.TileInventory; import slimeknights.tconstruct.library.utils.TagUtil; import slimeknights.tconstruct.smeltery.block.BlockMultiblockController; import slimeknights.tconstruct.smeltery.multiblock.MultiblockDetection; public abstract class TileMultiblock<T extends MultiblockDetection> extends TileInventory implements IMasterLogic { public static final String TAG_ACTIVE = "active"; public static final String TAG_MINPOS = "minPos"; public static final String TAG_MAXPOS = "maxPos"; protected static final int MAX_SIZE = 9; // consistancy by this point. All others do 9x9 protected boolean active; // Info about the structure/multiblock protected MultiblockDetection.MultiblockStructure info; protected T multiblock; /** smallest coordinate INSIDE the multiblock */ protected BlockPos minPos; /** biggest coordinate INSIDE the multiblock */ protected BlockPos maxPos; public TileMultiblock(String name, int inventorySize) { super(name, inventorySize); } public TileMultiblock(String name, int inventorySize, int maxStackSize) { super(name, inventorySize, maxStackSize); } /** Call in the constructor. Set the multiblock */ protected void setMultiblock(T multiblock) { this.multiblock = multiblock; } public BlockPos getMinPos() { return minPos; } public BlockPos getMaxPos() { return maxPos; } /** Called by the servants */ @Override public void notifyChange(IServantLogic servant, BlockPos pos) { checkMultiblockStructure(); } // Checks if the tank is fully built and updates status accordingly public void checkMultiblockStructure() { boolean wasActive = active; IBlockState state = this.getWorld().getBlockState(getPos()); if(!(state.getBlock() instanceof BlockMultiblockController)) { active = false; } else { EnumFacing in = state.getValue(BlockMultiblockController.FACING).getOpposite(); MultiblockDetection.MultiblockStructure structure = multiblock.detectMultiblock(this.getWorld(), this.getPos().offset(in), MAX_SIZE); if(structure == null) { active = false; updateStructureInfoInternal(null); } else { // we found a valid tank. booyah! active = true; MultiblockDetection.assignMultiBlock(this.getWorld(), this.getPos(), structure.blocks); updateStructureInfoInternal(structure); // we still have to update since something caused us to rebuild our stats // might be the tank size changed if(wasActive) { this.getWorld().notifyBlockUpdate(getPos(), state, state, 3); } } } // mark the block for updating so the controller block updates its graphics if(wasActive != active) { this.getWorld().notifyBlockUpdate(getPos(), state, state, 3); this.markDirty(); } } protected final void updateStructureInfoInternal(MultiblockDetection.MultiblockStructure structure) { info = structure; if(structure == null) { structure = new MultiblockDetection.MultiblockStructure(0, 0, 0, ImmutableList.<BlockPos>of(this.pos)); } if(info != null) { minPos = info.minPos.add(1, 1, 1); // add walls and floor maxPos = info.maxPos.add(-1, hasCeiling() ? -1 : 0, -1); // subtract walls, no ceiling } else { minPos = maxPos = this.pos; } updateStructureInfo(structure); } /** if true the maxPos will be adjusted accordingly that the structure has no ceiling */ protected boolean hasCeiling() { return true; } protected abstract void updateStructureInfo(MultiblockDetection.MultiblockStructure structure); public boolean isActive() { return active && (getWorld() == null || getWorld().isRemote || info != null); } public void setInvalid() { this.active = false; updateStructureInfoInternal(null); } @Override public void validate() { super.validate(); // on validation we set active to false so the tank checks anew if it's formed active = false; } @Nonnull @Override public NBTTagCompound writeToNBT(NBTTagCompound compound) { compound = super.writeToNBT(compound); compound.setBoolean(TAG_ACTIVE, active); compound.setTag(TAG_MINPOS, TagUtil.writePos(minPos)); compound.setTag(TAG_MAXPOS, TagUtil.writePos(maxPos)); return compound; } @Override public void readFromNBT(NBTTagCompound compound) { super.readFromNBT(compound); active = compound.getBoolean(TAG_ACTIVE); minPos = TagUtil.readPos(compound.getCompoundTag(TAG_MINPOS)); maxPos = TagUtil.readPos(compound.getCompoundTag(TAG_MAXPOS)); } @Override public SPacketUpdateTileEntity getUpdatePacket() { NBTTagCompound tag = new NBTTagCompound(); this.writeToNBT(tag); return new SPacketUpdateTileEntity(this.getPos(), this.getBlockMetadata(), tag); } @Override public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) { boolean wasActive = active; readFromNBT(pkt.getNbtCompound()); // update chunk (rendering) if the active state changed if(active != wasActive) { IBlockState state = getWorld().getBlockState(getPos()); getWorld().notifyBlockUpdate(getPos(), state, state, 3); } } @Nonnull @Override public NBTTagCompound getUpdateTag() { // new tag instead of super since default implementation calls the super of writeToNBT return writeToNBT(new NBTTagCompound()); } /** Returns true if it's a client world, false if no world or server */ public boolean isClientWorld() { return this.getWorld() != null && this.getWorld().isRemote; } /** Returns true if it's a server world, false if no world or client */ public boolean isServerWorld() { return this.getWorld() != null && !this.getWorld().isRemote; } }