/* * 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.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; import net.minecraft.inventory.container.ContainerType; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundNBT; import net.minecraft.network.PacketBuffer; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.ResourceLocation; import net.minecraftforge.client.model.data.IModelData; import net.minecraftforge.items.IItemHandler; import net.minecraftforge.registries.ForgeRegistries; import appeng.api.AEApi; import appeng.api.implementations.tiles.IChestOrDrive; import appeng.api.networking.GridFlags; 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.security.IActionSource; import appeng.api.networking.storage.IStorageGrid; import appeng.api.storage.ICellHandler; import appeng.api.storage.ICellInventory; import appeng.api.storage.ICellInventoryHandler; import appeng.api.storage.IMEInventoryHandler; import appeng.api.storage.IStorageChannel; import appeng.api.storage.data.IAEItemStack; import appeng.api.storage.data.IAEStack; import appeng.api.util.AECableType; import appeng.api.util.AEPartLocation; import appeng.api.util.DimensionalCoord; import appeng.block.storage.DriveSlotsState; import appeng.client.render.model.DriveModelData; import appeng.container.implementations.DriveContainer; import appeng.helpers.IPriorityHost; import appeng.me.GridAccessException; import appeng.me.helpers.MachineSource; import appeng.me.storage.DriveWatcher; import appeng.tile.grid.AENetworkInvTileEntity; import appeng.tile.inventory.AppEngCellInventory; import appeng.util.Platform; import appeng.util.inv.InvOperation; import appeng.util.inv.filter.IAEItemFilter; public class DriveTileEntity extends AENetworkInvTileEntity implements IChestOrDrive, IPriorityHost { private static final int BIT_POWER_MASK = 0x80000000; private static final int BIT_BLINK_MASK = 0x24924924; private static final int BIT_STATE_MASK = 0xDB6DB6DB; private final AppEngCellInventory inv = new AppEngCellInventory(this, 10); private final ICellHandler[] handlersBySlot = new ICellHandler[10]; private final DriveWatcher[] invBySlot = new DriveWatcher[10]; private final IActionSource mySrc; private boolean isCached = false; private Map>, List> inventoryHandlers; private int priority = 0; private boolean wasActive = false; // This is only used on the client private final Item[] cellItems = new Item[10]; /** * The state of all cells inside a drive as bitset, using the following format. * * Bit 31: power state. 0 = off, 1 = on. Bit 30: undefined Bit 29-0: 3 bits as * state of each cell with the cell in slot 0 located in the 3 least significant * bits. * * Cell states: Bit 2: blink. 0 = off, 1 = on. Bit 1-0: cell status * * */ private int state = 0; public DriveTileEntity(TileEntityType tileEntityTypeIn) { super(tileEntityTypeIn); this.mySrc = new MachineSource(this); this.getProxy().setFlags(GridFlags.REQUIRE_CHANNEL); this.inv.setFilter(new CellValidInventoryFilter()); this.inventoryHandlers = new IdentityHashMap<>(); } @Override protected void writeToStream(final PacketBuffer data) throws IOException { super.writeToStream(data); int newState = 0; if (this.getProxy().isActive()) { newState |= BIT_POWER_MASK; } for (int x = 0; x < this.getCellCount(); x++) { newState |= (this.getCellStatus(x) << (3 * x)); } data.writeInt(newState); writeCellItemIds(data); } private void writeCellItemIds(PacketBuffer data) { List cellItemIds = new ArrayList<>(getCellCount()); byte[] bm = new byte[getCellCount()]; for (int x = 0; x < this.getCellCount(); x++) { Item item = getCellItem(x); if (item != null && item.getRegistryName() != null) { ResourceLocation itemId = item.getRegistryName(); int idx = cellItemIds.indexOf(itemId); if (idx == -1) { cellItemIds.add(itemId); bm[x] = (byte) cellItemIds.size(); // We use 1-based in bm[] } else { bm[x] = (byte) (1 + idx); // 1-based indexing!! } } } // Write out the list of unique cell item ids data.writeByte(cellItemIds.size()); for (ResourceLocation itemId : cellItemIds) { data.writeResourceLocation(itemId); } // Then the lookup table for each slot for (int i = 0; i < getCellCount(); i++) { data.writeByte(bm[i]); } } @Override protected boolean readFromStream(final PacketBuffer data) throws IOException { boolean c = super.readFromStream(data); final int oldState = this.state; this.state = data.readInt(); c |= this.readCellItemIDs(data); return (this.state & BIT_STATE_MASK) != (oldState & BIT_STATE_MASK) || c; } private boolean readCellItemIDs(final PacketBuffer data) { int uniqueStrCount = data.readByte(); String[] uniqueStrs = new String[uniqueStrCount]; for (int i = 0; i < uniqueStrCount; i++) { uniqueStrs[i] = data.readString(); } boolean changed = false; for (int i = 0; i < getCellCount(); i++) { byte idx = data.readByte(); // an index of 0 indicates the slot is empty Item item = null; if (idx > 0) { --idx; String itemId = uniqueStrs[idx]; item = ForgeRegistries.ITEMS.getValue(new ResourceLocation(itemId)); } if (cellItems[i] != item) { changed = true; cellItems[i] = item; } } return changed; } @Override public int getCellCount() { return 10; } @Nullable @Override public Item getCellItem(int slot) { // Client-side we'll need to actually use the synced state if (world == null || world.isRemote) { return cellItems[slot]; } ItemStack stackInSlot = inv.getStackInSlot(slot); if (!stackInSlot.isEmpty()) { return stackInSlot.getItem(); } return null; } @Override public int getCellStatus(final int slot) { if (Platform.isClient()) { return (this.state >> (slot * 3)) & 3; } final DriveWatcher handler = this.invBySlot[slot]; if (handler == null) { return 0; } return handler.getStatus(); } @Override public boolean isPowered() { if (isRemote()) { return (this.state & BIT_POWER_MASK) == BIT_POWER_MASK; } return this.getProxy().isActive(); } @Override public boolean isCellBlinking(final int slot) { return ((this.state >> (slot * 3 + 2)) & 0x01) == 0x01; } @Override public void read(final CompoundNBT data) { super.read(data); this.isCached = false; this.priority = data.getInt("priority"); } @Override public CompoundNBT write(final CompoundNBT data) { super.write(data); data.putInt("priority", this.priority); return data; } @MENetworkEventSubscribe public void powerRender(final MENetworkPowerStatusChange c) { this.recalculateDisplay(); } private void recalculateDisplay() { final boolean currentActive = this.getProxy().isActive(); int newState = 0; if (currentActive) { newState |= BIT_POWER_MASK; } if (this.wasActive != currentActive) { this.wasActive = currentActive; try { this.getProxy().getGrid().postEvent(new MENetworkCellArrayUpdate()); } catch (final GridAccessException e) { // :P } } for (int x = 0; x < this.getCellCount(); x++) { newState |= (this.getCellStatus(x) << (3 * x)); } if (newState != this.state) { this.state = newState; this.markForUpdate(); } } @MENetworkEventSubscribe public void channelRender(final MENetworkChannelsChanged c) { this.recalculateDisplay(); } @Override public AECableType getCableConnectionType(final AEPartLocation dir) { return AECableType.SMART; } @Override public DimensionalCoord getLocation() { return new DimensionalCoord(this); } @Override public IItemHandler getInternalInventory() { return this.inv; } @Override public void onChangeInventory(final IItemHandler inv, final int slot, final InvOperation mc, final ItemStack removed, final ItemStack added) { if (this.isCached) { this.isCached = false; // recalculate the storage cell. this.updateState(); } try { this.getProxy().getGrid().postEvent(new MENetworkCellArrayUpdate()); final IStorageGrid gs = this.getProxy().getStorage(); Platform.postChanges(gs, removed, added, this.mySrc); } catch (final GridAccessException ignored) { } this.markForUpdate(); } private void updateState() { if (!this.isCached) { final Collection>> storageChannels = AEApi.instance().storage() .storageChannels(); storageChannels.forEach(channel -> this.inventoryHandlers.put(channel, new ArrayList<>(10))); double power = 2.0; for (int x = 0; x < this.inv.getSlots(); x++) { final ItemStack is = this.inv.getStackInSlot(x); this.invBySlot[x] = null; this.handlersBySlot[x] = null; if (!is.isEmpty()) { this.handlersBySlot[x] = AEApi.instance().registries().cell().getHandler(is); if (this.handlersBySlot[x] != null) { for (IStorageChannel> channel : storageChannels) { ICellInventoryHandler cell = this.handlersBySlot[x].getCellInventory(is, this, channel); if (cell != null) { this.inv.setHandler(x, cell); power += this.handlersBySlot[x].cellIdleDrain(is, cell); final DriveWatcher ih = new DriveWatcher(cell, is, this.handlersBySlot[x], this); ih.setPriority(this.priority); this.invBySlot[x] = ih; this.inventoryHandlers.get(channel).add(ih); break; } } } } } this.getProxy().setIdlePowerUsage(power); this.isCached = true; } } @Override public void onReady() { super.onReady(); this.updateState(); } @Override public List getCellArray(final IStorageChannel channel) { if (this.getProxy().isActive()) { this.updateState(); return this.inventoryHandlers.get(channel); } return Collections.emptyList(); } @Override public int getPriority() { return this.priority; } @Override public void setPriority(final int newValue) { this.priority = newValue; this.saveChanges(); this.isCached = false; // recalculate the storage cell. this.updateState(); try { this.getProxy().getGrid().postEvent(new MENetworkCellArrayUpdate()); } catch (final GridAccessException e) { // :P } } @Override public void blinkCell(final int slot) { this.state |= 1 << (slot * 3 + 2); this.recalculateDisplay(); } @Override public void saveChanges(final ICellInventory cellInventory) { this.world.markChunkDirty(this.pos, this); } private class CellValidInventoryFilter implements IAEItemFilter { @Override public boolean allowExtract(IItemHandler inv, int slot, int amount) { return true; } @Override public boolean allowInsert(IItemHandler inv, int slot, ItemStack stack) { return !stack.isEmpty() && AEApi.instance().registries().cell().isCellHandled(stack); } } @Override public ItemStack getItemStackRepresentation() { return AEApi.instance().definitions().blocks().drive().maybeStack(1).orElse(ItemStack.EMPTY); } @Nonnull @Override public IModelData getModelData() { return new DriveModelData(getUp(), getForward(), DriveSlotsState.fromChestOrDrive(this)); } @Override public ContainerType getContainerType() { return DriveContainer.TYPE; } }