diff --git a/bukkit/src/main/java/us/myles/ViaVersion/bukkit/platform/BukkitViaLoader.java b/bukkit/src/main/java/us/myles/ViaVersion/bukkit/platform/BukkitViaLoader.java index 49bf0aa91..3d2a53084 100644 --- a/bukkit/src/main/java/us/myles/ViaVersion/bukkit/platform/BukkitViaLoader.java +++ b/bukkit/src/main/java/us/myles/ViaVersion/bukkit/platform/BukkitViaLoader.java @@ -1,10 +1,12 @@ package us.myles.ViaVersion.bukkit.platform; import lombok.AllArgsConstructor; + import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; + import us.myles.ViaVersion.ViaVersionPlugin; import us.myles.ViaVersion.api.Via; import us.myles.ViaVersion.api.data.UserConnection; @@ -12,9 +14,11 @@ import us.myles.ViaVersion.api.minecraft.item.Item; import us.myles.ViaVersion.api.platform.ViaPlatformLoader; import us.myles.ViaVersion.bukkit.listeners.UpdateListener; import us.myles.ViaVersion.bukkit.listeners.protocol1_9to1_8.*; +import us.myles.ViaVersion.bukkit.providers.BukkitInvContainerItemProvider; import us.myles.ViaVersion.bukkit.providers.BukkitViaBulkChunkTranslator; import us.myles.ViaVersion.bukkit.providers.BukkitViaMovementTransmitter; import us.myles.ViaVersion.protocols.base.ProtocolInfo; +import us.myles.ViaVersion.protocols.protocol1_12to1_11_1.providers.InvContainerItemProvider; import us.myles.ViaVersion.protocols.protocol1_9to1_8.providers.BulkChunkTranslatorProvider; import us.myles.ViaVersion.protocols.protocol1_9to1_8.providers.HandItemProvider; import us.myles.ViaVersion.protocols.protocol1_9to1_8.providers.MovementTransmitterProvider; @@ -60,6 +64,7 @@ public class BukkitViaLoader implements ViaPlatformLoader { /* Providers */ Via.getManager().getProviders().use(BulkChunkTranslatorProvider.class, new BukkitViaBulkChunkTranslator()); Via.getManager().getProviders().use(MovementTransmitterProvider.class, new BukkitViaMovementTransmitter()); + Via.getManager().getProviders().use(InvContainerItemProvider.class, new BukkitInvContainerItemProvider()); Via.getManager().getProviders().use(HandItemProvider.class, new HandItemProvider() { @Override public Item getHandItem(final UserConnection info) { diff --git a/bukkit/src/main/java/us/myles/ViaVersion/bukkit/protocol1_12to1_11_1/BukkitInvContainerUpdateTask.java b/bukkit/src/main/java/us/myles/ViaVersion/bukkit/protocol1_12to1_11_1/BukkitInvContainerUpdateTask.java new file mode 100644 index 000000000..4e9fad983 --- /dev/null +++ b/bukkit/src/main/java/us/myles/ViaVersion/bukkit/protocol1_12to1_11_1/BukkitInvContainerUpdateTask.java @@ -0,0 +1,53 @@ +package us.myles.ViaVersion.bukkit.protocol1_12to1_11_1; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import us.myles.ViaVersion.bukkit.providers.BukkitInvContainerItemProvider; +import us.myles.ViaVersion.protocols.protocol1_12to1_11_1.storage.InvItemStorage; + +public class BukkitInvContainerUpdateTask implements Runnable { + + private BukkitInvContainerItemProvider provider; + private final UUID uuid; + private final List items; + + public BukkitInvContainerUpdateTask(BukkitInvContainerItemProvider provider, UUID uuid) { + this.provider = provider; + this.uuid = uuid; + this.items = Collections.synchronizedList(new ArrayList<>()); + } + + public void addItem(short windowId, short slotId, short anumber) { + InvItemStorage storage = new InvItemStorage(windowId, slotId, anumber); + items.add(storage); + } + + @Override + public void run() { + Player p = Bukkit.getServer().getPlayer(uuid); + if (p == null) { + provider.onTaskExecuted(uuid); + return; + } + try { + synchronized (items) { + for (InvItemStorage storage : items) { + Object packet = provider.buildWindowClickPacket(p, storage); + boolean result = provider.sendPlayer(p, packet); + if (!result) { + break; + } + } + items.clear(); + } + } finally { + provider.onTaskExecuted(uuid); + } + } +} \ No newline at end of file diff --git a/bukkit/src/main/java/us/myles/ViaVersion/bukkit/providers/BukkitInvContainerItemProvider.java b/bukkit/src/main/java/us/myles/ViaVersion/bukkit/providers/BukkitInvContainerItemProvider.java new file mode 100644 index 000000000..d4e7746c1 --- /dev/null +++ b/bukkit/src/main/java/us/myles/ViaVersion/bukkit/providers/BukkitInvContainerItemProvider.java @@ -0,0 +1,168 @@ +package us.myles.ViaVersion.bukkit.providers; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; + +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.bukkit.protocol1_12to1_11_1.BukkitInvContainerUpdateTask; +import us.myles.ViaVersion.bukkit.util.NMSUtil; +import us.myles.ViaVersion.protocols.base.ProtocolInfo; +import us.myles.ViaVersion.protocols.protocol1_12to1_11_1.providers.InvContainerItemProvider; +import us.myles.ViaVersion.protocols.protocol1_12to1_11_1.storage.InvItemStorage; +import us.myles.ViaVersion.util.ReflectionUtil; + +public class BukkitInvContainerItemProvider extends InvContainerItemProvider { + + private static Map updateTasks = new ConcurrentHashMap<>(); + private boolean supported; + // packet class + private Class wclickPacketClass; + private Object clickTypeEnum; + // Use for nms + private Method nmsItemMethod; + private Method ephandle; + private Field connection; + private Method packetMethod; + + public BukkitInvContainerItemProvider() { + this.supported = isSupported(); + setupReflection(); + } + + @Override + public boolean registerInvClickPacket(short windowId, short slotId, short anumber, UserConnection uconnection) { + if (!supported) { + return false; + } + ProtocolInfo info = uconnection.get(ProtocolInfo.class); + UUID uuid = info.getUuid(); + BukkitInvContainerUpdateTask utask = updateTasks.get(uuid); + final boolean registered = utask != null; + if (!registered) { + utask = new BukkitInvContainerUpdateTask(this, uuid); + updateTasks.put(uuid, utask); + } + // http://wiki.vg/index.php?title=Protocol&oldid=13223#Click_Window + utask.addItem(windowId, slotId, anumber); + if (!registered) { + scheduleTask(utask); + } + return true; + } + + public Object buildWindowClickPacket(Player p, InvItemStorage storage) { + if (!supported) { + return null; + } + InventoryView inv = p.getOpenInventory(); + short slotId = storage.getSlotId(); + if (slotId > inv.countSlots()) { + return null; // wrong container open? + } + ItemStack itemstack = inv.getItem(slotId); + if (itemstack == null) { + return null; + } + Object cinstance = null; + try { + cinstance = wclickPacketClass.newInstance(); + Object nmsItem = nmsItemMethod.invoke(null, itemstack); + ReflectionUtil.set(cinstance, "a", (int) storage.getWindowId()); + ReflectionUtil.set(cinstance, "slot", (int) slotId); + ReflectionUtil.set(cinstance, "button", 0); // shift + left mouse click + ReflectionUtil.set(cinstance, "d", storage.getActionNumber()); + ReflectionUtil.set(cinstance, "item", nmsItem); + int protocolId = ProtocolRegistry.SERVER_PROTOCOL; + if (protocolId == 47) { + ReflectionUtil.set(cinstance, "shift", 1); + } else if (protocolId >= 107) { // 1.9+ + ReflectionUtil.set(cinstance, "shift", clickTypeEnum); + } + } catch (Exception e) { + e.printStackTrace(); + } + return cinstance; + } + + public boolean sendPlayer(Player p, Object packet) { + if (packet == null) { + return false; + } + try { + Object entityPlayer = ephandle.invoke(p); + Object pconnection = connection.get(entityPlayer); + // send + packetMethod.invoke(pconnection, packet); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + return false; + } + return true; + } + + public void onTaskExecuted(UUID uuid) { + updateTasks.remove(uuid); + } + + private void scheduleTask(BukkitInvContainerUpdateTask utask) { + BukkitScheduler scheduler = Bukkit.getServer().getScheduler(); + Plugin instance = Bukkit.getServer().getPluginManager().getPlugin("ViaVersion"); + scheduler.runTaskLater(instance, utask, 2); // 2 ticks later (possible double click action). + } + + private void setupReflection() { + if (!supported) { + return; + } + try { + this.wclickPacketClass = NMSUtil.nms("PacketPlayInWindowClick"); + Class eclassz = NMSUtil.nms("InventoryClickType"); + Object[] constants = eclassz.getEnumConstants(); + this.clickTypeEnum = constants[1]; // QUICK_MOVE + Class citemStack = NMSUtil.obc("inventory.CraftItemStack"); + this.nmsItemMethod = citemStack.getDeclaredMethod("asNMSCopy", ItemStack.class); + } catch (Exception e) { + this.supported = false; + return; + } + try { + this.ephandle = NMSUtil.obc("entity.CraftPlayer").getDeclaredMethod("getHandle"); + } catch (NoSuchMethodException | ClassNotFoundException e) { + this.supported = false; + throw new RuntimeException("Couldn't find CraftPlayer", e); + } + try { + this.connection = NMSUtil.nms("EntityPlayer").getDeclaredField("playerConnection"); + } catch (NoSuchFieldException | ClassNotFoundException e) { + this.supported = false; + throw new RuntimeException("Couldn't find Player Connection", e); + } + try { + this.packetMethod = NMSUtil.nms("PlayerConnection").getDeclaredMethod("a", wclickPacketClass); + } catch (NoSuchMethodException | ClassNotFoundException e) { + this.supported = false; + throw new RuntimeException("Couldn't find CraftPlayer", e); + } + } + + private boolean isSupported() { + int protocolId = ProtocolRegistry.SERVER_PROTOCOL; + if (protocolId >= 47 && protocolId <= 316) { + return true; // 1.8 + } + // this is not needed on 1.12+ + return false; + } +} \ No newline at end of file diff --git a/common/src/main/java/us/myles/ViaVersion/api/PacketWrapper.java b/common/src/main/java/us/myles/ViaVersion/api/PacketWrapper.java index 6182d5d92..6421495e8 100644 --- a/common/src/main/java/us/myles/ViaVersion/api/PacketWrapper.java +++ b/common/src/main/java/us/myles/ViaVersion/api/PacketWrapper.java @@ -149,10 +149,11 @@ public class PacketWrapper { } } else { Pair read = readableObjects.poll(); - if (read.getKey().equals(type) || (type.getBaseClass().equals(read.getKey().getBaseClass()) && type.getOutputClass().equals(read.getKey().getOutputClass()))) { + Type rtype = read.getKey(); + if (rtype.equals(type) || (type.getBaseClass().equals(rtype.getBaseClass()) && type.getOutputClass().equals(rtype.getOutputClass()))) { return (T) read.getValue(); } else { - if (type == Type.NOTHING) { + if (rtype == Type.NOTHING) { return read(type); // retry } else { Exception e = new IOException("Unable to read type " + type.getTypeName() + ", found " + read.getKey().getTypeName()); diff --git a/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_12to1_11_1/Protocol1_12To1_11_1.java b/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_12to1_11_1/Protocol1_12To1_11_1.java index f153ea234..49d5cb373 100644 --- a/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_12to1_11_1/Protocol1_12To1_11_1.java +++ b/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_12to1_11_1/Protocol1_12To1_11_1.java @@ -12,6 +12,7 @@ import us.myles.ViaVersion.api.data.UserConnection; import us.myles.ViaVersion.api.entities.Entity1_12Types; import us.myles.ViaVersion.api.minecraft.chunks.Chunk; import us.myles.ViaVersion.api.minecraft.chunks.ChunkSection; +import us.myles.ViaVersion.api.platform.providers.ViaProviders; import us.myles.ViaVersion.api.protocol.Protocol; import us.myles.ViaVersion.api.remapper.PacketHandler; import us.myles.ViaVersion.api.remapper.PacketRemapper; @@ -19,6 +20,7 @@ import us.myles.ViaVersion.api.type.Type; import us.myles.ViaVersion.api.type.types.version.Types1_12; import us.myles.ViaVersion.packets.State; import us.myles.ViaVersion.protocols.protocol1_12to1_11_1.packets.InventoryPackets; +import us.myles.ViaVersion.protocols.protocol1_12to1_11_1.providers.InvContainerItemProvider; import us.myles.ViaVersion.protocols.protocol1_12to1_11_1.storage.EntityTracker; import us.myles.ViaVersion.protocols.protocol1_9_1_2to1_9_3_4.types.Chunk1_9_3_4Type; import us.myles.ViaVersion.protocols.protocol1_9_3to1_9_1_2.storage.ClientWorld; @@ -384,6 +386,11 @@ public class Protocol1_12To1_11_1 extends Protocol { newId += 3; return newId; } + + @Override + protected void register(ViaProviders providers) { + providers.register(InvContainerItemProvider.class, new InvContainerItemProvider()); + } @Override public void init(UserConnection userConnection) { diff --git a/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_12to1_11_1/packets/InventoryPackets.java b/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_12to1_11_1/packets/InventoryPackets.java index d59b713a2..743d52d0f 100644 --- a/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_12to1_11_1/packets/InventoryPackets.java +++ b/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_12to1_11_1/packets/InventoryPackets.java @@ -1,6 +1,7 @@ package us.myles.ViaVersion.protocols.protocol1_12to1_11_1.packets; import us.myles.ViaVersion.api.PacketWrapper; +import us.myles.ViaVersion.api.Via; import us.myles.ViaVersion.api.minecraft.item.Item; import us.myles.ViaVersion.api.remapper.PacketHandler; import us.myles.ViaVersion.api.remapper.PacketRemapper; @@ -8,6 +9,7 @@ import us.myles.ViaVersion.api.type.Type; import us.myles.ViaVersion.packets.State; import us.myles.ViaVersion.protocols.protocol1_12to1_11_1.BedRewriter; import us.myles.ViaVersion.protocols.protocol1_12to1_11_1.Protocol1_12To1_11_1; +import us.myles.ViaVersion.protocols.protocol1_12to1_11_1.providers.InvContainerItemProvider; public class InventoryPackets { public static void register(Protocol1_12To1_11_1 protocol) { @@ -118,8 +120,23 @@ public class InventoryPackets { handler(new PacketHandler() { @Override public void handle(PacketWrapper wrapper) throws Exception { + byte button = wrapper.get(Type.BYTE, 0); + int mode = wrapper.get(Type.VAR_INT, 0); Item item = wrapper.get(Type.ITEM, 0); - BedRewriter.toServerItem(item); + // QUICK_MOVE PATCH (Shift + (click/double click)) + if (mode == 1 && button == 0 && item == null) { + short windowId = wrapper.get(Type.UNSIGNED_BYTE, 0); + short slotId = wrapper.get(Type.SHORT, 0); + short anumber = wrapper.get(Type.SHORT, 1); + InvContainerItemProvider provider = Via.getManager().getProviders().get(InvContainerItemProvider.class); + boolean succeed = provider.registerInvClickPacket(windowId, slotId, anumber, wrapper.user()); + if (succeed) { + wrapper.cancel(); + } + // otherwise just pass through so the server sends the PacketPlayOutTransaction packet. + } else { + BedRewriter.toServerItem(item); + } } }); } diff --git a/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_12to1_11_1/providers/InvContainerItemProvider.java b/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_12to1_11_1/providers/InvContainerItemProvider.java new file mode 100644 index 000000000..e928136ea --- /dev/null +++ b/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_12to1_11_1/providers/InvContainerItemProvider.java @@ -0,0 +1,11 @@ +package us.myles.ViaVersion.protocols.protocol1_12to1_11_1.providers; + +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.api.platform.providers.Provider; + +public class InvContainerItemProvider implements Provider { + + public boolean registerInvClickPacket(short windowId, short slotId, short anumber, UserConnection uconnection) { + return false; // not supported :/ plays very sad violin + } +} \ No newline at end of file diff --git a/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_12to1_11_1/storage/InvItemStorage.java b/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_12to1_11_1/storage/InvItemStorage.java new file mode 100644 index 000000000..9deb38231 --- /dev/null +++ b/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_12to1_11_1/storage/InvItemStorage.java @@ -0,0 +1,15 @@ +package us.myles.ViaVersion.protocols.protocol1_12to1_11_1.storage; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +@AllArgsConstructor +@ToString +@Getter +public class InvItemStorage { + + private short windowId; + private short slotId; + private short actionNumber; +} \ No newline at end of file