My double-chest code (in-development)

Discussion in 'Resources' started by mattmoss, Jul 6, 2011.

Thread Status:
Not open for further replies.
  1. Offline

    mattmoss

    I'm working on a plugin where I need to handle the possibility of double chests. Rather than muck up the main code, I set about writing a couple of classes using the Chest and Inventory interfaces.

    Here is my initial implementation. Open-source (MIT License), so you may use it as you see fit. At the moment, I've done very limited testing, so I don't know if everything works correctly. If you decide to use/test these classes and find/fix bugs, I'd appreciate a post back here detailing your experiences. Thanks!

    (I may later put this into a Utils package of some sort... but for now, if you add this to your project, you'll want to replace the package com.splatbang.dwarfforge; statement with a package appropriate to your project.)

    File: BetterChest.java
    Code:
    /*
        Copyright (C) 2011 by Matthew D Moss
    
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
    
        The above copyright notice and this permission notice shall be included in
        all copies or substantial portions of the Software.
    
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
        THE SOFTWARE.
    */
    
    package com.splatbang.dwarfforge;
    
    import org.bukkit.Chunk;
    import org.bukkit.Material;
    import org.bukkit.World;
    import org.bukkit.block.Block;
    import org.bukkit.block.BlockFace;
    import org.bukkit.block.Chest;
    import org.bukkit.inventory.Inventory;
    import org.bukkit.material.MaterialData;
    
    
    public class BetterChest implements Chest {
    
        // Methods inherited from BlockState
        // At the moment, these all act upon the reference Chest only.
        public Block getBlock() {
            return ref.getBlock();
        }
    
        public Chunk getChunk() {
            return ref.getChunk();
        }
    
        public MaterialData getData() {
            return ref.getData();
        }
    
        public byte getLightLevel() {
            return ref.getLightLevel();
        }
    
        public byte getRawData() {
            return ref.getRawData();
        }
    
        public Material getType() {
            return ref.getType();
        }
    
        public int getTypeId() {
            return ref.getTypeId();
        }
    
        public World getWorld() {
            return ref.getWorld();
        }
    
        public int getX() {
            return ref.getX();
        }
    
        public int getY() {
            return ref.getY();
        }
    
        public int getZ() {
            return ref.getZ();
        }
    
        public void setData(MaterialData data) {
            ref.setData(data);
        }
    
        public void setType(Material type) {
            ref.setType(type);
        }
    
        public boolean setTypeId(int type) {
            return ref.setTypeId(type);
        }
    
        public boolean update() {
            return ref.update();
        }
    
        public boolean update(boolean force) {
            return ref.update(force);
        }
    
    
        // Methods inherited from ContainerBlock
        public Inventory getInventory() {
            Chest other = attached();
            if (other == null) {
                return ref.getInventory();
            }
            else {
                return new DoubleInventory(ref.getInventory(), other.getInventory());
            }
        }
    
    
        // BetterChest internals
        private static final BlockFace[] FACES = {
            BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST };
    
        private Chest ref;
    
        public BetterChest(Chest ref) {
            this.ref = ref;
        }
    
        private Chest attached() {
            // Find the first adjacent chest. Note: hacking of various sorts/degrees and/or
            // other plugins might allow multiple chests to be adjacent. Deal with that later
            // if it really becomes necessary (and at all possible to detect).
    
            Block block = ref.getBlock();
            for (BlockFace face : FACES) {
                Block other = block.getRelative(face);
                if (other.getType() == Material.CHEST) {
                    return (Chest) other.getState();    // Found it.
                }
            }
            return null;    // No other adjacent chest.
        }
    }
    
    File: DoubleInventory.java
    Code:
    /*
        Copyright (C) 2011 by Matthew D Moss
    
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
    
        The above copyright notice and this permission notice shall be included in
        all copies or substantial portions of the Software.
    
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
        THE SOFTWARE.
    */
    
    package com.splatbang.dwarfforge;
    
    import java.util.Arrays;
    import java.util.HashMap;
    
    import org.bukkit.Material;
    import org.bukkit.inventory.Inventory;
    import org.bukkit.inventory.ItemStack;
    
    
    public class DoubleInventory implements Inventory {
    
        // Methods inherited from Inventory
        public int getSize() {
            return major.getSize() + minor.getSize();
        }
    
        public String getName() {
            return major.getName() + ":" + minor.getName();
        }
    
        public ItemStack getItem(int index) {
            int majorSize = major.getSize();
            if (index < majorSize)
                return major.getItem(index);
            else
                return minor.getItem(index - majorSize);
        }
    
        public void setItem(int index, ItemStack item) {
            int majorSize = major.getSize();
            if (index < majorSize)
                major.setItem(index, item);
            else
                minor.setItem(index - majorSize, item);
        }
    
        public HashMap<Integer, ItemStack> addItem(ItemStack... items) {
            // TODO: Reasonable implementation?
            ItemStack[] remains = null;
            remains = major.addItem(items).values().toArray(remains);
            return minor.addItem(remains);
        }
    
        public HashMap<Integer, ItemStack> removeItem(ItemStack... items) {
            // TODO: Reasonable implementation?
            ItemStack[] remains = null;
            remains = major.removeItem(items).values().toArray(remains);
            return minor.removeItem(remains);
        }
    
        public ItemStack[] getContents() {
            return concat(major.getContents(), minor.getContents());
        }
    
        public void setContents(ItemStack[] items) {
            // TODO: Reasonable implementation?
            int majorSize = major.getSize();
            if (items.length <= majorSize) {
                major.setContents(items);
            }
            else {
                major.setContents(Arrays.copyOfRange(items, 0, majorSize));
                minor.setContents(Arrays.copyOfRange(items, majorSize, items.length - majorSize));
            }
        }
    
        public boolean contains(int materialId) {
            return major.contains(materialId) || minor.contains(materialId);
        }
    
        public boolean contains(Material material) {
            return major.contains(material) || minor.contains(material);
        }
    
        public boolean contains(ItemStack item) {
            return major.contains(item) || minor.contains(item);
        }
    
        public boolean contains(int materialId, int amount) {
            return major.contains(materialId, amount) || minor.contains(materialId, amount);
        }
    
        public boolean contains(Material material, int amount) {
            return major.contains(material, amount) || minor.contains(material, amount);
        }
    
        public boolean contains(ItemStack item, int amount) {
            return major.contains(item, amount) || minor.contains(item, amount);
        }
    
        public HashMap<Integer, ? extends ItemStack> all(int materialId) {
            return combineWithOffset(major.all(materialId), minor.all(materialId), major.getSize());
        }
    
        public HashMap<Integer, ? extends ItemStack> all(Material material) {
            return combineWithOffset(major.all(material), minor.all(material), major.getSize());
        }
    
        public HashMap<Integer, ? extends ItemStack> all(ItemStack item) {
            return combineWithOffset(major.all(item), minor.all(item), major.getSize());
        }
    
        public int first(int materialId) {
            int majorSize = major.getSize();
            int index = major.first(materialId);
            if (index < 0) {
                index = minor.first(materialId);
                if (index >= 0)
                    index += majorSize;
            }
            return index;
        }
    
        public int first(Material material) {
            int majorSize = major.getSize();
            int index = major.first(material);
            if (index < 0) {
                index = minor.first(material);
                if (index >= 0)
                    index += majorSize;
            }
            return index;
        }
    
        public int first(ItemStack item) {
            int majorSize = major.getSize();
            int index = major.first(item);
            if (index < 0) {
                index = minor.first(item);
                if (index >= 0)
                    index += majorSize;
            }
            return index;
        }
    
        public int firstEmpty() {
            int majorSize = major.getSize();
            int index = major.firstEmpty();
            if (index < 0) {
                index = minor.firstEmpty();
                if (index >= 0)
                    index += majorSize;
            }
            return index;
        }
    
        public void remove(int materialId) {
            major.remove(materialId);
            minor.remove(materialId);
        }
    
        public void remove(Material material) {
            major.remove(material);
            minor.remove(material);
        }
    
        public void remove(ItemStack item) {
            major.remove(item);
            minor.remove(item);
        }
    
        public void clear(int index) {
            int majorSize = major.getSize();
            if (index < majorSize)
                major.clear(index);
            else
                minor.clear(index - majorSize);
        }
    
        public void clear() {
            major.clear();
            minor.clear();
        }
    
    
        // DoubleInventory internals
        private Inventory major;
        private Inventory minor;
    
        public DoubleInventory(Inventory major, Inventory minor) {
            this.major = major;
            this.minor = minor;
        }
    
        private static <T> T[] concat(T[] first, T[] second) {
            T[] result = Arrays.copyOf(first, first.length + second.length);
            System.arraycopy(second, 0, result, first.length, second.length);
            return result;
        }
    
        private static <T> HashMap<Integer, ? extends T> combineWithOffset(HashMap<Integer, ? extends T> first,
                                                                           HashMap<Integer, ? extends T> second,
                                                                           int offset) {
            // TODO: Reasonable implementation?
            HashMap<Integer, T> result = new HashMap<Integer, T>(first);
    
            // Put in items from the second map, adjusting key values.
            for (Integer key : second.keySet()) {
                result.put(new Integer(key.intValue() + offset), (T) second.get(key));
            }
    
            return result;
        }
    }
    
    Example usage... I put this code into an onBlockDamage handler, then left-clicked chests on the server and watched the console output.
    Code:
                Block block = event.getBlock();
                if (block.getType() == Material.CHEST) {
                    BetterChest xyz = new BetterChest( (Chest) block.getState() );
                    Inventory inv = xyz.getInventory();
                    log.info("Chest '" + inv.getName() + "' contents:");
                    ItemStack[] items = inv.getContents();
                    for (ItemStack item : items) {
                        log.info("    " + item);
                    }
                }
    
    Fix for functions addItem and removeItem:

    Code:
         public HashMap<Integer, ItemStack> addItem(ItemStack... items) {
             // TODO: Reasonable implementation?
             HashMap<Integer, ItemStack> leftover = major.addItem(items);
             if (leftover != null && !leftover.isEmpty()) {
                 ItemStack[] rest = { }; // not null
                 rest = leftover.values().toArray(rest);
                 leftover = minor.addItem(rest);
             }
             return leftover;
         }
    
         public HashMap<Integer, ItemStack> removeItem(ItemStack... items) {
             // TODO: Reasonable implementation?
             HashMap<Integer, ItemStack> leftover = major.addItem(items);
             if (leftover != null && !leftover.isEmpty()) {
                 ItemStack[] rest = { }; // not null
                 rest = leftover.values().toArray(rest);
                 leftover = minor.removeItem(rest);
             }
             return leftover;
         }
    
    And just to say, with the above two files (incl. fixes in the 2nd post), my plugin was able to support double chests with this change, from:

    Code:
    Chest chest = (Chest) block.getState();
    
    to:

    Code:
    BetterChest chest = new BetterChest( (Chest) block.getState() );
    
    Needed that twice in my code -- no other changes! :D

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 17, 2016
    Mitsugaru, r3Fuze and steaks4uce like this.
  2. Offline

    jeffadkins51

    A triple chest would be cool.
    Im liking this though, good job.
     
  3. Offline

    mattmoss

    Sure... but Minecraft/Bukkit, on its own, doesn't support that. You can't (easily?) lay down a triple chest. Certainly, you can by hacking. During some quick experimenting, I could open a triple chest with GUI issues. Opening a quad-chest crashed my client. D:

    In any case, if a triple- or quad- or greater chest is needed/useful, this would be easy to expand. You are certainly welcome to try! My focus, for now, is just on the double-chest, since I'd like to get the code pretty solid for that case.
     
  4. Offline

    dumptruckman

    Omg, thank you so much for making DoubleInventory! Woot! Now I can make my plugin work exactly how I want! :D
     
  5. Offline

    Freezy

    Thanks this gave me a nice speed boost when fixing double chests for my LWC plugin.
    TidyUp
     
  6. Offline

    mattmoss

    Glad it works for you, @Freezy !
     
  7. Offline

    Freezy

    actually it didn't I just ended up using the "Chest attached(Block block)" function. And added my own code to handle desired double chest behaviour.

    The error I recieved had something to do with ItemStack[] to hashmap conversion, when merging the two inventories.
     
Thread Status:
Not open for further replies.

Share This Page