/* * This file is part of Applied Energistics 2. * Copyright (c) 2013 - 2014, 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.tile.storage; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.annotation.Nullable; import io.netty.buffer.ByteBuf; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.IInventory; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.EnumFacing; import net.minecraft.util.ITickable; import net.minecraftforge.common.capabilities.Capability; import appeng.api.AEApi; import appeng.api.config.AccessRestriction; import appeng.api.config.Actionable; import appeng.api.config.PowerMultiplier; import appeng.api.config.SecurityPermissions; import appeng.api.config.Settings; import appeng.api.config.SortDir; import appeng.api.config.SortOrder; import appeng.api.config.ViewItems; import appeng.api.implementations.tiles.IColorableTile; import appeng.api.implementations.tiles.IMEChest; import appeng.api.networking.GridFlags; import appeng.api.networking.IGrid; import appeng.api.networking.IGridNode; import appeng.api.networking.energy.IEnergyGrid; import appeng.api.networking.events.MENetworkCellArrayUpdate; import appeng.api.networking.events.MENetworkChannelsChanged; import appeng.api.networking.events.MENetworkEventSubscribe; import appeng.api.networking.events.MENetworkPowerStatusChange; import appeng.api.networking.events.MENetworkPowerStorage; import appeng.api.networking.events.MENetworkPowerStorage.PowerEventType; import appeng.api.networking.security.BaseActionSource; import appeng.api.networking.security.IActionHost; import appeng.api.networking.security.ISecurityGrid; import appeng.api.networking.security.MachineSource; import appeng.api.networking.security.PlayerSource; import appeng.api.networking.storage.IBaseMonitor; import appeng.api.networking.storage.IStorageGrid; import appeng.api.storage.ICellHandler; import appeng.api.storage.IMEInventory; import appeng.api.storage.IMEInventoryHandler; import appeng.api.storage.IMEMonitor; import appeng.api.storage.IMEMonitorHandlerReceiver; import appeng.api.storage.IStorageMonitorable; import appeng.api.storage.IStorageMonitorableAccessor; import appeng.api.storage.ITerminalHost; import appeng.api.storage.MEMonitorHandler; import appeng.api.storage.StorageChannel; import appeng.api.storage.data.IAEFluidStack; import appeng.api.storage.data.IAEItemStack; import appeng.api.storage.data.IAEStack; import appeng.api.util.AEColor; import appeng.api.util.IConfigManager; import appeng.capabilities.Capabilities; import appeng.helpers.IPriorityHost; import appeng.me.GridAccessException; import appeng.me.storage.MEInventoryHandler; import appeng.tile.TileEvent; import appeng.tile.events.TileEventType; import appeng.tile.grid.AENetworkPowerTile; import appeng.tile.inventory.AppEngInternalInventory; import appeng.tile.inventory.InvOperation; import appeng.util.ConfigManager; import appeng.util.IConfigManagerHost; import appeng.util.Platform; public class TileChest extends AENetworkPowerTile implements IMEChest, ITerminalHost, IPriorityHost, IConfigManagerHost, IColorableTile, ITickable { private static final ChestNoHandler NO_HANDLER = new ChestNoHandler(); private static final int[] SIDES = { 0 }; private static final int[] FRONT = { 1 }; private static final int[] NO_SLOTS = {}; private final AppEngInternalInventory inv = new AppEngInternalInventory( this, 2 ); private final BaseActionSource mySrc = new MachineSource( this ); private final IConfigManager config = new ConfigManager( this ); private ItemStack storageType; private long lastStateChange = 0; private int priority = 0; private int state = 0; private boolean wasActive = false; private AEColor paintedColor = AEColor.TRANSPARENT; private boolean isCached = false; private ICellHandler cellHandler; private MEMonitorHandler itemCell; private MEMonitorHandler fluidCell; private final Accessor accessor = new Accessor(); public TileChest() { this.setInternalMaxPower( PowerMultiplier.CONFIG.multiply( 40 ) ); this.getProxy().setFlags( GridFlags.REQUIRE_CHANNEL ); this.config.registerSetting( Settings.SORT_BY, SortOrder.NAME ); this.config.registerSetting( Settings.VIEW_MODE, ViewItems.ALL ); this.config.registerSetting( Settings.SORT_DIRECTION, SortDir.ASCENDING ); this.setInternalPublicPowerStorage( true ); this.setInternalPowerFlow( AccessRestriction.WRITE ); } @Override protected void PowerEvent( final PowerEventType x ) { if( x == PowerEventType.REQUEST_POWER ) { try { this.getProxy().getGrid().postEvent( new MENetworkPowerStorage( this, PowerEventType.REQUEST_POWER ) ); } catch( final GridAccessException e ) { // :( } } else { this.recalculateDisplay(); } } private void recalculateDisplay() { final int oldState = this.state; for( int x = 0; x < this.getCellCount(); x++ ) { this.state |= ( this.getCellStatus( x ) << ( 3 * x ) ); } if( this.isPowered() ) { this.state |= 0x40; } else { this.state &= ~0x40; } final boolean currentActive = this.getProxy().isActive(); if( this.wasActive != currentActive ) { this.wasActive = currentActive; try { this.getProxy().getGrid().postEvent( new MENetworkCellArrayUpdate() ); } catch( final GridAccessException e ) { // :P } } if( oldState != this.state ) { this.markForUpdate(); } } @Override public int getCellCount() { return 1; } private IMEInventoryHandler getHandler( final StorageChannel channel ) throws ChestNoHandler { if( !this.isCached ) { this.itemCell = null; this.fluidCell = null; final ItemStack is = this.inv.getStackInSlot( 1 ); if( is != null ) { this.isCached = true; this.cellHandler = AEApi.instance().registries().cell().getHandler( is ); if( this.cellHandler != null ) { double power = 1.0; final IMEInventoryHandler itemCell = this.cellHandler.getCellInventory( is, this, StorageChannel.ITEMS ); final IMEInventoryHandler fluidCell = this.cellHandler.getCellInventory( is, this, StorageChannel.FLUIDS ); if( itemCell != null ) { power += this.cellHandler.cellIdleDrain( is, itemCell ); } else if( fluidCell != null ) { power += this.cellHandler.cellIdleDrain( is, fluidCell ); } this.getProxy().setIdlePowerUsage( power ); this.itemCell = this.wrap( itemCell ); this.fluidCell = this.wrap( fluidCell ); } } } switch( channel ) { case FLUIDS: if( this.fluidCell == null ) { throw NO_HANDLER; } return this.fluidCell; case ITEMS: if( this.itemCell == null ) { throw NO_HANDLER; } return this.itemCell; default: } return null; } private MEMonitorHandler wrap( final IMEInventoryHandler h ) { if( h == null ) { return null; } final MEInventoryHandler ih = new MEInventoryHandler( h, h.getChannel() ); ih.setPriority( this.priority ); final MEMonitorHandler g = new ChestMonitorHandler( ih ); g.addListener( new ChestNetNotifier( h.getChannel() ), g ); return g; } @Override public int getCellStatus( final int slot ) { if( Platform.isClient() ) { return ( this.state >> ( slot * 3 ) ) & 3; } final ItemStack cell = this.inv.getStackInSlot( 1 ); final ICellHandler ch = AEApi.instance().registries().cell().getHandler( cell ); if( ch != null ) { try { final IMEInventoryHandler handler = this.getHandler( StorageChannel.ITEMS ); if( handler instanceof ChestMonitorHandler ) { return ch.getStatusForCell( cell, ( (ChestMonitorHandler) handler ).getInternalHandler() ); } } catch( final ChestNoHandler ignored ) { } try { final IMEInventoryHandler handler = this.getHandler( StorageChannel.FLUIDS ); if( handler instanceof ChestMonitorHandler ) { return ch.getStatusForCell( cell, ( (ChestMonitorHandler) handler ).getInternalHandler() ); } } catch( final ChestNoHandler ignored ) { } } return 0; } @Override public boolean isPowered() { if( Platform.isClient() ) { return ( this.state & 0x40 ) == 0x40; } boolean gridPowered = this.getAECurrentPower() > 64; if( !gridPowered ) { try { gridPowered = this.getProxy().getEnergy().isNetworkPowered(); } catch( final GridAccessException ignored ) { } } return super.getAECurrentPower() > 1 || gridPowered; } @Override public boolean isCellBlinking( final int slot ) { final long now = this.world.getTotalWorldTime(); if( now - this.lastStateChange > 8 ) { return false; } return ( ( this.state >> ( slot * 3 + 2 ) ) & 0x01 ) == 0x01; } @Override protected double extractAEPower( final double amt, final Actionable mode ) { double stash = 0.0; try { final IEnergyGrid eg = this.getProxy().getEnergy(); stash = eg.extractAEPower( amt, mode, PowerMultiplier.ONE ); if( stash >= amt ) { return stash; } } catch( final GridAccessException e ) { // no grid :( } // local battery! return super.extractAEPower( amt - stash, mode ) + stash; } @Override public void update() { if( this.world.isRemote ) { return; } final double idleUsage = this.getProxy().getIdlePowerUsage(); try { if( !this.getProxy().getEnergy().isNetworkPowered() ) { final double powerUsed = this.extractAEPower( idleUsage, Actionable.MODULATE, PowerMultiplier.CONFIG ); // drain if( powerUsed + 0.1 >= idleUsage != ( this.state & 0x40 ) > 0 ) { this.recalculateDisplay(); } } } catch( final GridAccessException e ) { final double powerUsed = this.extractAEPower( this.getProxy().getIdlePowerUsage(), Actionable.MODULATE, PowerMultiplier.CONFIG ); // drain if( powerUsed + 0.1 >= idleUsage != ( this.state & 0x40 ) > 0 ) { this.recalculateDisplay(); } } if( this.inv.getStackInSlot( 0 ) != null ) { this.tryToStoreContents(); } } @TileEvent( TileEventType.NETWORK_WRITE ) public void writeToStream_TileChest( final ByteBuf data ) { if( this.world.getTotalWorldTime() - this.lastStateChange > 8 ) { this.state = 0; } else { this.state &= 0x24924924; // just keep the blinks... } for( int x = 0; x < this.getCellCount(); x++ ) { this.state |= ( this.getCellStatus( x ) << ( 3 * x ) ); } if( this.isPowered() ) { this.state |= 0x40; } else { this.state &= ~0x40; } data.writeByte( this.state ); data.writeByte( this.paintedColor.ordinal() ); final ItemStack is = this.inv.getStackInSlot( 1 ); if( is == null ) { data.writeInt( 0 ); } else { data.writeInt( ( is.getItemDamage() << Platform.DEF_OFFSET ) | Item.getIdFromItem( is.getItem() ) ); } } @TileEvent( TileEventType.NETWORK_READ ) public boolean readFromStream_TileChest( final ByteBuf data ) { final int oldState = this.state; final ItemStack oldType = this.storageType; this.state = data.readByte(); final AEColor oldPaintedColor = this.paintedColor; this.paintedColor = AEColor.values()[data.readByte()]; final int item = data.readInt(); if( item == 0 ) { this.storageType = null; } else { this.storageType = new ItemStack( Item.getItemById( item & 0xffff ), 1, item >> Platform.DEF_OFFSET ); } this.lastStateChange = this.world.getTotalWorldTime(); return oldPaintedColor != this.paintedColor || ( this.state & 0xDB6DB6DB ) != ( oldState & 0xDB6DB6DB ) || !Platform.itemComparisons().isSameItem( oldType, this.storageType ); } @TileEvent( TileEventType.WORLD_NBT_READ ) public void readFromNBT_TileChest( final NBTTagCompound data ) { this.config.readFromNBT( data ); this.priority = data.getInteger( "priority" ); if( data.hasKey( "paintedColor" ) ) { this.paintedColor = AEColor.values()[data.getByte( "paintedColor" )]; } } @TileEvent( TileEventType.WORLD_NBT_WRITE ) public void writeToNBT_TileChest( final NBTTagCompound data ) { this.config.writeToNBT( data ); data.setInteger( "priority", this.priority ); data.setByte( "paintedColor", (byte) this.paintedColor.ordinal() ); } @MENetworkEventSubscribe public void powerRender( final MENetworkPowerStatusChange c ) { this.recalculateDisplay(); } @MENetworkEventSubscribe public void channelRender( final MENetworkChannelsChanged c ) { this.recalculateDisplay(); } @Override public IMEMonitor getItemInventory() { return this.itemCell; } @Override public IMEMonitor getFluidInventory() { return this.fluidCell; } @Override public IInventory getInternalInventory() { return this.inv; } @Override public void setInventorySlotContents( final int i, final ItemStack itemstack ) { this.inv.setInventorySlotContents( i, itemstack ); this.tryToStoreContents(); } @Override public void onChangeInventory( final IInventory inv, final int slot, final InvOperation mc, final ItemStack removed, final ItemStack added ) { if( slot == 1 ) { this.itemCell = null; this.fluidCell = null; this.isCached = false; // recalculate the storage cell. try { this.getProxy().getGrid().postEvent( new MENetworkCellArrayUpdate() ); final IStorageGrid gs = this.getProxy().getStorage(); Platform.postChanges( gs, removed, added, this.mySrc ); } catch( final GridAccessException ignored ) { } // update the neighbors if( this.world != null ) { Platform.notifyBlocksOfNeighbors( this.world, this.pos ); this.markForUpdate(); } } } @Override public boolean canInsertItem( final int slotIndex, final ItemStack insertingItem, final EnumFacing side ) { if( slotIndex == 1 ) { if( AEApi.instance().registries().cell().getCellInventory( insertingItem, this, StorageChannel.ITEMS ) != null ) { return true; } if( AEApi.instance().registries().cell().getCellInventory( insertingItem, this, StorageChannel.FLUIDS ) != null ) { return true; } } else { try { final IMEInventory cell = this.getHandler( StorageChannel.ITEMS ); final IAEItemStack returns = cell.injectItems( AEApi.instance().storage().createItemStack( this.inv.getStackInSlot( 0 ) ), Actionable.SIMULATE, this.mySrc ); return returns == null || returns.getStackSize() != insertingItem.getCount(); } catch( final ChestNoHandler ignored ) { } } return false; } @Override public boolean canExtractItem( final int slotIndex, final ItemStack extractedItem, final EnumFacing side ) { return slotIndex == 1; } @Override public int[] getAccessibleSlotsBySide( final EnumFacing side ) { if( EnumFacing.SOUTH == side ) { return FRONT; } if( this.isPowered() ) { try { if( this.getHandler( StorageChannel.ITEMS ) != null ) { return SIDES; } } catch( final ChestNoHandler e ) { // nope! } } return NO_SLOTS; } private void tryToStoreContents() { try { if( this.getStackInSlot( 0 ) != null ) { final IMEInventory cell = this.getHandler( StorageChannel.ITEMS ); final IAEItemStack returns = Platform.poweredInsert( this, cell, AEApi.instance().storage().createItemStack( this.inv.getStackInSlot( 0 ) ), this.mySrc ); if( returns == null ) { this.inv.setInventorySlotContents( 0, null ); } else { this.inv.setInventorySlotContents( 0, returns.getItemStack() ); } } } catch( final ChestNoHandler ignored ) { } } @Override public List getCellArray( final StorageChannel channel ) { if( this.getProxy().isActive() ) { try { return Collections.singletonList( this.getHandler( channel ) ); } catch( final ChestNoHandler e ) { // :P } } return new ArrayList(); } @Override public int getPriority() { return this.priority; } @Override public void setPriority( final int newValue ) { this.priority = newValue; this.itemCell = null; this.fluidCell = null; this.isCached = false; // recalculate the storage cell. try { this.getProxy().getGrid().postEvent( new MENetworkCellArrayUpdate() ); } catch( final GridAccessException e ) { // :P } } @Override public void blinkCell( final int slot ) { final long now = this.world.getTotalWorldTime(); if( now - this.lastStateChange > 8 ) { this.state = 0; } this.lastStateChange = now; this.state |= 1 << ( slot * 3 + 2 ); this.recalculateDisplay(); } public ItemStack getStorageType() { if( this.isPowered() ) { return this.storageType; } return ItemStack.EMPTY; } @Override public IConfigManager getConfigManager() { return this.config; } @Override public void updateSetting( final IConfigManager manager, final Enum settingName, final Enum newValue ) { } public boolean openGui( final EntityPlayer p, final ICellHandler ch, final ItemStack cell, final EnumFacing side ) { try { final IMEInventoryHandler invHandler = this.getHandler( StorageChannel.ITEMS ); if( ch != null && invHandler != null ) { ch.openChestGui( p, this, ch, invHandler, cell, StorageChannel.ITEMS ); return true; } } catch( final ChestNoHandler e ) { // :P } try { final IMEInventoryHandler invHandler = this.getHandler( StorageChannel.FLUIDS ); if( ch != null && invHandler != null ) { ch.openChestGui( p, this, ch, invHandler, cell, StorageChannel.FLUIDS ); return true; } } catch( final ChestNoHandler e ) { // :P } return false; } @Override public AEColor getColor() { return this.paintedColor; } @Override public boolean recolourBlock( final EnumFacing side, final AEColor newPaintedColor, final EntityPlayer who ) { if( this.paintedColor == newPaintedColor ) { return false; } this.paintedColor = newPaintedColor; this.markDirty(); this.markForUpdate(); return true; } @Override public void saveChanges( final IMEInventory cellInventory ) { this.world.markChunkDirty( this.pos, this ); } private static class ChestNoHandler extends Exception { private static final long serialVersionUID = 7995805326136526631L; } private class ChestNetNotifier> implements IMEMonitorHandlerReceiver { private final StorageChannel chan; public ChestNetNotifier( final StorageChannel chan ) { this.chan = chan; } @Override public boolean isValid( final Object verificationToken ) { if( this.chan == StorageChannel.ITEMS ) { return verificationToken == TileChest.this.itemCell; } if( this.chan == StorageChannel.FLUIDS ) { return verificationToken == TileChest.this.fluidCell; } return false; } @Override public void postChange( final IBaseMonitor monitor, final Iterable change, final BaseActionSource source ) { if( source == TileChest.this.mySrc || ( source instanceof PlayerSource && ( (PlayerSource) source ).via == TileChest.this ) ) { try { if( TileChest.this.getProxy().isActive() ) { TileChest.this.getProxy().getStorage().postAlterationOfStoredItems( this.chan, change, TileChest.this.mySrc ); } } catch( final GridAccessException e ) { // :( } } TileChest.this.blinkCell( 0 ); } @Override public void onListUpdate() { // not used here } } private class ChestMonitorHandler extends MEMonitorHandler { public ChestMonitorHandler( final IMEInventoryHandler t ) { super( t ); } private IMEInventoryHandler getInternalHandler() { final IMEInventoryHandler h = this.getHandler(); if( h instanceof MEInventoryHandler ) { return (IMEInventoryHandler) ( (MEInventoryHandler) h ).getInternal(); } return this.getHandler(); } @Override public T injectItems( final T input, final Actionable mode, final BaseActionSource src ) { if( src.isPlayer() && !this.securityCheck( ( (PlayerSource) src ).player, SecurityPermissions.INJECT ) ) { return input; } return super.injectItems( input, mode, src ); } private boolean securityCheck( final EntityPlayer player, final SecurityPermissions requiredPermission ) { if( TileChest.this.getTile() instanceof IActionHost && requiredPermission != null ) { final IGridNode gn = ( (IActionHost) TileChest.this.getTile() ).getActionableNode(); if( gn != null ) { final IGrid g = gn.getGrid(); if( g != null ) { final boolean requirePower = false; if( requirePower ) { final IEnergyGrid eg = g.getCache( IEnergyGrid.class ); if( !eg.isNetworkPowered() ) { return false; } } final ISecurityGrid sg = g.getCache( ISecurityGrid.class ); if( sg.hasPermission( player, requiredPermission ) ) { return true; } } } return false; } return true; } @Override public T extractItems( final T request, final Actionable mode, final BaseActionSource src ) { if( src.isPlayer() && !this.securityCheck( ( (PlayerSource) src ).player, SecurityPermissions.EXTRACT ) ) { return null; } return super.extractItems( request, mode, src ); } } @Override public boolean hasCapability( Capability capability, EnumFacing facing ) { if( capability == Capabilities.STORAGE_MONITORABLE_ACCESSOR && facing != getForward() ) { return true; } return super.hasCapability( capability, facing ); } @SuppressWarnings( "unchecked" ) @Override public T getCapability( Capability capability, @Nullable EnumFacing facing ) { if( capability == Capabilities.STORAGE_MONITORABLE_ACCESSOR && facing != getForward() ) { return (T) accessor; } return super.getCapability( capability, facing ); } private class Accessor implements IStorageMonitorableAccessor { @Nullable @Override public IStorageMonitorable getInventory( BaseActionSource src ) { if( Platform.canAccess( getProxy(), src ) ) { return TileChest.this; } return null; } } @Override public boolean isEmpty() { // TODO Auto-generated method stub return false; } }