) getField(EntityItemFrame.class, "c");
- DataWatcher.Item> dataWatcherItem = new DataWatcher.Item<>(dataWatcherObject, nmsStack);
- list.add(dataWatcherItem);
- setField(packet, "b", list);
- } catch (Exception e) {
- e.printStackTrace();
- return;
+ if (!ReflectionUtil.supports(13) && nbtObject == null) { //1.12 has no getOrCreate, call create if null!
+ Object tagCompound = ReflectionUtil.callConstructor(tagCompoundClass);
+ ReflectionUtil.callMethod(nbtObject, "setTag", tagCompound);
}
- ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet);
+ ReflectionUtil.callMethod(nbtObject, ReflectionUtil.supports(18) ? "a" : "setInt", "map", mapId);
+ Object dataWatcher = ReflectionUtil.callConstructorNull(dataWatcherClass, entityClass);
+
+ Object packet = ReflectionUtil.callConstructor(entityMetadataPacketClass,
+ entityId,
+ dataWatcher, //dummy watcher!
+ true
+ );
+
+ List list = new ArrayList<>();
+ Object dataWatcherObject = ReflectionUtil.getDeclaredField(entityItemFrameClass, ReflectionUtil.supports(17) ? "ao" : ReflectionUtil.supports(14) ? "ITEM" : ReflectionUtil.supports(13) ? "e" : "c");
+ Object dataWatcherItem = ReflectionUtil.callFirstConstructor(dataWatcherItemClass, dataWatcherObject, nmsStack);
+ list.add(dataWatcherItem);
+ ReflectionUtil.setDeclaredField(packet, "b", list);
+
+ ReflectionUtil.sendPacket(player, packet);
}
};
- public MapWrapper_v1_12_R1(ArrayImage image) {
- super(image);
+ /**
+ * Get the content that is wrapped
+ *
+ * @return The {@link ArrayImage}
+ */
+ public ArrayImage getContent() {
+ return content;
}
- @Override
+ /**
+ * Get the controller of this wrapper
+ *
+ * @return The {@link MapController}
+ */
public MapController getController() {
return controller;
}
diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java
similarity index 75%
rename from API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java
rename to src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java
index 8c9fe99..05c956a 100644
--- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java
+++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java
@@ -23,29 +23,42 @@
package tech.sbdevelopment.mapreflectionapi.api.events;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.Nullable;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.api.MapWrapper;
+/**
+ * This event gets fired when a map in the creative inventory gets updated
+ */
+@RequiredArgsConstructor
+@Getter
public class CreateInventoryMapUpdateEvent extends Event implements Cancellable {
private static final HandlerList handlerList = new HandlerList();
+ @Setter
+ private boolean cancelled;
+
private final Player player;
private final int slot;
private final ItemStack item;
private MapWrapper mapWrapper;
- private boolean cancelled;
-
- public CreateInventoryMapUpdateEvent(Player player, int slot, ItemStack item) {
- this.player = player;
- this.slot = slot;
- this.item = item;
- }
+ /**
+ * Construct a new {@link CreateInventoryMapUpdateEvent}
+ *
+ * @param player The player whose inventory is updated
+ * @param slot The new slot
+ * @param item The item in the new slot
+ * @param isAsync Is this event called async?
+ */
public CreateInventoryMapUpdateEvent(Player player, int slot, ItemStack item, boolean isAsync) {
super(isAsync);
this.player = player;
@@ -53,44 +66,24 @@ public class CreateInventoryMapUpdateEvent extends Event implements Cancellable
this.item = item;
}
- public static HandlerList getHandlerList() {
- return handlerList;
- }
-
- public Player getPlayer() {
- return player;
- }
-
- public int getSlot() {
- return slot;
- }
-
- public ItemStack getItem() {
- return item;
- }
-
- public MapWrapper getMapWrapper() {
- if (mapWrapper == null) {
- if (item == null) return null;
- if (item.getType() != Material.MAP) return null;
- MapReflectionAPI.getMapManager().getWrapperForId(player, item.getDurability());
- }
-
- return mapWrapper;
- }
-
@Override
public HandlerList getHandlers() {
return handlerList;
}
- @Override
- public boolean isCancelled() {
- return cancelled;
- }
+ /**
+ * Get the {@link MapWrapper} of the map of this event
+ *
+ * @return The {@link MapWrapper}
+ */
+ @Nullable
+ public MapWrapper getMapWrapper() {
+ if (mapWrapper == null) {
+ if (item == null) return null;
+ if (item.getType() != Material.MAP) return null;
+ mapWrapper = MapReflectionAPI.getMapManager().getWrapperForId(player, item.getDurability());
+ }
- @Override
- public void setCancelled(boolean b) {
- this.cancelled = b;
+ return mapWrapper;
}
}
diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java
similarity index 79%
rename from API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java
rename to src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java
index 72d40d0..4ab8825 100644
--- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java
+++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java
@@ -23,52 +23,42 @@
package tech.sbdevelopment.mapreflectionapi.api.events;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
+/**
+ * This event gets fired when a map creation is cancelled
+ */
+@RequiredArgsConstructor
+@Getter
public class MapCancelEvent extends Event implements Cancellable {
private static final HandlerList handlerList = new HandlerList();
- private final Player player;
- private final int id;
+ @Setter
private boolean cancelled;
- public MapCancelEvent(Player player, int id) {
- this.player = player;
- this.id = id;
- }
+ private final Player player;
+ private final int id;
+ /**
+ * Construct a new {@link MapCancelEvent}
+ *
+ * @param player The player who tried to create the map
+ * @param id The ID of the map
+ * @param isAsync Is this event called async?
+ */
public MapCancelEvent(Player player, int id, boolean isAsync) {
super(isAsync);
this.player = player;
this.id = id;
}
- public static HandlerList getHandlerList() {
- return handlerList;
- }
-
- public Player getPlayer() {
- return player;
- }
-
- public int getId() {
- return id;
- }
-
@Override
public HandlerList getHandlers() {
return handlerList;
}
-
- @Override
- public boolean isCancelled() {
- return cancelled;
- }
-
- @Override
- public void setCancelled(boolean b) {
- this.cancelled = b;
- }
}
diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java
similarity index 76%
rename from API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java
rename to src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java
index 327c762..aad264b 100644
--- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java
+++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java
@@ -23,17 +23,29 @@
package tech.sbdevelopment.mapreflectionapi.api.events;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.util.Vector;
+import org.jetbrains.annotations.Nullable;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.api.MapWrapper;
+/**
+ * This event gets fired when a player interact with a map
+ */
+@RequiredArgsConstructor
+@Getter
public class MapInteractEvent extends Event implements Cancellable {
private static final HandlerList handlerList = new HandlerList();
+ @Setter
+ private boolean cancelled;
+
private final Player player;
private final int entityID;
private final int action;
@@ -41,16 +53,17 @@ public class MapInteractEvent extends Event implements Cancellable {
private final int hand;
private ItemFrame frame;
private MapWrapper mapWrapper;
- private boolean cancelled;
-
- public MapInteractEvent(Player player, int entityID, int action, Vector vector, int hand) {
- this.player = player;
- this.entityID = entityID;
- this.action = action;
- this.vector = vector;
- this.hand = hand;
- }
+ /**
+ * Construct a new {@link MapInteractEvent}
+ *
+ * @param player The player who interacted
+ * @param entityID The ID of the entity the map is in
+ * @param action The interact action
+ * @param vector The location of the entity
+ * @param hand The hand the player clicked with
+ * @param isAsync Is this event called async?
+ */
public MapInteractEvent(Player player, int entityID, int action, Vector vector, int hand, boolean isAsync) {
super(isAsync);
this.player = player;
@@ -60,30 +73,17 @@ public class MapInteractEvent extends Event implements Cancellable {
this.hand = hand;
}
- public static HandlerList getHandlerList() {
+ @Override
+ public HandlerList getHandlers() {
return handlerList;
}
- public Player getPlayer() {
- return player;
- }
-
- public int getEntityID() {
- return entityID;
- }
-
- public int getAction() {
- return action;
- }
-
- public Vector getVector() {
- return vector;
- }
-
- public int getHand() {
- return hand;
- }
-
+ /**
+ * Get the {@link ItemFrame} the map is in
+ *
+ * @return The frame the map is in, or null if it's not a map
+ */
+ @Nullable
public ItemFrame getFrame() {
if (getMapWrapper() == null) return null;
@@ -93,6 +93,12 @@ public class MapInteractEvent extends Event implements Cancellable {
return frame;
}
+ /**
+ * Get the {@link MapWrapper} of the map
+ *
+ * @return The wrapper
+ */
+ @Nullable
public MapWrapper getMapWrapper() {
if (mapWrapper == null) {
mapWrapper = MapReflectionAPI.getMapManager().getWrapperForId(player, entityID);
@@ -100,19 +106,4 @@ public class MapInteractEvent extends Event implements Cancellable {
return mapWrapper;
}
-
- @Override
- public boolean isCancelled() {
- return cancelled;
- }
-
- @Override
- public void setCancelled(boolean b) {
- this.cancelled = b;
- }
-
- @Override
- public HandlerList getHandlers() {
- return handlerList;
- }
}
diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/exceptions/MapLimitExceededException.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java
similarity index 83%
rename from API/src/main/java/tech/sbdevelopment/mapreflectionapi/exceptions/MapLimitExceededException.java
rename to src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java
index 8fd4caa..47e3510 100644
--- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/exceptions/MapLimitExceededException.java
+++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java
@@ -21,9 +21,17 @@
* SOFTWARE.
*/
-package tech.sbdevelopment.mapreflectionapi.exceptions;
+package tech.sbdevelopment.mapreflectionapi.api.exceptions;
+/**
+ * This exception gets thrown if no map IDs are available
+ */
public class MapLimitExceededException extends Exception {
+ /**
+ * Construct a new {@link MapLimitExceededException}
+ *
+ * @param message The message in this exception
+ */
public MapLimitExceededException(String message) {
super(message);
}
diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java
similarity index 100%
rename from API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java
rename to src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java
new file mode 100644
index 0000000..38621f8
--- /dev/null
+++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java
@@ -0,0 +1,100 @@
+/*
+ * This file is part of MapReflectionAPI.
+ * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ *
+ * 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.
+ */
+
+package tech.sbdevelopment.mapreflectionapi.listeners;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.events.ListenerPriority;
+import com.comphenix.protocol.events.PacketAdapter;
+import com.comphenix.protocol.events.PacketEvent;
+import com.comphenix.protocol.wrappers.EnumWrappers;
+import com.comphenix.protocol.wrappers.WrappedEnumEntityUseAction;
+import org.bukkit.Bukkit;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.plugin.Plugin;
+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;
+
+public class PacketListener extends PacketAdapter {
+ public PacketListener(Plugin plugin) {
+ super(plugin, ListenerPriority.NORMAL, PacketType.Play.Server.MAP, PacketType.Play.Client.USE_ENTITY, PacketType.Play.Client.SET_CREATIVE_SLOT);
+ }
+
+ @Override
+ public void onPacketSending(PacketEvent event) {
+ if (event.getPacketType() != PacketType.Play.Server.MAP) return; //Make sure it's the right packet!
+
+ int id = event.getPacket().getIntegers().read(0); //Read first int (a); that's the MAP id
+
+ if (id < 0) {
+ //It's one of our maps, invert ID and let through!
+ int newId = -id;
+ event.getPacket().getIntegers().write(0, newId); //set the MAP id to the reverse
+ } else {
+ boolean async = !plugin.getServer().isPrimaryThread();
+ MapCancelEvent cancelEvent = new MapCancelEvent(event.getPlayer(), id, async);
+ if (MapReflectionAPI.getMapManager().isIdUsedBy(event.getPlayer(), id)) cancelEvent.setCancelled(true);
+ if (cancelEvent.getHandlers().getRegisteredListeners().length > 0)
+ Bukkit.getPluginManager().callEvent(cancelEvent);
+
+ if (cancelEvent.isCancelled()) event.setCancelled(true);
+ }
+ }
+
+ @Override
+ public void onPacketReceiving(PacketEvent event) {
+ if (event.getPacketType() == PacketType.Play.Client.USE_ENTITY) {
+ int entityId = event.getPacket().getIntegers().read(0); //entityId
+ WrappedEnumEntityUseAction action = event.getPacket().getEnumEntityUseActions().read(0);
+ EnumWrappers.EntityUseAction actionEnum = null;
+ EnumWrappers.Hand hand = null;
+ Vector pos = null;
+ try {
+ actionEnum = action.getAction();
+ hand = action.getHand();
+ pos = action.getPosition();
+ } catch (IllegalArgumentException ignored) {
+ }
+
+ boolean async = !plugin.getServer().isPrimaryThread();
+ MapInteractEvent interactEvent = new MapInteractEvent(event.getPlayer(), entityId, actionEnum != null ? actionEnum.ordinal() : 0, pos, hand != null ? hand.ordinal() : 0, async);
+ if (interactEvent.getFrame() != null && interactEvent.getMapWrapper() != null) {
+ Bukkit.getPluginManager().callEvent(interactEvent);
+ if (interactEvent.isCancelled()) event.setCancelled(true);
+ }
+ } else if (event.getPacketType() == PacketType.Play.Client.SET_CREATIVE_SLOT) {
+ int slot = event.getPacket().getIntegers().read(0);
+ ItemStack item = event.getPacket().getItemModifier().read(0);
+
+ boolean async = !plugin.getServer().isPrimaryThread();
+ CreateInventoryMapUpdateEvent updateEvent = new CreateInventoryMapUpdateEvent(event.getPlayer(), slot, item, async);
+ if (updateEvent.getMapWrapper() != null) {
+ Bukkit.getPluginManager().callEvent(updateEvent);
+ if (updateEvent.isCancelled()) event.setCancelled(true);
+ }
+ }
+ }
+}
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java
new file mode 100644
index 0000000..ac91b23
--- /dev/null
+++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java
@@ -0,0 +1,486 @@
+/*
+ * This file is part of MapReflectionAPI.
+ * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ *
+ * 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.
+ */
+
+package tech.sbdevelopment.mapreflectionapi.utils;
+
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.annotation.Nonnull;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * ReflectionUtil - Reflection handler for NMS and CraftBukkit.
+ * Caches the packet related methods and is asynchronous.
+ *
+ * This class does not handle null checks as most of the requests are from the
+ * other utility classes that already handle null checks.
+ *
+ * Clientbound Packets are considered fake
+ * updates to the client without changing the actual data. Since all the data is handled
+ * by the server.
+ *
+ * @author Crypto Morin, Stijn Bannink
+ * @version 2.1
+ */
+public class ReflectionUtil {
+ /**
+ * We use reflection mainly to avoid writing a new class for version barrier.
+ * The version barrier is for NMS that uses the Minecraft version as the main package name.
+ *
+ * E.g. EntityPlayer in 1.15 is in the class {@code net.minecraft.server.v1_15_R1}
+ * but in 1.14 it's in {@code net.minecraft.server.v1_14_R1}
+ * In order to maintain cross-version compatibility we cannot import these classes.
+ *
+ * Performance is not a concern for these specific statically initialized values.
+ */
+ public static final String VERSION;
+
+ static { // This needs to be right below VERSION because of initialization order.
+ // This package loop is used to avoid implementation-dependant strings like Bukkit.getVersion() or Bukkit.getBukkitVersion()
+ // which allows easier testing as well.
+ String found = null;
+ for (Package pack : Package.getPackages()) {
+ String name = pack.getName();
+
+ // .v because there are other packages.
+ if (name.startsWith("org.bukkit.craftbukkit.v")) {
+ found = pack.getName().split("\\.")[3];
+
+ // Just a final guard to make sure it finds this important class.
+ // As a protection for forge+bukkit implementation that tend to mix versions.
+ // The real CraftPlayer should exist in the package.
+ // Note: Doesn't seem to function properly. Will need to separate the version
+ // handler for NMS and CraftBukkit for softwares like catmc.
+ try {
+ Class.forName("org.bukkit.craftbukkit." + found + ".entity.CraftPlayer");
+ break;
+ } catch (ClassNotFoundException e) {
+ found = null;
+ }
+ }
+ }
+ if (found == null)
+ throw new IllegalArgumentException("Failed to parse server version. Could not find any package starting with name: 'org.bukkit.craftbukkit.v'");
+ VERSION = found;
+ }
+
+ /**
+ * The raw minor version number.
+ * E.g. {@code v1_17_R1} to {@code 17}
+ *
+ * @since 4.0.0
+ */
+ public static final int VER = Integer.parseInt(VERSION.substring(1).split("_")[1]);
+ /**
+ * Mojang remapped their NMS in 1.17 https://www.spigotmc.org/threads/spigot-bungeecord-1-17.510208/#post-4184317
+ */
+ public static final String
+ CRAFTBUKKIT = "org.bukkit.craftbukkit." + VERSION + '.',
+ NMS = v(17, "net.minecraft.").orElse("net.minecraft.server." + VERSION + '.');
+ /**
+ * A nullable public accessible field only available in {@code EntityPlayer}.
+ * This can be null if the player is offline.
+ */
+ private static final MethodHandle PLAYER_CONNECTION;
+ /**
+ * Responsible for getting the NMS handler {@code EntityPlayer} object for the player.
+ * {@code CraftPlayer} is simply a wrapper for {@code EntityPlayer}.
+ * Used mainly for handling packet related operations.
+ *
+ * This is also where the famous player {@code ping} field comes from!
+ */
+ private static final MethodHandle GET_HANDLE;
+ private static final MethodHandle GET_HANDLE_WORLD;
+ /**
+ * Sends a packet to the player's client through a {@code NetworkManager} which
+ * is where {@code ProtocolLib} controls packets by injecting channels!
+ */
+ private static final MethodHandle SEND_PACKET;
+
+ static {
+ Class> entityPlayer = getNMSClass("server.level", "EntityPlayer");
+ Class> worldServer = getNMSClass("server.level", "WorldServer");
+ Class> craftPlayer = getCraftClass("entity.CraftPlayer");
+ Class> craftWorld = getCraftClass("CraftWorld");
+ Class> playerConnection = getNMSClass("server.network", "PlayerConnection");
+
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ MethodHandle sendPacket = null, getHandle = null, getHandleWorld = null, connection = null;
+
+ try {
+ connection = lookup.findGetter(entityPlayer,
+ v(17, "b").orElse("playerConnection"), playerConnection);
+ getHandle = lookup.findVirtual(craftPlayer, "getHandle", MethodType.methodType(entityPlayer));
+ getHandleWorld = lookup.findVirtual(craftWorld, "getHandle", MethodType.methodType(worldServer));
+ sendPacket = lookup.findVirtual(playerConnection,
+ v(18, "a").orElse("sendPacket"),
+ MethodType.methodType(void.class, getNMSClass("network.protocol", "Packet")));
+ } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException ex) {
+ ex.printStackTrace();
+ }
+
+ PLAYER_CONNECTION = connection;
+ SEND_PACKET = sendPacket;
+ GET_HANDLE = getHandle;
+ GET_HANDLE_WORLD = getHandleWorld;
+ }
+
+ private ReflectionUtil() {
+ }
+
+ /**
+ * This method is purely for readability.
+ * No performance is gained.
+ *
+ * @since 5.0.0
+ */
+ public static VersionHandler v(int version, T handle) {
+ return new VersionHandler<>(version, handle);
+ }
+
+ /**
+ * Checks whether the server version is equal or greater than the given version.
+ *
+ * @param version the version to compare the server version with.
+ * @return true if the version is equal or newer, otherwise false.
+ * @since 4.0.0
+ */
+ public static boolean supports(int version) {
+ return VER >= version;
+ }
+
+ private static Class> wrapperToPrimitive(Class> clazz) {
+ if (clazz == Boolean.class) return boolean.class;
+ if (clazz == Integer.class) return int.class;
+ if (clazz == Double.class) return double.class;
+ if (clazz == Float.class) return float.class;
+ if (clazz == Long.class) return long.class;
+ if (clazz == Short.class) return short.class;
+ if (clazz == Byte.class) return byte.class;
+ if (clazz == Void.class) return void.class;
+ if (clazz == Character.class) return char.class;
+ if (clazz == ArrayList.class) return Collection.class;
+ return clazz;
+ }
+
+ private static Class>[] toParamTypes(Object... params) {
+ return Arrays.stream(params)
+ .map(obj -> obj != null ? wrapperToPrimitive(obj.getClass()) : null)
+ .toArray(Class>[]::new);
+ }
+
+ @Nullable
+ public static Class> getClass(@NotNull String name) {
+ try {
+ return Class.forName(name);
+ } catch (ClassNotFoundException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ @Nullable
+ public static Object callConstructorNull(Class> clazz, Class> paramClass) {
+ try {
+ Constructor> con = clazz.getConstructor(paramClass);
+ con.setAccessible(true);
+ return con.newInstance(clazz.cast(null));
+ } catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
+ InvocationTargetException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ @Nullable
+ public static Object callFirstConstructor(Class> clazz, Object... params) {
+ try {
+ Constructor> con = clazz.getConstructors()[0];
+ con.setAccessible(true);
+ return con.newInstance(params);
+ } catch (IllegalAccessException | InstantiationException |
+ InvocationTargetException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ @Nullable
+ public static Object callConstructor(Class> clazz, Object... params) {
+ try {
+ Constructor> con = clazz.getConstructor(toParamTypes(params));
+ con.setAccessible(true);
+ return con.newInstance(params);
+ } catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
+ InvocationTargetException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ @Nullable
+ public static Object callDeclaredConstructor(Class> clazz, Object... params) {
+ try {
+ Constructor> con = clazz.getDeclaredConstructor(toParamTypes(params));
+ con.setAccessible(true);
+ return con.newInstance(params);
+ } catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
+ InvocationTargetException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ @Nullable
+ public static Object callMethod(Class> clazz, String method, Object... params) {
+ try {
+ Method m = clazz.getMethod(method, toParamTypes(params));
+ m.setAccessible(true);
+ return m.invoke(null, params);
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ @Nullable
+ public static Object callMethod(Object obj, String method, Object... params) {
+ try {
+ Method m = obj.getClass().getMethod(method, toParamTypes(params));
+ m.setAccessible(true);
+ return m.invoke(obj, params);
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ @Nullable
+ public static Object callDeclaredMethod(Object obj, String method, Object... params) {
+ try {
+ Method m = obj.getClass().getDeclaredMethod(method, toParamTypes(params));
+ m.setAccessible(true);
+ return m.invoke(obj, params);
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ @Nullable
+ public static Object getField(Object object, String field) {
+ try {
+ Field f = object.getClass().getField(field);
+ f.setAccessible(true);
+ return f.get(object);
+ } catch (NoSuchFieldException | IllegalAccessException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ @Nullable
+ public static Object getDeclaredField(Class> clazz, String field) {
+ try {
+ Field f = clazz.getDeclaredField(field);
+ f.setAccessible(true);
+ return f.get(null);
+ } catch (NoSuchFieldException | IllegalAccessException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ @Nullable
+ public static Object getDeclaredField(Object object, String field) {
+ try {
+ Field f = object.getClass().getDeclaredField(field);
+ f.setAccessible(true);
+ return f.get(object);
+ } catch (NoSuchFieldException | IllegalAccessException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ public static void setDeclaredField(Object object, String field, Object value) {
+ try {
+ Field f = object.getClass().getDeclaredField(field);
+ f.setAccessible(true);
+ f.set(object, value);
+ } catch (NoSuchFieldException | IllegalAccessException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Get a NMS (net.minecraft.server) class which accepts a package for 1.17 compatibility.
+ *
+ * @param newPackage the 1.17 package name.
+ * @param name the name of the class.
+ * @return the NMS class or null if not found.
+ * @since 4.0.0
+ */
+ @javax.annotation.Nullable
+ public static Class> getNMSClass(@Nonnull String newPackage, @Nonnull String name) {
+ if (supports(17)) name = newPackage + '.' + name;
+ return getNMSClass(name);
+ }
+
+ /**
+ * Get a NMS (net.minecraft.server) class.
+ *
+ * @param name the name of the class.
+ * @return the NMS class or null if not found.
+ * @since 1.0.0
+ */
+ @javax.annotation.Nullable
+ public static Class> getNMSClass(@Nonnull String name) {
+ try {
+ return Class.forName(NMS + name);
+ } catch (ClassNotFoundException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Sends a packet to the player asynchronously if they're online.
+ * Packets are thread-safe.
+ *
+ * @param player the player to send the packet to.
+ * @param packets the packets to send.
+ * @return the async thread handling the packet.
+ * @see #sendPacketSync(Player, Object...)
+ * @since 1.0.0
+ */
+ @Nonnull
+ public static CompletableFuture sendPacket(@Nonnull Player player, @Nonnull Object... packets) {
+ return CompletableFuture.runAsync(() -> sendPacketSync(player, packets))
+ .exceptionally(ex -> {
+ ex.printStackTrace();
+ return null;
+ });
+ }
+
+ /**
+ * Sends a packet to the player synchronously if they're online.
+ *
+ * @param player the player to send the packet to.
+ * @param packets the packets to send.
+ * @see #sendPacket(Player, Object...)
+ * @since 2.0.0
+ */
+ public static void sendPacketSync(@Nonnull Player player, @Nonnull Object... packets) {
+ try {
+ Object handle = GET_HANDLE.invoke(player);
+ Object connection = PLAYER_CONNECTION.invoke(handle);
+
+ // Checking if the connection is not null is enough. There is no need to check if the player is online.
+ if (connection != null) {
+ for (Object packet : packets) SEND_PACKET.invoke(connection, packet);
+ }
+ } catch (Throwable throwable) {
+ throwable.printStackTrace();
+ }
+ }
+
+ @javax.annotation.Nullable
+ public static Object getHandle(@Nonnull Player player) {
+ Objects.requireNonNull(player, "Cannot get handle of null player");
+ try {
+ return GET_HANDLE.invoke(player);
+ } catch (Throwable throwable) {
+ throwable.printStackTrace();
+ return null;
+ }
+ }
+
+ @javax.annotation.Nullable
+ public static Object getHandle(@Nonnull World world) {
+ Objects.requireNonNull(world, "Cannot get handle of null world");
+ try {
+ return GET_HANDLE_WORLD.invoke(world);
+ } catch (Throwable throwable) {
+ throwable.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Get a CraftBukkit (org.bukkit.craftbukkit) class.
+ *
+ * @param name the name of the class to load.
+ * @return the CraftBukkit class or null if not found.
+ * @since 1.0.0
+ */
+ @javax.annotation.Nullable
+ public static Class> getCraftClass(@Nonnull String name) {
+ try {
+ return Class.forName(CRAFTBUKKIT + name);
+ } catch (ClassNotFoundException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ public static final class VersionHandler {
+ private int version;
+ private T handle;
+
+ private VersionHandler(int version, T handle) {
+ if (supports(version)) {
+ this.version = version;
+ this.handle = handle;
+ }
+ }
+
+ public VersionHandler v(int version, T handle) {
+ if (version == this.version)
+ throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version);
+ if (version > this.version && supports(version)) {
+ this.version = version;
+ this.handle = handle;
+ }
+ return this;
+ }
+
+ public T orElse(T handle) {
+ return this.version == 0 ? handle : this.handle;
+ }
+ }
+}
\ No newline at end of file
diff --git a/API/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
similarity index 87%
rename from API/src/main/resources/plugin.yml
rename to src/main/resources/plugin.yml
index 030ee7f..025688e 100644
--- a/API/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -5,4 +5,4 @@ api-version: 1.13
authors: [ inventivetalent, SBDeveloper ]
description: This API helps developer with viewing images on maps.
website: https://sbdevelopment.tech
-softdepend: [ BKCommonLib ]
\ No newline at end of file
+softdepend: [ BKCommonLib, ProtocolLib ]
\ No newline at end of file