/* * 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 io.netty.buffer.ByteBuf; import java.util.ArrayList; import java.util.Collections; import java.util.List; 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.server.gui.IUpdatePlayerListBox; import net.minecraft.util.EnumFacing; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidTankInfo; import net.minecraftforge.fluids.IFluidHandler; 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.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.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; import appeng.util.item.AEFluidStack; public class TileChest extends AENetworkPowerTile implements IMEChest, IFluidHandler, ITerminalHost, IPriorityHost, IConfigManagerHost, IColorableTile, IUpdatePlayerListBox { static final ChestNoHandler NO_HANDLER = new ChestNoHandler(); static final int[] SIDES = new int[] { 0 }; static final int[] FRONT = new int[] { 1 }; static final int[] NO_SLOTS = new int[] {}; final AppEngInternalInventory inv = new AppEngInternalInventory( this, 2 ); final BaseActionSource mySrc = new MachineSource( this ); final IConfigManager config = new ConfigManager( this ); ItemStack storageType; long lastStateChange = 0; int priority = 0; int state = 0; boolean wasActive = false; AEColor paintedColor = AEColor.Transparent; boolean isCached = false; private ICellHandler cellHandler; private MEMonitorHandler itemCell; private MEMonitorHandler fluidCell; public TileChest() { this.internalMaxPower = PowerMultiplier.CONFIG.multiply( 40 ); this.gridProxy.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.internalPublicPowerStorage = true; this.internalPowerFlow = AccessRestriction.WRITE; } @Override protected void PowerEvent( PowerEventType x ) { if( x == PowerEventType.REQUEST_POWER ) { try { this.gridProxy.getGrid().postEvent( new MENetworkPowerStorage( this, PowerEventType.REQUEST_POWER ) ); } catch( GridAccessException e ) { // :( } } else { this.recalculateDisplay(); } } private void recalculateDisplay() { 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; } boolean currentActive = this.gridProxy.isActive(); if( this.wasActive != currentActive ) { this.wasActive = currentActive; try { this.gridProxy.getGrid().postEvent( new MENetworkCellArrayUpdate() ); } catch( GridAccessException e ) { // :P } } if( oldState != this.state ) { this.markForUpdate(); } } @Override public int getCellCount() { return 1; } public IMEInventoryHandler getHandler( StorageChannel channel ) throws ChestNoHandler { if( !this.isCached ) { this.itemCell = null; this.fluidCell = null; 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; IMEInventoryHandler itemCell = this.cellHandler.getCellInventory( is, this, StorageChannel.ITEMS ); 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.gridProxy.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( IMEInventoryHandler h ) { if( h == null ) { return null; } MEInventoryHandler ih = new MEInventoryHandler( h, h.getChannel() ); ih.setPriority( this.priority ); MEMonitorHandler g = new ChestMonitorHandler( ih ); g.addListener( new ChestNetNotifier( h.getChannel() ), g ); return g; } @Override public int getCellStatus( int slot ) { if( Platform.isClient() ) { return ( this.state >> ( slot * 3 ) ) & 3; } ItemStack cell = this.inv.getStackInSlot( 1 ); ICellHandler ch = AEApi.instance().registries().cell().getHandler( cell ); if( ch != null ) { try { IMEInventoryHandler handler = this.getHandler( StorageChannel.ITEMS ); if( handler instanceof ChestMonitorHandler ) { return ch.getStatusForCell( cell, ( (ChestMonitorHandler) handler ).getInternalHandler() ); } } catch( ChestNoHandler ignored ) { } try { IMEInventoryHandler handler = this.getHandler( StorageChannel.FLUIDS ); if( handler instanceof ChestMonitorHandler ) { return ch.getStatusForCell( cell, ( (ChestMonitorHandler) handler ).getInternalHandler() ); } } catch( 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.gridProxy.getEnergy().isNetworkPowered(); } catch( GridAccessException ignored ) { } } return super.getAECurrentPower() > 1 || gridPowered; } @Override public boolean isCellBlinking( int slot ) { long now = this.worldObj.getTotalWorldTime(); if( now - this.lastStateChange > 8 ) { return false; } return ( ( this.state >> ( slot * 3 + 2 ) ) & 0x01 ) == 0x01; } @Override protected double extractAEPower( double amt, Actionable mode ) { double stash = 0.0; IEnergyGrid eg; try { eg = this.gridProxy.getEnergy(); stash = eg.extractAEPower( amt, mode, PowerMultiplier.ONE ); if( stash >= amt ) { return stash; } } catch( GridAccessException e ) { // no grid :( } // local battery! return super.extractAEPower( amt - stash, mode ) + stash; } @TileEvent( TileEventType.TICK ) public void Tick_TileChest() { if( this.worldObj.isRemote ) { return; } double idleUsage = this.gridProxy.getIdlePowerUsage(); try { if( !this.gridProxy.getEnergy().isNetworkPowered() ) { double powerUsed = this.extractAEPower( idleUsage, Actionable.MODULATE, PowerMultiplier.CONFIG ); // drain if( powerUsed + 0.1 >= idleUsage != ( this.state & 0x40 ) > 0 ) { this.recalculateDisplay(); } } } catch( GridAccessException e ) { double powerUsed = this.extractAEPower( this.gridProxy.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( ByteBuf data ) { if( this.worldObj.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() ); 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( ByteBuf data ) { int oldState = this.state; ItemStack oldType = this.storageType; this.state = data.readByte(); AEColor oldPaintedColor = this.paintedColor; this.paintedColor = AEColor.values()[data.readByte()]; 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.worldObj.getTotalWorldTime(); return oldPaintedColor != this.paintedColor || ( this.state & 0xDB6DB6DB ) != ( oldState & 0xDB6DB6DB ) || !Platform.isSameItemPrecise( oldType, this.storageType ); } @TileEvent( TileEventType.WORLD_NBT_READ ) public void readFromNBT_TileChest( 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( NBTTagCompound data ) { this.config.writeToNBT( data ); data.setInteger( "priority", this.priority ); data.setByte( "paintedColor", (byte) this.paintedColor.ordinal() ); } @MENetworkEventSubscribe public void powerRender( MENetworkPowerStatusChange c ) { this.recalculateDisplay(); } @MENetworkEventSubscribe public void channelRender( 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( int i, ItemStack itemstack ) { this.inv.setInventorySlotContents( i, itemstack ); this.tryToStoreContents(); } @Override public void onChangeInventory( IInventory inv, int slot, InvOperation mc, ItemStack removed, ItemStack added ) { if( slot == 1 ) { this.itemCell = null; this.fluidCell = null; this.isCached = false; // recalculate the storage cell. try { this.gridProxy.getGrid().postEvent( new MENetworkCellArrayUpdate() ); IStorageGrid gs = this.gridProxy.getStorage(); Platform.postChanges( gs, removed, added, this.mySrc ); } catch( GridAccessException ignored ) { } // update the neighbors if( this.worldObj != null ) { Platform.notifyBlocksOfNeighbors( this.worldObj, pos ); this.markForUpdate(); } } } @Override public boolean canInsertItem( int slotIndex, ItemStack insertingItem, 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 { IMEInventory cell = this.getHandler( StorageChannel.ITEMS ); IAEItemStack returns = cell.injectItems( AEApi.instance().storage().createItemStack( this.inv.getStackInSlot( 0 ) ), Actionable.SIMULATE, this.mySrc ); return returns == null || returns.getStackSize() != insertingItem.stackSize; } catch( ChestNoHandler ignored ) { } } return false; } @Override public boolean canExtractItem( int slotIndex, ItemStack extractedItem, EnumFacing side ) { return slotIndex == 1; } @Override public int[] getAccessibleSlotsBySide( EnumFacing side ) { if( EnumFacing.SOUTH == side ) { return FRONT; } if( this.isPowered() ) { try { if( this.getHandler( StorageChannel.ITEMS ) != null ) { return SIDES; } } catch( ChestNoHandler e ) { // nope! } } return NO_SLOTS; } private void tryToStoreContents() { try { if( this.getStackInSlot( 0 ) != null ) { IMEInventory cell = this.getHandler( StorageChannel.ITEMS ); 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( ChestNoHandler ignored ) { } } @Override public List getCellArray( StorageChannel channel ) { if( this.gridProxy.isActive() ) { try { return Collections.singletonList( this.getHandler( channel ) ); } catch( ChestNoHandler e ) { // :P } } return new ArrayList(); } @Override public int getPriority() { return this.priority; } @Override public void setPriority( int newValue ) { this.priority = newValue; this.itemCell = null; this.fluidCell = null; this.isCached = false; // recalculate the storage cell. try { this.gridProxy.getGrid().postEvent( new MENetworkCellArrayUpdate() ); } catch( GridAccessException e ) { // :P } } @Override public void blinkCell( int slot ) { long now = this.worldObj.getTotalWorldTime(); if( now - this.lastStateChange > 8 ) { this.state = 0; } this.lastStateChange = now; this.state |= 1 << ( slot * 3 + 2 ); this.recalculateDisplay(); } @Override public int fill( EnumFacing from, FluidStack resource, boolean doFill ) { double req = resource.amount / 500.0; double available = this.extractAEPower( req, Actionable.SIMULATE, PowerMultiplier.CONFIG ); if( available >= req - 0.01 ) { try { IMEInventoryHandler h = this.getHandler( StorageChannel.FLUIDS ); this.extractAEPower( req, Actionable.MODULATE, PowerMultiplier.CONFIG ); IAEStack results = h.injectItems( AEFluidStack.create( resource ), doFill ? Actionable.MODULATE : Actionable.SIMULATE, this.mySrc ); if( results == null ) { return resource.amount; } return resource.amount - (int) results.getStackSize(); } catch( ChestNoHandler ignored ) { } } return 0; } @Override public FluidStack drain( EnumFacing from, FluidStack resource, boolean doDrain ) { return null; } @Override public FluidStack drain( EnumFacing from, int maxDrain, boolean doDrain ) { return null; } @Override public boolean canFill( EnumFacing from, Fluid fluid ) { try { IMEInventoryHandler h = this.getHandler( StorageChannel.FLUIDS ); return h.canAccept( AEFluidStack.create( new FluidStack( fluid, 1 ) ) ); } catch( ChestNoHandler ignored ) { } return false; } @Override public boolean canDrain( EnumFacing from, Fluid fluid ) { return false; } @Override public FluidTankInfo[] getTankInfo( EnumFacing from ) { try { IMEInventoryHandler h = this.getHandler( StorageChannel.FLUIDS ); if( h.getChannel() == StorageChannel.FLUIDS ) { return new FluidTankInfo[] { new FluidTankInfo( null, 1 ) }; // eh? } } catch( ChestNoHandler ignored ) { } return null; } @Override public IStorageMonitorable getMonitorable( EnumFacing side, BaseActionSource src ) { if( Platform.canAccess( this.gridProxy, src ) && side != this.getForward() ) { return this; } return null; } public ItemStack getStorageType() { if( this.isPowered() ) { return this.storageType; } return null; } @Override public IConfigManager getConfigManager() { return this.config; } @Override public void updateSetting( IConfigManager manager, Enum settingName, Enum newValue ) { } public boolean openGui( EntityPlayer p, ICellHandler ch, ItemStack cell, EnumFacing side ) { try { IMEInventoryHandler invHandler = this.getHandler( StorageChannel.ITEMS ); if( ch != null && invHandler != null ) { ch.openChestGui( p, this, ch, invHandler, cell, StorageChannel.ITEMS ); return true; } } catch( ChestNoHandler e ) { // :P } try { IMEInventoryHandler invHandler = this.getHandler( StorageChannel.FLUIDS ); if( ch != null && invHandler != null ) { ch.openChestGui( p, this, ch, invHandler, cell, StorageChannel.FLUIDS ); return true; } } catch( ChestNoHandler e ) { // :P } return false; } @Override public AEColor getColor() { return this.paintedColor; } @Override public boolean recolourBlock( EnumFacing side, AEColor newPaintedColor, EntityPlayer who ) { if( this.paintedColor == newPaintedColor ) { return false; } this.paintedColor = newPaintedColor; this.markDirty(); this.markForUpdate(); return true; } @Override public void saveChanges( IMEInventory cellInventory ) { this.worldObj.markChunkDirty( pos, this ); } private static class ChestNoHandler extends Exception { private static final long serialVersionUID = 7995805326136526631L; } class ChestNetNotifier> implements IMEMonitorHandlerReceiver { final StorageChannel chan; public ChestNetNotifier( StorageChannel chan ) { this.chan = chan; } @Override public boolean isValid( 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( IBaseMonitor monitor, Iterable change, BaseActionSource source ) { if( source == TileChest.this.mySrc || ( source instanceof PlayerSource && ( (PlayerSource) source ).via == TileChest.this ) ) { try { if( TileChest.this.gridProxy.isActive() ) { TileChest.this.gridProxy.getStorage().postAlterationOfStoredItems( this.chan, change, TileChest.this.mySrc ); } } catch( GridAccessException e ) { // :( } } TileChest.this.blinkCell( 0 ); } @Override public void onListUpdate() { // not used here } } class ChestMonitorHandler extends MEMonitorHandler { public ChestMonitorHandler( IMEInventoryHandler t ) { super( t ); } public IMEInventoryHandler getInternalHandler() { IMEInventoryHandler h = this.getHandler(); if( h instanceof MEInventoryHandler ) { return (IMEInventoryHandler) ( (MEInventoryHandler) h ).getInternal(); } return this.getHandler(); } @Override public T injectItems( T input, Actionable mode, BaseActionSource src ) { if( src.isPlayer() && !this.securityCheck( ( (PlayerSource) src ).player, SecurityPermissions.INJECT ) ) { return input; } return super.injectItems( input, mode, src ); } private boolean securityCheck( EntityPlayer player, SecurityPermissions requiredPermission ) { if( TileChest.this.getTile() instanceof IActionHost && requiredPermission != null ) { boolean requirePower = false; IGridNode gn = ( (IActionHost) TileChest.this.getTile() ).getActionableNode(); if( gn != null ) { IGrid g = gn.getGrid(); if( g != null ) { if( requirePower ) { IEnergyGrid eg = g.getCache( IEnergyGrid.class ); if( !eg.isNetworkPowered() ) { return false; } } ISecurityGrid sg = g.getCache( ISecurityGrid.class ); if( sg.hasPermission( player, requiredPermission ) ) { return true; } } } return false; } return true; } @Override public T extractItems( T request, Actionable mode, BaseActionSource src ) { if( src.isPlayer() && !this.securityCheck( ( (PlayerSource) src ).player, SecurityPermissions.EXTRACT ) ) { return null; } return super.extractItems( request, mode, src ); } } }