450 lines
14 KiB
Java
450 lines
14 KiB
Java
/*
|
|
* 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 <http://www.gnu.org/licenses/lgpl>.
|
|
*/
|
|
|
|
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<IAEItemStack>[] invBySlot = new DriveWatcher[10];
|
|
private final IActionSource mySrc;
|
|
private boolean isCached = false;
|
|
private Map<IStorageChannel<? extends IAEStack<?>>, List<IMEInventoryHandler>> 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<ResourceLocation> 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<IStorageChannel<? extends IAEStack<?>>> 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<? extends IAEStack<?>> 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<IAEItemStack> 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<IMEInventoryHandler> 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;
|
|
}
|
|
}
|