🎨 Moved back to reflection, still WIP

This commit is contained in:
SBDeveloper 2022-07-01 12:07:10 +02:00
parent a4f60fd01e
commit 6d1dab78aa
53 changed files with 879 additions and 4667 deletions

View file

@ -0,0 +1,102 @@
/*
* 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;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import org.bukkit.Bukkit;
import org.bukkit.map.MapView;
import org.bukkit.plugin.java.JavaPlugin;
import tech.sbdevelopment.mapreflectionapi.api.MapManager;
import tech.sbdevelopment.mapreflectionapi.listeners.MapListener;
import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener;
import java.util.logging.Level;
public class MapReflectionAPI extends JavaPlugin {
private static MapReflectionAPI instance;
private static MapManager mapManager;
private ProtocolManager protocolManager;
public static MapReflectionAPI getInstance() {
if (instance == null) throw new IllegalStateException("The plugin is not enabled yet!");
return instance;
}
public static MapManager getMapManager() {
if (mapManager == null) throw new IllegalStateException("The plugin is not enabled yet!");
return mapManager;
}
@Override
public void onEnable() {
instance = this;
getLogger().info("----------------");
getLogger().info("MapReflectionAPI v" + getDescription().getVersion() + "");
getLogger().info("Made by © Copyright SBDevelopment 2022");
if (!Bukkit.getPluginManager().isPluginEnabled("BKCommonLib")) {
getLogger().severe("MapReflectionAPI requires BKCommonLib to function!");
Bukkit.getPluginManager().disablePlugin(this);
return;
}
protocolManager = ProtocolLibrary.getProtocolManager();
protocolManager.addPacketListener(new PacketListener(this));
try {
mapManager = new MapManager(this);
} catch (IllegalStateException e) {
getLogger().log(Level.SEVERE, e.getMessage(), e);
Bukkit.getPluginManager().disablePlugin(this);
return;
}
getLogger().info("Registering the events...");
Bukkit.getPluginManager().registerEvents(new MapListener(), this);
getLogger().info("Discovering occupied Map IDs...");
for (int s = 0; s < Short.MAX_VALUE; s++) {
try {
MapView view = Bukkit.getMap(s);
if (view != null) mapManager.registerOccupiedID(s);
} catch (Exception e) {
if (e.getMessage().toLowerCase().contains("invalid map dimension")) {
getLogger().log(Level.WARNING, e.getMessage(), e);
}
}
}
getLogger().info("MapReflectionAPI is enabled!");
getLogger().info("----------------");
}
@Override
public void onDisable() {
getLogger().info("MapReflectionAPI is disabled!");
instance = null;
}
}

View file

@ -0,0 +1,112 @@
/*
* 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.api;
import com.bergerkiller.bukkit.common.map.MapColorPalette;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Objects;
public class ArrayImage {
public byte[] array;
public int minX = 0;
public int minY = 0;
public int maxX = 128;
public int maxY = 128;
private int width;
private int height;
private int imageType = BufferedImage.TYPE_4BYTE_ABGR;
public ArrayImage(byte[] data) {
this.array = data;
}
/**
* Convert a {@link BufferedImage} to an ArrayImage
*
* @param image image to convert
*/
public ArrayImage(BufferedImage image) {
this.imageType = image.getType();
this.width = image.getWidth();
this.height = image.getHeight();
BufferedImage temp = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = temp.createGraphics();
graphics.drawImage(image, 0, 0, null);
graphics.dispose();
int[] pixels = new int[temp.getWidth() * temp.getHeight()];
temp.getRGB(0, 0, temp.getWidth(), temp.getHeight(), pixels, 0, temp.getWidth());
byte[] result = new byte[temp.getWidth() * temp.getHeight()];
for (int i = 0; i < pixels.length; i++) {
result[i] = MapColorPalette.getColor(new Color(pixels[i], true));
}
this.array = result;
}
public BufferedImage toBuffered() {
BufferedImage img = new BufferedImage(width, height, this.imageType);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
img.setRGB(x, y, MapColorPalette.getRealColor(array[y * width + x]).getRGB());
}
}
return img;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ArrayImage)) return false;
ArrayImage that = (ArrayImage) o;
return width == that.width && height == that.height && imageType == that.imageType && Arrays.equals(array, that.array);
}
@Override
public int hashCode() {
int result = Objects.hash(width, height, imageType);
result = 31 * result + Arrays.hashCode(array);
return result;
}
@Override
public String toString() {
return "ArrayImage{" +
"array=" + Arrays.toString(array) +
", minX=" + minX +
", minY=" + minY +
", maxX=" + maxX +
", maxY=" + maxY +
", width=" + width +
", height=" + height +
", imageType=" + imageType +
'}';
}
}

