Detecting if Location contains certain items

Discussion in 'Spigot Plugin Development' started by Cosmicluck, May 9, 2017.

  1. I am trying to efficiently check if the location of a cauldron contains a list of items, and if so it will give an item in return.

    I have tried a few different methods, including this one:

    Code (Text):
    Collection<Entity> ents = location.getWorld().getNearbyEntities(location, 1, 1, 1);
            if(!ents.isEmpty()) {
                List<ItemStack> entities = new ArrayList<>();
                for(Entity e : ents) {
                    if(e instanceof Item) {
                        entities.add(((Item) e).getItemStack());
                    }
                }
                for(ItemResult result : JsonReader.getResults()) {
                    List<ItemStack> ingredients = new ArrayList<>();
                    for(ItemIngredient ingredient : result.getIngredients()) {
                        ingredients.add(ingredient.build());
                    }
                    if(entities.containsAll(ingredients)) {
                        //Bukkit.broadcastMessage("Foo");
                    }
                }
            }
    The above code checks the location of a cauldron to get all the nearby entities within 1 radius. If its not empty, I then check each of those entities to make sure its an Item, then getting the Itemstack of that item and adding to the entities arraylist. After that's done, I loop through all of my result items and get their ingredients. Note here I am using two custom classes: ItemResult and ItemIngredient. As I loop through all the possible ingredients for each recipe, I add them to another ItemStack arraylist, ingredients. From here I thought checking if entities containsAll of ingredients would work, but it fires even if a player stands there and if there is only one ingredient when there should be more.

    Is there any efficient way to check if the location of a cauldron contains all the ingredients (ItemStacks) in order to continue with some other code, like broadcasting a test message.
     
  2. You are looping twice, try it like that (increases performance):

    Code (Text):
                label outer:
                for(ItemResult result : JsonReader.getResults()) {              
                    for(ItemIngredient ingredient : result.getIngredients()) {
                        if(!entities.contains(ingredient.build())continue outer;
                    }
                 
                        //Bukkit.broadcastMessage("Foo");
                 
                }
     
  3. Choco

    Moderator

    Hm...You may be able to do this a little bit more accurately in order to prevent items from outside the Cauldron being taken into consideration. Take a look at Chunk#getEntities() that returns an Entity array (Entity[]). Using this, filter through them to get only items, then compare their coordinates to assure that their location is within the block confines
    Code (Java):
    Block cauldron = // Cauldron somewhere
    Location itemLoc = cauldron.getLocation();
    int x = cauldron.getX(), y = cauldron.getY(), z = cauldron.getZ();

    ItemStack[] ingredients = Arrays.stream(cauldron.getChunk().getEntities())
        .filter(e -> e instanceof Item)
        .filter(e -> e.getLocation(itemLoc).getX() >= x && itemLoc.getX() <= x + 1.0)
        .filter(e -> e.getLocation(itemLoc).getY() >= y && itemLoc.getY() <= y + 1.0)
        .filter(e -> e.getLocation(itemLoc).getZ() >= z && itemLoc.getZ() <= z + 1.0)
        .map(e -> ((Item) e).getItemStack())
        .toArray(ItemStack[]::new);
    I'm using Entity#getLocation(Location) because #getLocation() creates a new Location object every invocation. Might as well have 1 Location rather than 6 :). Also, the method I wrote may also be slightly less mean than the alternative #getNearbyEntities() because it uses some NMS. The way Mojang does it is it gathers entities within an AABB (Axis Aligned Bounding Box) which I would assume does the exact same as what I'm doing in the stream above, but to be honest... it's Mojang. I haven't checked the code to be sure
     
    #3 Choco, May 9, 2017
    Last edited: May 10, 2017
    • Informative Informative x 1
    • Useful Useful x 1
  4. Thank you guys :) I will try both, I never knew about Array filtering and what I can see they will probably be my best friend

    @2008Choco Well I added it but I am not sure if it worked or not. Now "Foo" is broadcasted even when there is no items on the ground. Here's my updated code:

    Code (Text):
    Location itemLoc = location;
            double x = location.getX(), y = location.getY(), z = location.getZ();

            ItemStack[] ingredients = Arrays.stream(location.getChunk().getEntities())
                    .filter(e -> e instanceof Item)
                    .filter(e -> e.getLocation(itemLoc).getX() > x && itemLoc.getX() < x + 1)
                    .filter(e -> e.getLocation(itemLoc).getY() > x && itemLoc.getY() < y + 1)
                    .filter(e -> e.getLocation(itemLoc).getZ() > x && itemLoc.getZ() < z + 1)
                    .map(e -> ((Item) e).getItemStack())
                    .toArray(ItemStack[]::new);

            for(ItemResult result : JsonReader.getResults()) {
                if(Arrays.asList(ingredients).containsAll(result.getIngredientItems())) {
                    Bukkit.broadcastMessage("Foo");
                }
            }
     
    #4 Cosmicluck, May 9, 2017
    Last edited: May 9, 2017
    • Informative Informative x 1
  5. Choco

    Moderator

    To confirm, your "x", "y" and "z" variables are the coordinates of the cauldron block, correct? It may be helpful to print the contents of the array list (their coordinates) as well as the coordinates of the cauldron. Also, since #containsAll() compares values with #equals(), it may not be exact because it also takes into consideration of amounts (which you may perhaps want). You may have to make your own method that compares the items in the list.

    Side note: Thank you for figuring out that you had to cast "e". Heh... that was my mistake
     
  6. Only a small sidenote:

    .filter(e -> e.getLocation(itemLoc).getX() > x && itemLoc.getX() < x + 1)
    .filter(e -> e.getLocation(itemLoc).getY() > x && itemLoc.getY() < y + 1)
    .filter(e -> e.getLocation(itemLoc).getZ() > x && itemLoc.getZ() < z + 1)

    Apart from that, isn't a > x && a < x+1 always returning False when working with Integers?
     
  7. Choco

    Moderator

    Well first of all... Yea that's why you don't copy and paste code, because you get dumb-asses like me that don't particularly re-review the code after writing it ;) Good catch! Second of all, perhaps. I'm not certain. To be sure, instead, adding 1.0 may be a good idea :)
     
  8. Well, the 1.0D won't make a difference. Imagine a = 3

    x = 2 (or smaller): (a > x => 3 > 2 && a < x+1 => 3 < 3) => (True && False) => False
    x = a = 3: (a > x => 3 > 3 && a < x+1 => 3 < 4) => (False && True) => False
    x = 4 (or higher): (a > x => 3 > 4 && a < x+1 => 3 < 5) => (False && True) => False

    => a > x && a < x+1 = False (for all numbers in R)
     
  9. Choco

    Moderator

    If it were to be <= and >=, then the logic would be correct. I suppose I just forgot an equals sign. What I was intending on checking was if the item was greater than the lowest point of the cauldron, but less than the greatest point of the cauldron. The logic I wrote made sense, but for items that were just on the axis were not included (you provided whole numbers in your example. Use decimals, and it is correct).

    This is what happens when I don't test the code I spoonfeed. (Don't spoonfeed, kids :D).
     
    • Funny Funny x 1
  10. Well - filtering everything heavily increases performance :p
     
  11. Choco

    Moderator

    This is essentially what #getNearbyEntities() does, but it filters through all loaded entities, whereas my method filters through all entities loaded in the chunk that the cauldron is present. Keep in mind that a radius may not be confined to a 16x16 area when calling #getNearbyEntities(). (Also see my edit before you quoted. I kinda explained my reasoning)
    EDIT: Speaking of which, I've edited my initial reply to contain the corrected code