VirtualInventory Background

This commit is contained in:
NichtStudioCode 2021-07-14 18:14:57 +02:00
parent 2be6a60022
commit 623bb94c5d
6 changed files with 231 additions and 115 deletions

@ -19,7 +19,7 @@ public class InvUI implements Listener {
private final List<Runnable> disableHandlers = new ArrayList<>(); private final List<Runnable> disableHandlers = new ArrayList<>();
private final Plugin plugin; private final Plugin plugin;
public InvUI() { private InvUI() {
plugin = ReflectionUtils.getFieldValue(PLUGIN_CLASS_LOADER_PLUGIN_FIELD, getClass().getClassLoader()); plugin = ReflectionUtils.getFieldValue(PLUGIN_CLASS_LOADER_PLUGIN_FIELD, getClass().getClassLoader());
Bukkit.getPluginManager().registerEvents(this, plugin); Bukkit.getPluginManager().registerEvents(this, plugin);
} }

@ -369,4 +369,16 @@ public interface GUI extends GUIParent {
*/ */
void fillRectangle(int x, int y, int width, @NotNull VirtualInventory virtualInventory, boolean replaceExisting); void fillRectangle(int x, int y, int width, @NotNull VirtualInventory virtualInventory, boolean replaceExisting);
/**
* Fills a rectangle of a {@link VirtualInventory} in this {@link GUI}.
*
* @param x The x coordinate where the rectangle should start
* @param y The y coordinate where the rectangle should start
* @param width The line length of the rectangle. (VirtualInventory does not define a width)
* @param virtualInventory The {@link VirtualInventory} to be put into this {@link GUI}.
* @param background The {@link ItemBuilder} for empty slots of the {@link VirtualInventory}
* @param replaceExisting If existing {@link SlotElement}s should be replaced.
*/
void fillRectangle(int x, int y, int width, @NotNull VirtualInventory virtualInventory, @Nullable ItemBuilder background, boolean replaceExisting);
} }

