/**
* This class was created by <Vazkii>. It's distributed as
* part of the Botania Mod. Get the Source Code in github:
* https://github.com/Vazkii/Botania
*
* Botania is Open Source and distributed under the
* Botania License: http://botaniamod.net/license.php
*
* File Created @ [Feb 14, 2015, 3:28:54 PM (GMT)]
*/
package vazkii.botania.api.corporea;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.regex.Pattern;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.ForgeDirection;
import vazkii.botania.api.BotaniaAPI;
public final class CorporeaHelper {
private static final List<IInventory> empty = Collections.unmodifiableList(new ArrayList());
private static final WeakHashMap<List<ICorporeaSpark>, List<IInventory>> cachedNetworks = new WeakHashMap();
private static final List<ICorporeaAutoCompleteController> autoCompleteControllers = new ArrayList<ICorporeaAutoCompleteController>();
private static final Pattern patternControlCode = Pattern.compile("(?i)\\u00A7[0-9A-FK-OR]");
public static final String[] WILDCARD_STRINGS = new String[] {
"...", "~", "+", "?" , "*"
};
/**
* How many items were matched in the last request. If java had "out" params like C# this wouldn't be needed :V
*/
public static int lastRequestMatches = 0;
/**
* How many items were extracted in the last request.
*/
public static int lastRequestExtractions = 0;
/**
* Gets a list of all the inventories on this spark network. This list is cached for use once every tick,
* and if something changes during that tick it'll still have the first result.
*/
public static List<IInventory> getInventoriesOnNetwork(ICorporeaSpark spark) {
ICorporeaSpark master = spark.getMaster();
if(master == null)
return empty;
List<ICorporeaSpark> network = master.getConnections();
if(cachedNetworks.containsKey(network)) {
List<IInventory> cache = cachedNetworks.get(network);
if(cache != null)
return cache;
}
List<IInventory> inventories = new ArrayList();
if(network != null)
for(ICorporeaSpark otherSpark : network)
if(otherSpark != null) {
IInventory inv = otherSpark.getInventory();
if(inv != null)
inventories.add(inv);
}
cachedNetworks.put(network, inventories);
return inventories;
}
/**
* Gets the amount of available items in the network of the type passed in, checking NBT or not.
* The higher level functions that use a List< IInventory > or a Map< IInventory, Integer > should be
* called instead if the context for those exists to avoid having to get the values again.
*/
public static int getCountInNetwork(ItemStack stack, ICorporeaSpark spark, boolean checkNBT) {
List<IInventory> inventories = getInventoriesOnNetwork(spark);
return getCountInNetwork(stack, inventories, checkNBT);
}
/**
* Gets the amount of available items in the network of the type passed in, checking NBT or not.
* The higher level function that use a Map< IInventory, Integer > should be
* called instead if the context for this exists to avoid having to get the value again.
*/
public static int getCountInNetwork(ItemStack stack, List<IInventory> inventories, boolean checkNBT) {
Map<IInventory, Integer> map = getInventoriesWithItemInNetwork(stack, inventories, checkNBT);
return getCountInNetwork(stack, map, checkNBT);
}
/**
* Gets the amount of available items in the network of the type passed in, checking NBT or not.
*/
public static int getCountInNetwork(ItemStack stack, Map<IInventory, Integer> inventories, boolean checkNBT) {
int count = 0;
for(IInventory inv : inventories.keySet())
count += inventories.get(inv);
return count;
}
/**
* Gets a Map mapping IInventories to the amount of items of the type passed in that exist
* The higher level function that use a List< IInventory > should be
* called instead if the context for this exists to avoid having to get the value again.
*/
public static Map<IInventory, Integer> getInventoriesWithItemInNetwork(ItemStack stack, ICorporeaSpark spark, boolean checkNBT) {
List<IInventory> inventories = getInventoriesOnNetwork(spark);
return getInventoriesWithItemInNetwork(stack, inventories, checkNBT);
}
/**
* Gets a Map mapping IInventories to the amount of items of the type passed in that exist
* The deeper level function that use a List< IInventory > should be
* called instead if the context for this exists to avoid having to get the value again.
*/
public static Map<IInventory, Integer> getInventoriesWithItemInNetwork(ItemStack stack,List<IInventory> inventories, boolean checkNBT) {
Map<IInventory, Integer> countMap = new HashMap<IInventory, Integer>();
List<IWrappedInventory> wrappedInventories = BotaniaAPI.internalHandler.wrapInventory(inventories);
for (IWrappedInventory inv : wrappedInventories) {
CorporeaRequest request = new CorporeaRequest(stack, checkNBT, -1);
inv.countItems(request);
if (request.foundItems > 0) {
countMap.put(inv.getWrappedObject(), request.foundItems);
}
}
return countMap;
}
/**
* Bridge for requestItem() using an ItemStack.
*/
public static List<ItemStack> requestItem(ItemStack stack, ICorporeaSpark spark, boolean checkNBT, boolean doit) {
return requestItem(stack, stack.stackSize, spark, checkNBT, doit);
}
/**
* Bridge for requestItem() using a String and an item count.
*/
public static List<ItemStack> requestItem(String name, int count, ICorporeaSpark spark, boolean doit) {
return requestItem(name, count, spark, false, doit);
}
/**
* Requests list of ItemStacks of the type passed in from the network, or tries to, checking NBT or not.
* This will remove the items from the adequate inventories unless the "doit" parameter is false.
* Returns a new list of ItemStacks of the items acquired or an empty list if none was found.
* Case itemCount is -1 it'll find EVERY item it can.
* <br><br>
* The "matcher" parameter has to be an ItemStack or a String, if the first it'll check if the
* two stacks are similar using the "checkNBT" parameter, else it'll check if the name of the item
* equals or matches (case a regex is passed in) the matcher string.
* <br><br>
* When requesting counting of items, individual stacks may exceed maxStackSize for
* purposes of counting huge amounts.
*/
public static List<ItemStack> requestItem(Object matcher, int itemCount, ICorporeaSpark spark, boolean checkNBT, boolean doit) {
List<ItemStack> stacks = new ArrayList<ItemStack>();
CorporeaRequestEvent event = new CorporeaRequestEvent(matcher, itemCount, spark, checkNBT, doit);
if(MinecraftForge.EVENT_BUS.post(event))
return stacks;
List<IInventory> inventories = getInventoriesOnNetwork(spark);
List<IWrappedInventory> inventoriesW = BotaniaAPI.internalHandler.wrapInventory(inventories);
Map<ICorporeaInterceptor, ICorporeaSpark> interceptors = new HashMap<ICorporeaInterceptor, ICorporeaSpark>();
CorporeaRequest request = new CorporeaRequest(matcher, checkNBT, itemCount);
for(IWrappedInventory inv : inventoriesW) {
ICorporeaSpark invSpark = inv.getSpark();
Object originalInventory = inv.getWrappedObject();
if(originalInventory instanceof ICorporeaInterceptor) {
ICorporeaInterceptor interceptor = (ICorporeaInterceptor) originalInventory;
interceptor.interceptRequest(matcher, itemCount, invSpark, spark, stacks, inventories, doit);
interceptors.put(interceptor, invSpark);
}
if(doit) {
stacks.addAll(inv.extractItems(request));
} else {
stacks.addAll(inv.countItems(request));
}
}
lastRequestMatches = request.foundItems;
lastRequestExtractions = request.extractedItems;
for(ICorporeaInterceptor interceptor : interceptors.keySet())
interceptor.interceptRequestLast(matcher, itemCount, interceptors.get(interceptor), spark, stacks, inventories, doit);
return stacks;
}
/**
* Gets the spark attached to the inventory passed case it's a TileEntity.
*/
public static ICorporeaSpark getSparkForInventory(IInventory inv) {
if(!(inv instanceof TileEntity))
return null;
TileEntity tile = (TileEntity) inv;
return getSparkForBlock(tile.getWorldObj(), tile.xCoord, tile.yCoord, tile.zCoord);
}
/**
* Gets the spark attached to the block in the coords passed in. Note that the coords passed
* in are for the block that the spark will be on, not the coords of the spark itself.
*/
public static ICorporeaSpark getSparkForBlock(World world, int x, int y, int z) {
List<ICorporeaSpark> sparks = world.getEntitiesWithinAABB(ICorporeaSpark.class, AxisAlignedBB.getBoundingBox(x, y + 1, z, x + 1, y + 2, z + 1));
return sparks.isEmpty() ? null : sparks.get(0);
}
/**
* Gets if the block in the coords passed in has a spark attached. Note that the coords passed
* in are for the block that the spark will be on, not the coords of the spark itself.
*/
public static boolean doesBlockHaveSpark(World world, int x, int y, int z) {
return getSparkForBlock(world, x, y, z) != null;
}
/**
* Gets if the slot passed in can be extracted from by a spark.
*/
public static boolean isValidSlot(IInventory inv, int slot) {
return !(inv instanceof ISidedInventory) || arrayHas(((ISidedInventory) inv).getAccessibleSlotsFromSide(ForgeDirection.UP.ordinal()), slot) && ((ISidedInventory) inv).canExtractItem(slot, inv.getStackInSlot(slot), ForgeDirection.UP.ordinal());
}
/**
* Gets if two stacks match.
*/
public static boolean stacksMatch(ItemStack stack1, ItemStack stack2, boolean checkNBT) {
return stack1 != null && stack2 != null && stack1.isItemEqual(stack2) && (!checkNBT || ItemStack.areItemStackTagsEqual(stack1, stack2));
}
/**
* Gets if the name of a stack matches the string passed in.
*/
public static boolean stacksMatch(ItemStack stack, String s) {
if(stack == null)
return false;
boolean contains = false;
for(String wc : WILDCARD_STRINGS) {
if(s.endsWith(wc)) {
contains = true;
s = s.substring(0, s.length() - wc.length());
}
else if(s.startsWith(wc)) {
contains = true;
s = s.substring(wc.length());
}
if(contains)
break;
}
String name = stripControlCodes(stack.getDisplayName().toLowerCase().trim());
return equalOrContain(name, s, contains) || equalOrContain(name + "s", s, contains) || equalOrContain(name + "es", s, contains) || name.endsWith("y") && equalOrContain(name.substring(0, name.length() - 1) + "ies", s, contains);
}
/**
* Clears the cached networks, called once per tick, should not be called outside
* of the botania code.
*/
public static void clearCache() {
cachedNetworks.clear();
}
/**
* Helper method to check if an int array contains an int.
*/
public static boolean arrayHas(int[] arr, int val) {
for (int element : arr)
if(element == val)
return true;
return false;
}
/**
* Helper method to make stacksMatch() less messy.
*/
public static boolean equalOrContain(String s1, String s2, boolean contain) {
return contain ? s1.contains(s2) : s1.equals(s2);
}
/**
* Registers a ICorporeaAutoCompleteController
*/
public static void registerAutoCompleteController(ICorporeaAutoCompleteController controller) {
autoCompleteControllers.add(controller);
}
/**
* Returns if the auto complete helper should run
*/
public static boolean shouldAutoComplete() {
for(ICorporeaAutoCompleteController controller : autoCompleteControllers)
if(controller.shouldAutoComplete())
return true;
return false;
}
// Copy from StringUtils
public static String stripControlCodes(String str) {
return patternControlCode.matcher(str).replaceAll("");
}
}