View file

@ -0,0 +1,183 @@
/*
* 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.api;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import tech.sbdevelopment.mapreflectionapi.exceptions.MapLimitExceededException;
public interface MapController {
/**
* Add a viewer
*
* @param player {@link Player} to add
*/
void addViewer(Player player) throws MapLimitExceededException;
/**
* Remove a viewer
*
* @param player {@link OfflinePlayer} to remove
*/
void removeViewer(OfflinePlayer player);
/**
* Remove all viewers
*/
void clearViewers();
/**
* Check if a player is viewing
*
* @param player {@link OfflinePlayer} to check
* @return <code>true</code> if the player is viewing
*/
boolean isViewing(OfflinePlayer player);
/**
* Get the map ID for a player
*
* @param player {@link OfflinePlayer} to get the ID for
* @return the ID, or <code>-1</code> if no ID exists (i.e. the player is not viewing)
*/
int getMapId(OfflinePlayer player);
/**
* Update the image
*
* @param content new {@link ArrayImage} content
*/
void update(ArrayImage content);
ArrayImage getContent();
/**
* Send the content to a player
*
* @param player {@link Player} receiver of the content
*/
void sendContent(Player player);
/**
* Send the content to a player
*
* @param player {@link Player} receiver of the content
* @param withoutQueue if <code>true</code>, the content will be sent immediately
*/
void sendContent(Player player, boolean withoutQueue);
/**
* Show in a player's inventory
*
* @param player {@link Player}
* @param slot slot to show the map in
* @param force if <code>false</code>, the map will not be shown if the player is in creative mode
*/
void showInInventory(Player player, int slot, boolean force);
/**
* Show in a player's inventory
*
* @param player {@link Player}
* @param slot slot to show the map in
*/
void showInInventory(Player player, int slot);
/**
* Show in a player's hand
*
* @param player {@link Player}
* @param force if <code>false</code>, the map will not be shown if the player is not holding a map, or is in creative mode
* @see #showInFrame(Player, ItemFrame, boolean)
*/
void showInHand(Player player, boolean force);
/**
* Show in a player's hand
*
* @param player {@link Player}
*/
void showInHand(Player player);
/**
* Show in an {@link ItemFrame}
*
* @param player {@link Player} that will be able to see the map
* @param frame {@link ItemFrame} to show the map in
*/
void showInFrame(Player player, ItemFrame frame);
/**
* Show in an {@link ItemFrame}
*
* @param player {@link Player} that will be able to see the map
* @param frame {@link ItemFrame} to show the map in
* @param force if <code>false</code>, the map will not be shown if there is not Map-Item in the ItemFrame
*/
void showInFrame(Player player, ItemFrame frame, boolean force);
/**
* Show in an {@link ItemFrame}
*
* @param player {@link Player} that will be able to see the map
* @param entityId Entity-ID of the {@link ItemFrame} to show the map in
*/
void showInFrame(Player player, int entityId);
/**
* Show in an {@link ItemFrame}
*
* @param player {@link Player} that will be able to see the map
* @param entityId Entity-ID of the {@link ItemFrame} to show the map in
* @param debugInfo {@link String} to show when a player looks at the map, or <code>null</code>
*/
void showInFrame(Player player, int entityId, String debugInfo);
/**
* Clear a frame
*
* @param player {@link Player} that will be able to see the cleared frame
* @param entityId Entity-ID of the {@link ItemFrame} to clear
*/
void clearFrame(Player player, int entityId);
/**
* Clear a frame
*
* @param player {@link Player} that will be able to see the cleared frame
* @param frame {@link ItemFrame} to clear
*/
void clearFrame(Player player, ItemFrame frame);
/**
* Get an {@link ItemFrame} by its entity ID
*
* @param world The world the {@link ItemFrame} is in
* @param entityId Entity-ID of the {@link ItemFrame}
* @return The found {@link ItemFrame}, or <code>null</code>
*/
ItemFrame getItemFrameById(World world, int entityId);
}

