diff --git a/.idea/encodings.xml b/.idea/encodings.xml index e45f72b..387fa9e 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -29,6 +29,8 @@ + + diff --git a/NMS-v1_21_R1/pom.xml b/NMS-v1_21_R1/pom.xml new file mode 100644 index 0000000..598c369 --- /dev/null +++ b/NMS-v1_21_R1/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_21_R1 + + + 1.21-R0.1-SNAPSHOT + 21 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R1.java b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R1.java new file mode 100644 index 0000000..c349201 --- /dev/null +++ b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R1.java @@ -0,0 +1,139 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.MapId; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link MapSender_v1_21_R1} sends the Map packets to players. + */ +public class MapSender_v1_21_R1 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_21_R1() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final MapId id = new MapId(-id0); + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.b updateData = new WorldMap.b( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().c.a(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} diff --git a/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R1.java b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R1.java new file mode 100644 index 0000000..bae1bea --- /dev/null +++ b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R1.java @@ -0,0 +1,243 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.resources.MinecraftKey; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import net.minecraft.world.item.Items; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_21_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +public class MapWrapper_v1_21_R1 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_21_R1.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_21_R1.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_21_R1.sendMap(id, MapWrapper_v1_21_R1.this.content, player); + } else { + MapSender_v1_21_R1.addToQueue(id, MapWrapper_v1_21_R1.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_21_R1.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().cc.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().cc.j(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().c.a(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_21_R1.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().a(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.a().a(BuiltInRegistries.aq.a(MinecraftKey.a("minecraft:map_id")), mapId); //getOrCreateTag putInt + + List> list = new ArrayList<>(); + DataWatcher.c dataWatcherItem = DataWatcher.c.a(EntityItemFrame.f, nmsStack); + list.add(dataWatcherItem); + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, list); + + ((CraftPlayer) player).getHandle().c.a(packet); + } + }; + + public MapWrapper_v1_21_R1(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R1.java b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R1.java new file mode 100644 index 0000000..e722e1c --- /dev/null +++ b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R1.java @@ -0,0 +1,126 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_21_R1 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = packetPlayInSetCreativeSlot.b(); + ItemStack item = packetPlayInSetCreativeSlot.e(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + ChannelPipeline pipeline = networkManager.n.pipeline(); //connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + Channel channel = networkManager.n; //connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.c, vec3dObj.d, vec3dObj.e); //x, y, z + } +} diff --git a/pom.xml b/pom.xml index 1f55a66..1c36662 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,7 @@ API Dist + NMS-v1_21_R1 NMS-v1_20_R3 NMS-v1_20_R2 NMS-v1_20_R1