@ -1,6 +1,7 @@
package de.studiocode.invui.gui; package de.studiocode.invui.gui;
import de.studiocode.invui.item.Item; import de.studiocode.invui.item.Item;
import de.studiocode.invui.item.ItemBuilder;
import de.studiocode.invui.virtualinventory.VirtualInventory; import de.studiocode.invui.virtualinventory.VirtualInventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -13,7 +14,7 @@ public interface SlotElement {
SlotElement getHoldingElement(); SlotElement getHoldingElement();
/** /**
* Contains an Item * Contains an {@link Item}
*/ */
class ItemSlotElement implements SlotElement { class ItemSlotElement implements SlotElement {
@ -40,16 +41,24 @@ public interface SlotElement {
} }
/** /**
* Links to a slot in a virtual inventory * Links to a slot in a {@link VirtualInventory}
*/ */
class VISlotElement implements SlotElement { class VISlotElement implements SlotElement {
private final VirtualInventory virtualInventory; private final VirtualInventory virtualInventory;
private final int slot; private final int slot;
private final ItemBuilder background;
public VISlotElement(VirtualInventory virtualInventory, int slot) { public VISlotElement(VirtualInventory virtualInventory, int slot) {
this.virtualInventory = virtualInventory; this.virtualInventory = virtualInventory;
this.slot = slot; this.slot = slot;
this.background = null;
}
public VISlotElement(VirtualInventory virtualInventory, int slot, ItemBuilder background) {
this.virtualInventory = virtualInventory;
this.slot = slot;
this.background = background;
} }
public VirtualInventory getVirtualInventory() { public VirtualInventory getVirtualInventory() {
@ -60,13 +69,15 @@ public interface SlotElement {
return slot; return slot;
} }
public ItemStack getItemStack() { public ItemBuilder getBackground() {
return virtualInventory.getUnsafeItemStack(slot); return background;
} }
@Override @Override
public ItemStack getItemStack(UUID viewerUUID) { public ItemStack getItemStack(UUID viewerUUID) {
return getItemStack(); ItemStack itemStack = virtualInventory.getUnsafeItemStack(slot);
if (itemStack == null && background != null) itemStack = background.buildFor(viewerUUID);
return itemStack;
} }
@Override @Override
@ -77,7 +88,7 @@ public interface SlotElement {
} }
/** /**
* Links to a slot in another GUI * Links to a slot in another {@link GUI}
*/ */
class LinkedSlotElement implements SlotElement { class LinkedSlotElement implements SlotElement {

@ -11,6 +11,7 @@ import de.studiocode.invui.gui.structure.Structure;
import de.studiocode.invui.item.Item; import de.studiocode.invui.item.Item;
import de.studiocode.invui.item.ItemBuilder; import de.studiocode.invui.item.ItemBuilder;
import de.studiocode.invui.util.ArrayUtils; import de.studiocode.invui.util.ArrayUtils;
import de.studiocode.invui.util.InventoryUtils;
import de.studiocode.invui.util.SlotUtils; import de.studiocode.invui.util.SlotUtils;
import de.studiocode.invui.virtualinventory.VirtualInventory; import de.studiocode.invui.virtualinventory.VirtualInventory;
import de.studiocode.invui.virtualinventory.event.ItemUpdateEvent; import de.studiocode.invui.virtualinventory.event.ItemUpdateEvent;
@ -23,8 +24,11 @@ import de.studiocode.invui.window.impl.merged.split.SplitWindow;
import de.studiocode.invui.window.impl.single.SingleWindow; import de.studiocode.invui.window.impl.single.SingleWindow;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -73,112 +77,143 @@ public abstract class BaseGUI implements GUI {
} else event.setCancelled(true); // Only VISlotElements have allowed interactions } else event.setCancelled(true); // Only VISlotElements have allowed interactions
} }
@SuppressWarnings("deprecation")
private void handleVISlotElementClick(VISlotElement element, InventoryClickEvent event) { private void handleVISlotElementClick(VISlotElement element, InventoryClickEvent event) {
VirtualInventory virtualInventory = element.getVirtualInventory(); // these actions are ignored as they don't modify the inventory
InventoryAction action = event.getAction();
if (action != InventoryAction.CLONE_STACK
&& action != InventoryAction.DROP_ALL_CURSOR
&& action != InventoryAction.DROP_ONE_CURSOR
) {
event.setCancelled(true);
VirtualInventory inventory = element.getVirtualInventory();
int slot = element.getSlot(); int slot = element.getSlot();
Player player = (Player) event.getWhoClicked(); Player player = (Player) event.getWhoClicked();
ItemStack cursor = event.getCursor(); ItemStack cursor = event.getCursor();
if (cursor != null && cursor.getType().isAir()) cursor = null;
ItemStack clicked = event.getCurrentItem(); ItemStack clicked = event.getCurrentItem();
if (clicked != null && clicked.getType().isAir()) clicked = null;
ItemStack technicallyClicked = inventory.getItemStack(slot);
if (inventory.isSynced(slot, clicked) || didClickBackgroundItem(player, element, inventory, slot, clicked)) {
switch (event.getClick()) {
case LEFT:
handleVILeftClick(event, inventory, slot, player, technicallyClicked, cursor);
break;
case RIGHT:
handleVIRightClick(event, inventory, slot, player, technicallyClicked, cursor);
break;
case SHIFT_RIGHT:
case SHIFT_LEFT:
handleVIItemShift(event, inventory, slot, player, technicallyClicked);
break;
case NUMBER_KEY:
handleVINumberKey(event, inventory, slot, player, technicallyClicked);
break;
case SWAP_OFFHAND:
handleVIOffHandKey(event, inventory, slot, player, technicallyClicked);
break;
case DROP:
handleVIDrop(false, event, inventory, slot, player, technicallyClicked);
break;
case CONTROL_DROP:
handleVIDrop(true, event, inventory, slot, player, technicallyClicked);
break;
case DOUBLE_CLICK:
handleVIDoubleClick(event, inventory, player, cursor);
break;
}
}
}
}
private boolean didClickBackgroundItem(Player player, VISlotElement element, VirtualInventory inventory, int slot, ItemStack clicked) {
UUID uuid = player.getUniqueId();
return inventory.getUnsafeItemStack(slot) == null
&& (isBuilderSimilar(background, uuid, clicked) || isBuilderSimilar(element.getBackground(), uuid, clicked));
}
private boolean isBuilderSimilar(ItemBuilder builder, UUID uuid, ItemStack expected) {
return builder != null && builder.buildFor(uuid).isSimilar(expected);
}
@SuppressWarnings("deprecation")
private void handleVILeftClick(InventoryClickEvent event, VirtualInventory inventory, int slot, Player player, ItemStack clicked, ItemStack cursor) {
// nothing happens if both cursor and clicked stack are empty
if (clicked == null && cursor == null) return;
UpdateReason updateReason = new PlayerUpdateReason(player, event); UpdateReason updateReason = new PlayerUpdateReason(player, event);
if (virtualInventory.isSynced(slot, clicked)) { if (cursor == null) {
boolean cancel = false; // if the cursor is empty, pick the stack up
if (inventory.setItemStack(updateReason, slot, null))
switch (event.getAction()) { event.setCursor(clicked);
} else if (clicked == null) {
case CLONE_STACK: // if the clicked slot is empty, place the item on the cursor there
case DROP_ALL_CURSOR: if (inventory.setItemStack(updateReason, slot, cursor))
case DROP_ONE_CURSOR: event.setCursor(null);
// empty, this does not affect anything } else if (cursor.isSimilar(clicked)) {
break; // if the items on the cursor are similar to the clicked ones, add them to the stack
int cursorAmount = cursor.getAmount();
case DROP_ONE_SLOT: int added = inventory.addItemAmount(updateReason, slot, cursorAmount);
case PICKUP_ONE: if (added != 0) {
cancel = virtualInventory.addItemAmount(updateReason, slot, -1) != -1; if (added == cursorAmount) {
break; event.setCursor(null);
} else {
case DROP_ALL_SLOT: cursor.setAmount(cursorAmount - added);
case PICKUP_ALL: event.setCursor(cursor);
cancel = !virtualInventory.setItemStack(updateReason, slot, null); }
// set null }
break; } else if (!cursor.isSimilar(clicked)) {
// if the stacks are not similar, swap them
case PICKUP_HALF: if (inventory.setItemStack(updateReason, slot, cursor))
int amount = virtualInventory.getAmount(slot); event.setCursor(clicked);
int halfAmount = amount / 2;
int newAmount = virtualInventory.setItemAmount(updateReason, slot, halfAmount);
// amount did not change as predicted
if (newAmount != halfAmount) {
cancel = true;
// action wasn't completely cancelled
if (newAmount != amount) {
int cursorAmount = amount - newAmount;
cancel = true;
ItemStack newCursorStack = clicked.clone();
newCursorStack.setAmount(cursorAmount);
event.setCursor(newCursorStack);
} }
} }
break; @SuppressWarnings("deprecation")
private void handleVIRightClick(InventoryClickEvent event, VirtualInventory inventory, int slot, Player player, ItemStack clicked, ItemStack cursor) {
// nothing happens if both cursor and clicked stack are empty
if (clicked == null && cursor == null) return;
case PLACE_SOME: UpdateReason updateReason = new PlayerUpdateReason(player, event);
case PLACE_ALL:
int amountLeft = virtualInventory.putItemStack(updateReason, slot, cursor); if (cursor == null) {
if (amountLeft > 0) { // if the cursor is empty, split the stack to the cursor
cancel = true; // if the stack is not divisible by 2, give the cursor the bigger part
if (amountLeft != cursor.getAmount()) int clickedAmount = clicked.getAmount();
cursor.setAmount(amountLeft); int newClickedAmount = clickedAmount / 2;
int newCursorAmount = clickedAmount - newClickedAmount;
cursor = clicked.clone();
clicked.setAmount(newClickedAmount);
cursor.setAmount(newCursorAmount);
if (inventory.setItemStack(updateReason, slot, clicked))
event.setCursor(cursor);
} else {
// put one item from the cursor in the inventory
ItemStack toAdd = cursor.clone();
toAdd.setAmount(1);
int remains = inventory.putItemStack(updateReason, slot, toAdd);
if (remains == 0) {
cursor.setAmount(cursor.getAmount() - 1);
event.setCursor(cursor);
}
} }
break;
case PLACE_ONE:
ItemStack itemStack = cursor.clone();
itemStack.setAmount(1);
cancel = virtualInventory.putItemStack(updateReason, slot, itemStack) != 0;
break;
case SWAP_WITH_CURSOR:
cancel = !virtualInventory.setItemStack(updateReason, slot, event.getCursor());
break;
case COLLECT_TO_CURSOR:
cancel = true;
ItemStack newCursorStack = cursor.clone();
newCursorStack.setAmount(virtualInventory.collectToCursor(updateReason, newCursorStack));
event.setCursor(newCursorStack);
break;
case MOVE_TO_OTHER_INVENTORY:
cancel = true;
handleMoveToOtherInventory(player, event, virtualInventory, slot, updateReason);
break;
case HOTBAR_MOVE_AND_READD:
case HOTBAR_SWAP:
cancel = handleHotbarSwap(player, event, virtualInventory, slot, updateReason);
break;
default:
// action not supported
cancel = true;
break;
} }
event.setCancelled(cancel); private void handleVIItemShift(InventoryClickEvent event, VirtualInventory inventory, int slot, Player player, ItemStack clicked) {
} else event.setCancelled(true); if (clicked == null) return;
}
private void handleMoveToOtherInventory(Player player, InventoryClickEvent event, VirtualInventory inventory, int slot, UpdateReason reason) { UpdateReason updateReason = new PlayerUpdateReason(player, event);
Window window = WindowManager.getInstance().findOpenWindow(player).orElse(null); Window window = WindowManager.getInstance().findOpenWindow(player).orElse(null);
ItemUpdateEvent updateEvent = inventory.callUpdateEvent(updateReason, slot, clicked, null);
ItemStack invStack = inventory.getItemStack(slot);
ItemUpdateEvent updateEvent = inventory.callUpdateEvent(reason, slot, invStack, null);
if (!updateEvent.isCancelled()) { if (!updateEvent.isCancelled()) {
int leftOverAmount; int leftOverAmount;
@ -192,29 +227,71 @@ public abstract class BaseGUI implements GUI {
otherGui = this; otherGui = this;
} }
leftOverAmount = ((BaseGUI) otherGui).putIntoVirtualInventories(reason, invStack, inventory); leftOverAmount = ((BaseGUI) otherGui).putIntoVirtualInventories(updateReason, clicked, inventory);
} else { } else {
leftOverAmount = 0; leftOverAmount = 0;
HashMap<Integer, ItemStack> leftover = event.getWhoClicked().getInventory().addItem(inventory.getItemStack(slot)); HashMap<Integer, ItemStack> leftover = event.getWhoClicked().getInventory().addItem(inventory.getItemStack(slot));
if (!leftover.isEmpty()) leftOverAmount = leftover.get(0).getAmount(); if (!leftover.isEmpty()) leftOverAmount = leftover.get(0).getAmount();
} }
invStack.setAmount(leftOverAmount); clicked.setAmount(leftOverAmount);
inventory.setItemStackSilently(slot, invStack); inventory.setItemStackSilently(slot, clicked);
} }
} }
private boolean handleHotbarSwap(Player player, InventoryClickEvent event, VirtualInventory inventory, int slot, UpdateReason reason) { // TODO: add support for merged windows
private void handleVINumberKey(InventoryClickEvent event, VirtualInventory inventory, int slot, Player player, ItemStack clicked) {
Window window = WindowManager.getInstance().findOpenWindow(player).orElse(null); Window window = WindowManager.getInstance().findOpenWindow(player).orElse(null);
if (window instanceof SingleWindow) { if (window instanceof SingleWindow) {
Inventory playerInventory = player.getInventory();
int hotbarButton = event.getHotbarButton(); int hotbarButton = event.getHotbarButton();
ItemStack hotbarItem = player.getInventory().getItem(hotbarButton); ItemStack hotbarItem = playerInventory.getItem(hotbarButton);
if (hotbarItem != null) hotbarItem = hotbarItem.clone(); if (hotbarItem != null) hotbarItem = hotbarItem.clone();
return !inventory.setItemStack(reason, slot, hotbarItem); UpdateReason updateReason = new PlayerUpdateReason(player, event);
} // TODO: add support for merged windows
return true; if (inventory.setItemStack(updateReason, slot, hotbarItem))
playerInventory.setItem(hotbarButton, clicked);
}
}
// TODO: add support for merged windows
private void handleVIOffHandKey(InventoryClickEvent event, VirtualInventory inventory, int slot, Player player, ItemStack clicked) {
Window window = WindowManager.getInstance().findOpenWindow(player).orElse(null);
if (window instanceof SingleWindow) {
PlayerInventory playerInventory = player.getInventory();
ItemStack offhandItem = playerInventory.getItemInOffHand();
UpdateReason updateReason = new PlayerUpdateReason(player, event);
if (inventory.setItemStack(updateReason, slot, offhandItem))
playerInventory.setItemInOffHand(clicked);
}
}
private void handleVIDrop(boolean ctrl, InventoryClickEvent event, VirtualInventory inventory, int slot, Player player, ItemStack clicked) {
if (clicked == null) return;
UpdateReason updateReason = new PlayerUpdateReason(player, event);
if (ctrl) {
if (inventory.setItemStack(updateReason, slot, null)) {
InventoryUtils.dropItemLikePlayer(player, clicked);
}
} else if (inventory.addItemAmount(updateReason, slot, -1) == -1) {
clicked.setAmount(1);
InventoryUtils.dropItemLikePlayer(player, clicked);
}
}
@SuppressWarnings("deprecation")
private void handleVIDoubleClick(InventoryClickEvent event, VirtualInventory inventory, Player player, ItemStack cursor) {
if (cursor == null) return;
UpdateReason updateReason = new PlayerUpdateReason(player, event);
cursor.setAmount(inventory.collectToCursor(updateReason, cursor));
event.setCursor(cursor);
} }
@Override @Override
@ -586,13 +663,18 @@ public abstract class BaseGUI implements GUI {
@Override @Override
public void fillRectangle(int x, int y, int width, @NotNull VirtualInventory virtualInventory, boolean replaceExisting) { public void fillRectangle(int x, int y, int width, @NotNull VirtualInventory virtualInventory, boolean replaceExisting) {
fillRectangle(x, y, width, virtualInventory, null, replaceExisting);
}
@Override
public void fillRectangle(int x, int y, int width, @NotNull VirtualInventory virtualInventory, @Nullable ItemBuilder background, boolean replaceExisting) {
int height = (int) Math.ceil((double) virtualInventory.getSize() / (double) width); int height = (int) Math.ceil((double) virtualInventory.getSize() / (double) width);
int slotIndex = 0; int slotIndex = 0;
for (int slot : SlotUtils.getSlotsRect(x, y, width, height, this.width)) { for (int slot : SlotUtils.getSlotsRect(x, y, width, height, this.width)) {
if (slotIndex >= virtualInventory.getSize()) return; if (slotIndex >= virtualInventory.getSize()) return;
if (hasSlotElement(slot) && !replaceExisting) continue; if (hasSlotElement(slot) && !replaceExisting) continue;
setSlotElement(slot, new VISlotElement(virtualInventory, slotIndex)); setSlotElement(slot, new VISlotElement(virtualInventory, slotIndex, background));
slotIndex++; slotIndex++;
} }
} }

@ -2,6 +2,9 @@ package de.studiocode.invui.util;
import de.studiocode.invui.gui.GUI; import de.studiocode.invui.gui.GUI;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -32,4 +35,12 @@ public class InventoryUtils {
return false; return false;
} }
public static void dropItemLikePlayer(Player player, ItemStack itemStack) {
Location location = player.getLocation();
location.add(0, 1.5, 0); // not the eye location
Item item = location.getWorld().dropItem(location, itemStack);
item.setPickupDelay(40);
item.setVelocity(location.getDirection().multiply(0.35));
}
} }

@ -56,7 +56,7 @@ public abstract class BaseWindow implements Window {
protected void redrawItem(int index, SlotElement element, boolean setItem) { protected void redrawItem(int index, SlotElement element, boolean setItem) {
// put ItemStack in inventory // put ItemStack in inventory
ItemStack itemStack; ItemStack itemStack;
if (element == null) { if (element == null || (element instanceof VISlotElement && element.getItemStack(viewerUUID) == null)) {
ItemBuilder background = getGuiAt(index).getFirst().getBackground(); ItemBuilder background = getGuiAt(index).getFirst().getBackground();
itemStack = background == null ? null : background.buildFor(viewerUUID); itemStack = background == null ? null : background.buildFor(viewerUUID);
} else itemStack = element.getItemStack(viewerUUID); } else itemStack = element.getItemStack(viewerUUID);