package choonster.testmod3.util;
import choonster.testmod3.Logger;
import com.google.common.base.Throwables;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.inventory.InventoryHelper;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraft.world.World;
import net.minecraft.world.storage.loot.LootContext;
import net.minecraft.world.storage.loot.LootTable;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.wrapper.PlayerArmorInvWrapper;
import net.minecraftforge.items.wrapper.PlayerOffhandInvWrapper;
import javax.annotation.Nullable;
import java.lang.invoke.MethodHandle;
import java.util.*;
import java.util.function.Predicate;
/**
* Utility methods for inventories.
*
* @author Choonster
*/
public class InventoryUtils {
/**
* Get the {@link EntityEquipmentSlot} with the specified index (as returned by {@link EntityEquipmentSlot#getSlotIndex()}.
*
* @param index The index
* @return The equipment slot
*/
public static EntityEquipmentSlot getEquipmentSlotFromIndex(int index) {
for (final EntityEquipmentSlot equipmentSlot : EntityEquipmentSlot.values()) {
if (equipmentSlot.getSlotIndex() == index) {
return equipmentSlot;
}
}
throw new IllegalArgumentException(String.format("Invalid equipment slot index %d", index));
}
/**
* A reference to {@link LootTable#shuffleItems}.
*/
private static final MethodHandle SHUFFLE_ITEMS = ReflectionUtil.findMethod(LootTable.class, "shuffleItems", "func_186463_a", List.class, int.class, Random.class);
/**
* Fill an {@link IItemHandler} with random loot from a {@link LootTable}.
* <p>
* Adapted from {@link LootTable#fillInventory}.
*
* @param itemHandler The inventory to fill with loot
* @param lootTable The LootTable to generate loot from
* @param random The Random object to use in the loot generation
* @param context The LootContext to use in the loot generation
*/
public static void fillItemHandlerWithLoot(IItemHandler itemHandler, LootTable lootTable, Random random, LootContext context) {
final List<ItemStack> items = lootTable.generateLootForPools(random, context);
final List<Integer> emptySlots = getEmptySlotsRandomized(itemHandler, random);
try {
SHUFFLE_ITEMS.invokeExact(lootTable, items, emptySlots.size(), random);
} catch (Throwable throwable) {
Throwables.propagate(throwable);
}
for (final ItemStack itemStack : items) {
if (emptySlots.isEmpty()) {
Logger.warn("Tried to over-fill %s while generating loot.");
return;
}
final int slot = emptySlots.remove(emptySlots.size() - 1);
final ItemStack remainder = itemHandler.insertItem(slot, itemStack, false);
if (!remainder.isEmpty()) {
Logger.warn("Couldn't fully insert %s into slot %d of %s, %d items remain.", itemStack, slot, itemHandler, remainder.getCount());
}
}
}
/**
* Get a list containing the indices of the empty slots in an {@link IItemHandler} in random order.
* <p>
* Adapted from {@link LootTable#getEmptySlotsRandomized}.
*
* @param itemHandler The inventory
* @param random The Random object
* @return The slot indices
*/
private static List<Integer> getEmptySlotsRandomized(IItemHandler itemHandler, Random random) {
final List<Integer> emptySlots = new ArrayList<>();
for (int slot = 0; slot < itemHandler.getSlots(); ++slot) {
if (itemHandler.getStackInSlot(slot).isEmpty()) {
emptySlots.add(slot);
}
}
Collections.shuffle(emptySlots, random);
return emptySlots;
}
/**
* Get a list of the {@link IItemHandler}'s contents with the stacks randomly split.
* <p>
* Adapted from {@link InventoryHelper#dropInventoryItems}.
*
* @param itemHandler The inventory
* @param random The Random object
* @return The drops list
*/
public static List<ItemStack> dropItemHandlerContents(IItemHandler itemHandler, Random random) {
final List<ItemStack> drops = new ArrayList<>();
for (int slot = 0; slot < itemHandler.getSlots(); ++slot) {
while (!itemHandler.getStackInSlot(slot).isEmpty()) {
final int amount = random.nextInt(21) + 10;
if (!itemHandler.extractItem(slot, amount, true).isEmpty()) {
final ItemStack itemStack = itemHandler.extractItem(slot, amount, false);
drops.add(itemStack);
}
}
}
return drops;
}
/**
* An entity inventory type.
*/
public enum EntityInventoryType {
MAIN,
HAND,
ARMOUR;
@Override
public String toString() {
return super.toString().toLowerCase(Locale.ENGLISH);
}
}
/**
* Get the main inventory of the specified entity.
* <p>
* For players, this returns the main inventory. For other entities, this returns null.
*
* @param entity The entity
* @return The inventory, if any
*/
@Nullable
public static IItemHandler getMainInventory(Entity entity) {
if (entity instanceof EntityPlayer) {
return entity.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, EnumFacing.UP);
}
return null;
}
/**
* Get the hand inventory of the specified entity.
* <p>
* For players, this returns the off hand inventory. For other entities, this returns the {@link EnumFacing#UP} {@link IItemHandler} capability.
*
* @param entity The entity
* @return The hand inventory, if any
*/
@Nullable
public static IItemHandler getHandInventory(Entity entity) {
if (entity instanceof EntityPlayer) {
return new PlayerOffhandInvWrapper(((EntityPlayer) entity).inventory);
}
return entity.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, EnumFacing.UP);
}
/**
* Get the armour inventory of the specified entity.
* <p>
* For players, this returns the armour inventory. For other entities, this returns the {@link EnumFacing#NORTH} {@link IItemHandler} capability.
*
* @param entity The entity
* @return The inventory, if any
*/
@Nullable
public static IItemHandler getArmourInventory(Entity entity) {
if (entity instanceof EntityPlayer) {
return new PlayerArmorInvWrapper(((EntityPlayer) entity).inventory);
}
return entity.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, EnumFacing.NORTH);
}
/**
* Get the entity's inventory of the specified type.
*
* @param entity The entity
* @param inventoryType The inventory type.
* @return The inventory, if any
*/
@Nullable
public static IItemHandler getInventoryForType(final Entity entity, final EntityInventoryType inventoryType) {
switch (inventoryType) {
case MAIN:
return getMainInventory(entity);
case HAND:
return getHandInventory(entity);
case ARMOUR:
return getArmourInventory(entity);
}
return null;
}
/**
* Try to perform an operation for each of the specified inventory types, stopping at the first successful operation.
* <p>
* Only performs the operation on inventory types that exist for the entity.
* <p>
* This is mainly useful in {@link Item#onUpdate(ItemStack, World, Entity, int, boolean)}, where the item can be in any of the player's inventories.
*
* @param entity The entity
* @param operation The operation to perform
* @param inventoryTypes The inventory types to perform the operation on, in order
* @return The inventory type of the first successful operation, or null if all operations failed
*/
@Nullable
public static EntityInventoryType forEachEntityInventory(final Entity entity, final Predicate<IItemHandler> operation, final EntityInventoryType... inventoryTypes) {
for (final EntityInventoryType inventoryType : inventoryTypes) {
final IItemHandler inventory = getInventoryForType(entity, inventoryType);
if (inventory != null && operation.test(inventory)) {
return inventoryType;
}
}
return null;
}
}