/* * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * @author GeyserMC * @link https://github.com/GeyserMC/Geyser */ package org.geysermc.connector.network.translators.inventory.translators; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; import org.geysermc.connector.network.translators.inventory.SlotType; import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; import org.geysermc.connector.network.translators.item.translators.BannerTranslator; import java.util.Collections; import java.util.List; public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { /** * A map of Bedrock patterns to Java index. Used to request for a specific banner pattern. */ private static final Object2IntMap PATTERN_TO_INDEX = new Object2IntOpenHashMap<>(); static { // Added from left-to-right then up-to-down in the order Java presents it int index = 1; PATTERN_TO_INDEX.put("bl", index++); PATTERN_TO_INDEX.put("br", index++); PATTERN_TO_INDEX.put("tl", index++); PATTERN_TO_INDEX.put("tr", index++); PATTERN_TO_INDEX.put("bs", index++); PATTERN_TO_INDEX.put("ts", index++); PATTERN_TO_INDEX.put("ls", index++); PATTERN_TO_INDEX.put("rs", index++); PATTERN_TO_INDEX.put("cs", index++); PATTERN_TO_INDEX.put("ms", index++); PATTERN_TO_INDEX.put("drs", index++); PATTERN_TO_INDEX.put("dls", index++); PATTERN_TO_INDEX.put("ss", index++); PATTERN_TO_INDEX.put("cr", index++); PATTERN_TO_INDEX.put("sc", index++); PATTERN_TO_INDEX.put("bt", index++); PATTERN_TO_INDEX.put("tt", index++); PATTERN_TO_INDEX.put("bts", index++); PATTERN_TO_INDEX.put("tts", index++); PATTERN_TO_INDEX.put("ld", index++); PATTERN_TO_INDEX.put("rd", index++); PATTERN_TO_INDEX.put("lud", index++); PATTERN_TO_INDEX.put("rud", index++); PATTERN_TO_INDEX.put("mc", index++); PATTERN_TO_INDEX.put("mr", index++); PATTERN_TO_INDEX.put("vh", index++); PATTERN_TO_INDEX.put("hh", index++); PATTERN_TO_INDEX.put("vhr", index++); PATTERN_TO_INDEX.put("hhb", index++); PATTERN_TO_INDEX.put("bo", index++); index++; // Bordure indented, does not appear to exist in Bedrock? PATTERN_TO_INDEX.put("gra", index++); PATTERN_TO_INDEX.put("gru", index); // Bricks do not appear to be a pattern on Bedrock, either } public LoomInventoryTranslator() { super(4, "minecraft:loom[facing=north]", ContainerType.LOOM, UIInventoryUpdater.INSTANCE); } @Override public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, ContainerSlotType bedrockSourceContainer, int javaSourceSlot, ContainerSlotType bedrockDestinationContainer, int javaDestinationSlot) { if (javaDestinationSlot != 1) { return false; } GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot); if (itemStack.isEmpty()) { return false; } // Reject the item if Bedrock is attempting to put in a dye that is not a dye in Java Edition return !itemStack.getMapping(session).getJavaIdentifier().endsWith("_dye"); } @Override public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { // If the LOOM_MATERIAL slot is not empty, we are crafting a pattern that does not come from an item return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED && inventory.getItem(2).isEmpty(); } @Override public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { // TODO: I anticipate this will be changed in the future to use something non-deprecated. Keep an eye out. StackRequestActionData data = request.getActions()[1]; if (!(data instanceof CraftResultsDeprecatedStackRequestActionData craftData)) { return rejectRequest(request); } // Get the patterns compound tag List newBlockEntityTag = craftData.getResultItems()[0].getTag().getList("Patterns", NbtType.COMPOUND); // Get the pattern that the Bedrock client requests - the last pattern in the Patterns list NbtMap pattern = newBlockEntityTag.get(newBlockEntityTag.size() - 1); // Get the Java index of this pattern int index = PATTERN_TO_INDEX.getOrDefault(pattern.getString("Pattern"), -1); if (index == -1) { return rejectRequest(request); } // Java's formula: 4 * row + col // And the Java loom window has a fixed row/width of four // So... Number / 4 = row (so we don't have to bother there), and number % 4 is our column, which leads us back to our index. :) ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), index); session.sendDownstreamPacket(packet); GeyserItemStack inputCopy = inventory.getItem(0).copy(1); inputCopy.setNetId(session.getNextItemNetId()); // Add the pattern manually, for better item synchronization if (inputCopy.getNbt() == null) { inputCopy.setNbt(new CompoundTag("")); } CompoundTag blockEntityTag = inputCopy.getNbt().get("BlockEntityTag"); CompoundTag javaBannerPattern = BannerTranslator.getJavaBannerPattern(pattern); if (blockEntityTag != null) { ListTag patternsList = blockEntityTag.get("Patterns"); if (patternsList != null) { patternsList.add(javaBannerPattern); } else { patternsList = new ListTag("Patterns", Collections.singletonList(javaBannerPattern)); blockEntityTag.put(patternsList); } } else { blockEntityTag = new CompoundTag("BlockEntityTag"); ListTag patternsList = new ListTag("Patterns", Collections.singletonList(javaBannerPattern)); blockEntityTag.put(patternsList); inputCopy.getNbt().put(blockEntityTag); } // Set the new item as the output inventory.setItem(3, inputCopy, session); return translateRequest(session, inventory, request); } @Override public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { return switch (slotInfoData.getContainer()) { case LOOM_INPUT -> 0; case LOOM_DYE -> 1; case LOOM_MATERIAL -> 2; case LOOM_RESULT, CREATIVE_OUTPUT -> 3; default -> super.bedrockSlotToJava(slotInfoData); }; } @Override public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { return switch (slot) { case 0 -> new BedrockContainerSlot(ContainerSlotType.LOOM_INPUT, 9); case 1 -> new BedrockContainerSlot(ContainerSlotType.LOOM_DYE, 10); case 2 -> new BedrockContainerSlot(ContainerSlotType.LOOM_MATERIAL, 11); case 3 -> new BedrockContainerSlot(ContainerSlotType.LOOM_RESULT, 50); default -> super.javaSlotToBedrockContainer(slot); }; } @Override public int javaSlotToBedrock(int slot) { return switch (slot) { case 0 -> 9; case 1 -> 10; case 2 -> 11; case 3 -> 50; default -> super.javaSlotToBedrock(slot); }; } @Override public SlotType getSlotType(int javaSlot) { if (javaSlot == 3) { return SlotType.OUTPUT; } return super.getSlotType(javaSlot); } }