diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/gui/ActionPlayerCloseVanillaCrafting.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/gui/ActionPlayerCloseVanillaCrafting.java new file mode 100644 index 000000000..156a4b0a6 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/gui/ActionPlayerCloseVanillaCrafting.java @@ -0,0 +1,20 @@ +package net.swofty.type.skyblockgeneric.event.actions.player.gui; + +import net.minestom.server.event.inventory.InventoryCloseEvent; +import net.minestom.server.inventory.PlayerInventory; +import net.swofty.type.generic.event.EventNodes; +import net.swofty.type.generic.event.HypixelEvent; +import net.swofty.type.generic.event.HypixelEventClass; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; +import net.swofty.type.skyblockgeneric.utility.PlayerInventoryCrafting; + +public class ActionPlayerCloseVanillaCrafting implements HypixelEventClass { + @HypixelEvent(node = EventNodes.PLAYER, requireDataLoaded = true) + public void run(InventoryCloseEvent event) { + if (!(event.getInventory() instanceof PlayerInventory)) { + return; + } + + PlayerInventoryCrafting.returnCraftingGrid((SkyBlockPlayer) event.getPlayer()); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/gui/ActionPlayerInteractWithCrafting.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/gui/ActionPlayerInteractWithCrafting.java index 277d537ac..d753b96c7 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/gui/ActionPlayerInteractWithCrafting.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/gui/ActionPlayerInteractWithCrafting.java @@ -2,12 +2,14 @@ import net.minestom.server.event.inventory.InventoryPreClickEvent; import net.minestom.server.inventory.PlayerInventory; -import net.minestom.server.item.ItemStack; +import net.minestom.server.inventory.click.Click; import net.swofty.type.generic.event.EventNodes; import net.swofty.type.generic.event.HypixelEvent; import net.swofty.type.generic.event.HypixelEventClass; -import net.swofty.type.skyblockgeneric.gui.inventories.sbmenu.GUICrafting; import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; +import net.swofty.type.skyblockgeneric.utility.PlayerInventoryCrafting; + +import java.util.List; public class ActionPlayerInteractWithCrafting implements HypixelEventClass { @HypixelEvent(node = EventNodes.PLAYER, requireDataLoaded = true) @@ -15,13 +17,44 @@ public void run(InventoryPreClickEvent event) { SkyBlockPlayer player = (SkyBlockPlayer) event.getPlayer(); if (!(event.getInventory() instanceof PlayerInventory)) return; - if (event.getSlot() < 37 || event.getSlot() > 40) return; - event.setCancelled(true); - player.addAndUpdateItem(player.getInventory().getCursorItem()); - player.getInventory().setCursorItem(ItemStack.AIR); - player.getInventory().update(); + if (event.getClick() instanceof Click.LeftDrag(List slots)) { + handleDrag(event, player, slots); + return; + } + + if (event.getClick() instanceof Click.RightDrag(List slots)) { + handleDrag(event, player, slots); + return; + } + + if (PlayerInventoryCrafting.isResultSlot(event.getSlot())) { + event.setCancelled(true); + PlayerInventoryCrafting.craft(player, event.getClick()); + return; + } + + if (PlayerInventoryCrafting.isCraftingSlot(event.getSlot()) + || event.getClick() instanceof Click.LeftShift + || event.getClick() instanceof Click.RightShift) { + PlayerInventoryCrafting.refreshNextTick(player); + } + } + + private void handleDrag(InventoryPreClickEvent event, SkyBlockPlayer player, Iterable slots) { + boolean refresh = false; + for (int slot : slots) { + if (PlayerInventoryCrafting.isResultSlot(slot)) { + event.setCancelled(true); + return; + } + if (PlayerInventoryCrafting.isCraftingSlot(slot)) { + refresh = true; + } + } - player.openView(new GUICrafting()); + if (refresh) { + PlayerInventoryCrafting.refreshNextTick(player); + } } } diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/utility/PlayerInventoryCrafting.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/utility/PlayerInventoryCrafting.java new file mode 100644 index 000000000..adf085820 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/utility/PlayerInventoryCrafting.java @@ -0,0 +1,291 @@ +package net.swofty.type.skyblockgeneric.utility; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import net.kyori.adventure.text.format.TextDecoration; +import net.minestom.server.MinecraftServer; +import net.minestom.server.component.DataComponents; +import net.minestom.server.inventory.PlayerInventory; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.swofty.commons.StringUtility; +import net.swofty.commons.skyblock.item.ItemType; +import net.swofty.type.generic.event.HypixelEventHandler; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.skyblockgeneric.event.custom.ItemCraftEvent; +import net.swofty.type.skyblockgeneric.item.SkyBlockItem; +import net.swofty.type.skyblockgeneric.item.crafting.SkyBlockRecipe; +import net.swofty.type.skyblockgeneric.item.updater.PlayerItemUpdater; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.stream.Collectors; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public final class PlayerInventoryCrafting { + private static final String[] DEFAULT_CRAFT_ERROR = new String[]{"§cYou cannot craft this item right now."}; + private static final int RESULT_SLOT = 36; + private static final int[] CRAFT_SLOTS = new int[]{37, 38, 39, 40}; + private static final int[] RECIPE_GRID_INDEXES = new int[]{0, 1, 3, 4}; + private static final int RECIPE_GRID_SIZE = 9; + + public static boolean isCraftingSlot(int slot) { + return Arrays.stream(CRAFT_SLOTS).anyMatch(craftSlot -> craftSlot == slot); + } + + public static boolean isResultSlot(int slot) { + return slot == RESULT_SLOT; + } + + public static void refreshNextTick(SkyBlockPlayer player) { + MinecraftServer.getSchedulerManager().scheduleNextTick(() -> refresh(player)); + } + + public static void refresh(SkyBlockPlayer player) { + PlayerInventory inventory = player.getInventory(); + SkyBlockRecipe recipe = parseRecipe(inventory); + inventory.setItemStack(RESULT_SLOT, renderResult(player, recipe)); + inventory.update(); + } + + public static void returnCraftingGrid(SkyBlockPlayer player) { + PlayerInventory inventory = player.getInventory(); + ItemStack[] itemsToReturn = Arrays.stream(CRAFT_SLOTS) + .mapToObj(inventory::getItemStack) + .filter(item -> !item.isAir()) + .toArray(ItemStack[]::new); + + inventory.setItemStack(RESULT_SLOT, ItemStack.AIR); + for (int slot : CRAFT_SLOTS) { + inventory.setItemStack(slot, ItemStack.AIR); + } + + for (ItemStack item : itemsToReturn) { + returnToMainInventory(player, item); + } + + inventory.update(); + } + + public static void craft(SkyBlockPlayer player, Click click) { + PlayerInventory inventory = player.getInventory(); + SkyBlockRecipe recipe = parseRecipe(inventory); + if (recipe == null) { + refresh(player); + return; + } + + SkyBlockRecipe.CraftingResult permissionResult = recipe.getCanCraft().apply(player); + if (permissionResult == null || !permissionResult.allowed()) { + refresh(player); + return; + } + + int amount = recipe.getAmount(); + ItemStack craftedItem = PlayerItemUpdater.playerUpdate(player, recipe.getResult().getItemStack()) + .amount(amount) + .build(); + + if (click instanceof Click.LeftShift || click instanceof Click.RightShift) { + craftToInventory(player, recipe, craftedItem, amount); + } else { + craftToCursor(player, recipe, craftedItem, amount); + } + + refresh(player); + player.getInventory().update(); + } + + private static ItemStack renderResult(SkyBlockPlayer player, SkyBlockRecipe recipe) { + if (recipe == null) { + return ItemStack.AIR; + } + + SkyBlockRecipe.CraftingResult result = recipe.getCanCraft().apply(player); + if (result == null || !result.allowed()) { + String[] craftErrorMessages = getCraftErrorMessages(result); + return ItemStackCreator.getStack( + craftErrorMessages[0], + Material.BEDROCK, + 1, + Arrays.copyOfRange(craftErrorMessages, 1, craftErrorMessages.length) + ).build(); + } + + ItemStack.Builder builder = PlayerItemUpdater.playerUpdate(player, recipe.getResult().getItemStack()) + .amount(recipe.getAmount()); + + ArrayList lore = new ArrayList<>(); + var existingLore = builder.build().get(DataComponents.LORE); + if (existingLore != null) { + existingLore.stream().map(line -> "§7" + StringUtility.getTextFromComponent(line)).forEach(lore::add); + } + builder.set(DataComponents.LORE, ItemStackCreator.literalLoreComponents(lore).stream() + .map(line -> line.decoration(TextDecoration.ITALIC, false)) + .collect(Collectors.toList())); + + return builder.build(); + } + + private static void craftToInventory(SkyBlockPlayer player, SkyBlockRecipe recipe, ItemStack craftedItem, int amountPerCraft) { + int maxCraftsByInventory = getMaxCraftsByInventory(player, craftedItem, amountPerCraft); + SkyBlockItem[] simulatedItems = getCurrentRecipeAsItems(player.getInventory()); + int craftedCount = 0; + + while (craftedCount < maxCraftsByInventory) { + SkyBlockRecipe.CraftingResult craftResult = recipe.getCanCraft().apply(player); + if (craftResult == null || !craftResult.allowed()) { + break; + } + + try { + simulatedItems = recipe.consume(simulatedItems); + } catch (Exception e) { + break; + } + + craftedCount++; + } + + int totalCraftedAmount = craftedCount * amountPerCraft; + if (totalCraftedAmount <= 0) { + return; + } + + applyConsumedGrid(player, simulatedItems); + player.addAndUpdateItem(craftedItem.withAmount(totalCraftedAmount)); + HypixelEventHandler.callCustomEvent( + new ItemCraftEvent(player, new SkyBlockItem(craftedItem.withAmount(totalCraftedAmount)), recipe) + ); + } + + private static void craftToCursor(SkyBlockPlayer player, SkyBlockRecipe recipe, ItemStack craftedItem, int amount) { + PlayerInventory inventory = player.getInventory(); + ItemStack cursorItemStack = inventory.getCursorItem(); + SkyBlockItem cursorItem = new SkyBlockItem(cursorItemStack); + ItemType cursorItemType = cursorItem.getAttributeHandler().getPotentialType(); + ItemType resultItemType = recipe.getResult().getAttributeHandler().getPotentialType(); + + if (!cursorItemStack.isAir() && (cursorItemType == null || !cursorItemType.equals(resultItemType))) { + return; + } + + int currentCursorAmount = cursorItemStack.isAir() ? 0 : cursorItemStack.amount(); + int newAmount = currentCursorAmount + amount; + if (newAmount > craftedItem.material().maxStackSize()) { + return; + } + + SkyBlockItem[] toReplace; + try { + toReplace = recipe.consume(getCurrentRecipeAsItems(inventory)); + } catch (Exception e) { + return; + } + + applyConsumedGrid(player, toReplace); + inventory.setCursorItem(craftedItem.withAmount(newAmount)); + HypixelEventHandler.callCustomEvent(new ItemCraftEvent(player, new SkyBlockItem(craftedItem), recipe)); + } + + private static int getMaxCraftsByInventory(SkyBlockPlayer player, ItemStack craftedItem, int amountPerCraft) { + if (amountPerCraft <= 0) { + return 0; + } + + int maxStackSize = craftedItem.material().maxStackSize(); + int availableSpace = 0; + for (int slot = 0; slot < 36; slot++) { + ItemStack stack = player.getInventory().getItemStack(slot); + if (stack.isAir()) { + availableSpace += maxStackSize; + continue; + } + if (stack.isSimilar(craftedItem) && stack.amount() < maxStackSize) { + availableSpace += maxStackSize - stack.amount(); + } + } + + return availableSpace / amountPerCraft; + } + + private static void returnToMainInventory(SkyBlockPlayer player, ItemStack item) { + ItemStack updatedItem = PlayerItemUpdater.playerUpdate(player, item, true).build(); + int remaining = updatedItem.amount(); + int maxStackSize = updatedItem.material().maxStackSize(); + PlayerInventory inventory = player.getInventory(); + + for (int slot = 0; slot < 36 && remaining > 0; slot++) { + ItemStack stack = inventory.getItemStack(slot); + if (!stack.isSimilar(updatedItem) || stack.amount() >= maxStackSize) { + continue; + } + + int moved = Math.min(remaining, maxStackSize - stack.amount()); + inventory.setItemStack(slot, stack.withAmount(stack.amount() + moved)); + remaining -= moved; + } + + for (int slot = 0; slot < 36 && remaining > 0; slot++) { + ItemStack stack = inventory.getItemStack(slot); + if (!stack.isAir()) { + continue; + } + + int moved = Math.min(remaining, maxStackSize); + inventory.setItemStack(slot, updatedItem.withAmount(moved)); + remaining -= moved; + } + + if (remaining > 0) { + player.addToStash(new SkyBlockItem(updatedItem.withAmount(remaining))); + } + } + + private static SkyBlockRecipe parseRecipe(PlayerInventory inventory) { + return SkyBlockRecipe.parseRecipe(getCurrentRecipeStacks(inventory)); + } + + private static ItemStack[] getCurrentRecipeStacks(PlayerInventory inventory) { + ItemStack[] stacks = new ItemStack[RECIPE_GRID_SIZE]; + Arrays.fill(stacks, ItemStack.AIR); + + for (int i = 0; i < CRAFT_SLOTS.length; i++) { + stacks[RECIPE_GRID_INDEXES[i]] = inventory.getItemStack(CRAFT_SLOTS[i]); + } + return stacks; + } + + private static SkyBlockItem[] getCurrentRecipeAsItems(PlayerInventory inventory) { + ItemStack[] rawStacks = getCurrentRecipeStacks(inventory); + SkyBlockItem[] stacks = new SkyBlockItem[RECIPE_GRID_SIZE]; + for (int i = 0; i < rawStacks.length; i++) { + stacks[i] = new SkyBlockItem(rawStacks[i]); + } + return stacks; + } + + private static void applyConsumedGrid(SkyBlockPlayer player, SkyBlockItem[] toReplace) { + PlayerInventory inventory = player.getInventory(); + for (int i = 0; i < CRAFT_SLOTS.length; i++) { + SkyBlockItem replacement = toReplace[RECIPE_GRID_INDEXES[i]]; + if (replacement == null || replacement.getItemStack().material() == Material.BEDROCK) { + inventory.setItemStack(CRAFT_SLOTS[i], ItemStack.AIR); + } else { + inventory.setItemStack( + CRAFT_SLOTS[i], + PlayerItemUpdater.playerUpdate(player, replacement.getItemStack()).build() + ); + } + } + } + + private static String[] getCraftErrorMessages(SkyBlockRecipe.CraftingResult result) { + if (result == null || result.errorMessage() == null || result.errorMessage().length == 0) { + return DEFAULT_CRAFT_ERROR; + } + return result.errorMessage(); + } +}