mirror of https://github.com/Minestom/Minestom.git
417 lines
15 KiB
Java
417 lines
15 KiB
Java
package net.minestom.server.inventory;
|
|
|
|
import net.minestom.server.entity.Player;
|
|
import net.minestom.server.event.ArmorEquipEvent;
|
|
import net.minestom.server.inventory.click.InventoryClickLoopHandler;
|
|
import net.minestom.server.inventory.click.InventoryClickProcessor;
|
|
import net.minestom.server.inventory.click.InventoryClickResult;
|
|
import net.minestom.server.inventory.condition.InventoryCondition;
|
|
import net.minestom.server.item.ItemStack;
|
|
import net.minestom.server.item.StackingRule;
|
|
import net.minestom.server.network.packet.server.play.EntityEquipmentPacket;
|
|
import net.minestom.server.network.packet.server.play.SetSlotPacket;
|
|
import net.minestom.server.network.packet.server.play.WindowItemsPacket;
|
|
import net.minestom.server.network.player.PlayerConnection;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
|
|
import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*;
|
|
|
|
public class PlayerInventory implements InventoryModifier, InventoryClickHandler {
|
|
|
|
public static final int INVENTORY_SIZE = 46;
|
|
|
|
private Player player;
|
|
private ItemStack[] items = new ItemStack[INVENTORY_SIZE];
|
|
private ItemStack cursorItem = ItemStack.getAirItem();
|
|
|
|
private List<InventoryCondition> inventoryConditions = new CopyOnWriteArrayList<>();
|
|
private InventoryClickProcessor clickProcessor = new InventoryClickProcessor();
|
|
|
|
public PlayerInventory(Player player) {
|
|
this.player = player;
|
|
|
|
for (int i = 0; i < items.length; i++) {
|
|
items[i] = ItemStack.getAirItem();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ItemStack getItemStack(int slot) {
|
|
return this.items[slot];
|
|
}
|
|
|
|
@Override
|
|
public ItemStack[] getItemStacks() {
|
|
return Arrays.copyOf(items, items.length);
|
|
}
|
|
|
|
@Override
|
|
public List<InventoryCondition> getInventoryConditions() {
|
|
return inventoryConditions;
|
|
}
|
|
|
|
@Override
|
|
public void addInventoryCondition(InventoryCondition inventoryCondition) {
|
|
InventoryCondition condition = (p, slot, clickType, inventoryConditionResult) -> {
|
|
slot = convertSlot(slot, OFFSET);
|
|
inventoryCondition.accept(p, slot, clickType, inventoryConditionResult);
|
|
};
|
|
|
|
this.inventoryConditions.add(condition);
|
|
}
|
|
|
|
@Override
|
|
public void setItemStack(int slot, ItemStack itemStack) {
|
|
safeItemInsert(slot, itemStack);
|
|
}
|
|
|
|
@Override
|
|
public boolean addItemStack(ItemStack itemStack) {
|
|
synchronized (this) {
|
|
StackingRule stackingRule = itemStack.getStackingRule();
|
|
for (int i = 0; i < items.length - 10; i++) {
|
|
ItemStack item = items[i];
|
|
StackingRule itemStackingRule = item.getStackingRule();
|
|
if (itemStackingRule.canBeStacked(itemStack, item)) {
|
|
int itemAmount = itemStackingRule.getAmount(item);
|
|
if (itemAmount == stackingRule.getMaxSize())
|
|
continue;
|
|
int itemStackAmount = itemStackingRule.getAmount(itemStack);
|
|
int totalAmount = itemStackAmount + itemAmount;
|
|
if (!stackingRule.canApply(itemStack, totalAmount)) {
|
|
item = itemStackingRule.apply(item, itemStackingRule.getMaxSize());
|
|
|
|
sendSlotRefresh((short) convertToPacketSlot(i), item);
|
|
itemStack = stackingRule.apply(itemStack, totalAmount - stackingRule.getMaxSize());
|
|
} else {
|
|
item.setAmount((byte) totalAmount);
|
|
sendSlotRefresh((short) convertToPacketSlot(i), item);
|
|
return true;
|
|
}
|
|
} else if (item.isAir()) {
|
|
setItemStack(i, itemStack);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public ItemStack getItemInMainHand() {
|
|
return getItemStack(player.getHeldSlot());
|
|
}
|
|
|
|
public void setItemInMainHand(ItemStack itemStack) {
|
|
safeItemInsert(player.getHeldSlot(), itemStack);
|
|
}
|
|
|
|
public ItemStack getItemInOffHand() {
|
|
return getItemStack(OFFHAND_SLOT);
|
|
}
|
|
|
|
public void setItemInOffHand(ItemStack itemStack) {
|
|
safeItemInsert(OFFHAND_SLOT, itemStack);
|
|
}
|
|
|
|
public ItemStack getHelmet() {
|
|
return getItemStack(HELMET_SLOT);
|
|
}
|
|
|
|
public void setHelmet(ItemStack itemStack) {
|
|
safeItemInsert(HELMET_SLOT, itemStack);
|
|
}
|
|
|
|
public ItemStack getChestplate() {
|
|
return getItemStack(CHESTPLATE_SLOT);
|
|
}
|
|
|
|
public void setChestplate(ItemStack itemStack) {
|
|
safeItemInsert(CHESTPLATE_SLOT, itemStack);
|
|
}
|
|
|
|
public ItemStack getLeggings() {
|
|
return getItemStack(LEGGINGS_SLOT);
|
|
}
|
|
|
|
public void setLeggings(ItemStack itemStack) {
|
|
safeItemInsert(LEGGINGS_SLOT, itemStack);
|
|
}
|
|
|
|
public ItemStack getBoots() {
|
|
return getItemStack(BOOTS_SLOT);
|
|
}
|
|
|
|
public void setBoots(ItemStack itemStack) {
|
|
safeItemInsert(BOOTS_SLOT, itemStack);
|
|
}
|
|
|
|
public void update() {
|
|
PlayerConnection playerConnection = player.getPlayerConnection();
|
|
playerConnection.sendPacket(createWindowItemsPacket());
|
|
}
|
|
|
|
public void refreshSlot(int slot) {
|
|
sendSlotRefresh((short) convertToPacketSlot(slot), getItemStack(slot));
|
|
}
|
|
|
|
public ItemStack getCursorItem() {
|
|
return cursorItem;
|
|
}
|
|
|
|
public void setCursorItem(ItemStack cursorItem) {
|
|
this.cursorItem = cursorItem;
|
|
}
|
|
|
|
public ItemStack getEquipment(EntityEquipmentPacket.Slot slot) {
|
|
switch (slot) {
|
|
case MAIN_HAND:
|
|
return getItemInMainHand();
|
|
case OFF_HAND:
|
|
return getItemInOffHand();
|
|
case HELMET:
|
|
return getHelmet();
|
|
case CHESTPLATE:
|
|
return getChestplate();
|
|
case LEGGINGS:
|
|
return getLeggings();
|
|
case BOOTS:
|
|
return getBoots();
|
|
default:
|
|
throw new NullPointerException("Equipment slot cannot be null");
|
|
}
|
|
}
|
|
|
|
private void safeItemInsert(int slot, ItemStack itemStack) {
|
|
synchronized (this) {
|
|
itemStack = itemStack == null ? ItemStack.getAirItem() : itemStack;
|
|
|
|
EntityEquipmentPacket.Slot equipmentSlot;
|
|
|
|
if (slot == player.getHeldSlot()) {
|
|
equipmentSlot = EntityEquipmentPacket.Slot.MAIN_HAND;
|
|
} else if (slot == OFFHAND_SLOT) {
|
|
equipmentSlot = EntityEquipmentPacket.Slot.OFF_HAND;
|
|
} else if (slot == HELMET_SLOT) {
|
|
equipmentSlot = EntityEquipmentPacket.Slot.HELMET;
|
|
ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent(itemStack, ArmorEquipEvent.ArmorSlot.HELMET);
|
|
player.callEvent(ArmorEquipEvent.class, armorEquipEvent);
|
|
itemStack = armorEquipEvent.getArmorItem();
|
|
} else if (slot == CHESTPLATE_SLOT) {
|
|
equipmentSlot = EntityEquipmentPacket.Slot.CHESTPLATE;
|
|
ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent(itemStack, ArmorEquipEvent.ArmorSlot.CHESTPLATE);
|
|
player.callEvent(ArmorEquipEvent.class, armorEquipEvent);
|
|
itemStack = armorEquipEvent.getArmorItem();
|
|
} else if (slot == LEGGINGS_SLOT) {
|
|
equipmentSlot = EntityEquipmentPacket.Slot.LEGGINGS;
|
|
ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent(itemStack, ArmorEquipEvent.ArmorSlot.LEGGINGS);
|
|
player.callEvent(ArmorEquipEvent.class, armorEquipEvent);
|
|
itemStack = armorEquipEvent.getArmorItem();
|
|
} else if (slot == BOOTS_SLOT) {
|
|
equipmentSlot = EntityEquipmentPacket.Slot.BOOTS;
|
|
ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent(itemStack, ArmorEquipEvent.ArmorSlot.BOOTS);
|
|
player.callEvent(ArmorEquipEvent.class, armorEquipEvent);
|
|
itemStack = armorEquipEvent.getArmorItem();
|
|
} else {
|
|
equipmentSlot = null;
|
|
}
|
|
|
|
if (itemStack != null) {
|
|
this.items[slot] = itemStack;
|
|
}
|
|
|
|
// Refresh inventory items
|
|
update();
|
|
|
|
// Sync equipment
|
|
if (equipmentSlot != null) {
|
|
player.syncEquipment(equipmentSlot);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void setItemStack(int slot, int offset, ItemStack itemStack) {
|
|
slot = convertSlot(slot, offset);
|
|
safeItemInsert(slot, itemStack);
|
|
}
|
|
|
|
protected ItemStack getItemStack(int slot, int offset) {
|
|
slot = convertSlot(slot, offset);
|
|
return this.items[slot];
|
|
}
|
|
|
|
protected int convertToPacketSlot(int slot) {
|
|
if (slot > -1 && slot < 9) { // Held bar 0-9
|
|
slot = slot + 36;
|
|
} else if (slot > 8 && slot < 36) { // Inventory 9-35
|
|
slot = slot;
|
|
} else if (slot >= CRAFT_RESULT && slot <= CRAFT_SLOT_4) { // Crafting 36-40
|
|
slot = slot - 36;
|
|
} else if (slot >= HELMET_SLOT && slot <= BOOTS_SLOT) { // Armor 41-44
|
|
slot = slot - 36;
|
|
} else if (slot == OFFHAND_SLOT) { // Off hand
|
|
slot = 45;
|
|
}
|
|
return slot;
|
|
}
|
|
|
|
private void sendSlotRefresh(short slot, ItemStack itemStack) {
|
|
SetSlotPacket setSlotPacket = new SetSlotPacket();
|
|
setSlotPacket.windowId = (byte) (slot > 35 && slot < INVENTORY_SIZE ? 0 : -2);
|
|
setSlotPacket.slot = slot;
|
|
setSlotPacket.itemStack = itemStack;
|
|
player.getPlayerConnection().sendPacket(setSlotPacket);
|
|
}
|
|
|
|
private WindowItemsPacket createWindowItemsPacket() {
|
|
ItemStack[] convertedSlots = new ItemStack[INVENTORY_SIZE];
|
|
|
|
for (int i = 0; i < items.length; i++) {
|
|
int slot = convertToPacketSlot(i);
|
|
convertedSlots[slot] = items[i];
|
|
}
|
|
|
|
WindowItemsPacket windowItemsPacket = new WindowItemsPacket();
|
|
windowItemsPacket.windowId = 0;
|
|
windowItemsPacket.count = INVENTORY_SIZE;
|
|
windowItemsPacket.items = convertedSlots;
|
|
return windowItemsPacket;
|
|
}
|
|
|
|
@Override
|
|
public void leftClick(Player player, int slot) {
|
|
ItemStack cursor = getCursorItem();
|
|
ItemStack clicked = getItemStack(convertSlot(slot, OFFSET));
|
|
|
|
InventoryClickResult clickResult = clickProcessor.leftClick(getInventoryConditions(), player, slot, clicked, cursor);
|
|
|
|
if (clickResult.doRefresh())
|
|
sendSlotRefresh((short) slot, clicked);
|
|
|
|
setItemStack(slot, OFFSET, clickResult.getClicked());
|
|
setCursorItem(clickResult.getCursor());
|
|
}
|
|
|
|
@Override
|
|
public void rightClick(Player player, int slot) {
|
|
ItemStack cursor = getCursorItem();
|
|
ItemStack clicked = getItemStack(slot, OFFSET);
|
|
|
|
InventoryClickResult clickResult = clickProcessor.rightClick(getInventoryConditions(), player, slot, clicked, cursor);
|
|
|
|
if (clickResult.doRefresh())
|
|
sendSlotRefresh((short) slot, clicked);
|
|
|
|
setItemStack(slot, OFFSET, clickResult.getClicked());
|
|
setCursorItem(clickResult.getCursor());
|
|
}
|
|
|
|
@Override
|
|
public void middleClick(Player player, int slot) {
|
|
|
|
}
|
|
|
|
@Override
|
|
public void drop(Player player, int mode, int slot, int button) {
|
|
ItemStack cursor = getCursorItem();
|
|
ItemStack clicked = slot == -999 ? null : getItemStack(slot, OFFSET);
|
|
|
|
InventoryClickResult clickResult = clickProcessor.drop(getInventoryConditions(), player,
|
|
mode, slot, button, clicked, cursor);
|
|
|
|
if (clickResult.doRefresh())
|
|
sendSlotRefresh((short) slot, clicked);
|
|
|
|
ItemStack resultClicked = clickResult.getClicked();
|
|
if (resultClicked != null)
|
|
setItemStack(slot, OFFSET, resultClicked);
|
|
setCursorItem(clickResult.getCursor());
|
|
}
|
|
|
|
@Override
|
|
public void shiftClick(Player player, int slot) {
|
|
ItemStack cursor = getCursorItem();
|
|
ItemStack clicked = getItemStack(slot, OFFSET);
|
|
|
|
boolean hotbarClick = convertToPacketSlot(slot) < 9;
|
|
InventoryClickResult clickResult = clickProcessor.shiftClick(getInventoryConditions(), player, slot, clicked, cursor,
|
|
new InventoryClickLoopHandler(0, items.length, 1,
|
|
i -> {
|
|
if (hotbarClick) {
|
|
return i < 9 ? i + 9 : i - 9;
|
|
} else {
|
|
return convertSlot(i, OFFSET);
|
|
}
|
|
},
|
|
index -> getItemStack(index, OFFSET),
|
|
(index, itemStack) -> setItemStack(index, OFFSET, itemStack)));
|
|
|
|
if (clickResult == null)
|
|
return;
|
|
|
|
if (clickResult.doRefresh())
|
|
update();
|
|
|
|
setCursorItem(clickResult.getCursor());
|
|
}
|
|
|
|
@Override
|
|
public void changeHeld(Player player, int slot, int key) {
|
|
if (!getCursorItem().isAir())
|
|
return;
|
|
|
|
ItemStack heldItem = getItemStack(key);
|
|
ItemStack clicked = getItemStack(slot, OFFSET);
|
|
|
|
InventoryClickResult clickResult = clickProcessor.changeHeld(getInventoryConditions(), player, slot, key, clicked, heldItem);
|
|
|
|
if (clickResult.doRefresh())
|
|
sendSlotRefresh((short) slot, clicked);
|
|
|
|
setItemStack(slot, OFFSET, clickResult.getClicked());
|
|
setItemStack(key, clickResult.getCursor());
|
|
}
|
|
|
|
@Override
|
|
public void dragging(Player player, int slot, int button) {
|
|
ItemStack cursor = getCursorItem();
|
|
ItemStack clicked = null;
|
|
if (slot != -999)
|
|
clicked = getItemStack(slot, OFFSET);
|
|
|
|
InventoryClickResult clickResult = clickProcessor.dragging(getInventoryConditions(), player,
|
|
slot, button,
|
|
clicked, cursor, s -> getItemStack(s, OFFSET),
|
|
(s, item) -> setItemStack(s, OFFSET, item));
|
|
|
|
if (clickResult == null)
|
|
return;
|
|
|
|
if (clickResult.doRefresh())
|
|
update();
|
|
|
|
setCursorItem(clickResult.getCursor());
|
|
}
|
|
|
|
@Override
|
|
public void doubleClick(Player player, int slot) {
|
|
ItemStack cursor = getCursorItem();
|
|
|
|
InventoryClickResult clickResult = clickProcessor.doubleClick(getInventoryConditions(), player, slot, cursor,
|
|
new InventoryClickLoopHandler(0, items.length, 1,
|
|
i -> i < 9 ? i + 9 : i - 9,
|
|
index -> items[index],
|
|
(index, itemStack) -> setItemStack(index, itemStack)));
|
|
|
|
if (clickResult == null)
|
|
return;
|
|
|
|
if (clickResult.doRefresh())
|
|
update();
|
|
|
|
setCursorItem(clickResult.getCursor());
|
|
}
|
|
}
|