diff --git a/common/src/main/java/com/lishid/openinv/util/Cache.java b/common/src/main/java/com/lishid/openinv/util/Cache.java index 155bcf1..fff4b09 100644 --- a/common/src/main/java/com/lishid/openinv/util/Cache.java +++ b/common/src/main/java/com/lishid/openinv/util/Cache.java @@ -19,11 +19,11 @@ package com.lishid.openinv.util; import com.google.common.collect.Multimap; import com.google.common.collect.TreeMultimap; import java.util.ArrayList; -import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; /** * A minimal thread-safe time-based cache implementation backed by a HashMap and TreeMultimap. @@ -45,20 +45,9 @@ public class Cache { * @param postRemoval Function used to perform any operations required when a key is invalidated */ public Cache(final long retention, final Function inUseCheck, final Function postRemoval) { - this.internal = new HashMap(); + this.internal = new HashMap<>(); - this.expiry = TreeMultimap.create(new Comparator() { - @Override - public int compare(final Long long1, final Long long2) { - return long1.compareTo(long2); - } - }, - new Comparator() { - @Override - public int compare(final K k1, final K k2) { - return k1 == k2 || k1 != null && k1.equals(k2) ? 0 : 1; - } - }); + this.expiry = TreeMultimap.create(Long::compareTo, (k1, k2) -> Objects.equals(k1, k2) ? 0 : 1); this.retention = retention; this.inUseCheck = inUseCheck; @@ -160,7 +149,7 @@ public class Cache { private void lazyCheck() { long now = System.currentTimeMillis(); synchronized (this.internal) { - List inUse = new ArrayList(); + List inUse = new ArrayList<>(); for (Iterator> iterator = this.expiry.entries().iterator(); iterator .hasNext();) { Map.Entry entry = iterator.next(); diff --git a/common/src/main/java/com/lishid/openinv/util/Pair.java b/common/src/main/java/com/lishid/openinv/util/Pair.java new file mode 100644 index 0000000..81f080a --- /dev/null +++ b/common/src/main/java/com/lishid/openinv/util/Pair.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2011-2019 lishid. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lishid.openinv.util; + +/** + * Simple tuple. + * + * @param the left value + * @param the right value + * + * @author Jikoo + */ +public class Pair { + + private L left; + private R right; + + public Pair(L left, R right) { + this.left = left; + this.right = right; + } + + public L getLeft() { + return left; + } + + public void setLeft(L left) { + this.left = left; + } + + public R getRight() { + return right; + } + + public void setRight(R right) { + this.right = right; + } + +} diff --git a/common/src/main/java/com/lishid/openinv/util/SingleFieldList.java b/common/src/main/java/com/lishid/openinv/util/SingleFieldList.java new file mode 100644 index 0000000..58e910d --- /dev/null +++ b/common/src/main/java/com/lishid/openinv/util/SingleFieldList.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2011-2019 lishid. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lishid.openinv.util; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; +import org.jetbrains.annotations.NotNull; + +/** + * A List implementation intended for wrapping a single field. + * + * @param the type of the field + * + * @author Jikoo + */ +public class SingleFieldList extends AbstractCollection implements List { + + private final Supplier fieldGetter; + private final Consumer fieldSetter; + + public SingleFieldList(@NotNull Supplier fieldGetter, @NotNull Consumer fieldSetter) { + this.fieldGetter = fieldGetter; + this.fieldSetter = fieldSetter; + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean contains(Object o) { + return Objects.equals(o, fieldGetter.get()); + } + + @NotNull + @Override + public Iterator iterator() { + return listIterator(); + } + + @Override + public boolean addAll(int index, @NotNull Collection c) { + return super.addAll(c); + } + + @Override + public boolean equals(Object o) { + return o instanceof SingleFieldList + && fieldGetter.equals(((SingleFieldList) o).fieldGetter) + && fieldSetter.equals(((SingleFieldList) o).fieldSetter); + } + + @Override + public int hashCode() { + return fieldSetter.hashCode() * 17 * fieldGetter.hashCode(); + } + + @Override + public V get(int index) { + if (index != 0) { + throw new IndexOutOfBoundsException(); + } + return fieldGetter.get(); + } + + @Override + public V set(int index, V element) { + if (index != 0) { + throw new IndexOutOfBoundsException(); + } + + V old = fieldGetter.get(); + fieldSetter.accept(element); + + return old; + } + + @Override + public void add(int index, V element) { + throw new UnsupportedOperationException(); + } + + @Override + public V remove(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public int indexOf(Object o) { + return fieldGetter.get().equals(o) ? 0 : -1; + } + + @Override + public int lastIndexOf(Object o) { + return indexOf(o); + } + + @NotNull + @Override + public ListIterator listIterator() { + return new ListIterator() { + private boolean hasNext = true; + + @Override + public boolean hasNext() { + return hasNext; + } + + @Override + public V next() { + if (!hasNext) { + throw new NoSuchElementException(); + } + return fieldGetter.get(); + } + + @Override + public boolean hasPrevious() { + return !hasNext; + } + + @Override + public V previous() { + if (hasNext) { + throw new NoSuchElementException(); + } + return fieldGetter.get(); + } + + @Override + public int nextIndex() { + return hasNext ? 0 : 1; + } + + @Override + public int previousIndex() { + return hasNext ? -1 : 0; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public void set(V v) { + fieldSetter.accept(v); + } + + @Override + public void add(V v) { + throw new UnsupportedOperationException(); + } + }; + } + + @NotNull + @Override + public ListIterator listIterator(int index) { + if (index != 0) { + throw new IndexOutOfBoundsException(); + } + return listIterator(); + } + + @NotNull + @Override + public List subList(int fromIndex, int toIndex) { + if (fromIndex != 0 || toIndex != 1) { + throw new IndexOutOfBoundsException(); + } + + return this; + } + + @Override + public void clear() {} + +} diff --git a/internal/v1_14_R1/src/main/java/com/lishid/openinv/internal/v1_14_R1/PlayerDataManager.java b/internal/v1_14_R1/src/main/java/com/lishid/openinv/internal/v1_14_R1/PlayerDataManager.java index e79af66..fdaab64 100644 --- a/internal/v1_14_R1/src/main/java/com/lishid/openinv/internal/v1_14_R1/PlayerDataManager.java +++ b/internal/v1_14_R1/src/main/java/com/lishid/openinv/internal/v1_14_R1/PlayerDataManager.java @@ -100,6 +100,7 @@ public class PlayerDataManager implements IPlayerDataManager { String title; if (inventory instanceof SpecialEnderChest) { HumanEntity owner = (HumanEntity) ((SpecialEnderChest) inventory).getBukkitOwner(); + //noinspection ConstantConditions // Owner name can be null when loaded under certain conditions. title = (owner.getName() != null ? owner.getName() : owner.getUniqueId().toString()) + "'s Ender Chest"; } else if (inventory instanceof SpecialPlayerInventory) { EntityHuman owner = ((PlayerInventory) inventory).player; @@ -142,9 +143,9 @@ public class PlayerDataManager implements IPlayerDataManager { return Containers.GENERIC_9X3; case 36: return Containers.GENERIC_9X4; - case 41: // PLAYER case 45: return Containers.GENERIC_9X5; + case 41: // PLAYER case 54: return Containers.GENERIC_9X6; } diff --git a/internal/v1_14_R1/src/main/java/com/lishid/openinv/internal/v1_14_R1/SpecialPlayerInventory.java b/internal/v1_14_R1/src/main/java/com/lishid/openinv/internal/v1_14_R1/SpecialPlayerInventory.java index e81b99c..e1f0aff 100644 --- a/internal/v1_14_R1/src/main/java/com/lishid/openinv/internal/v1_14_R1/SpecialPlayerInventory.java +++ b/internal/v1_14_R1/src/main/java/com/lishid/openinv/internal/v1_14_R1/SpecialPlayerInventory.java @@ -18,6 +18,8 @@ package com.lishid.openinv.internal.v1_14_R1; import com.google.common.collect.ImmutableList; import com.lishid.openinv.internal.ISpecialPlayerInventory; +import com.lishid.openinv.util.Pair; +import com.lishid.openinv.util.SingleFieldList; import java.util.Iterator; import java.util.List; import java.util.function.Predicate; @@ -37,7 +39,6 @@ import net.minecraft.server.v1_14_R1.ItemArmor; import net.minecraft.server.v1_14_R1.ItemStack; import net.minecraft.server.v1_14_R1.NBTTagCompound; import net.minecraft.server.v1_14_R1.NBTTagList; -import net.minecraft.server.v1_14_R1.NonNullList; import net.minecraft.server.v1_14_R1.PacketPlayOutSetSlot; import net.minecraft.server.v1_14_R1.PlayerInventory; import net.minecraft.server.v1_14_R1.ReportedException; @@ -45,6 +46,7 @@ import net.minecraft.server.v1_14_R1.World; import org.bukkit.Location; import org.bukkit.craftbukkit.v1_14_R1.entity.CraftHumanEntity; import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftInventoryCrafting; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.InventoryHolder; @@ -56,8 +58,10 @@ public class SpecialPlayerInventory extends PlayerInventory implements ISpecialP private final CraftInventory inventory; private boolean playerOnline; private EntityHuman player; - private NonNullList items, armor, extraSlots; - private List> f; + private List items, armor, extraSlots, crafting; + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") // Backing field is mutable. + private final List cursor = new SingleFieldList<>(this::getCarried, this::setCarried); + private List> f; public SpecialPlayerInventory(final Player bukkitPlayer, final Boolean online) { super(PlayerDataManager.getHandle(bukkitPlayer)); @@ -67,7 +71,8 @@ public class SpecialPlayerInventory extends PlayerInventory implements ISpecialP this.items = this.player.inventory.items; this.armor = this.player.inventory.armor; this.extraSlots = this.player.inventory.extraSlots; - this.f = ImmutableList.of(this.items, this.armor, this.extraSlots); + this.crafting = ((CraftInventoryCrafting) this.player.defaultContainer.getBukkitView().getTopInventory()).getInventory().getContents(); + this.f = ImmutableList.of(this.items, this.armor, this.extraSlots, this.crafting, this.cursor); } @Override @@ -79,11 +84,13 @@ public class SpecialPlayerInventory extends PlayerInventory implements ISpecialP for (int i = 0; i < getSize(); ++i) { this.player.inventory.setItem(i, getRawItem(i)); } + // Crafting/cursor are not insertable while player is offline and do not need special treatment. this.player.inventory.itemInHandIndex = this.itemInHandIndex; this.items = this.player.inventory.items; this.armor = this.player.inventory.armor; this.extraSlots = this.player.inventory.extraSlots; - this.f = ImmutableList.of(this.items, this.armor, this.extraSlots); + this.crafting = ((CraftInventoryCrafting) this.player.defaultContainer.getBukkitView().getTopInventory()).getInventory().getContents(); + this.f = ImmutableList.of(this.items, this.armor, this.extraSlots, this.crafting, this.cursor); this.playerOnline = true; } } @@ -124,8 +131,8 @@ public class SpecialPlayerInventory extends PlayerInventory implements ISpecialP } private ItemStack getRawItem(int i) { - NonNullList list = null; - for (NonNullList next : this.f) { + List list = null; + for (List next : this.f) { if (i < next.size()) { list = next; break; @@ -171,7 +178,7 @@ public class SpecialPlayerInventory extends PlayerInventory implements ISpecialP @Override public int getSize() { - return 45; + return 54; } @Override @@ -179,30 +186,35 @@ public class SpecialPlayerInventory extends PlayerInventory implements ISpecialP return !this.getViewers().isEmpty(); } - @Override - public void setItem(int i, final ItemStack itemstack) { - List list = this.items; - - if (i >= list.size()) { + private Pair, Integer> getLocalizedIndex(int i) { + List localList = null; + for (List list : this.f) { + if (i < list.size()) { + localList = list; + break; + } i -= list.size(); - list = this.armor; - } else { + } + + if (localList == this.armor) { + i = this.getReversedArmorSlotNum(i); + } else if (localList == this.items) { i = this.getReversedItemSlotNum(i); } - if (i >= list.size()) { - i -= list.size(); - list = this.extraSlots; - } else if (list == this.armor) { - i = this.getReversedArmorSlotNum(i); - } + return new Pair<>(localList, i); + } - if (i >= list.size()) { + @Override + public void setItem(int i, final ItemStack itemstack) { + Pair, Integer> localizedIndex = getLocalizedIndex(i); + if (localizedIndex.getLeft() == null + // TODO: should this be a constant instead of comparing to transient slot containers? + || !playerOnline && (localizedIndex.getLeft() == crafting || localizedIndex.getLeft() == cursor)) { this.player.drop(itemstack, true); - return; + } else { + localizedIndex.getLeft().set(localizedIndex.getRight(), itemstack); } - - list.set(i, itemstack); } @Override @@ -212,51 +224,26 @@ public class SpecialPlayerInventory extends PlayerInventory implements ISpecialP @Override public ItemStack splitStack(int i, final int j) { - List list = this.items; + Pair, Integer> localizedIndex = getLocalizedIndex(i); - if (i >= list.size()) { - i -= list.size(); - list = this.armor; - } else { - i = this.getReversedItemSlotNum(i); - } - - if (i >= list.size()) { - i -= list.size(); - list = this.extraSlots; - } else if (list == this.armor) { - i = this.getReversedArmorSlotNum(i); - } - - if (i >= list.size()) { + if (localizedIndex.getLeft() == null) { return ItemStack.a; } - return list.get(i).isEmpty() ? ItemStack.a : ContainerUtil.a(list, i, j); + return localizedIndex.getLeft().get(i).isEmpty() ? ItemStack.a : ContainerUtil.a(localizedIndex.getLeft(), localizedIndex.getRight(), j); } @Override public ItemStack splitWithoutUpdate(int i) { - List list = this.items; + Pair, Integer> localizedIndex = getLocalizedIndex(i); - if (i >= list.size()) { - i -= list.size(); - list = this.armor; - } else { - i = this.getReversedItemSlotNum(i); - } - - if (i >= list.size()) { - i -= list.size(); - list = this.extraSlots; - } else if (list == this.armor) { - i = this.getReversedArmorSlotNum(i); - } - - if (i >= list.size()) { + if (localizedIndex.getLeft() == null) { return ItemStack.a; } + List list = localizedIndex.getLeft(); + i = localizedIndex.getRight(); + if (!list.get(i).isEmpty()) { ItemStack itemstack = list.get(i); @@ -326,7 +313,7 @@ public class SpecialPlayerInventory extends PlayerInventory implements ISpecialP } if (!this.a(itemstack, itemstack1)) { - remains -= (itemstack1.getMaxStackSize() < this.getMaxStackSize() ? itemstack1.getMaxStackSize() : this.getMaxStackSize()) - itemstack1.getCount(); + remains -= Math.min(itemstack1.getMaxStackSize(), this.getMaxStackSize()) - itemstack1.getCount(); } if (remains <= 0) {