View file

@ -0,0 +1,179 @@
/*
* 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.api;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.Nullable;
import tech.sbdevelopment.mapreflectionapi.exceptions.MapLimitExceededException;
import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
public class MapManager {
protected final Set<Integer> OCCUPIED_IDS = new HashSet<>();
private final List<MapWrapper> MANAGED_MAPS = new CopyOnWriteArrayList<>();
private final Class<?> wrapperClass;
public MapManager(JavaPlugin plugin) throws IllegalStateException {
String packageName = Bukkit.getServer().getClass().getPackage().getName();
String version = packageName.substring(packageName.lastIndexOf('.') + 1);
plugin.getLogger().info("Initializing the map manager for Minecraft version " + version + "...");
try {
final Class<?> clazz = Class.forName("tech.sbdevelopment.mapreflectionapi.nms.MapWrapper_" + version);
if (MapWrapper.class.isAssignableFrom(clazz)) {
wrapperClass = clazz;
} else {
throw new IllegalStateException("Plugin corrupted! Detected invalid MapWrapper class.");
}
} catch (Exception ex) {
throw new IllegalStateException("This Spigot version (" + version + ") is not supported! Contact the developer to get support.");
}
}
@Nullable
public MapWrapper wrapImage(BufferedImage image) {
return wrapImage(new ArrayImage(image));
}
@Nullable
public MapWrapper wrapImage(ArrayImage image) {
for (MapWrapper wrapper : MANAGED_MAPS) {
if (wrapper.getContent().equals(image)) return wrapper;
}
return wrapNewImage(image);
}
private MapWrapper wrapNewImage(ArrayImage image) {
try {
MapWrapper wrapper = (MapWrapper) wrapperClass.getDeclaredConstructor(ArrayImage.class).newInstance(image);
MANAGED_MAPS.add(wrapper);
return wrapper;
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) {
e.printStackTrace();
return null;
}
}
public void unwrapImage(MapWrapper wrapper) {
//TODO Cancel IDs
wrapper.getController().clearViewers();
MANAGED_MAPS.remove(wrapper);
}
public Set<MapWrapper> getMapsVisibleTo(OfflinePlayer player) {
Set<MapWrapper> visible = new HashSet<>();
for (MapWrapper wrapper : MANAGED_MAPS) {
if (wrapper.getController().isViewing(player)) {
visible.add(wrapper);
}
}
return visible;
}
public MapWrapper getWrapperForId(OfflinePlayer player, int id) {
for (MapWrapper wrapper : getMapsVisibleTo(player)) {
if (wrapper.getController().getMapId(player) == id) {
return wrapper;
}
}
return null;
}
public void registerOccupiedID(int id) {
OCCUPIED_IDS.add(id);
}
public void unregisterOccupiedID(int id) {
OCCUPIED_IDS.remove(id);
}
public Set<Integer> getOccupiedIdsFor(OfflinePlayer player) {
Set<Integer> ids = new HashSet<>();
for (MapWrapper wrapper : MANAGED_MAPS) {
int s = wrapper.getController().getMapId(player);
if (s >= 0) {
ids.add(s);
}
}
return ids;
}
public boolean isIdUsedBy(OfflinePlayer player, int id) {
return id > 0 && getOccupiedIdsFor(player).contains(id);
}
public int getNextFreeIdFor(Player player) throws MapLimitExceededException {
Set<Integer> occupied = getOccupiedIdsFor(player);
//Add the 'default' occupied IDs
occupied.addAll(OCCUPIED_IDS);
int largest = 0;
for (Integer s : occupied) {
if (s > largest) {
largest = s;
}
}
//Simply increase the maximum id if it's still small enough
if (largest + 1 < Integer.MAX_VALUE) {
return largest + 1;
}
//Otherwise iterate through all options until there is an unused id
for (int s = 0; s < Integer.MAX_VALUE; s++) {
if (!occupied.contains(s)) {
return s;
}
}
//If we end up here, this player has no more free ids. Let's hope nobody uses this many Maps.
throw new MapLimitExceededException("'" + player + "' reached the maximum amount of available Map-IDs");
}
public void clearAllMapsFor(OfflinePlayer player) {
for (MapWrapper wrapper : getMapsVisibleTo(player)) {
wrapper.getController().removeViewer(player);
}
}
public MapWrapper getDuplicate(ArrayImage image) {
for (MapWrapper wrapper : MANAGED_MAPS) {
if (image.equals(wrapper.getContent())) {
return wrapper;
}
}
return null;
}
}

View file

@ -0,0 +1,165 @@
/*
* 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.api;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.util.ReflectionUtil;
import tech.sbdevelopment.mapreflectionapi.util.ReflectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class MapSender {
private static final List<QueuedMap> sendQueue = new ArrayList<>();
private static int senderID = -1;
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();
}
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);
}
public static void sendMap(final int id0, final ArrayImage content, final Player player) {
if (player == null || !player.isOnline()) {
List<QueuedMap> 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;
}
Class<?> packetClass = ReflectionUtils.getNMSClass("network.protocol.game", "PacketPlayOutMap");
final int id = -id0;
Object packet;
if (ReflectionUtils.supports(17)) { //1.17+
Class<?> worldMapClass = ReflectionUtils.getNMSClass("world.level.saveddata.maps", "WorldMap");
Object updateData = ReflectionUtil.callConstructor(worldMapClass,
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
);
packet = ReflectionUtil.callConstructor(packetClass,
id, //ID
(byte) 0, //Scale
false, //Show icons
new ArrayList<>(), //Icons
updateData
);
} else if (ReflectionUtils.supports(14)) { //1.16-1.14
packet = ReflectionUtil.callConstructor(packetClass,
id, //ID
(byte) 0, //Scale
false, //Tracking position
false, //Locked
new ArrayList<>(), //Icons
content.array, //Data
content.minX, //X pos
content.minY, //Y pos
content.maxX, //X size (2nd X pos)
content.maxY //Y size (2nd Y pos)
);
} else { //1.13-
packet = ReflectionUtil.callConstructor(packetClass,
id, //ID
(byte) 0, //Scale
false, //???
new ArrayList<>(), //Icons
content.array, //Data
content.minX, //X pos
content.minY, //Y pos
content.maxX, //X size (2nd X pos)
content.maxY //Y size (2nd Y pos)
);
}
ReflectionUtils.sendPacket(player, packet);
}
static final class QueuedMap {
private final int id;
private final ArrayImage image;
private final Player player;
QueuedMap(int id, ArrayImage image, Player player) {
this.id = id;
this.image = image;
this.player = player;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
QueuedMap that = (QueuedMap) obj;
return this.id == that.id &&
Objects.equals(this.image, that.image) &&
Objects.equals(this.player, that.player);
}
@Override
public int hashCode() {
return Objects.hash(id, image, player);
}
@Override
public String toString() {
return "QueuedMap[" +
"id=" + id + ", " +
"image=" + image + ", " +
"player=" + player + ']';
}
}
}

View file

@ -0,0 +1,290 @@
/*
* 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.api;
import org.bukkit.*;
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.exceptions.MapLimitExceededException;
import tech.sbdevelopment.mapreflectionapi.util.ReflectionUtil;
import tech.sbdevelopment.mapreflectionapi.util.ReflectionUtils;
import java.util.*;
public class MapWrapper {
protected ArrayImage content;
public MapWrapper(ArrayImage image) {
this.content = image;
}
protected MapController controller = new MapController() {
private final Map<UUID, Integer> 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.this.content = duplicate.getContent();
return;
}
MapWrapper.this.content = content;
for (UUID id : viewers.keySet()) {
sendContent(Bukkit.getPlayer(id));
}
}
@Override
public ArrayImage getContent() {
return MapWrapper.this.getContent();
}
@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.sendMap(id, MapWrapper.this.content, player);
} else {
MapSender.addToQueue(id, MapWrapper.this.content, player);
}
}
@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);
}
Object playerHandle = ReflectionUtils.getHandle(player);
Object inventoryMenu = ReflectionUtil.getField(playerHandle, ReflectionUtils.supports(19) ? "bT" : ReflectionUtils.supports(17) ? "bU" : "defaultContainer");
int windowId = (int) ReflectionUtil.getField(inventoryMenu, ReflectionUtils.supports(17) ? "j" : "windowId");
ItemStack stack = new ItemStack(ReflectionUtils.supports(13) ? Material.FILLED_MAP : Material.MAP, 1);
Class<?> craftStackClass = ReflectionUtils.getCraftClass("CraftItemStack");
Object nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack);
Class<?> setSlotPacketClass = ReflectionUtils.getNMSClass("network.protocol.game", "PacketPlayOutSetSlot");
Object packet;
if (ReflectionUtils.supports(17)) { //1.17+
int stateId = (int) ReflectionUtil.callMethod(inventoryMenu, ReflectionUtils.supports(18) ? "j" : "getStateId");
packet = ReflectionUtil.callConstructor(setSlotPacketClass,
windowId,
stateId,
slot,
nmsStack
);
} else { //1.16-
packet = ReflectionUtil.callConstructor(setSlotPacketClass,
windowId,
slot,
nmsStack
);
}
ReflectionUtils.sendPacket(player, 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() != (ReflectionUtils.supports(13) ? Material.FILLED_MAP : Material.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() != (ReflectionUtils.supports(13) ? Material.FILLED_MAP : Material.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(ReflectionUtils.supports(13) ? Material.FILLED_MAP : Material.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.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) {
Object worldHandle = ReflectionUtils.getHandle(world);
Object nmsEntity = ReflectionUtil.callMethod(worldHandle, ReflectionUtils.supports(18) ? "a" : "getEntity");
if (nmsEntity == null) return null;
if (!ReflectionUtils.supports(17)) {
nmsEntity = ReflectionUtil.callMethod(nmsEntity, "getBukkitEntity");
}
if (nmsEntity instanceof ItemFrame) return (ItemFrame) nmsEntity;
return null;
}
private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) {
Class<?> craftStackClass = ReflectionUtils.getCraftClass("CraftItemStack");
Object nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack);
Object nbtObject = ReflectionUtil.callMethod(nmsStack, ReflectionUtils.supports(19) ? "v" : ReflectionUtils.supports(18) ? "u" : ReflectionUtils.supports(13) ? "getOrCreateTag" : "getTag");
if (!ReflectionUtils.supports(13) && nbtObject == null) { //1.12 has no getOrCreate, call create if null!
Class<?> tagCompoundClass = ReflectionUtils.getCraftClass("NBTTagCompound");
Object tagCompound = ReflectionUtil.callConstructor(tagCompoundClass);
ReflectionUtil.callMethod(nbtObject, "setTag", tagCompound);
}
ReflectionUtil.callMethod(nbtObject, ReflectionUtils.supports(18) ? "a" : "setInt", "map", mapId);
Class<?> entityClass = ReflectionUtils.getNMSClass("world.entity", "Entity");
Class<?> dataWatcherClass = ReflectionUtils.getNMSClass("network.syncher", "DataWatcher");
Object dataWatcher = ReflectionUtil.callConstructor(dataWatcherClass, entityClass.cast(null));
Class<?> entityMetadataPacketClass = ReflectionUtils.getNMSClass("network.protocol.game", "PacketPlayOutEntityMetadata");
Object packet = ReflectionUtil.callConstructor(entityMetadataPacketClass,
entityId,
dataWatcher, //dummy watcher!
true
);
List<Object> list = new ArrayList<>();
Class<?> entityItemFrameClass = ReflectionUtils.getNMSClass("world.entity.decoration", "EntityItemFrame");
Object dataWatcherObject = ReflectionUtil.getDeclaredField(entityItemFrameClass, ReflectionUtils.supports(17) ? "ao" : ReflectionUtils.supports(14) ? "ITEM" : ReflectionUtils.supports(13) ? "e" : "c");
Class<?> dataWatcherItemClass = ReflectionUtils.getNMSClass("network.syncher", "DataWatcher$Item");
Object dataWatcherItem = ReflectionUtil.callConstructor(dataWatcherItemClass, dataWatcherObject, nmsStack);
list.add(dataWatcherItem);
ReflectionUtil.setDeclaredField(packet, "b", list);
ReflectionUtils.sendPacket(player, packet);
}
};
public ArrayImage getContent() {
return content;
}
public MapController getController() {
return controller;
}
}

View file

@ -0,0 +1,96 @@
/*
* 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.api.events;
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 tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.api.MapWrapper;
public class CreateInventoryMapUpdateEvent extends Event implements Cancellable {
private static final HandlerList handlerList = new HandlerList();
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;
}
public CreateInventoryMapUpdateEvent(Player player, int slot, ItemStack item, boolean isAsync) {
super(isAsync);
this.player = player;
this.slot = slot;
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;
}
@Override
public void setCancelled(boolean b) {
this.cancelled = b;
}
}

View file

@ -0,0 +1,74 @@
/*
* 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.api.events;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class MapCancelEvent extends Event implements Cancellable {
private static final HandlerList handlerList = new HandlerList();
private final Player player;
private final int id;
private boolean cancelled;
public MapCancelEvent(Player player, int id) {
this.player = player;
this.id = id;
}
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;
}
}

View file

@ -0,0 +1,118 @@
/*
* 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.api.events;
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 tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.api.MapWrapper;
public class MapInteractEvent extends Event implements Cancellable {
private static final HandlerList handlerList = new HandlerList();
private final Player player;
private final int entityID;
private final int action;
private final Vector vector;
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;
}
public MapInteractEvent(Player player, int entityID, int action, Vector vector, int hand, boolean isAsync) {
super(isAsync);
this.player = player;
this.entityID = entityID;
this.action = action;
this.vector = vector;
this.hand = hand;
}
public static HandlerList getHandlerList() {
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;
}
public ItemFrame getFrame() {
if (getMapWrapper() == null) return null;
if (frame == null) {
frame = getMapWrapper().getController().getItemFrameById(player.getWorld(), entityID);
}
return frame;
}
public MapWrapper getMapWrapper() {
if (mapWrapper == null) {
mapWrapper = MapReflectionAPI.getMapManager().getWrapperForId(player, entityID);
}
return mapWrapper;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean b) {
this.cancelled = b;
}
@Override
public HandlerList getHandlers() {
return handlerList;
}
}

View file

@ -0,0 +1,30 @@
/*
* 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.exceptions;
public class MapLimitExceededException extends Exception {
public MapLimitExceededException(String message) {
super(message);
}
}

View file

@ -0,0 +1,43 @@
/*
* 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 org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.MapInitializeEvent;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
public class MapListener implements Listener {
@EventHandler
public void onQuit(PlayerQuitEvent e) {
MapReflectionAPI.getMapManager().clearAllMapsFor(e.getPlayer());
}
@EventHandler
public void onMapInitialize(MapInitializeEvent e) {
int id = e.getMap().getId();
if (id > 0) MapReflectionAPI.getMapManager().registerOccupiedID(id);
}
}

View file

@ -0,0 +1,94 @@
/*
* 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 = action.getAction();
EnumWrappers.Hand hand = action.getHand();
Vector pos = action.getPosition();
boolean async = !plugin.getServer().isPrimaryThread();
MapInteractEvent interactEvent = new MapInteractEvent(event.getPlayer(), entityId, actionEnum.ordinal(), pos, hand.ordinal(), 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);
}
}
}
}

View file

@ -0,0 +1,165 @@
/*
* 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.util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* <b>ReflectionUtil</b> - Reflection handler for NMS and CraftBukkit.<br>
* Caches the packet related methods and is asynchronous.
* <p>
* This class does not handle null checks as most of the requests are from the
* other utility classes that already handle null checks.
* <p>
* <a href="https://wiki.vg/Protocol">Clientbound Packets</a> 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 {
private ReflectionUtil() {
}
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;
return clazz;
}
private static Class<?>[] toParamTypes(Object... params) {
return Arrays.stream(params)
.map(obj -> wrapperToPrimitive(obj.getClass()))
.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 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(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(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, toParamTypes(value));
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
}
}
}

View file

@ -0,0 +1,381 @@
/*
* 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.util;
import org.bukkit.World;
import org.bukkit.entity.Player;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
/**
* <b>ReflectionUtils</b> - Reflection handler for NMS and CraftBukkit.<br>
* Caches the packet related methods and is asynchronous.
* <p>
* This class does not handle null checks as most of the requests are from the
* other utility classes that already handle null checks.
* <p>
* <a href="https://wiki.vg/Protocol">Clientbound Packets</a> are considered fake
* updates to the client without changing the actual data. Since all the data is handled
* by the server.
* <p>
* A useful resource used to compare mappings is <a href="https://minidigger.github.io/MiniMappingViewer/#/spigot">Mini's Mapping Viewer</a>
*
* @author Crypto Morin
* @version 6.0.1
*/
public final class ReflectionUtils {
/**
* 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.
* <p>
* 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.
* <p>
* Performance is not a concern for these specific statically initialized values.
*/
public static final String VERSION;
/**
* 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.
* <p>
* 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 { // 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;
}
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 ReflectionUtils() {
}
/**
* This method is purely for readability.
* No performance is gained.
*
* @since 5.0.0
*/
public static <T> VersionHandler<T> v(int version, T handle) {
return new VersionHandler<>(version, handle);
}
public static <T> CallableVersionHandler<T> v(int version, Callable<T> handle) {
return new CallableVersionHandler<>(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;
}
/**
* 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
*/
@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
*/
@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<Void> 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();
}
}
@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;
}
}
@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;
}
}
@Nullable
public static Object getConnection(@Nonnull Player player) {
Objects.requireNonNull(player, "Cannot get connection of null player");
try {
Object handle = GET_HANDLE.invoke(player);
return PLAYER_CONNECTION.invoke(handle);
} 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
*/
@Nullable
public static Class<?> getCraftClass(@Nonnull String name) {
try {
return Class.forName(CRAFTBUKKIT + name);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
return null;
}
}
public static Class<?> getArrayClass(String clazz, boolean nms) {
clazz = "[L" + (nms ? NMS : CRAFTBUKKIT) + clazz + ';';
try {
return Class.forName(clazz);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
return null;
}
}
public static Class<?> toArrayClass(Class<?> clazz) {
try {
return Class.forName("[L" + clazz.getName() + ';');
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
return null;
}
}
public static final class VersionHandler<T> {
private int version;
private T handle;
private VersionHandler(int version, T handle) {
if (supports(version)) {
this.version = version;
this.handle = handle;
}
}
public VersionHandler<T> 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;
}
}
public static final class CallableVersionHandler<T> {
private int version;
private Callable<T> handle;
private CallableVersionHandler(int version, Callable<T> handle) {
if (supports(version)) {
this.version = version;
this.handle = handle;
}
}
public CallableVersionHandler<T> v(int version, Callable<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(Callable<T> handle) {
try {
return (this.version == 0 ? handle : this.handle).call();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
}