ItemUpdateEvent

Created and implemented ItemUpdateEvent
Added javadoc to VirtualInventory
This commit is contained in:
NichtStudioCode 2021-01-25 18:08:49 +01:00
parent a4bdf57360
commit ea35d94dda
4 changed files with 500 additions and 128 deletions

@ -8,6 +8,8 @@ import java.util.UUID;
public interface SlotElement {
ItemStackHolder getItemStackHolder();
interface ItemStackHolder {
ItemStack getItemStack(UUID viewerUUID);
@ -34,6 +36,11 @@ public interface SlotElement {
return item.getItemBuilder().buildFor(viewerUUID);
}
@Override
public ItemStackHolder getItemStackHolder() {
return this;
}
}
/**
@ -66,6 +73,11 @@ public interface SlotElement {
return getItemStack();
}
@Override
public ItemStackHolder getItemStackHolder() {
return this;
}
}
/**
@ -90,19 +102,15 @@ public interface SlotElement {
return slot;
}
public SlotElement getBottomSlotElement() {
public ItemStackHolder getItemStackHolder() {
LinkedSlotElement element = this;
while (true) {
SlotElement below = element.getGui().getSlotElement(element.getSlotIndex());
if (below instanceof LinkedSlotElement) element = (LinkedSlotElement) below;
else return below;
else return (ItemStackHolder) below;
}
}
public ItemStackHolder getItemStackHolder() {
return (ItemStackHolder) getBottomSlotElement();
}
}
}

@ -15,9 +15,7 @@ import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.*;
abstract class IndexedGUI implements GUI {
@ -48,10 +46,13 @@ abstract class IndexedGUI implements GUI {
VirtualInventory virtualInventory = element.getVirtualInventory();
int index = element.getIndex();
Player player = (Player) event.getWhoClicked();
ItemStack cursor = event.getCursor();
ItemStack clicked = event.getCurrentItem();
if (virtualInventory.isSynced(index, clicked)) {
boolean cancelled = false;
switch (event.getAction()) {
case CLONE_STACK:
@ -62,28 +63,28 @@ abstract class IndexedGUI implements GUI {
case DROP_ONE_SLOT:
case PICKUP_ONE:
virtualInventory.removeOne(index);
cancelled = virtualInventory.removeOne(player, index);
break;
case DROP_ALL_SLOT:
case PICKUP_ALL:
virtualInventory.removeItem(index);
cancelled = virtualInventory.removeItem(player, index);
break;
case PICKUP_HALF:
virtualInventory.removeHalf(index);
cancelled = virtualInventory.removeHalf(player, index);
break;
case PLACE_ALL:
virtualInventory.place(index, cursor);
cancelled = virtualInventory.place(player, index, cursor);
break;
case PLACE_ONE:
virtualInventory.placeOne(index, cursor);
cancelled = virtualInventory.placeOne(player, index, cursor);
break;
case PLACE_SOME:
virtualInventory.setMaxAmount(index);
cancelled = virtualInventory.setToMaxAmount(player, index);
break;
case MOVE_TO_OTHER_INVENTORY:
@ -93,7 +94,8 @@ abstract class IndexedGUI implements GUI {
event.getWhoClicked().getInventory().addItem(virtualInventory.getItemStack(index));
if (!leftover.isEmpty()) leftOverAmount = leftover.get(0).getAmount();
virtualInventory.setAmount(index, leftOverAmount);
// TODO: find a way to cancel at this point
virtualInventory.setAmount(player, index, leftOverAmount, true);
break;
default:
@ -101,6 +103,8 @@ abstract class IndexedGUI implements GUI {
event.setCancelled(true);
break;
}
if (cancelled) event.setCancelled(true);
} else event.setCancelled(true);
}
@ -108,6 +112,7 @@ abstract class IndexedGUI implements GUI {
public void handleItemShift(InventoryClickEvent event) {
event.setCancelled(true);
Player player = (Player) event.getWhoClicked();
ItemStack clicked = event.getCurrentItem();
List<VirtualInventory> virtualInventories = getAllVirtualInventories();
@ -116,7 +121,7 @@ abstract class IndexedGUI implements GUI {
for (VirtualInventory virtualInventory : virtualInventories) {
ItemStack toAdd = clicked.clone();
toAdd.setAmount(amountLeft);
amountLeft = virtualInventory.addItem(toAdd);
amountLeft = virtualInventory.addItem(player, toAdd);
if (amountLeft == 0) break;
}
@ -128,10 +133,11 @@ abstract class IndexedGUI implements GUI {
private List<VirtualInventory> getAllVirtualInventories() {
List<VirtualInventory> virtualInventories = new ArrayList<>();
ArrayUtils
.findAllOccurrences(slotElements, element -> element instanceof VISlotElement)
.values().stream()
.map(element -> ((VISlotElement) element).getVirtualInventory())
Arrays.stream(slotElements)
.filter(Objects::nonNull)
.map(SlotElement::getItemStackHolder)
.filter(holder -> holder instanceof VISlotElement)
.map(holder -> ((VISlotElement) holder).getVirtualInventory())
.forEach(vi -> {
if (!virtualInventories.contains(vi)) virtualInventories.add(vi);
});
@ -174,12 +180,8 @@ abstract class IndexedGUI implements GUI {
SlotElement slotElement = slotElements[index];
if (slotElement != null) {
if (slotElement instanceof ItemSlotElement) {
return ((ItemSlotElement) slotElement).getItem();
} else if (slotElement instanceof LinkedSlotElement) {
SlotElement bottom = ((LinkedSlotElement) slotElement).getBottomSlotElement();
if (bottom instanceof ItemSlotElement) return ((ItemSlotElement) bottom).getItem();
}
ItemStackHolder holder = slotElement.getItemStackHolder();
if (holder instanceof ItemSlotElement) return ((ItemSlotElement) holder).getItem();
}
return null;
@ -188,11 +190,7 @@ abstract class IndexedGUI implements GUI {
@Override
public ItemStackHolder getItemStackHolder(int index) {
SlotElement slotElement = slotElements[index];
if (slotElement instanceof ItemStackHolder) {
return (ItemStackHolder) slotElement;
} else if (slotElement instanceof LinkedSlotElement) {
return ((LinkedSlotElement) slotElement).getItemStackHolder();
} else return null;
return slotElement == null ? null : slotElement.getItemStackHolder();
}
@Override

@ -2,9 +2,12 @@ package de.studiocode.invgui.virtualinventory;
import de.studiocode.invgui.InvGui;
import de.studiocode.invgui.util.ArrayUtils;
import de.studiocode.invgui.util.Pair;
import de.studiocode.invgui.virtualinventory.event.ItemUpdateEvent;
import de.studiocode.invgui.window.Window;
import org.bukkit.Bukkit;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -19,45 +22,318 @@ public class VirtualInventory implements ConfigurationSerializable {
private int size;
private ItemStack[] items;
/**
* Creates a new {@link VirtualInventory}.
*
* @param uuid The {@link UUID} this {@link VirtualInventory} should have.
* Can be null, only used for serialization.
* @param size The size of the {@link VirtualInventory} ( size > 0 )
* @param items An array of {@link ItemStack} which reflects the contents of this
* {@link VirtualInventory}, therefore the length of that array has
* to be the same as <code>size</code>.
*/
public VirtualInventory(@Nullable UUID uuid, int size, @NotNull ItemStack[] items) {
if (size < 1) throw new IllegalArgumentException("size cannot be smaller than 1");
if (items.length != size) throw new IllegalArgumentException("items length has to be the same as size");
this.uuid = uuid;
this.size = size;
this.items = items;
}
/**
* Creates a new {@link VirtualInventory}.
*
* @param uuid The {@link UUID} this {@link VirtualInventory} should have.
* Can be null, only used for serialization.
* @param size The size of the {@link VirtualInventory}
*/
public VirtualInventory(@Nullable UUID uuid, int size) {
this(uuid, size, new ItemStack[size]);
}
/**
* Deserializes to {@link VirtualInventory}
*
* @param args The args which contain the data to deserialize
* @return The deserialized {@link VirtualInventory}
*/
public static VirtualInventory deserialize(@NotNull Map<String, Object> args) {
//noinspection unchecked
return new VirtualInventory(UUID.fromString((String) args.get("uuid")),
(int) args.get("size"), ((ArrayList<ItemStack>) args.get("items")).toArray(new ItemStack[0]));
}
/**
* Gets the size of this {@link VirtualInventory}.
*
* @return The size of this {@link VirtualInventory}
*/
public int getSize() {
return size;
}
/**
* Changes the size of this {@link VirtualInventory}, removing
* existing {@link ItemStack}s reduced.
*
* @param size The new size of this {@link VirtualInventory}
*/
public void resize(int size) {
this.size = size;
this.items = Arrays.copyOf(items, size);
}
/**
* Checks if the {@link ItemStack} on that slot index is the same
* as the assumed {@link ItemStack} provided as parameter.
*
* @param index The slot index
* @param assumedStack The assumed {@link ItemStack}
* @return If the {@link ItemStack} on that slot is the same as the assumed {@link ItemStack}
*/
public boolean isSynced(int index, ItemStack assumedStack) {
ItemStack actualStack = items[index];
return (actualStack == null && assumedStack == null)
|| (actualStack != null && actualStack.equals(assumedStack));
}
public int addItem(ItemStack itemStack) {
/**
* Sets an {@link ItemStack} on a specific slot.
*
* @param index The slot index
* @param itemStack The {@link ItemStack} that should be put on that slot
*/
public void setItem(int index, ItemStack itemStack) {
items[index] = itemStack.clone();
notifyWindows();
}
/**
* Gets the {@link ItemStack} on a specific slot.
*
* @param index The slot index
* @return The {@link ItemStack} on that slot
*/
public ItemStack getItemStack(int index) {
return items[index];
}
/**
* Checks if there is an {@link ItemStack} on a specific slot.
*
* @param index The slot index
* @return If there is an {@link ItemStack} on that slot
*/
public boolean hasItem(int index) {
return items[index] != null;
}
/**
* Sets an {@link ItemStack} on a specific slot or adds the amount
* if there already is an {@link ItemStack} on that slot.
*
* @param player The player that did this or <code>null</code> if it wasn't a player.
* @param index The slot index
* @param itemStack The {@link ItemStack} to place
* @return If the action has been cancelled
*/
public boolean place(Player player, int index, ItemStack itemStack) {
ItemStack there = items[index];
int currentAmount = there == null ? 0 : there.getAmount();
ItemUpdateEvent event = createAndCallEvent(player, itemStack, index, currentAmount,
currentAmount + itemStack.getAmount());
if (!event.isCancelled()) {
if (there == null) {
setItem(index, itemStack);
} else {
there.setAmount(currentAmount + itemStack.getAmount());
notifyWindows();
}
return false;
}
return true;
}
/**
* Puts on of an {@link ItemStack} on a specific slots or adds one
* if there is already an {@link ItemStack} on that slot.
*
* @param player The player that did this or <code>null</code> if it wasn't a player.
* @param index The slot index
* @param itemStack The {@link ItemStack} to place one of
* @return If the action has been cancelled
*/
public boolean placeOne(Player player, int index, ItemStack itemStack) {
ItemStack there = items[index];
int currentAmount = there == null ? 0 : there.getAmount();
ItemUpdateEvent event = createAndCallEvent(player, itemStack, index,
currentAmount, currentAmount + 1);
if (!event.isCancelled()) {
if (there == null) {
ItemStack single = itemStack.clone();
single.setAmount(1);
setItem(index, single);
} else {
there.setAmount(currentAmount + 1);
notifyWindows();
}
return false;
}
return true;
}
/**
* Changes the amount of an {@link ItemStack} on a specific slot.
*
* @param player The player that did this or <code>null</code> if it wasn't a player.
* @param index The slot index
* @param amount The new amount
* @return If the action has been cancelled
*/
public boolean setAmount(Player player, int index, int amount, boolean ignoreCancelled) {
ItemStack itemStack = items[index];
if (itemStack != null) {
int currentAmount = itemStack.getAmount();
ItemUpdateEvent event = createAndCallEvent(player, itemStack, index, currentAmount, amount);
if (!event.isCancelled() || ignoreCancelled) {
if (amount == 0) items[index] = null;
else itemStack.setAmount(amount);
notifyWindows();
return false;
}
}
return true;
}
/**
* Changes the amount of an {@link ItemStack} on a specific slot
* to the {@link ItemStack}'s {@link ItemStack#getMaxStackSize()}.
*
* @param player The player that did this or <code>null</code> if it wasn't a player.
* @param index The slot index
* @return If the action has been cancelled
*/
public boolean setToMaxAmount(Player player, int index) {
ItemStack itemStack = items[index];
if (itemStack != null) {
int currentAmount = itemStack.getAmount();
int newAmount = itemStack.getMaxStackSize();
ItemUpdateEvent event = createAndCallEvent(player, itemStack, index,
currentAmount, newAmount);
if (!event.isCancelled()) {
itemStack.setAmount(newAmount);
notifyWindows();
} else return true;
}
return false;
}
/**
* Removes an {@link ItemStack} on a specific slot from
* the {@link VirtualInventory}.
*
* @param player The player that did this or <code>null</code> if it wasn't a player.
* @param index The slot index
* @return If the action has been cancelled
*/
public boolean removeItem(Player player, int index) {
ItemStack itemStack = items[index];
if (itemStack != null) {
ItemUpdateEvent event =createAndCallEvent(player, itemStack, index,
itemStack.getAmount(), 0);
if (!event.isCancelled()) {
items[index] = null;
notifyWindows();
} else return true;
}
return false;
}
/**
* Removes one from an {@link ItemStack} on a specific slot.
*
* @param player The player that did this or <code>null</code> if it wasn't a player.
* @param index The slot index
* @return If the action has been cancelled
*/
public boolean removeOne(Player player, int index) {
ItemStack itemStack = items[index];
if (itemStack != null) {
int currentAmount = itemStack.getAmount();
int newAmount = currentAmount - 1;
ItemUpdateEvent event = createAndCallEvent(player, itemStack, index, currentAmount, newAmount);
if (!event.isCancelled()) {
if (newAmount > 0) itemStack.setAmount(newAmount);
else items[index] = null;
notifyWindows();
} else return true;
}
return false;
}
/**
* Removes half of the {@link ItemStack} on a specific slot.
*
* @param player The player that did this or <code>null</code> if it wasn't a player.
* @param index The slot index
* @return If the action has been cancelled
*/
public boolean removeHalf(Player player, int index) {
ItemStack itemStack = items[index];
if (itemStack != null) {
int currentAmount = itemStack.getAmount();
int newAmount = itemStack.getAmount() / 2;
ItemUpdateEvent event = createAndCallEvent(player, itemStack, index, currentAmount, newAmount);
if (!event.isCancelled()) {
if (newAmount > 0) itemStack.setAmount(newAmount);
else items[index] = null;
notifyWindows();
} else return true;
}
return false;
}
/**
* Adds an {@link ItemStack} to the {@link VirtualInventory}.
*
* @param player The player that did this or <code>null</code> if it wasn't a player.
* @param itemStack The {@link ItemStack} to add
* @return The amount of items that couldn't be added
*/
public int addItem(Player player, ItemStack itemStack) {
final int originalAmount = itemStack.getAmount();
int amountLeft = originalAmount;
// find all slots where the item partially fits and add it there
ItemStack partialStack;
while ((partialStack = findPartialSlot(itemStack)) != null && amountLeft != 0)
amountLeft = addTo(partialStack, amountLeft);
Pair<Integer, ItemStack> partialSlot;
while ((partialSlot = findPartialSlot(itemStack)) != null && amountLeft != 0)
amountLeft = addTo(player, partialSlot, amountLeft);
if (amountLeft != 0) {
// there are still items left, put the rest on an empty slot
@ -65,8 +341,8 @@ public class VirtualInventory implements ConfigurationSerializable {
if (emptyIndex != -1) {
ItemStack leftover = itemStack.clone();
leftover.setAmount(amountLeft);
items[emptyIndex] = leftover;
amountLeft = 0;
if (!place(player, emptyIndex, leftover))
amountLeft = 0;
}
}
@ -77,111 +353,49 @@ public class VirtualInventory implements ConfigurationSerializable {
return amountLeft;
}
private ItemStack findPartialSlot(ItemStack itemStack) {
for (ItemStack currentStack : items) {
private Pair<Integer, ItemStack> findPartialSlot(ItemStack itemStack) {
for (int i = 0; i < items.length; i++) {
ItemStack currentStack = items[i];
if (currentStack != null && currentStack.getAmount() < currentStack.getMaxStackSize()
&& currentStack.isSimilar(itemStack)) return currentStack;
&& currentStack.isSimilar(itemStack)) return new Pair<>(i, currentStack);
}
return null;
}
private int addTo(ItemStack itemStack, int amount) {
private int addTo(Player player, Pair<Integer, ItemStack> partialSlot, int amount) {
int index = partialSlot.getFirst();
ItemStack itemStack = partialSlot.getSecond();
int maxAddable = Math.min(itemStack.getMaxStackSize() - itemStack.getAmount(), amount);
itemStack.setAmount(itemStack.getAmount() + maxAddable);
return amount - maxAddable;
}
public void setItem(int index, ItemStack itemStack) {
items[index] = itemStack.clone();
notifyWindows();
}
public void setAmount(int index, int amount) {
ItemStack itemStack = items[index];
if (itemStack != null) {
if (amount == 0) items[index] = null;
else itemStack.setAmount(amount);
notifyWindows();
}
}
public void setMaxAmount(int index) {
ItemStack itemStack = items[index];
if (itemStack != null) {
itemStack.setAmount(itemStack.getMaxStackSize());
notifyWindows();
}
}
public ItemStack getItemStack(int index) {
return items[index];
}
public void removeItem(int index) {
if (items[index] != null) {
items[index] = null;
notifyWindows();
}
}
public void removeOne(int index) {
ItemStack itemStack = items[index];
if (itemStack != null) {
int amount = itemStack.getAmount() - 1;
if (amount > 0) itemStack.setAmount(amount);
else items[index] = null;
notifyWindows();
}
}
public void removeHalf(int index) {
ItemStack itemStack = items[index];
if (itemStack != null) {
int amount = itemStack.getAmount() / 2;
if (amount > 0) itemStack.setAmount(amount);
else items[index] = null;
notifyWindows();
}
}
public void place(int index, ItemStack itemStack) {
ItemStack there = items[index];
int currentAmount = there == null ? 0 : there.getAmount();
if (there == null) {
setItem(index, itemStack);
} else {
there.setAmount(currentAmount + itemStack.getAmount());
notifyWindows();
}
}
public void placeOne(int index, ItemStack itemStack) {
ItemStack there = items[index];
int currentAmount = there == null ? 0 : there.getAmount();
if (there == null) {
ItemStack single = itemStack.clone();
single.setAmount(1);
setItem(index, single);
} else {
there.setAmount(currentAmount + 1);
notifyWindows();
}
}
public boolean hasItem(int index) {
return items[index] != null;
int currentAmount = itemStack.getAmount();
int newAmount = currentAmount + maxAddable;
ItemUpdateEvent event = createAndCallEvent(player, itemStack, index, currentAmount, newAmount);
if (!event.isCancelled()) {
itemStack.setAmount(itemStack.getAmount() + maxAddable);
return amount - maxAddable;
} else return amount;
}
/**
* Adds a {@link Window} to the window set, telling the {@link VirtualInventory} that it is
* currently being displayed in that {@link Window}.
*
* @param window The {@link Window} the {@link VirtualInventory} is currently displayed in.
*/
public void addWindow(Window window) {
windows.add(window);
}
/**
* Removes an {@link Window} from the window set, telling the {@link VirtualInventory} that it
* is no longer being displayed in that {@link Window}.
*
* @param window The {@link Window} the {@link VirtualInventory} is no longer displayed in.
*/
public void removeWindow(Window window) {
windows.remove(window);
}
@ -191,10 +405,27 @@ public class VirtualInventory implements ConfigurationSerializable {
windows.forEach(window -> window.handleVirtualInventoryUpdate(this)));
}
private ItemUpdateEvent createAndCallEvent(Player player, ItemStack itemStack, int index, int previousAmount, int newAmount) {
ItemUpdateEvent event = new ItemUpdateEvent(this, player, itemStack, index, previousAmount, newAmount);
Bukkit.getPluginManager().callEvent(event);
return event;
}
/**
* Gets the {@link UUID} of this {@link VirtualInventory}.
*
* @return The {@link UUID} of this {@link VirtualInventory}
*/
public UUID getUuid() {
return uuid;
}
/**
* Serializes this {@link VirtualInventory} to a {@link Map}
*
* @return A {@link Map} that contains the serialized data of this {@link VirtualInventory}
*/
@NotNull
@Override
public Map<String, Object> serialize() {

@ -0,0 +1,135 @@
package de.studiocode.invgui.virtualinventory.event;
import de.studiocode.invgui.virtualinventory.VirtualInventory;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* An event that is called whenever a slot inside a {@link VirtualInventory} gets updated.
*/
public class ItemUpdateEvent extends Event implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private final VirtualInventory virtualInventory;
private final ItemStack itemStack;
private final Player player;
private final int slot;
private final int previousAmount;
private final int newAmount;
private boolean cancelled;
/**
* Creates a new {@link ItemUpdateEvent}.
*
* @param virtualInventory The {@link VirtualInventory} where this action takes place.
* @param player The {@link Player} who changed the {@link ItemStack} or <code>null</code>
* if it wasn't a {@link Player}
* @param itemStack The {@link ItemStack} that is affected
* @param slot The slot that is affected
* @param previousAmount The previous amount of the {@link ItemStack}
* @param newAmount The amount that the {@link ItemStack} will have if the event is not being cancelled
*/
public ItemUpdateEvent(@NotNull VirtualInventory virtualInventory, @Nullable Player player, @NotNull ItemStack itemStack, int slot, int previousAmount,
int newAmount) {
this.virtualInventory = virtualInventory;
this.player = player;
this.itemStack = itemStack;
this.slot = slot;
this.previousAmount = previousAmount;
this.newAmount = newAmount;
}
/**
* Gets the {@link HandlerList} of this {@link Event}
*
* @return The {@link HandlerList} of this {@link Event}
*/
public static HandlerList getHandlerList() {
return handlers;
}
/**
* Gets the {@link VirtualInventory} where this action takes place.
*
* @return The {@link VirtualInventory}
*/
public VirtualInventory getVirtualInventory() {
return virtualInventory;
}
/**
* Gets the {@link Player} who changed the {@link ItemStack} or <code>null</code>
* if it wasn't a {@link Player}.
*
* @return The {@link Player}
*/
public Player getPlayer() {
return player;
}
/**
* Gets the {@link ItemStack} involved in this action.
*
* @return The {@link ItemStack}
*/
public ItemStack getItemStack() {
return itemStack;
}
/**
* Gets the slot that is affected.
*
* @return The slot
*/
public int getSlot() {
return slot;
}
/**
* Gets the previous amount of the {@link ItemStack}
*
* @return The previous amount
*/
public int getPreviousAmount() {
return previousAmount;
}
/**
* Gets the new amount of the {@link ItemStack} if the event
* isn't being cancelled.
*
* @return The new amount
*/
public int getNewAmount() {
return newAmount;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
/**
* Gets the {@link HandlerList} of this {@link Event}
*
* @return The {@link HandlerList} of this {@link Event}
*/
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
}