package mcjty.gearswap.blocks;
import mcjty.gearswap.Config;
import mcjty.gearswap.GearSwap;
import mcjty.gearswap.items.ModItems;
import mcjty.gearswap.varia.InventoryHelper;
import mcjty.gearswap.varia.Tools;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.Packet;
import net.minecraft.network.play.server.S35PacketUpdateTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.util.ForgeDirection;
public class GearSwapperTE extends TileEntity implements ISidedInventory {
// First 4 slots are the ghost slots for the front icons
// Next there are 4 times 9+4 slots for the remembered states.
// Then there are 16 slots general inventory.
// Finally we optionally have 4*4 slots for baubles.
private ItemStack stacks[] = new ItemStack[4 + 4*(9+4) + 16 + 4*4];
public static final int SLOT_GHOST = 4;
public static final int SLOT_BUFFER = SLOT_GHOST + 4*(9+4);
public static final int SLOT_BAUBLES = SLOT_BUFFER + 16;
public static final int MODE_PLAYERINV = 0;
public static final int MODE_LOCALINV = 1;
public static final int MODE_REMOTEINV = 2;
private int exportModes[] = new int[] { MODE_PLAYERINV, MODE_LOCALINV, MODE_REMOTEINV };
@Override
public boolean canUpdate() {
return false;
}
@Override
public void readFromNBT(NBTTagCompound tagCompound) {
super.readFromNBT(tagCompound);
readRestorableFromNBT(tagCompound);
}
public void readRestorableFromNBT(NBTTagCompound tagCompound) {
readBufferFromNBT(tagCompound);
exportModes[0] = tagCompound.getInteger("export0");
exportModes[1] = tagCompound.getInteger("export1");
exportModes[2] = tagCompound.getInteger("export2");
}
private void readBufferFromNBT(NBTTagCompound tagCompound) {
NBTTagList bufferTagList = tagCompound.getTagList("Items", Constants.NBT.TAG_COMPOUND);
for (int i = 0 ; i < bufferTagList.tagCount() ; i++) {
NBTTagCompound nbtTagCompound = bufferTagList.getCompoundTagAt(i);
setStackInSlot(i, ItemStack.loadItemStackFromNBT(nbtTagCompound));
}
}
@Override
public void writeToNBT(NBTTagCompound tagCompound) {
super.writeToNBT(tagCompound);
writeRestorableToNBT(tagCompound);
}
public void writeRestorableToNBT(NBTTagCompound tagCompound) {
writeBufferToNBT(tagCompound);
tagCompound.setInteger("export0", exportModes[0]);
tagCompound.setInteger("export1", exportModes[1]);
tagCompound.setInteger("export2", exportModes[2]);
}
private void writeBufferToNBT(NBTTagCompound tagCompound) {
NBTTagList bufferTagList = new NBTTagList();
for (int i = 0 ; i < stacks.length ; i++) {
ItemStack stack = getStackInSlot(i);
NBTTagCompound nbtTagCompound = new NBTTagCompound();
if (stack != null) {
stack.writeToNBT(nbtTagCompound);
}
bufferTagList.appendTag(nbtTagCompound);
}
tagCompound.setTag("Items", bufferTagList);
}
public int getExportMode(int i) {
return exportModes[i];
}
public void toggelExportMode(int i) {
exportModes[i]++;
if (exportModes[i] > MODE_REMOTEINV) {
exportModes[i] = MODE_PLAYERINV;
}
markDirty();
worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
}
public void setFaceIconSlot(int index, ItemStack stack) {
setInventorySlotContents(index, stack);
worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
}
// Get total player inventory count. This is 9+4 (hotbar+armor) without baubles
// and 9+4+4 with baubles.
private int getPlayerInventorySize() {
if (GearSwap.baubles) {
return 9+4+4;
} else {
return 9+4;
}
}
// Virtual inventory index. From 0 to 9 is hotbar, The four slots after that are armor
private int getInventoryIndex(int i) {
if (i < 9) {
return i;
} else {
return (i-9) + 36;
}
}
private ItemStack getStackFromPlayerInventory(int index, EntityPlayer player) {
if (index < 9+4) {
return player.inventory.getStackInSlot(getInventoryIndex(index));
} else if (GearSwap.baubles) {
IInventory baubles = Tools.getBaubles(player);
if (baubles != null) {
return baubles.getStackInSlot(index - (9+4));
}
}
return null;
}
private void putStackInPlayerInventory(int index, EntityPlayer player, ItemStack stack) {
if (index < 9+4) {
player.inventory.setInventorySlotContents(getInventoryIndex(index), stack);
} else if (GearSwap.baubles) {
IInventory baubles = Tools.getBaubles(player);
if (baubles != null) {
baubles.setInventorySlotContents(index - (9+4), stack);
}
}
}
// Get the internal slot where we keep a ghost copy of the player inventory item.
private int getInternalInventoryIndex(int index, int i) {
if (i >= 9+4) {
// We have a baubles slot.
return SLOT_BAUBLES + index * 4 + (i-(9+4));
} else {
return 4 + index * (9 + 4) + i;
}
}
public void rememberSetup(int index, EntityPlayer player) {
setFaceIconSlot(index, player.getHeldItem());
for (int i = 0 ; i < getPlayerInventorySize() ; i++) {
ItemStack stack = getStackFromPlayerInventory(i, player);
if (stack != null && stack.stackSize == 0) {
// For some weird reason it seems baubles can have a 0 stacksize?
stack = stack.copy();
stack.stackSize = 1;
}
setInventorySlotContents(getInternalInventoryIndex(index, i), stack);
}
}
public void restoreSetup(int index, EntityPlayer player) {
InventoryPlayer inventory = player.inventory;
// Set aside the current hotbar + armor slots (+ optional baubles slots).
ItemStack[] currentStacks = new ItemStack[getPlayerInventorySize()];
for (int i = 0 ; i < getPlayerInventorySize() ; i++) {
currentStacks[i] = getStackFromPlayerInventory(i, player);
putStackInPlayerInventory(i, player, new ItemStack(ModItems.forceEmptyItem));
}
// Find stacks in all possible sources to replace the current selection
for (int i = 0 ; i < getPlayerInventorySize() ; i++) {
ItemStack desiredStack = getStackInSlot(getInternalInventoryIndex(index, i));
if (desiredStack == null || desiredStack.getItem() == ModItems.forceEmptyItem) {
// Either we don't have specific needs for this slot or we want it to be cleared.
// In both cases we keep the slot empty here.
} else {
ItemStack foundStack = findBestMatchingStack(desiredStack, currentStacks, inventory);
// Can be that we didn't find anything. In any case, we simply put whatever we found in the slot.
putStackInPlayerInventory(i, player, foundStack);
}
}
// First we check all slots that we don't need to be cleared and we put back the item
// from currentStacks if that's still there. In all other slots we temporarily set
// our dummy item so that we don't accidently overwrite that in the next step.
for (int i = 0 ; i < getPlayerInventorySize() ; i++) {
ItemStack stack = getStackFromPlayerInventory(i, player);
if (stack == null) {
if (currentStacks[i] != null && currentStacks[i].getItem() != ModItems.forceEmptyItem) {
int internalInventoryIndex = getInternalInventoryIndex(index, i);
ItemStack desiredStack = getStackInSlot(internalInventoryIndex);
// First check if we don't want to force the slot to be empty
if (desiredStack == null || desiredStack.getItem() != ModItems.forceEmptyItem) {
putStackInPlayerInventory(i, player, currentStacks[i]);
currentStacks[i] = null;
}
}
}
}
// All items that we didn't find a place for we need to back somewhere.
for (int i = 0 ; i < getPlayerInventorySize() ; i++) {
if (currentStacks[i] != null) {
if (storeItem(inventory, currentStacks[i])) {
currentStacks[i] = null;
}
}
}
// Now we clear the dummy items from our slots.
for (int i = 0 ; i < getPlayerInventorySize() ; i++) {
ItemStack stack = getStackFromPlayerInventory(i, player);
if (stack != null && stack.getItem() == ModItems.forceEmptyItem) {
putStackInPlayerInventory(i, player, null);
}
}
// Now it is possible that some of the items we couldn't place back because the slots in the hotbar
// were locked. Now that they are unlocked we can try again.
for (int i = 0 ; i < getPlayerInventorySize() ; i++) {
if (currentStacks[i] != null) {
if (inventory.addItemStackToInventory(currentStacks[i])) {
currentStacks[i] = null;
}
}
}
// Finally it is possible that some items could not be placed anywhere.
for (int i = 0 ; i < getPlayerInventorySize() ; i++) {
if (currentStacks[i] != null) {
EntityItem entityItem = new EntityItem(worldObj, xCoord, yCoord, zCoord, currentStacks[i]);
worldObj.spawnEntityInWorld(entityItem);
}
}
markDirty();
player.openContainer.detectAndSendChanges();
}
private boolean storeItem(InventoryPlayer inventory, ItemStack item) {
for (int exportMode : exportModes) {
switch (exportMode) {
case MODE_PLAYERINV:
if (inventory.addItemStackToInventory(item)) {
return true;
}
break;
case MODE_LOCALINV: {
int left = InventoryHelper.mergeItemStack(this, item, SLOT_BUFFER, SLOT_BUFFER + 16, null);
if (left == 0) {
return true;
}
item.stackSize = left;
break;
}
case MODE_REMOTEINV:
for (ForgeDirection direction : ForgeDirection.VALID_DIRECTIONS) {
TileEntity te = worldObj.getTileEntity(xCoord + direction.offsetX, yCoord + direction.offsetY, zCoord + direction.offsetZ);
if (te instanceof IInventory) {
IInventory otherInventory = (IInventory) te;
int left = InventoryHelper.mergeItemStackSafe(otherInventory, direction.getOpposite().ordinal(), item, 0, otherInventory.getSizeInventory(), null);
if (left == 0) {
return true;
}
item.stackSize = left;
}
}
break;
}
}
return false;
}
private ItemStack findBestMatchingStack(ItemStack desired, ItemStack[] currentStacks, InventoryPlayer inventoryPlayer) {
ItemStack bestSoFar = null;
desired = desired.copy();
// Correct for 0 stackSize which seems to be needed in some cases.
if (desired.stackSize == 0) {
desired.stackSize = 1;
}
while (desired.stackSize > 0) {
ItemStack stack = findBestMatchingStackWithScore(desired, currentStacks, inventoryPlayer, bestSoFar);
if (stack == null) {
return bestSoFar;
}
if (bestSoFar == null) {
bestSoFar = stack;
} else {
bestSoFar.stackSize += stack.stackSize;
}
desired.stackSize -= stack.stackSize;
}
return bestSoFar;
}
private class BestScore {
public int score = -1;
public Source source = null;
public int index = -1;
}
/**
* Find the best matching item. If 'bestMatch' is already given then we already found one before and so we
* now need an exact match for remaining items.
* @param desired
* @param source
* @param bestScore
* @param bestMatch
*/
private void findBestMatchingStackWithScore(ItemStack desired, Source source, BestScore bestScore, ItemStack bestMatch) {
for (int i = 0 ; i < source.getStackCount() ; i++) {
ItemStack current = source.getStack(i);
if (bestMatch != null && current != null) {
if (!bestMatch.isItemEqual(current)) {
continue;
}
if (!ItemStack.areItemStackTagsEqual(bestMatch, current)) {
continue;
}
}
int score = calculateMatchingScore(desired, current);
if (score > bestScore.score) {
bestScore.score = score;
bestScore.source = source;
bestScore.index = i;
}
}
}
private ItemStack findBestMatchingStackWithScore(final ItemStack desired, final ItemStack[] currentStacks, final InventoryPlayer inventoryPlayer, ItemStack bestMatch) {
final BestScore bestScore = new BestScore();
findBestMatchingStackWithScore(desired, new OriginalStackSource(currentStacks), bestScore, bestMatch);
findBestMatchingStackWithScore(desired, new PlayerSource(inventoryPlayer), bestScore, bestMatch);
findBestMatchingStackWithScore(desired, new InternalSource(this), bestScore, bestMatch);
// Check external inventories.
for (final ForgeDirection direction : ForgeDirection.VALID_DIRECTIONS) {
TileEntity te = worldObj.getTileEntity(xCoord + direction.offsetX, yCoord + direction.offsetY, zCoord + direction.offsetZ);
if (te instanceof IInventory) {
findBestMatchingStackWithScore(desired, new ExternalInventorySource((IInventory) te, direction), bestScore, bestMatch);
}
}
if (bestScore.source != null) {
return bestScore.source.extractAmount(bestScore.index, desired.stackSize);
}
return null;
}
private int calculateMatchingScore(ItemStack desired, ItemStack current) {
if (current == null) {
return -1;
}
if (ItemStack.areItemStackTagsEqual(desired, current) && desired.isItemEqual(current)) {
return 1000;
}
if (desired.getItem().equals(current.getItem()) && desired.getTagCompound() != null && current.getTagCompound() != null) {
if (itemsMatchForSpecificTags(desired, current)) {
return 700;
}
}
if (desired.isItemEqual(current)) {
return 500;
}
if (desired.getItem().equals(current.getItem())) {
return 200;
}
return -1;
}
private boolean itemsMatchForSpecificTags(ItemStack desired, ItemStack current) {
if (Config.tagsThatHaveToMatch.containsKey(desired.getUnlocalizedName())) {
String[] tags = Config.tagsThatHaveToMatch.get(desired.getUnlocalizedName());
boolean ok = true;
for (String tag : tags) {
NBTBase tag1 = desired.getTagCompound().getTag(tag);
NBTBase tag2 = current.getTagCompound().getTag(tag);
if (tag1 == null && tag2 != null) {
ok = false;
break;
} else if (tag1 != null && tag2 == null) {
ok = false;
break;
} else if (tag1 != null && !tag1.equals(tag2)) {
ok = false;
break;
}
}
if (ok) {
return true;
}
}
return false;
}
@Override
public Packet getDescriptionPacket() {
NBTTagCompound nbtTag = new NBTTagCompound();
this.writeToNBT(nbtTag);
return new S35PacketUpdateTileEntity(this.xCoord, this.yCoord, this.zCoord, 1, nbtTag);
}
@Override
public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity packet) {
readFromNBT(packet.func_148857_g());
}
// IInventory
@Override
public int getSizeInventory() {
return stacks.length;
}
@Override
public ItemStack getStackInSlot(int index) {
if (index >= stacks.length) {
return null;
}
return stacks[index];
}
public boolean isGhostSlot(int index) {
return (index >= 0 && index < SLOT_BUFFER) || (GearSwap.baubles && index >= SLOT_BAUBLES && index < SLOT_BAUBLES+16);
}
public boolean isIconSlot(int index) {
return index >= 0 && index < SLOT_GHOST;
}
public boolean isBufferSlot(int index) { return index >= SLOT_BUFFER && index < SLOT_BUFFER + 16; }
@Override
public ItemStack decrStackSize(int index, int amount) {
if (index >= stacks.length) {
return null;
}
if (isGhostSlot(index)) {
ItemStack old = stacks[index];
stacks[index] = null;
if (old == null) {
return null;
}
old.stackSize = 0;
return old;
} else {
if (stacks[index] != null) {
if (stacks[index].stackSize <= amount) {
ItemStack old = stacks[index];
stacks[index] = null;
markDirty();
return old;
}
ItemStack its = stacks[index].splitStack(amount);
if (stacks[index].stackSize == 0) {
stacks[index] = null;
}
markDirty();
return its;
}
return null;
}
}
@Override
public ItemStack getStackInSlotOnClosing(int index) {
return null;
}
public void setStackInSlot(int index, ItemStack stack) {
if (index >= stacks.length) {
return;
}
stacks[index] = stack;
}
@Override
public void setInventorySlotContents(int index, ItemStack stack) {
if (index >= stacks.length) {
return;
}
if (isGhostSlot(index)) {
if (stack != null) {
stacks[index] = stack.copy();
} else {
stacks[index] = null;
}
} else {
stacks[index] = stack;
if (stack != null && stack.stackSize > getInventoryStackLimit()) {
stack.stackSize = getInventoryStackLimit();
}
}
markDirty();
}
@Override
public String getInventoryName() {
return "Gear Swapper";
}
@Override
public boolean hasCustomInventoryName() {
return false;
}
@Override
public int getInventoryStackLimit() {
return 64;
}
@Override
public boolean isUseableByPlayer(EntityPlayer player) {
return true;
}
@Override
public void openInventory() {
}
@Override
public void closeInventory() {
}
@Override
public boolean isItemValidForSlot(int index, ItemStack stack) {
return !isGhostSlot(index);
}
@Override
public boolean canExtractItem(int index, ItemStack stack, int side) {
return index >= SLOT_BUFFER;
}
@Override
public int[] getAccessibleSlotsFromSide(int side) {
return new int[] {
SLOT_BUFFER, SLOT_BUFFER+1, SLOT_BUFFER+2, SLOT_BUFFER+3,
SLOT_BUFFER+4, SLOT_BUFFER+5, SLOT_BUFFER+6, SLOT_BUFFER+7,
SLOT_BUFFER+8, SLOT_BUFFER+9, SLOT_BUFFER+10, SLOT_BUFFER+11,
SLOT_BUFFER+12, SLOT_BUFFER+13, SLOT_BUFFER+14, SLOT_BUFFER+15
};
}
@Override
public boolean canInsertItem(int index, ItemStack stack, int side) {
return index >= SLOT_BUFFER;
}
}