diff --git a/.gitignore b/.gitignore index e3f5493..4841f93 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ buildNumber.properties .mvn/timing.properties # https://github.com/takari/maven-wrapper#usage-without-binary-jar .mvn/wrapper/maven-wrapper.jar +.flattened-pom.xml # Eclipse m2e generated files # Eclipse Core diff --git a/.idea/.gitignore b/.idea/.gitignore index 13566b8..ecca007 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -5,4 +5,4 @@ /httpRequests/ # Datasource local storage ignored files /dataSources/ -/dataSources.local.xml +/dataSources.local.xml \ No newline at end of file diff --git a/.idea/copyright/SBDevelopment.xml b/.idea/copyright/SBDevelopment.xml index 7471535..9a257e5 100644 --- a/.idea/copyright/SBDevelopment.xml +++ b/.idea/copyright/SBDevelopment.xml @@ -1,6 +1,6 @@ - - + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..26f52f6 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml index d8e9561..a672138 100644 --- a/.idea/discord.xml +++ b/.idea/discord.xml @@ -1,7 +1,7 @@ - - + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 337d139..242311a 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -1,6 +1,10 @@ + + + + @@ -17,8 +21,20 @@ - - + + + + + + + + + + + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml index 285bc57..01cf610 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,8 +1,5 @@ - - - @@ -17,20 +14,11 @@ - + \ No newline at end of file diff --git a/.idea/ros.xml b/.idea/ros.xml new file mode 100644 index 0000000..29eeb8c --- /dev/null +++ b/.idea/ros.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/API/pom.xml b/API/pom.xml new file mode 100644 index 0000000..a276161 --- /dev/null +++ b/API/pom.xml @@ -0,0 +1,178 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-API + + + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + org.projectlombok + lombok + 1.18.28 + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + + package + + shade + + + false + + + com.bergerkiller.bukkit.common + tech.sbdevelopment.mapreflectionapi.libs.bkcommonlib + + + org.bstats + tech.sbdevelopment.mapreflectionapi.libs.bstats + + + + + + + + org.projectlombok + lombok-maven-plugin + 1.18.20.0 + + ${project.basedir}/src/main/java + ${maven.lombok.delombok-target} + false + + + + generate-sources + + delombok + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.5.0 + + ${jdk.version} + ${maven.lombok.delombok-target} + + **/com/bergerkiller/bukkit/common/io/*.java + **/com/bergerkiller/bukkit/common/map/*.java + **/com/bergerkiller/bukkit/common/map/color/*.java + **/tech/sbdevelopment/mapreflectionapi/*.java + **/tech/sbdevelopment/mapreflectionapi/cmd/*.java + **/tech/sbdevelopment/mapreflectionapi/managers/*.java + **/tech/sbdevelopment/mapreflectionapi/utils/*.java + **/tech/sbdevelopment/mapreflectionapi/listeners/*.java + + + + + + + src/main/resources + true + + plugin.yml + + + + src/main/resources + + plugin.yml + + + + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + MG-Dev Jenkins CI Maven Repository + https://ci.mg-dev.eu/plugin/repository/everything + + + dmulloy2-repo + https://repo.dmulloy2.net/repository/public/ + + + + + + org.projectlombok + lombok + 1.18.28 + provided + + + org.bstats + bstats-bukkit + 3.0.2 + compile + + + + + org.jetbrains + annotations-java5 + 23.0.0 + provided + + + io.netty + netty-transport + 4.1.77.Final + provided + + + diff --git a/src/main/java/com/bergerkiller/bukkit/common/LICENSE b/API/src/main/java/com/bergerkiller/bukkit/common/LICENSE similarity index 100% rename from src/main/java/com/bergerkiller/bukkit/common/LICENSE rename to API/src/main/java/com/bergerkiller/bukkit/common/LICENSE diff --git a/src/main/java/com/bergerkiller/bukkit/common/README.md b/API/src/main/java/com/bergerkiller/bukkit/common/README.md similarity index 100% rename from src/main/java/com/bergerkiller/bukkit/common/README.md rename to API/src/main/java/com/bergerkiller/bukkit/common/README.md diff --git a/src/main/java/com/bergerkiller/bukkit/common/io/BitInputStream.java b/API/src/main/java/com/bergerkiller/bukkit/common/io/BitInputStream.java similarity index 97% rename from src/main/java/com/bergerkiller/bukkit/common/io/BitInputStream.java rename to API/src/main/java/com/bergerkiller/bukkit/common/io/BitInputStream.java index 4b89417..90631c8 100644 --- a/src/main/java/com/bergerkiller/bukkit/common/io/BitInputStream.java +++ b/API/src/main/java/com/bergerkiller/bukkit/common/io/BitInputStream.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/com/bergerkiller/bukkit/common/io/BitPacket.java b/API/src/main/java/com/bergerkiller/bukkit/common/io/BitPacket.java similarity index 93% rename from src/main/java/com/bergerkiller/bukkit/common/io/BitPacket.java rename to API/src/main/java/com/bergerkiller/bukkit/common/io/BitPacket.java index f841f39..fcc5516 100644 --- a/src/main/java/com/bergerkiller/bukkit/common/io/BitPacket.java +++ b/API/src/main/java/com/bergerkiller/bukkit/common/io/BitPacket.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java b/API/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java similarity index 96% rename from src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java rename to API/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java index f3ea19a..affdef6 100644 --- a/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java +++ b/API/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,8 +27,6 @@ import java.awt.*; import java.io.InputStream; import java.util.Arrays; -import static com.cryptomorin.xseries.reflection.XReflection.supports; - /** * Additional functionality on top of Bukkit's MapPalette */ @@ -52,12 +50,14 @@ public class MapColorPalette { MCSDBubbleFormat bubbleData = new MCSDBubbleFormat(); try { String bub_path_postfix; - if (supports(17)) { + if (ReflectionUtil.supports(17)) { bub_path_postfix = "map_1_17.bub"; - } else if (supports(16)) { + } else if (ReflectionUtil.supports(16)) { bub_path_postfix = "map_1_16.bub"; - } else { + } else if (ReflectionUtil.supports(12)) { bub_path_postfix = "map_1_12.bub"; + } else { + bub_path_postfix = "map_1_8_8.bub"; } String bub_path = "/tech/sbdevelopment/mapreflectionapi/libs/bkcommonlib/internal/resources/map/" + bub_path_postfix; InputStream input = MapColorPalette.class.getResourceAsStream(bub_path); diff --git a/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDBubbleFormat.java b/API/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDBubbleFormat.java similarity index 100% rename from src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDBubbleFormat.java rename to API/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDBubbleFormat.java diff --git a/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDGenBukkit.java b/API/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDGenBukkit.java similarity index 94% rename from src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDGenBukkit.java rename to API/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDGenBukkit.java index b4a4e22..ea06d57 100644 --- a/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDGenBukkit.java +++ b/API/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDGenBukkit.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDWebbingCodec.java b/API/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDWebbingCodec.java similarity index 98% rename from src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDWebbingCodec.java rename to API/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDWebbingCodec.java index 57f1710..1331937 100644 --- a/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDWebbingCodec.java +++ b/API/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDWebbingCodec.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/com/bergerkiller/bukkit/common/map/color/MapColorSpaceData.java b/API/src/main/java/com/bergerkiller/bukkit/common/map/color/MapColorSpaceData.java similarity index 98% rename from src/main/java/com/bergerkiller/bukkit/common/map/color/MapColorSpaceData.java rename to API/src/main/java/com/bergerkiller/bukkit/common/map/color/MapColorSpaceData.java index 1e09f4d..c6185d6 100644 --- a/src/main/java/com/bergerkiller/bukkit/common/map/color/MapColorSpaceData.java +++ b/API/src/main/java/com/bergerkiller/bukkit/common/map/color/MapColorSpaceData.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java similarity index 86% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java index 0d76789..ac18791 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java @@ -30,15 +30,15 @@ import tech.sbdevelopment.mapreflectionapi.listeners.MapListener; import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; import tech.sbdevelopment.mapreflectionapi.managers.Configuration; import tech.sbdevelopment.mapreflectionapi.utils.MainUtil; +import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; import tech.sbdevelopment.mapreflectionapi.utils.UpdateManager; import java.util.logging.Level; -import static com.cryptomorin.xseries.reflection.XReflection.supports; - public class MapReflectionAPI extends JavaPlugin { private static MapReflectionAPI instance; private static MapManager mapManager; + private static PacketListener packetListener; /** * Get the plugin instance @@ -68,8 +68,8 @@ public class MapReflectionAPI extends JavaPlugin { getLogger().info("MapReflectionAPI v" + getDescription().getVersion()); getLogger().info("Made by © Copyright SBDevelopment 2023"); - if (!supports(12)) { - getLogger().severe("MapReflectionAPI only supports Minecraft 1.12 - 1.20.5!"); + if (!ReflectionUtil.supports(12)) { + getLogger().severe("MapReflectionAPI only supports Minecraft 1.12 - 1.19.4!"); Bukkit.getPluginManager().disablePlugin(this); return; } @@ -90,11 +90,26 @@ public class MapReflectionAPI extends JavaPlugin { getLogger().info("Loading the commands..."); getCommand("mapmanager").setExecutor(new MapManagerCMD()); - getLogger().info("Loading the map manager..."); - mapManager = new MapManager(); + try { + packetListener = PacketListener.construct(this); + } catch (IllegalStateException e) { + getLogger().log(Level.SEVERE, e.getMessage(), e); + Bukkit.getPluginManager().disablePlugin(this); + return; + } + packetListener.init(this); + + try { + mapManager = new MapManager(this); + } catch (IllegalStateException e) { + getLogger().log(Level.SEVERE, e.getMessage(), e); + Bukkit.getPluginManager().disablePlugin(this); + return; + } if (Configuration.getInstance().isAllowVanilla()) { - getLogger().info("Vanilla Maps are allowed. Discovering occupied Map IDs..."); + getLogger().info("Vanilla Maps are allowed!"); + getLogger().info("Discovering occupied Map IDs..."); int occupiedIDs = 0; for (int s = 0; s < Short.MAX_VALUE; s++) { try { @@ -114,9 +129,8 @@ public class MapReflectionAPI extends JavaPlugin { getLogger().info("Registering the listeners..."); Bukkit.getPluginManager().registerEvents(new MapListener(), this); - Bukkit.getPluginManager().registerEvents(new PacketListener(), this); - getLogger().info("Loading metrics..."); + getLogger().info("Enabling metrics..."); Metrics metrics = new Metrics(this, 16033); metrics.addCustomChart(new SingleLineChart("managed_maps", () -> mapManager.getManagedMapsCount())); @@ -168,6 +182,9 @@ public class MapReflectionAPI extends JavaPlugin { @Override public void onDisable() { + getLogger().info("Disabling the packet handler..."); + if (packetListener != null) Bukkit.getOnlinePlayers().forEach(p -> packetListener.removePlayer(p)); + getLogger().info("MapReflectionAPI is disabled!"); instance = null; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/AbstractMapWrapper.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/AbstractMapWrapper.java similarity index 93% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/AbstractMapWrapper.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/AbstractMapWrapper.java index fc44a5b..b6e56c8 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/AbstractMapWrapper.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/AbstractMapWrapper.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,4 +33,4 @@ public abstract class AbstractMapWrapper { getController().cancelSend(); getController().clearViewers(); } -} +} \ No newline at end of file diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/ArrayImage.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/ArrayImage.java similarity index 97% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/ArrayImage.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/ArrayImage.java index d58cfb2..c32abdf 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/ArrayImage.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/ArrayImage.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java similarity index 96% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java index ca0927f..773c4a9 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java similarity index 92% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java index 978e8ac..11a71cd 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java @@ -19,6 +19,7 @@ package tech.sbdevelopment.mapreflectionapi.api; import org.bukkit.OfflinePlayer; +import org.bukkit.World; import org.bukkit.entity.ItemFrame; import org.bukkit.entity.Player; @@ -116,4 +117,13 @@ public interface MapController extends IMapController { * @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 null + */ + ItemFrame getItemFrameById(World world, int entityId); } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java similarity index 84% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java index 8b05f72..00b906d 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,29 +18,47 @@ package tech.sbdevelopment.mapreflectionapi.api; +import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; -import org.bukkit.World; -import org.bukkit.entity.ItemFrame; import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Nullable; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; import tech.sbdevelopment.mapreflectionapi.managers.Configuration; -import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; 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; -import static com.cryptomorin.xseries.reflection.XReflection.*; - /** * The {@link MapManager} manages all the maps. It also contains functions for wrapping. */ public class MapManager { protected final Set occupiedIds = new HashSet<>(); protected final List managedMaps = 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("Enabling MapManager for " + 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."); + } + } /** * Get the amount of maps managed by the plugin @@ -129,9 +147,15 @@ public class MapManager { * @return The wrapper */ private MapWrapper wrapNewImage(ArrayImage image) { - MapWrapper wrapper = new MapWrapper(image); - managedMaps.add(wrapper); - return wrapper; + try { + MapWrapper wrapper = (MapWrapper) wrapperClass.getDeclaredConstructor(ArrayImage.class).newInstance(image); + managedMaps.add(wrapper); + return wrapper; + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | + InvocationTargetException e) { + e.printStackTrace(); + return null; + } } /** @@ -177,30 +201,6 @@ public class MapManager { return null; } - /** - * 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 null - */ - public ItemFrame getItemFrameById(World world, int entityId) { - Object worldHandle = ReflectionUtil.getHandle(world); - Object nmsEntity = ReflectionUtil.callMethod(worldHandle, supports(18) ? "a" : "getEntity", entityId); - if (nmsEntity == null) return null; - - Object craftEntity = ReflectionUtil.callMethod(nmsEntity, "getBukkitEntity"); - if (craftEntity == null) return null; - - Class itemFrameClass = getNMSClass("world.entity.decoration", "EntityItemFrame"); - if (itemFrameClass == null) return null; - - if (craftEntity.getClass().isAssignableFrom(itemFrameClass)) - return (ItemFrame) itemFrameClass.cast(craftEntity); - - return null; - } - /** * Register an occupied map ID * diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/types/Event.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java similarity index 57% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/types/Event.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index fac8c14..2d7f9cb 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/types/Event.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -16,31 +16,27 @@ * along with this program. If not, see . */ -package tech.sbdevelopment.mapreflectionapi.api.events.types; +package tech.sbdevelopment.mapreflectionapi.api; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.bukkit.event.HandlerList; - -@NoArgsConstructor -public class Event extends org.bukkit.event.Event { - public Event(boolean isAsync) { - super(isAsync); - } +/** + * A {@link MapWrapper} wraps one image. + */ +public abstract class MapWrapper extends AbstractMapWrapper { + protected ArrayImage content; /** - * A list of EventHandlers listening to this event. - */ - @Getter - private static final HandlerList handlerList = new HandlerList(); - - /** - * Get the EventHandlers listening to this event. + * Construct a new {@link MapWrapper} * - * @return The EventHandlers listening to this event. + * @param image The {@link ArrayImage} to wrap */ - @Override - public HandlerList getHandlers() { - return handlerList; + public MapWrapper(ArrayImage image) { + this.content = image; } -} + + public ArrayImage getContent() { + return content; + } + + @Override + public abstract MapController getController(); +} \ No newline at end of file diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java similarity index 92% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java index a027e09..df07c10 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java @@ -86,7 +86,7 @@ public interface MultiMapController extends IMapController { * Show this {@link MultiMapController} in {@link ItemFrame}s * * @param player {@link Player} that will be able to see the maps - * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (int[rows][columns]) + * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (int[width][height]) * @see MapController#showInFrame(Player, int) */ void showInFrames(Player player, Integer[][] entityIdMatrix); @@ -95,7 +95,7 @@ public interface MultiMapController extends IMapController { * Show this {@link MultiMapController} in {@link ItemFrame}s * * @param player {@link Player} that will be able to see the maps - * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (int[rows][columns]) + * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (int[width][height]) * @param callable {@link DebugCallable} which will be called to display debug information, or null * @see MapController#showInFrame(Player, int, String) */ @@ -105,7 +105,7 @@ public interface MultiMapController extends IMapController { * Show this {@link MultiMapController} in {@link ItemFrame}s * * @param player {@link Player} that will be able to see the maps - * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (ItemFrame[rows][columns]) + * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (ItemFrame[width][height]) * @param force if false, the map will not be shown if there is not Map-Item in the ItemFrames * @see MapController#showInFrame(Player, ItemFrame, boolean) */ @@ -115,7 +115,7 @@ public interface MultiMapController extends IMapController { * Show this {@link MultiMapController} in {@link ItemFrame}s * * @param player {@link Player} that will be able to see the maps - * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (ItemFrame[rows][columns]) + * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (ItemFrame[width][height]) * @see MapController#showInFrame(Player, ItemFrame) */ void showInFrames(Player player, ItemFrame[][] itemFrameMatrix); @@ -124,7 +124,7 @@ public interface MultiMapController extends IMapController { * Clear the frames * * @param player {@link Player} that will be able to see the cleared frames - * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (int[rows][columns]) + * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (int[width][height]) */ void clearFrames(Player player, Integer[][] entityIdMatrix); @@ -132,7 +132,7 @@ public interface MultiMapController extends IMapController { * Clear the frames * * @param player {@link Player} that will be able to see the cleared frames - * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (ItemFrame[rows][columns]) + * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (ItemFrame[width][height]) */ void clearFrames(Player player, ItemFrame[][] itemFrameMatrix); @@ -144,11 +144,11 @@ public interface MultiMapController extends IMapController { * Called to get debug information for a frame * * @param controller the {@link MapController} - * @param row Row of the current frame - * @param column Column of the current frame + * @param x X-Position of the current frame + * @param y Y-Position of the current frame * @return {@link String} to show when a player looks at the map, or null * @see MapController#showInFrame(Player, int, String) */ - String call(MapController controller, int row, int column); + String call(MapController controller, int x, int y); } } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java similarity index 58% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java index be6efd7..7d22541 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java @@ -25,75 +25,52 @@ import org.jetbrains.annotations.NotNull; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; +import java.awt.*; import java.awt.image.BufferedImage; import java.util.HashSet; import java.util.Set; import java.util.UUID; +import static tech.sbdevelopment.mapreflectionapi.utils.MainUtil.validateArrayDimensions; + /** * A {@link MultiMapWrapper} wraps one image split in pieces. */ public class MultiMapWrapper extends AbstractMapWrapper { private final MapWrapper[][] wrapperMatrix; - /** - * Creates a new {@link MultiMapWrapper} from the given image. - * The image will be split into the given amount of rows and columns. - * - * @param image The image to wrap - * @param rows The amount of rows - * @param columns The amount of columns - */ public MultiMapWrapper(BufferedImage image, int rows, int columns) { - this(splitImage(image, rows, columns)); + this(splitImage(image, columns, rows)); } - /** - * Creates a new {@link MultiMapWrapper} from the given image. - * The image will be split into the given amount of rows and columns. - * - * @param image The image to wrap - * @param rows The amount of rows - * @param columns The amount of columns - */ public MultiMapWrapper(ArrayImage image, int rows, int columns) { - this(splitImage(image.toBuffered(), rows, columns)); + this(splitImage(image.toBuffered(), columns, rows)); } - /** - * Creates a new {@link MultiMapWrapper} from the given image. - * - * @param imageMatrix The image matrix to wrap - */ - protected MultiMapWrapper(ArrayImage[][] imageMatrix) { + public MultiMapWrapper(ArrayImage[][] imageMatrix) { wrapperMatrix = new MapWrapper[imageMatrix.length][imageMatrix[0].length]; - for (int row = 0; row < imageMatrix.length; row++) { - if (imageMatrix[row].length != imageMatrix[0].length) { + for (int x = 0; x < imageMatrix.length; x++) { + if (imageMatrix[x].length != imageMatrix[0].length) { throw new IllegalArgumentException("An image in a MultiMapWrapper is not rectangular!"); } - for (int column = 0; column < imageMatrix[row].length; column++) { - wrapperMatrix[row][column] = MapReflectionAPI.getMapManager().wrapImage(imageMatrix[row][column]); + for (int y = 0; y < imageMatrix[x].length; y++) { + wrapperMatrix[x][y] = MapReflectionAPI.getMapManager().wrapImage(imageMatrix[x][y]); } } } - /** - * Creates a new {@link MultiMapWrapper} from the given image. - * - * @param imageMatrix The image matrix to wrap - */ - protected MultiMapWrapper(BufferedImage[][] imageMatrix) { + public MultiMapWrapper(BufferedImage[][] imageMatrix) { wrapperMatrix = new MapWrapper[imageMatrix.length][imageMatrix[0].length]; - for (int row = 0; row < imageMatrix.length; row++) { - if (imageMatrix[row].length != imageMatrix[0].length) { + for (int x = 0; x < imageMatrix.length; x++) { + if (imageMatrix[x].length != imageMatrix[0].length) { throw new IllegalArgumentException("An image in a MultiMapWrapper is not rectangular!"); } - for (int column = 0; column < imageMatrix[row].length; column++) { - wrapperMatrix[row][column] = MapReflectionAPI.getMapManager().wrapImage(imageMatrix[row][column]); + for (int y = 0; y < imageMatrix[x].length; y++) { + wrapperMatrix[x][y] = MapReflectionAPI.getMapManager().wrapImage(imageMatrix[x][y]); } } } @@ -140,10 +117,10 @@ public class MultiMapWrapper extends AbstractMapWrapper { @Override public void update(@NotNull ArrayImage content) { - ArrayImage[][] split = splitImage(content.toBuffered(), wrapperMatrix.length, wrapperMatrix[0].length); - for (int row = 0; row < wrapperMatrix.length; row++) { - for (int column = 0; column < wrapperMatrix[row].length; column++) { - wrapperMatrix[row][column].getController().update(split[row][column]); + ArrayImage[][] split = splitImage(content.toBuffered(), wrapperMatrix[0].length, wrapperMatrix.length); + for (int x = 0; x < wrapperMatrix.length; x++) { + for (int y = 0; y < wrapperMatrix[x].length; y++) { + wrapperMatrix[x][y].getController().update(split[x][y]); } } } @@ -173,27 +150,33 @@ public class MultiMapWrapper extends AbstractMapWrapper { @Override public void showInFrames(Player player, Integer[][] entityIdMatrix) { - for (int row = 0; row < entityIdMatrix.length; row++) { - for (int column = 0; column < entityIdMatrix[row].length; column++) { - wrapperMatrix[row][column].getController().showInFrame(player, entityIdMatrix[row][column]); + validateArrayDimensions(wrapperMatrix, entityIdMatrix); + + for (int x = 0; x < entityIdMatrix.length; x++) { + for (int y = 0; y < entityIdMatrix[x].length; y++) { + wrapperMatrix[y][x].getController().showInFrame(player, entityIdMatrix[x][wrapperMatrix.length - 1 - y]); } } } @Override public void showInFrames(Player player, Integer[][] entityIdMatrix, DebugCallable callable) { - for (int row = 0; row < entityIdMatrix.length; row++) { - for (int column = 0; column < entityIdMatrix[row].length; column++) { - wrapperMatrix[row][column].getController().showInFrame(player, entityIdMatrix[row][column], callable.call(wrapperMatrix[row][column].getController(), row, column)); + validateArrayDimensions(wrapperMatrix, entityIdMatrix); + + for (int x = 0; x < entityIdMatrix.length; x++) { + for (int y = 0; y < entityIdMatrix[x].length; y++) { + wrapperMatrix[y][x].getController().showInFrame(player, entityIdMatrix[x][wrapperMatrix.length - 1 - y], callable.call(wrapperMatrix[y][x].getController(), x, y)); } } } @Override public void showInFrames(Player player, ItemFrame[][] itemFrameMatrix, boolean force) { - for (int row = 0; row < itemFrameMatrix.length; row++) { - for (int column = 0; column < itemFrameMatrix[row].length; column++) { - wrapperMatrix[row][column].getController().showInFrame(player, itemFrameMatrix[row][column], force); + validateArrayDimensions(wrapperMatrix, itemFrameMatrix); + + for (int x = 0; x < itemFrameMatrix.length; x++) { + for (int y = 0; y < itemFrameMatrix[x].length; y++) { + wrapperMatrix[y][x].getController().showInFrame(player, itemFrameMatrix[x][wrapperMatrix.length - 1 - y], force); } } } @@ -205,47 +188,47 @@ public class MultiMapWrapper extends AbstractMapWrapper { @Override public void clearFrames(Player player, Integer[][] entityIdMatrix) { - for (int row = 0; row < entityIdMatrix.length; row++) { - for (int column = 0; column < entityIdMatrix[row].length; column++) { - wrapperMatrix[row][column].getController().clearFrame(player, entityIdMatrix[row][column]); + validateArrayDimensions(wrapperMatrix, entityIdMatrix); + + for (int x = 0; x < entityIdMatrix.length; x++) { + for (int y = 0; y < entityIdMatrix[x].length; y++) { + wrapperMatrix[y][x].getController().clearFrame(player, entityIdMatrix[x][y]); } } } @Override public void clearFrames(Player player, ItemFrame[][] itemFrameMatrix) { - for (int row = 0; row < itemFrameMatrix.length; row++) { - for (int column = 0; column < itemFrameMatrix[row].length; column++) { - wrapperMatrix[row][column].getController().clearFrame(player, itemFrameMatrix[row][column]); + validateArrayDimensions(wrapperMatrix, itemFrameMatrix); + + for (int x = 0; x < itemFrameMatrix.length; x++) { + for (int y = 0; y < itemFrameMatrix[x].length; y++) { + wrapperMatrix[y][x].getController().clearFrame(player, itemFrameMatrix[x][y]); } } } }; - /** - * Splits a BufferedImage into a matrix of ArrayImages. - * - * @param image The image to split - * @param rows The number of rows - * @param columns The number of columns - * @return The matrix of ArrayImages + /* + * Modified Method from http://kalanir.blogspot.de/2010/02/how-to-split-image-into-chunks-java.html */ - private static ArrayImage[][] splitImage(final BufferedImage image, final int rows, final int columns) { + private static ArrayImage[][] splitImage(final BufferedImage image, final int columns, final int rows) { int chunkWidth = image.getWidth() / columns; int chunkHeight = image.getHeight() / rows; ArrayImage[][] images = new ArrayImage[rows][columns]; + for (int x = 0; x < rows; x++) { + for (int y = 0; y < columns; y++) { + BufferedImage raw = new BufferedImage(chunkWidth, chunkHeight, image.getType()); - for (int i = 0; i < rows; i++) { - for (int j = 0; j < columns; j++) { - int x = j * chunkWidth; - int y = i * chunkHeight; + Graphics2D gr = raw.createGraphics(); + gr.drawImage(image, 0, 0, chunkWidth, chunkHeight, chunkWidth * y, chunkHeight * x, chunkWidth * y + chunkWidth, chunkHeight * x + chunkHeight, null); + gr.dispose(); - BufferedImage raw = image.getSubimage(x, y, chunkWidth, chunkHeight); - images[i][j] = new ArrayImage(raw); + images[x][y] = new ArrayImage(raw); + raw.flush(); } } - return images; } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreativeInventoryMapUpdateEvent.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java similarity index 74% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreativeInventoryMapUpdateEvent.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java index e2f4334..a003ddc 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreativeInventoryMapUpdateEvent.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java @@ -20,40 +20,53 @@ 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.NotNull; import org.jetbrains.annotations.Nullable; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; -import tech.sbdevelopment.mapreflectionapi.api.events.types.CancellableEvent; -import tech.sbdevelopment.mapreflectionapi.utils.XMaterial; /** * This event gets fired when a map in the creative inventory gets updated */ @RequiredArgsConstructor @Getter -public class CreativeInventoryMapUpdateEvent extends CancellableEvent { +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; /** - * Construct a new {@link CreativeInventoryMapUpdateEvent} + * 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 CreativeInventoryMapUpdateEvent(Player player, int slot, ItemStack item, boolean isAsync) { + public CreateInventoryMapUpdateEvent(Player player, int slot, ItemStack item, boolean isAsync) { super(isAsync); this.player = player; this.slot = slot; this.item = item; } + @Override + public @NotNull HandlerList getHandlers() { + return handlerList; + } + /** * Get the {@link MapWrapper} of the map of this event * @@ -63,7 +76,7 @@ public class CreativeInventoryMapUpdateEvent extends CancellableEvent { public MapWrapper getMapWrapper() { if (mapWrapper == null) { if (item == null) return null; - if (!XMaterial.FILLED_MAP.isSimilar(item)) return null; + if (item.getType() != Material.MAP) return null; mapWrapper = MapReflectionAPI.getMapManager().getWrapperForId(player, item.getDurability()); } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java similarity index 76% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java index 5fe3ac1..b7aa6c9 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java @@ -20,15 +20,23 @@ package tech.sbdevelopment.mapreflectionapi.api.events; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.Setter; import org.bukkit.entity.Player; -import tech.sbdevelopment.mapreflectionapi.api.events.types.CancellableEvent; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; /** * This event gets fired when a map creation is cancelled */ @RequiredArgsConstructor @Getter -public class MapCancelEvent extends CancellableEvent { +public class MapCancelEvent extends Event implements Cancellable { + private static final HandlerList handlerList = new HandlerList(); + @Setter + private boolean cancelled; + private final Player player; private final int id; @@ -44,4 +52,9 @@ public class MapCancelEvent extends CancellableEvent { this.player = player; this.id = id; } + + @Override + public @NotNull HandlerList getHandlers() { + return handlerList; + } } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java similarity index 86% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java index ce3f59d..65b9a6e 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java @@ -21,9 +21,11 @@ package tech.sbdevelopment.mapreflectionapi.api.events; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; -import tech.sbdevelopment.mapreflectionapi.api.events.types.Event; /** * This event gets fired when the content of a {@link MapWrapper} is updated @@ -31,6 +33,8 @@ import tech.sbdevelopment.mapreflectionapi.api.events.types.Event; @RequiredArgsConstructor @Getter public class MapContentUpdateEvent extends Event { + private static final HandlerList handlerList = new HandlerList(); + private final MapWrapper wrapper; private final ArrayImage content; @Setter @@ -48,4 +52,9 @@ public class MapContentUpdateEvent extends Event { this.wrapper = wrapper; this.content = content; } + + @Override + public @NotNull HandlerList getHandlers() { + return handlerList; + } } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java similarity index 79% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java index 99fc4ab..6bbcb4a 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java @@ -20,20 +20,28 @@ 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.NotNull; import org.jetbrains.annotations.Nullable; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; -import tech.sbdevelopment.mapreflectionapi.api.events.types.CancellableEvent; /** * This event gets fired when a player interact with a map */ @RequiredArgsConstructor @Getter -public class MapInteractEvent extends CancellableEvent { +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; @@ -61,6 +69,11 @@ public class MapInteractEvent extends CancellableEvent { this.hand = hand; } + @Override + public @NotNull HandlerList getHandlers() { + return handlerList; + } + /** * Get the {@link ItemFrame} the map is in * @@ -68,8 +81,10 @@ public class MapInteractEvent extends CancellableEvent { */ @Nullable public ItemFrame getFrame() { + if (getMapWrapper() == null) return null; + if (frame == null) { - frame = MapReflectionAPI.getMapManager().getItemFrameById(player.getWorld(), entityID); + frame = getMapWrapper().getController().getItemFrameById(player.getWorld(), entityID); } return frame; } @@ -81,11 +96,10 @@ public class MapInteractEvent extends CancellableEvent { */ @Nullable public MapWrapper getMapWrapper() { - if (getFrame() == null) return null; if (mapWrapper == null) { - if (!frame.hasMetadata(MapWrapper.REFERENCE_METADATA)) return null; - mapWrapper = (MapWrapper) frame.getMetadata(MapWrapper.REFERENCE_METADATA).get(0).value(); + mapWrapper = MapReflectionAPI.getMapManager().getWrapperForId(player, entityID); } + return mapWrapper; } } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/package-info.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/package-info.java similarity index 91% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/package-info.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/package-info.java index 96733cc..56c07cd 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/package-info.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/package-info.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java similarity index 92% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java index 55a2cc7..9aa6672 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/package-info.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/package-info.java similarity index 91% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/package-info.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/package-info.java index bff336f..7fb11d0 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/package-info.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/package-info.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java similarity index 91% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java index b3d3b45..6c697b6 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java similarity index 96% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java index 4284c2a..5fe9c42 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java similarity index 95% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java index 33ee0b3..4c8729e 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java new file mode 100644 index 0000000..d56a89e --- /dev/null +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -0,0 +1,80 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.listeners; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.util.Vector; + +public abstract class PacketListener implements Listener { + protected JavaPlugin plugin; + + public static PacketListener construct(JavaPlugin plugin) throws IllegalStateException { + String packageName = Bukkit.getServer().getClass().getPackage().getName(); + String version = packageName.substring(packageName.lastIndexOf('.') + 1); + + plugin.getLogger().info("Enabling PacketListener for " + version + "..."); + + try { + final Class clazz = Class.forName("tech.sbdevelopment.mapreflectionapi.nms.PacketListener_" + version); + if (PacketListener.class.isAssignableFrom(clazz)) { + return (PacketListener) clazz.getDeclaredConstructor().newInstance(); + } else { + throw new IllegalStateException("Plugin corrupted! Detected invalid PacketListener class."); + } + } catch (Exception ex) { + throw new IllegalStateException("This Minecraft version (" + version + ") is not supported! Contact the developer to get support."); + } + } + + public void init(JavaPlugin plugin) { + this.plugin = plugin; + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + @EventHandler + public void onJoin(PlayerJoinEvent e) { + injectPlayer(e.getPlayer()); + } + + @EventHandler + public void onQuit(PlayerQuitEvent e) { + removePlayer(e.getPlayer()); + } + + protected abstract void injectPlayer(Player p); + + public abstract void removePlayer(Player p); + + protected abstract Vector vec3DToVector(Object vec3d); + + protected boolean hasField(Object packet, String field) { + try { + packet.getClass().getDeclaredField(field); + return true; + } catch (NoSuchFieldException ex) { + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java similarity index 95% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java index f5acc72..ab55bef 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/MainUtil.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/MainUtil.java similarity index 76% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/utils/MainUtil.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/MainUtil.java index 0688c0f..5556e0e 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/MainUtil.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/MainUtil.java @@ -18,9 +18,6 @@ package tech.sbdevelopment.mapreflectionapi.utils; -import java.util.Map; -import java.util.function.Supplier; - public class MainUtil { private MainUtil() { } @@ -38,4 +35,10 @@ public class MainUtil { return true; } } + + public static void validateArrayDimensions(A[][] arrayOne, B[][] arrayTwo) { + if (arrayOne.length != arrayTwo.length || arrayOne[0].length != arrayTwo[0].length) { + throw new IllegalArgumentException("The dimensions of two provided arrays (" + arrayOne.getClass().getName() + ", " + arrayTwo.getClass().getName() + ") do not match!"); + } + } } diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java new file mode 100644 index 0000000..bcf93d8 --- /dev/null +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java @@ -0,0 +1,537 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.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.*; +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]); + /** + * The raw minor version number. + * E.g. {@code v1_18_R2} to {@code 2} + * + * @since 4.0.0 + */ + public static final int VER_MINOR = toInt(VERSION.substring(1).split("_")[2].substring(1), 0); + /** + * 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; + MethodHandle getHandle = null; + MethodHandle getHandleWorld = null; + MethodHandle connection = null; + + try { + connection = lookup.findGetter(entityPlayer, + supports(20) ? "c" : supports(17) ? "b" : "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; + } + + /** + * Checks whether the server version is equal or greater than the given version. + *

+ * PAY ATTENTION! The minor version is based on the NMS version. + * This means that v1_19_R3 has major version 19 and minor version 3. + * + * @param major the major version to compare the server version with. + * @param minor the minor 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 major, int minor) { + return VER >= major && VER_MINOR >= minor; + } + + /** + * Helper class converted to {@link List} + * + * @param The storage type + */ + public static class ListParam extends ArrayList { + + } + + /** + * Helper class converted to {@link Collection} + * + * @param The storage type + */ + public static class CollectionParam extends ArrayList { + + } + + 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 == CollectionParam.class) return Collection.class; + if (clazz == ListParam.class) return List.class; + if (clazz == ArrayList.class) return Collection.class; //LEGACY! + if (clazz == HashMap.class) return Map.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; + } + } + + public static boolean hasField(Object packet, String field) { + try { + packet.getClass().getDeclaredField(field); + return true; + } catch (NoSuchFieldException ex) { + return false; + } + } + + @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; + } + } + + private static int toInt(String string, int def) { + return string.isBlank() ? def : Integer.parseInt(string); + } +} \ No newline at end of file diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java similarity index 97% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java index 671a511..631c663 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -143,12 +143,14 @@ public class UpdateManager { File pluginFile = getPluginFile(); // /plugins/XXX.jar if (pluginFile == null) { this.downloadResponse.accept(DownloadResponse.ERROR, null); + Bukkit.getLogger().info("Pluginfile is null"); return; } File updateFolder = Bukkit.getUpdateFolderFile(); if (!updateFolder.exists()) { if (!updateFolder.mkdirs()) { this.downloadResponse.accept(DownloadResponse.ERROR, null); + Bukkit.getLogger().info("Updatefolder doesn't exists, and can't be made"); return; } } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java similarity index 97% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java index 50154a5..bb2db31 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_12.bub b/API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_12.bub similarity index 100% rename from src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_12.bub rename to API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_12.bub diff --git a/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_16.bub b/API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_16.bub similarity index 100% rename from src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_16.bub rename to API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_16.bub diff --git a/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_17.bub b/API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_17.bub similarity index 100% rename from src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_17.bub rename to API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_17.bub diff --git a/API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_8_8.bub b/API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_8_8.bub new file mode 100644 index 0000000..1e22e3a Binary files /dev/null and b/API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_8_8.bub differ diff --git a/src/main/resources/config.yml b/API/src/main/resources/config.yml similarity index 100% rename from src/main/resources/config.yml rename to API/src/main/resources/config.yml diff --git a/src/main/resources/plugin.yml b/API/src/main/resources/plugin.yml similarity index 100% rename from src/main/resources/plugin.yml rename to API/src/main/resources/plugin.yml diff --git a/Dist/pom.xml b/Dist/pom.xml new file mode 100644 index 0000000..d9f9fc6 --- /dev/null +++ b/Dist/pom.xml @@ -0,0 +1,124 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + jar + + MapReflectionAPI-Dist + + + ../target + ${project.parent.name}-${project.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.0.0-M2 + + true + + + + org.apache.maven.plugins + maven-shade-plugin + 3.3.0 + + + package + + shade + + + false + + + + + tech.sbdevelopment:MapReflectionAPI* + + + + + + + + + + + + tech.sbdevelopment + MapReflectionAPI-API + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_20_R4 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_19_R3 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_18_R2 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_16_R3 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_17_R1 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_15_R1 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_14_R1 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_13_R2 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_12_R1 + ${project.parent.version} + + + \ No newline at end of file diff --git a/NMS-v1_12_R1/pom.xml b/NMS-v1_12_R1/pom.xml new file mode 100644 index 0000000..adaa593 --- /dev/null +++ b/NMS-v1_12_R1/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_12_R1 + + + 1.12.2-R0.1-SNAPSHOT + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java new file mode 100644 index 0000000..f24528d --- /dev/null +++ b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java @@ -0,0 +1,166 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_12_R1.PacketPlayOutMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * The {@link MapSender_v1_12_R1} sends the Map packets to players. + */ +public class MapSender_v1_12_R1 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_12_R1() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + PacketPlayOutMap packet = new PacketPlayOutMap( + 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) + ); + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + 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 + ']'; + } + } +} diff --git a/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_12_R1.java b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_12_R1.java new file mode 100644 index 0000000..4d710ab --- /dev/null +++ b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_12_R1.java @@ -0,0 +1,249 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_12_R1.*; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_12_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_12_R1.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class MapWrapper_v1_12_R1 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_12_R1.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_12_R1.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_12_R1.sendMap(id, MapWrapper_v1_12_R1.this.content, player); + } else { + MapSender_v1_12_R1.addToQueue(id, MapWrapper_v1_12_R1.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_12_R1.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().defaultContainer.windowId; + + ItemStack stack = new ItemStack(Material.MAP, 1); + net.minecraft.server.v1_12_R1.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, slot, nmsStack); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_12_R1.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().getEntity(entityId); + if (entity == null) return null; + + org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity(); + if (bukkitEntity instanceof ItemFrame) return (ItemFrame) bukkitEntity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.server.v1_12_R1.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + if (nmsStack.getTag() == null) nmsStack.setTag(new NBTTagCompound()); //No orCreate on 1.12.2! + nmsStack.getTag().setInt("map", mapId); //getTag putInt + + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, new DataWatcher(null), true); + + try { + List> list = new ArrayList<>(); + DataWatcherObject dataWatcherObject = (DataWatcherObject) getDeclaredField(EntityItemFrame.class, "c"); + DataWatcher.Item dataWatcherItem = new DataWatcher.Item<>(dataWatcherObject, nmsStack); + list.add(dataWatcherItem); + setDeclaredField(packet, "b", list); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + }; + + public MapWrapper_v1_12_R1(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_12_R1.java b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_12_R1.java new file mode 100644 index 0000000..f2a6924 --- /dev/null +++ b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_12_R1.java @@ -0,0 +1,123 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.server.v1_12_R1.*; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_12_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class PacketListener_v1_12_R1 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap) { + PacketPlayOutMap packetPlayOutMap = (PacketPlayOutMap) packet; + + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity) { + PacketPlayInUseEntity packetPlayInUseEntity = (PacketPlayInUseEntity) packet; + + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + PacketPlayInUseEntity.EnumEntityUseAction action = packetPlayInUseEntity.a(); //action + EnumHand hand = packetPlayInUseEntity.b(); //hand + Vec3D pos = packetPlayInUseEntity.c(); //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, action.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot) { + PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot = (PacketPlayInSetCreativeSlot) packet; + + int slot = packetPlayInSetCreativeSlot.a(); + ItemStack item = packetPlayInSetCreativeSlot.getItemStack(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + ChannelPipeline pipeline = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel.pipeline(); + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + Channel channel = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel; + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D)) return new Vector(0, 0, 0); + + Vec3D vec3dObj = (Vec3D) vec3d; + return new Vector(vec3dObj.x, vec3dObj.y, vec3dObj.z); + } +} diff --git a/NMS-v1_13_R2/pom.xml b/NMS-v1_13_R2/pom.xml new file mode 100644 index 0000000..17b0098 --- /dev/null +++ b/NMS-v1_13_R2/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_13_R2 + + + 1.13.2-R0.1-SNAPSHOT + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java new file mode 100644 index 0000000..d82d5fc --- /dev/null +++ b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java @@ -0,0 +1,166 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_13_R2.PacketPlayOutMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * The {@link MapSender_v1_13_R2} sends the Map packets to players. + */ +public class MapSender_v1_13_R2 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_13_R2() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + PacketPlayOutMap packet = new PacketPlayOutMap( + 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) + ); + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + 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; + var 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 + ']'; + } + } +} diff --git a/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_13_R2.java b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_13_R2.java new file mode 100644 index 0000000..145dc66 --- /dev/null +++ b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_13_R2.java @@ -0,0 +1,248 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_13_R2.*; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_13_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_13_R2.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class MapWrapper_v1_13_R2 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_13_R2.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_13_R2.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_13_R2.sendMap(id, MapWrapper_v1_13_R2.this.content, player); + } else { + MapSender_v1_13_R2.addToQueue(id, MapWrapper_v1_13_R2.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_13_R2.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().defaultContainer.windowId; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.server.v1_13_R2.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, slot, nmsStack); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_13_R2.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().getEntity(entityId); + if (entity == null) return null; + + org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity(); + if (bukkitEntity instanceof ItemFrame) return (ItemFrame) bukkitEntity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.server.v1_13_R2.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.getOrCreateTag().setInt("map", mapId); //getOrCreateTag putInt + + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, new DataWatcher(null), true); + + try { + List> list = new ArrayList<>(); + DataWatcherObject dataWatcherObject = (DataWatcherObject) getDeclaredField(EntityItemFrame.class, "e"); + DataWatcher.Item dataWatcherItem = new DataWatcher.Item<>(dataWatcherObject, nmsStack); + list.add(dataWatcherItem); + setDeclaredField(packet, "b", list); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + }; + + public MapWrapper_v1_13_R2(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_13_R2.java b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_13_R2.java new file mode 100644 index 0000000..2180ab0 --- /dev/null +++ b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_13_R2.java @@ -0,0 +1,123 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.server.v1_13_R2.*; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_13_R2.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class PacketListener_v1_13_R2 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap) { + PacketPlayOutMap packetPlayOutMap = (PacketPlayOutMap) packet; + + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity) { + PacketPlayInUseEntity packetPlayInUseEntity = (PacketPlayInUseEntity) packet; + + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + PacketPlayInUseEntity.EnumEntityUseAction action = packetPlayInUseEntity.b(); //action + EnumHand hand = packetPlayInUseEntity.c(); //hand + Vec3D pos = packetPlayInUseEntity.d(); //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, action.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot) { + PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot = (PacketPlayInSetCreativeSlot) packet; + + int slot = packetPlayInSetCreativeSlot.b(); + ItemStack item = packetPlayInSetCreativeSlot.getItemStack(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + ChannelPipeline pipeline = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel.pipeline(); + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + Channel channel = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel; + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D)) return new Vector(0, 0, 0); + + Vec3D vec3dObj = (Vec3D) vec3d; + return new Vector(vec3dObj.x, vec3dObj.y, vec3dObj.z); + } +} diff --git a/NMS-v1_14_R1/pom.xml b/NMS-v1_14_R1/pom.xml new file mode 100644 index 0000000..9e67e14 --- /dev/null +++ b/NMS-v1_14_R1/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_14_R1 + + + 1.14.4-R0.1-SNAPSHOT + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java new file mode 100644 index 0000000..49ebfae --- /dev/null +++ b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java @@ -0,0 +1,167 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_14_R1.PacketPlayOutMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * The {@link MapSender_v1_14_R1} sends the Map packets to players. + */ +public class MapSender_v1_14_R1 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_14_R1() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + PacketPlayOutMap packet = new PacketPlayOutMap( + 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) + ); + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + 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; + var 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 + ']'; + } + } +} diff --git a/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_14_R1.java b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_14_R1.java new file mode 100644 index 0000000..4a73b9f --- /dev/null +++ b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_14_R1.java @@ -0,0 +1,248 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_14_R1.*; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_14_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class MapWrapper_v1_14_R1 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_14_R1.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_14_R1.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_14_R1.sendMap(id, MapWrapper_v1_14_R1.this.content, player); + } else { + MapSender_v1_14_R1.addToQueue(id, MapWrapper_v1_14_R1.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_14_R1.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().defaultContainer.windowId; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.server.v1_14_R1.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, slot, nmsStack); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_14_R1.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().getEntity(entityId); + if (entity == null) return null; + + org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity(); + if (bukkitEntity instanceof ItemFrame) return (ItemFrame) bukkitEntity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.server.v1_14_R1.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.getOrCreateTag().setInt("map", mapId); //getOrCreateTag putInt + + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, new DataWatcher(null), true); + + try { + List> list = new ArrayList<>(); + DataWatcherObject dataWatcherObject = (DataWatcherObject) getDeclaredField(EntityItemFrame.class, "ITEM"); + DataWatcher.Item dataWatcherItem = new DataWatcher.Item<>(dataWatcherObject, nmsStack); + list.add(dataWatcherItem); + setDeclaredField(packet, "b", list); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + }; + + public MapWrapper_v1_14_R1(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_14_R1.java b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_14_R1.java new file mode 100644 index 0000000..551a54b --- /dev/null +++ b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_14_R1.java @@ -0,0 +1,123 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.server.v1_14_R1.*; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class PacketListener_v1_14_R1 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap) { + PacketPlayOutMap packetPlayOutMap = (PacketPlayOutMap) packet; + + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity) { + PacketPlayInUseEntity packetPlayInUseEntity = (PacketPlayInUseEntity) packet; + + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + PacketPlayInUseEntity.EnumEntityUseAction action = packetPlayInUseEntity.b(); //action + EnumHand hand = packetPlayInUseEntity.c(); //hand + Vec3D pos = packetPlayInUseEntity.d(); //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, action.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot) { + PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot = (PacketPlayInSetCreativeSlot) packet; + + int slot = packetPlayInSetCreativeSlot.b(); + ItemStack item = packetPlayInSetCreativeSlot.getItemStack(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + ChannelPipeline pipeline = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel.pipeline(); + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + Channel channel = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel; + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D)) return new Vector(0, 0, 0); + + Vec3D vec3dObj = (Vec3D) vec3d; + return new Vector(vec3dObj.x, vec3dObj.y, vec3dObj.z); + } +} diff --git a/NMS-v1_15_R1/pom.xml b/NMS-v1_15_R1/pom.xml new file mode 100644 index 0000000..4f6f9df --- /dev/null +++ b/NMS-v1_15_R1/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_15_R1 + + + 1.15.2-R0.1-SNAPSHOT + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java new file mode 100644 index 0000000..a7b83d9 --- /dev/null +++ b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java @@ -0,0 +1,167 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_15_R1.PacketPlayOutMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * The {@link MapSender_v1_15_R1} sends the Map packets to players. + */ +public class MapSender_v1_15_R1 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_15_R1() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + PacketPlayOutMap packet = new PacketPlayOutMap( + 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) + ); + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + 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; + var 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 + ']'; + } + } +} diff --git a/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_15_R1.java b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_15_R1.java new file mode 100644 index 0000000..7308ace --- /dev/null +++ b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_15_R1.java @@ -0,0 +1,249 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_15_R1.*; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_15_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class MapWrapper_v1_15_R1 extends MapWrapper { + + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_15_R1.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_15_R1.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_15_R1.sendMap(id, MapWrapper_v1_15_R1.this.content, player); + } else { + MapSender_v1_15_R1.addToQueue(id, MapWrapper_v1_15_R1.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_15_R1.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().defaultContainer.windowId; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.server.v1_15_R1.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, slot, nmsStack); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_15_R1.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().getEntity(entityId); + if (entity == null) return null; + + org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity(); + if (bukkitEntity instanceof ItemFrame) return (ItemFrame) bukkitEntity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.server.v1_15_R1.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.getOrCreateTag().setInt("map", mapId); //getOrCreateTag putInt + + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, new DataWatcher(null), true); + + try { + List> list = new ArrayList<>(); + DataWatcherObject dataWatcherObject = (DataWatcherObject) getDeclaredField(EntityItemFrame.class, "ITEM"); + DataWatcher.Item dataWatcherItem = new DataWatcher.Item<>(dataWatcherObject, nmsStack); + list.add(dataWatcherItem); + setDeclaredField(packet, "b", list); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + }; + + public MapWrapper_v1_15_R1(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_15_R1.java b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_15_R1.java new file mode 100644 index 0000000..a73dd04 --- /dev/null +++ b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_15_R1.java @@ -0,0 +1,123 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.server.v1_15_R1.*; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class PacketListener_v1_15_R1 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap) { + PacketPlayOutMap packetPlayOutMap = (PacketPlayOutMap) packet; + + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity) { + PacketPlayInUseEntity packetPlayInUseEntity = (PacketPlayInUseEntity) packet; + + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + PacketPlayInUseEntity.EnumEntityUseAction action = packetPlayInUseEntity.b(); //action + EnumHand hand = packetPlayInUseEntity.c(); //hand + Vec3D pos = packetPlayInUseEntity.d(); //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, action.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot) { + PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot = (PacketPlayInSetCreativeSlot) packet; + + int slot = packetPlayInSetCreativeSlot.b(); + ItemStack item = packetPlayInSetCreativeSlot.getItemStack(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + ChannelPipeline pipeline = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel.pipeline(); + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + Channel channel = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel; + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D)) return new Vector(0, 0, 0); + + Vec3D vec3dObj = (Vec3D) vec3d; + return new Vector(vec3dObj.x, vec3dObj.y, vec3dObj.z); + } +} diff --git a/NMS-v1_16_R3/pom.xml b/NMS-v1_16_R3/pom.xml new file mode 100644 index 0000000..46edaa9 --- /dev/null +++ b/NMS-v1_16_R3/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_16_R3 + + + 1.16.4-R0.1-SNAPSHOT + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java new file mode 100644 index 0000000..e4ed5cf --- /dev/null +++ b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java @@ -0,0 +1,167 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_16_R3.PacketPlayOutMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * The {@link MapSender_v1_16_R3} sends the Map packets to players. + */ +public class MapSender_v1_16_R3 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_16_R3() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + PacketPlayOutMap packet = new PacketPlayOutMap( + 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) + ); + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + 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; + var 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 + ']'; + } + } +} diff --git a/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_16_R3.java b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_16_R3.java new file mode 100644 index 0000000..492fd29 --- /dev/null +++ b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_16_R3.java @@ -0,0 +1,248 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_16_R3.*; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_16_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class MapWrapper_v1_16_R3 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_16_R3.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_16_R3.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_16_R3.sendMap(id, MapWrapper_v1_16_R3.this.content, player); + } else { + MapSender_v1_16_R3.addToQueue(id, MapWrapper_v1_16_R3.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_16_R3.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().defaultContainer.windowId; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.server.v1_16_R3.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, slot, nmsStack); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_16_R3.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().getEntity(entityId); + if (entity == null) return null; + + org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity(); + if (bukkitEntity instanceof ItemFrame) return (ItemFrame) bukkitEntity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.server.v1_16_R3.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.getOrCreateTag().setInt("map", mapId); //getOrCreateTag putInt + + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, new DataWatcher(null), true); + + try { + List> list = new ArrayList<>(); + DataWatcherObject dataWatcherObject = (DataWatcherObject) getDeclaredField(EntityItemFrame.class, "ITEM"); + DataWatcher.Item dataWatcherItem = new DataWatcher.Item<>(dataWatcherObject, nmsStack); + list.add(dataWatcherItem); + setDeclaredField(packet, "b", list); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + }; + + public MapWrapper_v1_16_R3(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_16_R3.java b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_16_R3.java new file mode 100644 index 0000000..5f6fa98 --- /dev/null +++ b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_16_R3.java @@ -0,0 +1,123 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.server.v1_16_R3.*; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class PacketListener_v1_16_R3 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap) { + PacketPlayOutMap packetPlayOutMap = (PacketPlayOutMap) packet; + + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity) { + PacketPlayInUseEntity packetPlayInUseEntity = (PacketPlayInUseEntity) packet; + + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + PacketPlayInUseEntity.EnumEntityUseAction action = packetPlayInUseEntity.b(); //action + EnumHand hand = packetPlayInUseEntity.c(); //hand + Vec3D pos = packetPlayInUseEntity.d(); //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, action.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot) { + PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot = (PacketPlayInSetCreativeSlot) packet; + + int slot = packetPlayInSetCreativeSlot.b(); + ItemStack item = packetPlayInSetCreativeSlot.getItemStack(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + ChannelPipeline pipeline = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel.pipeline(); + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + Channel channel = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel; + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D)) return new Vector(0, 0, 0); + + Vec3D vec3dObj = (Vec3D) vec3d; + return new Vector(vec3dObj.x, vec3dObj.y, vec3dObj.z); + } +} diff --git a/NMS-v1_17_R1/pom.xml b/NMS-v1_17_R1/pom.xml new file mode 100644 index 0000000..2608809 --- /dev/null +++ b/NMS-v1_17_R1/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_17_R1 + + + 1.17.1-R0.1-SNAPSHOT + 17 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_17_R1.java b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_17_R1.java new file mode 100644 index 0000000..050df5b --- /dev/null +++ b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_17_R1.java @@ -0,0 +1,138 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link MapSender_v1_17_R1} sends the Map packets to players. + */ +public class MapSender_v1_17_R1 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_17_R1() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.b updateData = new WorldMap.b( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().b.sendPacket(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} \ No newline at end of file diff --git a/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_17_R1.java b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_17_R1.java new file mode 100644 index 0000000..8ba9201 --- /dev/null +++ b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_17_R1.java @@ -0,0 +1,251 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.network.syncher.DataWatcherObject; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_17_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class MapWrapper_v1_17_R1 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_17_R1.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_17_R1.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_17_R1.sendMap(id, MapWrapper_v1_17_R1.this.content, player); + } else { + MapSender_v1_17_R1.addToQueue(id, MapWrapper_v1_17_R1.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_17_R1.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().bU.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().bU.getStateId(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().b.sendPacket(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_17_R1.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().getEntity(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.getOrCreateTag().setInt("map", mapId); //getOrCreateTag putInt + + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, new DataWatcher(null), true); + + try { + List> list = new ArrayList<>(); + DataWatcherObject dataWatcherObject = (DataWatcherObject) getDeclaredField(EntityItemFrame.class, "ao"); + DataWatcher.Item dataWatcherItem = new DataWatcher.Item<>(dataWatcherObject, nmsStack); + list.add(dataWatcherItem); + setDeclaredField(packet, "b", list); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + ((CraftPlayer) player).getHandle().b.sendPacket(packet); + } + }; + + public MapWrapper_v1_17_R1(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} \ No newline at end of file diff --git a/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_17_R1.java b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_17_R1.java new file mode 100644 index 0000000..f06f3f5 --- /dev/null +++ b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_17_R1.java @@ -0,0 +1,121 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_17_R1 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = packetPlayInSetCreativeSlot.b(); + ItemStack item = packetPlayInSetCreativeSlot.getItemStack(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + ChannelPipeline pipeline = ((CraftPlayer) p).getHandle().b.a.k.pipeline(); //connection connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + Channel channel = ((CraftPlayer) p).getHandle().b.a.k; //connection connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.b, vec3dObj.c, vec3dObj.d); //x, y, z + } +} \ No newline at end of file diff --git a/NMS-v1_18_R2/pom.xml b/NMS-v1_18_R2/pom.xml new file mode 100644 index 0000000..5dfc581 --- /dev/null +++ b/NMS-v1_18_R2/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_18_R2 + + + 1.18.2-R0.1-SNAPSHOT + 17 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_18_R2.java b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_18_R2.java new file mode 100644 index 0000000..ebe34fe --- /dev/null +++ b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_18_R2.java @@ -0,0 +1,138 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link MapSender_v1_18_R2} sends the Map packets to players. + */ +public class MapSender_v1_18_R2 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_18_R2() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.b updateData = new WorldMap.b( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().b.a(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} \ No newline at end of file diff --git a/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_18_R2.java b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_18_R2.java new file mode 100644 index 0000000..1ab2a22 --- /dev/null +++ b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_18_R2.java @@ -0,0 +1,251 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.network.syncher.DataWatcherObject; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_18_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class MapWrapper_v1_18_R2 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_18_R2.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_18_R2.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_18_R2.sendMap(id, MapWrapper_v1_18_R2.this.content, player); + } else { + MapSender_v1_18_R2.addToQueue(id, MapWrapper_v1_18_R2.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_18_R2.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().bU.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().bU.j(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().b.a(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_18_R2.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().a(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.u().a("map", mapId); //getOrCreateTag putInt + + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, new DataWatcher(null), true); + + try { + List> list = new ArrayList<>(); + DataWatcherObject dataWatcherObject = (DataWatcherObject) getDeclaredField(EntityItemFrame.class, "ao"); + DataWatcher.Item dataWatcherItem = new DataWatcher.Item<>(dataWatcherObject, nmsStack); + list.add(dataWatcherItem); + setDeclaredField(packet, "b", list); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + ((CraftPlayer) player).getHandle().b.a(packet); + } + }; + + public MapWrapper_v1_18_R2(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_18_R2.java b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_18_R2.java new file mode 100644 index 0000000..7524a29 --- /dev/null +++ b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_18_R2.java @@ -0,0 +1,121 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_18_R2 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = packetPlayInSetCreativeSlot.b(); + ItemStack item = packetPlayInSetCreativeSlot.c(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + ChannelPipeline pipeline = ((CraftPlayer) p).getHandle().b.a.m.pipeline(); //connection connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + Channel channel = ((CraftPlayer) p).getHandle().b.a.m; //connection connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.b, vec3dObj.c, vec3dObj.d); //x, y, z + } +} diff --git a/NMS-v1_19_R3/pom.xml b/NMS-v1_19_R3/pom.xml new file mode 100644 index 0000000..08f0828 --- /dev/null +++ b/NMS-v1_19_R3/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_19_R3 + + + 1.19.4-R0.1-SNAPSHOT + 17 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_19_R3.java b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_19_R3.java new file mode 100644 index 0000000..50d5bbd --- /dev/null +++ b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_19_R3.java @@ -0,0 +1,138 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link MapSender_v1_19_R3} sends the Map packets to players. + */ +public class MapSender_v1_19_R3 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_19_R3() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.b updateData = new WorldMap.b( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().b.a(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} diff --git a/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_19_R3.java b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_19_R3.java new file mode 100644 index 0000000..e0e0615 --- /dev/null +++ b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_19_R3.java @@ -0,0 +1,239 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_19_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +public class MapWrapper_v1_19_R3 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_19_R3.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_19_R3.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_19_R3.sendMap(id, MapWrapper_v1_19_R3.this.content, player); + } else { + MapSender_v1_19_R3.addToQueue(id, MapWrapper_v1_19_R3.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_19_R3.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().bO.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().bO.j(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().b.a(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_19_R3.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().a(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.v().a("map", mapId); //getOrCreateTag putInt + + List> list = new ArrayList<>(); + DataWatcher.b dataWatcherItem = DataWatcher.b.a(EntityItemFrame.g, nmsStack); + list.add(dataWatcherItem); + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, list); + + ((CraftPlayer) player).getHandle().b.a(packet); + } + }; + + public MapWrapper_v1_19_R3(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java new file mode 100644 index 0000000..7af3493 --- /dev/null +++ b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java @@ -0,0 +1,125 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_19_R3 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = packetPlayOutMap.a(); //mapId + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = packetPlayInSetCreativeSlot.a(); + ItemStack item = packetPlayInSetCreativeSlot.c(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().b, "h"); + ChannelPipeline pipeline = networkManager.m.pipeline(); //connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().b, "h"); + Channel channel = networkManager.m; //connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.c, vec3dObj.d, vec3dObj.e); //x, y, z + } +} diff --git a/NMS-v1_20_R2/pom.xml b/NMS-v1_20_R2/pom.xml new file mode 100644 index 0000000..337240b --- /dev/null +++ b/NMS-v1_20_R2/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_20_R2 + + + 1.20.2-R0.1-SNAPSHOT + 17 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R2.java b/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R2.java new file mode 100644 index 0000000..dd25b06 --- /dev/null +++ b/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R2.java @@ -0,0 +1,138 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link MapSender_v1_20_R2} sends the Map packets to players. + */ +public class MapSender_v1_20_R2 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_20_R2() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.b updateData = new WorldMap.b( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().c.a(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} diff --git a/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R2.java b/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R2.java new file mode 100644 index 0000000..98ca1e2 --- /dev/null +++ b/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R2.java @@ -0,0 +1,239 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +public class MapWrapper_v1_20_R2 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_20_R2.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_20_R2.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_20_R2.sendMap(id, MapWrapper_v1_20_R2.this.content, player); + } else { + MapSender_v1_20_R2.addToQueue(id, MapWrapper_v1_20_R2.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_20_R2.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().bR.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().bR.j(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().c.a(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_20_R2.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().a(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.w().a("map", mapId); //getOrCreateTag putInt + + List> list = new ArrayList<>(); + DataWatcher.b dataWatcherItem = DataWatcher.b.a(EntityItemFrame.g, nmsStack); + list.add(dataWatcherItem); + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, list); + + ((CraftPlayer) player).getHandle().c.a(packet); + } + }; + + public MapWrapper_v1_20_R2(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R2.java b/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R2.java new file mode 100644 index 0000000..736343d --- /dev/null +++ b/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R2.java @@ -0,0 +1,126 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_20_R2 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = packetPlayInSetCreativeSlot.a(); + ItemStack item = packetPlayInSetCreativeSlot.d(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + ChannelPipeline pipeline = networkManager.n.pipeline(); //connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + Channel channel = networkManager.n; //connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.c, vec3dObj.d, vec3dObj.e); //x, y, z + } +} diff --git a/NMS-v1_20_R3/pom.xml b/NMS-v1_20_R3/pom.xml new file mode 100644 index 0000000..3b5b147 --- /dev/null +++ b/NMS-v1_20_R3/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_20_R3 + + + 1.20.4-R0.1-SNAPSHOT + 17 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R3.java b/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R3.java new file mode 100644 index 0000000..bb9de64 --- /dev/null +++ b/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R3.java @@ -0,0 +1,138 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link MapSender_v1_20_R3} sends the Map packets to players. + */ +public class MapSender_v1_20_R3 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_20_R3() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.b updateData = new WorldMap.b( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().c.a(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} diff --git a/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R3.java b/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R3.java new file mode 100644 index 0000000..698a53e --- /dev/null +++ b/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R3.java @@ -0,0 +1,239 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +public class MapWrapper_v1_20_R3 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_20_R3.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_20_R3.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_20_R3.sendMap(id, MapWrapper_v1_20_R3.this.content, player); + } else { + MapSender_v1_20_R3.addToQueue(id, MapWrapper_v1_20_R3.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_20_R3.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().bR.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().bR.j(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().c.a(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_20_R3.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().a(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.w().a("map", mapId); //getOrCreateTag putInt + + List> list = new ArrayList<>(); + DataWatcher.b dataWatcherItem = DataWatcher.b.a(EntityItemFrame.g, nmsStack); + list.add(dataWatcherItem); + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, list); + + ((CraftPlayer) player).getHandle().c.a(packet); + } + }; + + public MapWrapper_v1_20_R3(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R3.java b/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R3.java new file mode 100644 index 0000000..d66b550 --- /dev/null +++ b/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R3.java @@ -0,0 +1,126 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_20_R3 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = packetPlayInSetCreativeSlot.a(); + ItemStack item = packetPlayInSetCreativeSlot.d(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + ChannelPipeline pipeline = networkManager.n.pipeline(); //connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + Channel channel = networkManager.n; //connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.c, vec3dObj.d, vec3dObj.e); //x, y, z + } +} diff --git a/NMS-v1_20_R4/pom.xml b/NMS-v1_20_R4/pom.xml new file mode 100644 index 0000000..f3f63e7 --- /dev/null +++ b/NMS-v1_20_R4/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_20_R4 + + + 1.20.6-R0.1-SNAPSHOT + 21 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R4.java b/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R4.java new file mode 100644 index 0000000..dec1ac9 --- /dev/null +++ b/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R4.java @@ -0,0 +1,141 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.MapId; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link MapSender_v1_20_R4} sends the Map packets to players. + */ +public class MapSender_v1_20_R4 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_20_R4() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.b updateData = new WorldMap.b( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + MapId mapId = new MapId(id); + + PacketPlayOutMap packet = new PacketPlayOutMap( + mapId, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().c.a(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} diff --git a/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R4.java b/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R4.java new file mode 100644 index 0000000..9760612 --- /dev/null +++ b/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R4.java @@ -0,0 +1,243 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import net.minecraft.world.level.saveddata.maps.MapId; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_20_R4.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +public class MapWrapper_v1_20_R4 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_20_R4.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_20_R4.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_20_R4.sendMap(id, MapWrapper_v1_20_R4.this.content, player); + } else { + MapSender_v1_20_R4.addToQueue(id, MapWrapper_v1_20_R4.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_20_R4.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().cb.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().cb.j(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().c.a(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_20_R4.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().a(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + MapId mapId1 = new MapId(mapId); + nmsStack.b(DataComponents.B, mapId1); //set + + List> list = new ArrayList<>(); + DataWatcher.c dataWatcherItem = DataWatcher.c.a(EntityItemFrame.g, nmsStack); + list.add(dataWatcherItem); + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, list); + + ((CraftPlayer) player).getHandle().c.a(packet); + } + }; + + public MapWrapper_v1_20_R4(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R4.java b/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R4.java new file mode 100644 index 0000000..a774625 --- /dev/null +++ b/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R4.java @@ -0,0 +1,126 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_20_R4 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = (int) getDeclaredField(packetPlayInSetCreativeSlot, supports(20, 4) ? "b" : "a"); //slot, 1.20.5 = b, lower is a + ItemStack item = (ItemStack) getDeclaredField(packetPlayInSetCreativeSlot, supports(20, 4) ? "e" : "d"); //item, 1.20.5 = e, lower is d + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + ChannelPipeline pipeline = networkManager.n.pipeline(); //connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + Channel channel = networkManager.n; //connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.c, vec3dObj.d, vec3dObj.e); //x, y, z + } +} diff --git a/NMS-v1_21_R1/pom.xml b/NMS-v1_21_R1/pom.xml new file mode 100644 index 0000000..598c369 --- /dev/null +++ b/NMS-v1_21_R1/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_21_R1 + + + 1.21-R0.1-SNAPSHOT + 21 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R1.java b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R1.java new file mode 100644 index 0000000..8e4ba08 --- /dev/null +++ b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R1.java @@ -0,0 +1,141 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.MapId; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link MapSender_v1_21_R1} sends the Map packets to players. + */ +public class MapSender_v1_21_R1 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_21_R1() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.b updateData = new WorldMap.b( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + MapId mapId = new MapId(id); + + PacketPlayOutMap packet = new PacketPlayOutMap( + mapId, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().c.a(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} diff --git a/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R1.java b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R1.java new file mode 100644 index 0000000..453343d --- /dev/null +++ b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R1.java @@ -0,0 +1,243 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import net.minecraft.world.level.saveddata.maps.MapId; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_21_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +public class MapWrapper_v1_21_R1 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_21_R1.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_21_R1.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_21_R1.sendMap(id, MapWrapper_v1_21_R1.this.content, player); + } else { + MapSender_v1_21_R1.addToQueue(id, MapWrapper_v1_21_R1.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_21_R1.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().cc.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().cc.j(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().c.a(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_21_R1.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().a(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + MapId mapId1 = new MapId(mapId); + nmsStack.b(DataComponents.B, mapId1); //set + + List> list = new ArrayList<>(); + DataWatcher.c dataWatcherItem = DataWatcher.c.a(EntityItemFrame.f, nmsStack); + list.add(dataWatcherItem); + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, list); + + ((CraftPlayer) player).getHandle().c.a(packet); + } + }; + + public MapWrapper_v1_21_R1(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R1.java b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R1.java new file mode 100644 index 0000000..21411c5 --- /dev/null +++ b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R1.java @@ -0,0 +1,126 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_21_R1 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = (int) getDeclaredField(packetPlayInSetCreativeSlot, supports(20, 4) ? "b" : "a"); //slot, 1.20.5 = b, lower is a + ItemStack item = (ItemStack) getDeclaredField(packetPlayInSetCreativeSlot, supports(20, 4) ? "e" : "d"); //item, 1.20.5 = e, lower is d + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + ChannelPipeline pipeline = networkManager.n.pipeline(); //connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + Channel channel = networkManager.n; //connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.c, vec3dObj.d, vec3dObj.e); //x, y, z + } +} diff --git a/NMS-v1_21_R2/pom.xml b/NMS-v1_21_R2/pom.xml new file mode 100644 index 0000000..005a5df --- /dev/null +++ b/NMS-v1_21_R2/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_21_R2 + + + 1.21.3-R0.1-SNAPSHOT + 21 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R2.java b/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R2.java new file mode 100644 index 0000000..3620bc8 --- /dev/null +++ b/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R2.java @@ -0,0 +1,141 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.MapId; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_21_R2.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link MapSender_v1_21_R2} sends the Map packets to players. + */ +public class MapSender_v1_21_R2 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_21_R2() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.c updateData = new WorldMap.c( + 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 + ); + + MapId mapId = new MapId(id); + + PacketPlayOutMap packet = new PacketPlayOutMap( + mapId, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().f.a(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} diff --git a/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R2.java b/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R2.java new file mode 100644 index 0000000..0dbc9ac --- /dev/null +++ b/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R2.java @@ -0,0 +1,242 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import net.minecraft.world.level.saveddata.maps.MapId; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_21_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +public class MapWrapper_v1_21_R2 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_21_R2.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_21_R2.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_21_R2.sendMap(id, MapWrapper_v1_21_R2.this.content, player); + } else { + MapSender_v1_21_R2.addToQueue(id, MapWrapper_v1_21_R2.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_21_R2.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().cc.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().cc.j(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().f.a(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_21_R2.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().a(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + MapId mapId1 = new MapId(mapId); + nmsStack.b(DataComponents.L, mapId1); //set + + List> list = new ArrayList<>(); + DataWatcher.c dataWatcherItem = DataWatcher.c.a(EntityItemFrame.e, nmsStack); + list.add(dataWatcherItem); + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, list); + + ((CraftPlayer) player).getHandle().f.a(packet); + } + }; + + public MapWrapper_v1_21_R2(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R2.java b/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R2.java new file mode 100644 index 0000000..487ed32 --- /dev/null +++ b/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R2.java @@ -0,0 +1,126 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_21_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_21_R2 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = (int) getDeclaredField(packetPlayInSetCreativeSlot, supports(20, 4) ? "b" : "a"); //slot, 1.20.5 = b, lower is a + ItemStack item = (ItemStack) getDeclaredField(packetPlayInSetCreativeSlot, supports(20, 4) ? "e" : "d"); //item, 1.20.5 = e, lower is d + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + ChannelPipeline pipeline = networkManager.n.pipeline(); //connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + Channel channel = networkManager.n; //connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.d, vec3dObj.e, vec3dObj.f); //x, y, z + } +} diff --git a/README.md b/README.md index 1f20539..2575bac 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ # MapReflectionAPI -This plugin helps developer with displaying images on maps. It supports Spigot 1.12 - 1.21. +This plugin helps developer with viewing images on maps. It supports Spigot 1.12 - 1.19. ## Usage: -### Using the API: - First, include the API using Maven: ```xml @@ -17,8 +15,8 @@ First, include the API using Maven: tech.sbdevelopment - MapReflectionAPI - 1.6.4 + MapReflectionAPI-API + 1.5 provided ``` @@ -52,8 +50,17 @@ controller.showInHand(p, true); It's also possible to split one image onto multiple itemframes. For example using the following code. ```java -//--- Wrap image (into 2 rows and 2 columns) --- -MultiMapWrapper wrapper = MapReflectionAPI.getMapManager().wrapMultiImage(ImageIO.read(new File("image.png")), 2, 2); +BufferedImage leftTopFrame = ...; +BufferedImage leftBottomFrame = ...; +BufferedImage rightTopFrame = ...; +BufferedImage rightBottomFrame = ...; +BufferedImage[][] images = { + {leftBottomFrame, leftTopFrame}, + {rightBottomFrame, rightTopFrame} +}; + +//--- Wrap image --- +MultiMapWrapper wrapper = MapReflectionAPI.getMapManager().wrapMultiImage(images); MultiMapController controller = wrapper.getController(); final Player p = Bukkit.getPlayer("SBDeveloper"); @@ -74,94 +81,17 @@ ItemFrame leftBottomFrame = ...; ItemFrame rightTopFrame = ...; ItemFrame rightBottomFrame = ...; ItemFrame[][] frames = { - {leftTopFrame, rightTopFrame}, - {leftBottomFrame, rightBottomFrame} + {leftBottomFrame, leftTopFrame}, + {rightBottomFrame, rightTopFrame} }; controller.showInFrames(p, frames, true); ``` More information can be found on the [JavaDoc](https://sbdevelopment.tech/javadoc/mapreflectionapi/). -### Notes on map render distance: - -MapReflectionAPI does not implement render distance to images shown on maps. This should be implemented by yourself. An example of this is below. - -```java -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerRespawnEvent; - -public class MapRenderDistanceListener implements Listener { - private static final int MAP_RENDER_DISTANCE_SQUARED = 1024; - - @EventHandler - public void onPlayerJoin(PlayerJoinEvent e) { - Bukkit.getScheduler().runTaskLaterAsynchronously(MyPluginInstance, () -> { - //Show the maps to the player which are within distance - }); - } - - @EventHandler - public void onPlayerLeave(PlayerQuitEvent e) { - Bukkit.getScheduler().runTaskLaterAsynchronously(MyPluginInstance, () -> { - //Hide the maps to the player which are within distance - }); - } - - @EventHandler - public void onPlayerChangeWorld(PlayerChangedWorldEvent e) { - //Hide all the maps in the e.getFrom() world - - Bukkit.getScheduler().runTaskLaterAsynchronously(MyPluginInstance, () -> { - //Show the maps to the player which are within distance in e.getPlayer().getWorld() - }, 20); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onPlayerDeath(PlayerDeathEvent e) { - //Hide all the maps in the e.getEntity().getWorld() - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onPlayerRespawn(PlayerRespawnEvent e) { - Bukkit.getScheduler().runTaskLaterAsynchronously(MyPluginInstance, () -> { - //Show the maps to the player which are within distance in e.getPlayer().getWorld() - }, 20); - } - - @EventHandler - public void onPlayerMove(PlayerMoveEvent e) { - //Hide all the maps in the e.getFrom() world - //Show the maps to the player which are within distance in e.getTo().getWorld() - - //FOR EXAMPLE: - if (e.getTo() == null) return; - if (e.getFrom().getChunk().equals(e.getTo().getChunk())) return; - - for (Frame frame : API.getFramesInWorld(e.getPlayer().getWorld())) { - double distanceSquared = e.getTo().distanceSquared(frame.getLocation()); - - if (distanceSquared > MAP_RENDER_DISTANCE_SQUARED) { - API.hideFrame(e.getPlayer(), frame); - } else { - API.showFrame(e.getPlayer(), frame); - } - } - } - - @EventHandler - public void onPlayerTeleport(PlayerTeleportEvent e) { - //Hide all the maps in the e.getFrom() world - //Show the maps to the player which are within distance in e.getTo().getWorld() - - //SEE EXAMPLE ABOVE - } -} -``` - ## Credits: -This is a fork of [MapManager](https://github.com/InventivetalentDev/MapManager). It updates the API to the latest version of Minecraft and uses other dependencies. +This is a fork of [MapManager](https://github.com/InventivetalentDev/MapManager). It updates the API to 1.19 and uses +other dependencies. This plugin includes classes from BKCommonLib. Please checkout the README in that package for more information. diff --git a/pom.xml b/pom.xml index d830bee..933e673 100644 --- a/pom.xml +++ b/pom.xml @@ -24,18 +24,37 @@ tech.sbdevelopment MapReflectionAPI - 1.6.6 - jar + pom + ${revision} MapReflectionAPI This API helps developer with viewing images on maps. https://sbdplugins.nl + 1.5 UTF-8 - ${project.build.directory}/javadoc-delombok + 11 + + API + Dist + NMS-v1_21_R1 + NMS-v1_20_R4 + NMS-v1_20_R3 + NMS-v1_20_R2 + NMS-v1_21_R2 + NMS-v1_19_R3 + NMS-v1_18_R2 + NMS-v1_17_R1 + NMS-v1_16_R3 + NMS-v1_15_R1 + NMS-v1_14_R1 + NMS-v1_13_R2 + NMS-v1_12_R1 + + sbdevelopment-repo @@ -44,113 +63,60 @@ + clean package org.apache.maven.plugins maven-compiler-plugin - 3.14.0 + 3.11.0 - 11 - - - org.projectlombok - lombok - 1.18.34 - - + ${jdk.version} - org.apache.maven.plugins - maven-shade-plugin - 3.6.0 - - - package - - shade - - - false - - - com.bergerkiller.bukkit.common - tech.sbdevelopment.mapreflectionapi.libs.bkcommonlib - - - org.bstats - tech.sbdevelopment.mapreflectionapi.libs.bstats - - - com.cryptomorin.xseries - tech.sbdevelopment.mapreflectionapi.libs.xseries - - - - - - - - org.projectlombok - lombok-maven-plugin - 1.18.20.0 + org.codehaus.mojo + flatten-maven-plugin + 1.5.0 - ${project.basedir}/src/main/java - ${maven.lombok.delombok-target} - false + true - generate-sources + flatten + process-resources - delombok + flatten + + + + flatten.clean + clean + + clean - - - org.projectlombok - lombok - 1.18.34 - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.11.2 + maven-toolchains-plugin + 3.1.0 + + + + toolchain + + + - 11 - ${maven.lombok.delombok-target} - - **/com/bergerkiller/bukkit/common/io/*.java - **/com/bergerkiller/bukkit/common/map/*.java - **/com/bergerkiller/bukkit/common/map/color/*.java - **/tech/sbdevelopment/mapreflectionapi/*.java - **/tech/sbdevelopment/mapreflectionapi/cmd/*.java - **/tech/sbdevelopment/mapreflectionapi/managers/*.java - **/tech/sbdevelopment/mapreflectionapi/utils/*.java - **/tech/sbdevelopment/mapreflectionapi/listeners/*.java - + + + ${jdk.version} + + - - - src/main/resources - true - - plugin.yml - - - - src/main/resources - - plugin.yml - - - @@ -158,57 +124,14 @@ spigot-repo https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - MG-Dev Jenkins CI Maven Repository - https://ci.mg-dev.eu/plugin/repository/everything - - - dmulloy2-repo - https://repo.dmulloy2.net/repository/public/ - - - jitpack.io - https://jitpack.io - org.spigotmc spigot-api - 1.21.5-R0.1-SNAPSHOT - provided - - - org.projectlombok - lombok - 1.18.34 - provided - - - org.bstats - bstats-bukkit - 3.1.0 - compile - - - com.github.cryptomorin - XSeries - 13.1.0 - - - - - org.jetbrains - annotations-java5 - 24.1.0 - provided - - - io.netty - netty-transport - 4.1.118.Final + 1.20.1-R0.1-SNAPSHOT provided - \ No newline at end of file + diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java deleted file mode 100644 index b43f148..0000000 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package tech.sbdevelopment.mapreflectionapi.api; - -import lombok.Data; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; -import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; - -import java.util.ArrayList; -import java.util.List; - -import static com.cryptomorin.xseries.reflection.XReflection.*; -import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.sendPacket; - -/** - * The {@link MapSender} sends the Map packets to players. - */ -public class MapSender { - private static final List sendQueue = new ArrayList<>(); - private static int senderID = -1; - - private MapSender() { - } - - /** - * Add a map to the send queue - * - * @param id The ID of the map - * @param content The {@link ArrayImage} to view on the map - * @param player The {@link Player} to view for - */ - public static void addToQueue(final int id, final ArrayImage content, final Player player) { - QueuedMap toSend = new QueuedMap(id, content, player); - if (sendQueue.contains(toSend)) return; - sendQueue.add(toSend); - - runSender(); - } - - /** - * Cancels a senderID in the sender queue - * - * @param s The senderID to cancel - */ - public static void cancelID(int s) { - sendQueue.removeIf(queuedMap -> queuedMap.id == s); - } - - /** - * Run the sender task - */ - private static void runSender() { - if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) - return; - - senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { - if (sendQueue.isEmpty()) return; - - for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { - QueuedMap current = sendQueue.get(0); - if (current == null) return; - - sendMap(current.id, current.image, current.player); - - if (!sendQueue.isEmpty()) sendQueue.remove(0); - } - }, 0, 2); - } - - private static final Class packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap"); - private static final Class worldMapData = supports(17) ? getNMSClass("world.level.saveddata.maps", supports(21, 2) ? "WorldMap$c" : "WorldMap$b") : null; //1.21.2+ uses WorldMap$c, 1.17+ uses WorldMap$b - private static final Class mapIdClazz = supports(21) ? getNMSClass("world.level.saveddata.maps", "MapId") : null; - - /** - * Send a map to a player - * - * @param id0 The ID of the map - * @param content The {@link ArrayImage} to view on the map - * @param player The {@link Player} to view for - */ - public static void sendMap(final int id0, final ArrayImage content, final Player player) { - if (player == null || !player.isOnline()) { - List toRemove = new ArrayList<>(); - for (QueuedMap qMap : sendQueue) { - if (qMap == null) continue; - - if (qMap.player == null || !qMap.player.isOnline()) { - toRemove.add(qMap); - } - } - Bukkit.getScheduler().cancelTask(senderID); - sendQueue.removeAll(toRemove); - - return; - } - - int id = -id0; - - Object packet; - if (supports(20, 4)) { //1.20.5+ - Object updateData = ReflectionUtil.callConstructor(worldMapData, - 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 - ); - - Object mapId = ReflectionUtil.callConstructor(mapIdClazz, id); - - packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, - mapId, //ID - (byte) 0, //Scale, 0 = 1 block per pixel - false, //Show icons - new ReflectionUtil.CollectionParam<>(), //Icons - updateData - ); - } else if (supports(17)) { //1.17+ - Object updateData = ReflectionUtil.callConstructor(worldMapData, - 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(packetPlayOutMapClass, - id, //ID - (byte) 0, //Scale, 0 = 1 block per pixel - false, //Show icons - new ReflectionUtil.CollectionParam<>(), //Icons - updateData - ); - } else if (supports(14)) { //1.16-1.14 - packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, - id, //ID - (byte) 0, //Scale, 0 = 1 block per pixel - false, //Tracking position - false, //Locked - new ReflectionUtil.CollectionParam<>(), //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(packetPlayOutMapClass, - id, //ID - (byte) 0, //Scale, 0 = 1 block per pixel - false, //??? - new ReflectionUtil.CollectionParam<>(), //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) - ); - } - - sendPacket(player, packet); - } - - @Data - static final class QueuedMap { - private final int id; - private final ArrayImage image; - private final Player player; - } -} diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java deleted file mode 100644 index c2b6edd..0000000 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ /dev/null @@ -1,387 +0,0 @@ -/* - * This file is part of MapReflectionAPI. - * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package tech.sbdevelopment.mapreflectionapi.api; - -import lombok.Getter; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.OfflinePlayer; -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 org.jetbrains.annotations.NotNull; -import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; -import tech.sbdevelopment.mapreflectionapi.api.events.MapContentUpdateEvent; -import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; -import tech.sbdevelopment.mapreflectionapi.managers.Configuration; -import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; -import tech.sbdevelopment.mapreflectionapi.utils.XMaterial; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static com.cryptomorin.xseries.reflection.XReflection.*; -import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getHandle; -import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.sendPacket; - -/** - * A {@link MapWrapper} wraps one image. - */ -@Getter -public class MapWrapper extends AbstractMapWrapper { - public static final String REFERENCE_METADATA = "MAP_WRAPPER_REF"; - protected ArrayImage content; - - /** - * Construct a new {@link MapWrapper} - * - * @param image The {@link ArrayImage} to wrap - */ - public MapWrapper(ArrayImage image) { - this.content = image; - } - - private static final Class craftStackClass = getCraftClass("inventory.CraftItemStack"); - private static final Class setSlotPacketClass = getNMSClass("network.protocol.game", "PacketPlayOutSetSlot"); - private static final Class entityClass = getNMSClass("world.entity", "Entity"); - private static final Class dataWatcherClass = getNMSClass("network.syncher", "DataWatcher"); - private static final Class entityMetadataPacketClass = getNMSClass("network.protocol.game", "PacketPlayOutEntityMetadata"); - private static final Class entityItemFrameClass = getNMSClass("world.entity.decoration", "EntityItemFrame"); - private static final Class dataWatcherItemClass = getNMSClass("network.syncher", "DataWatcher$Item"); - - protected MapController controller = new MapController() { - private final Map viewers = new HashMap<>(); - - @Override - public void addViewer(Player player) throws MapLimitExceededException { - if (!isViewing(player)) { - viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); - } - } - - @Override - public void removeViewer(OfflinePlayer player) { - viewers.remove(player.getUniqueId()); - } - - @Override - public void clearViewers() { - for (UUID uuid : viewers.keySet()) { - viewers.remove(uuid); - } - } - - @Override - public boolean isViewing(OfflinePlayer player) { - if (player == null) return false; - return viewers.containsKey(player.getUniqueId()); - } - - @Override - public int getMapId(OfflinePlayer player) { - if (isViewing(player)) { - return viewers.get(player.getUniqueId()); - } - return -1; - } - - @Override - public void update(@NotNull ArrayImage content) { - boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); - MapContentUpdateEvent event = new MapContentUpdateEvent(MapWrapper.this, content, async); - Bukkit.getPluginManager().callEvent(event); - - if (Configuration.getInstance().isImageCache()) { - MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); - if (duplicate != null) { - MapWrapper.this.content = duplicate.getContent(); - return; - } - } - - MapWrapper.this.content = content; - - if (event.isSendContent()) { - for (UUID id : viewers.keySet()) { - sendContent(Bukkit.getPlayer(id)); - } - } - } - - @Override - public void sendContent(Player player) { - sendContent(player, false); - } - - @Override - public void sendContent(Player player, boolean withoutQueue) { - if (!isViewing(player)) return; - - int id = getMapId(player); - if (withoutQueue) { - MapSender.sendMap(id, MapWrapper.this.content, player); - } else { - MapSender.addToQueue(id, MapWrapper.this.content, player); - } - } - - @Override - public void cancelSend() { - for (int s : viewers.values()) { - MapSender.cancelID(s); - } - } - - @Override - public void showInInventory(Player player, int slot, boolean force) { - if (!isViewing(player)) return; - - if (player.getGameMode() == GameMode.CREATIVE && !force) return; - - if (slot < 9) { - slot += 36; - } else if (slot > 35 && slot != 45) { - slot = 8 - (slot - 36); - } - - String inventoryMenuName; - if (supports(21)) { - //1.21.5 = bQ, 1.21 - 1.21.4 = cc - inventoryMenuName = supports(21, 4) ? "bQ" : "cc"; - } else if (supports(20)) { - //1.20.5 = cb, 1.20.2 - 1.20.4 = bR, 1.20(.1) = bQ - inventoryMenuName = supports(20, 4) ? "cb" : supports(20, 2) ? "bR" : "bQ"; - } else if (supports(19)) { - //1.19.4 = bO, >= 1.19.3 = bT - inventoryMenuName = supports(19, 3) ? "bO" : "bT"; - } else if (supports(18)) { - //1.18.1 = ap, 1.18(.2) = ao - inventoryMenuName = supports(18, 1) ? "bV" : "bU"; - } else if (supports(17)) { - //1.17, same as 1.18(.2) - inventoryMenuName = "bU"; - } else { - //1.12-1.16 - inventoryMenuName = "defaultContainer"; - } - Object inventoryMenu = ReflectionUtil.getField(getHandle(player), inventoryMenuName); - - Object nmsStack = asCraftItemStack(player); - - Object packet; - if (supports(17)) { //1.17+ - int stateId = (int) ReflectionUtil.callMethod(inventoryMenu, supports(18) ? "j" : "getStateId"); - - packet = ReflectionUtil.callConstructor(setSlotPacketClass, - 0, //0 = Player inventory - stateId, - slot, - nmsStack - ); - } else { //1.16- - packet = ReflectionUtil.callConstructor(setSlotPacketClass, - 0, //0 = Player inventory - slot, - nmsStack - ); - } - - 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() != XMaterial.FILLED_MAP.parseMaterial() && !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() != XMaterial.FILLED_MAP.parseMaterial() && !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(XMaterial.FILLED_MAP.parseMaterial(), 1); - if (debugInfo != null) { - ItemMeta itemMeta = stack.getItemMeta(); - itemMeta.setDisplayName(debugInfo); - stack.setItemMeta(itemMeta); - } - - Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { - ItemFrame frame = MapReflectionAPI.getMapManager().getItemFrameById(player.getWorld(), entityId); - if (frame != null) { - frame.removeMetadata(REFERENCE_METADATA, MapReflectionAPI.getInstance()); - frame.setMetadata(REFERENCE_METADATA, new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper.this)); - } - - sendItemFramePacket(player, entityId, stack, getMapId(player)); - }); - } - - @Override - public void clearFrame(Player player, int entityId) { - sendItemFramePacket(player, entityId, null, -1); - Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { - ItemFrame frame = MapReflectionAPI.getMapManager().getItemFrameById(player.getWorld(), entityId); - if (frame != null) frame.removeMetadata(REFERENCE_METADATA, MapReflectionAPI.getInstance()); - }); - } - - @Override - public void clearFrame(Player player, ItemFrame frame) { - clearFrame(player, frame.getEntityId()); - } - - private Object asCraftItemStack(Player player) { - return createCraftItemStack(new ItemStack(XMaterial.FILLED_MAP.parseMaterial(), 1, (short) getMapId(player)), (short) getMapId(player)); - } - - private Object createCraftItemStack(@NotNull ItemStack stack, int mapId) { - if (mapId < 0) return null; - - Object nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack); - - //1.20.5 uses new NBT compound system - if (supports(20, 4)) { - Object mapIdComponent = ReflectionUtil.getDeclaredField(getNMSClass("core.component", "DataComponents"), supports(21, 4) ? "M" : supports(21, 2) ? "L" : "B"); //1.21.2+ uses L, otherwise B - Object mapId1 = ReflectionUtil.callConstructor(getNMSClass("world.level.saveddata.maps", "MapId"), mapId); - - // Use generic reflection because of generics - // T ItemStack#b(DataComponentType dataComponentType, T t) - try { - Method m = nmsStack.getClass().getMethod("b", getNMSClass("core.component", "DataComponentType"), Object.class); - m.setAccessible(true); - m.invoke(nmsStack, mapIdComponent, mapId1); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { - ex.printStackTrace(); - return null; - } - } else if (supports(13)) { - String nbtObjectName; - if (supports(20)) { //1.20 - nbtObjectName = "w"; - } else if (supports(19)) { //1.19 - nbtObjectName = "v"; - } else if (supports(18)) { //1.18 - nbtObjectName = supports(18, 1) ? "t" : "u"; //1.18.1 = t, 1.18(.2) = u - } else { //1.13 - 1.17 - nbtObjectName = "getOrCreateTag"; - } - Object nbtObject = ReflectionUtil.callMethod(nmsStack, nbtObjectName); - ReflectionUtil.callMethod(nbtObject, supports(18) ? "a" : "setInt", "map", mapId); - } - return nmsStack; - } - - private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { - Object nmsStack = createCraftItemStack(stack, mapId); - - String dataWatcherObjectName; - if (supports(21)) { //1.21 - dataWatcherObjectName = supports(21, 2) ? "e" : "f"; //1.21.2+ = e, 1.21(.1) = f - } else if (supports(19, 3)) { //1.19.3 and 1.20(.1) - dataWatcherObjectName = "g"; - } else if (supports(19)) { //1.19-1.19.2 - dataWatcherObjectName = "ao"; - } else if (supports(18)) { //1.18 - dataWatcherObjectName = supports(18, 1) ? "ap" : "ao"; //1.18.1 = ap, 1.18(.2) = ao - } else if (supports(17)) { //1.17 - dataWatcherObjectName = "ao"; - } else if (supports(14)) { //1.14 - 1.16 - dataWatcherObjectName = "ITEM"; - } else if (supports(13)) { //1.13 - dataWatcherObjectName = "e"; - } else { //1.12 - dataWatcherObjectName = "c"; - } - Object dataWatcherObject = ReflectionUtil.getDeclaredField(entityItemFrameClass, dataWatcherObjectName); - - ReflectionUtil.ListParam list = new ReflectionUtil.ListParam<>(); - - Object packet; - if (supports(19, 3)) { //1.19.3 - Class dataWatcherRecordClass = getNMSClass("network.syncher", "DataWatcher$" + (supports(20, 4) ? "c" : "b")); - // Sadly not possible to use ReflectionUtil (in its current state), because of the Object parameter - Object dataWatcherItem; - try { - Method m = dataWatcherRecordClass.getMethod("a", dataWatcherObject.getClass(), Object.class); - m.setAccessible(true); - dataWatcherItem = m.invoke(null, dataWatcherObject, nmsStack); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { - ex.printStackTrace(); - return; - } - list.add(dataWatcherItem); - - packet = ReflectionUtil.callConstructor(entityMetadataPacketClass, - entityId, - list - ); - } else { //1.19.2 or lower - Object dataWatcher = ReflectionUtil.callConstructorNull(dataWatcherClass, entityClass); - - packet = ReflectionUtil.callConstructor(entityMetadataPacketClass, - entityId, - dataWatcher, //dummy watcher! - true - ); - - Object dataWatcherItem = ReflectionUtil.callFirstConstructor(dataWatcherItemClass, dataWatcherObject, nmsStack); - list.add(dataWatcherItem); - ReflectionUtil.setDeclaredField(packet, "b", list); - } - - sendPacket(player, packet); - } - }; -} diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/types/CancellableEvent.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/types/CancellableEvent.java deleted file mode 100644 index 96987f7..0000000 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/types/CancellableEvent.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * This file is part of MapReflectionAPI. - * Copyright (c) 2023 inventivetalent / SBDevelopment - All Rights Reserved - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package tech.sbdevelopment.mapreflectionapi.api.events.types; - -import lombok.NoArgsConstructor; -import org.bukkit.event.Cancellable; - -@NoArgsConstructor -public class CancellableEvent extends Event implements Cancellable { - /** - * If this event gets cancelled. - */ - private boolean cancelled; - - public CancellableEvent(boolean isAsync) { - super(isAsync); - } - - /** - * Check if this event gets cancelled. - * - * @return true if cancelled, false if not - */ - @Override - public boolean isCancelled() { - return cancelled; - } - - /** - * Set if this event gets cancelled. - * - * @param cancelled true if you wish to cancel this event - */ - @Override - public void setCancelled(boolean cancelled) { - this.cancelled = cancelled; - } -} diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java deleted file mode 100644 index eef5b61..0000000 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * This file is part of MapReflectionAPI. - * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package tech.sbdevelopment.mapreflectionapi.listeners; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelDuplexHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPromise; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.util.Vector; -import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; -import tech.sbdevelopment.mapreflectionapi.api.events.CreativeInventoryMapUpdateEvent; -import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; -import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; -import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; - -import java.util.concurrent.TimeUnit; - -import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getConnection; -import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; -import static com.cryptomorin.xseries.reflection.XReflection.*; - -public class PacketListener implements Listener { - private static final Class packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap"); - private static final Class packetPlayInUseEntityClass = getNMSClass("network.protocol.game", "PacketPlayInUseEntity"); - private static final Class packetPlayInSetCreativeSlotClass = getNMSClass("network.protocol.game", "PacketPlayInSetCreativeSlot"); - private static final Class vec3DClass = getNMSClass("world.phys", "Vec3D"); - private static final Class craftStackClass = getCraftClass("inventory.CraftItemStack"); - private static final Class playerCommonConnection; - - static { - if (supports(20, 2)) { - // The packet send method has been abstracted from ServerGamePacketListenerImpl to ServerCommonPacketListenerImpl in 1.20.2 - playerCommonConnection = getNMSClass("server.network", "ServerCommonPacketListenerImpl"); - } else { - playerCommonConnection = getNMSClass("server.network", "PlayerConnection"); - } - } - - @EventHandler - public void onJoin(PlayerJoinEvent e) { - injectPlayer(e.getPlayer()); - } - - @EventHandler - public void onQuit(PlayerQuitEvent e) { - removePlayer(e.getPlayer()); - } - - private void injectPlayer(Player player) { - ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { - @Override - public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { - boolean cancel = false; - - if (packet.getClass().isAssignableFrom(packetPlayOutMapClass)) { - Object packetPlayOutMap = packetPlayOutMapClass.cast(packet); - - int id; - boolean inv = false; - if (supports(20, 4)) { //1.20.4 uses MapId class and record classes (final fields...) - Object mapId = getDeclaredField(packetPlayOutMap, "b"); - id = (int) getDeclaredField(mapId, "c"); - - if (id < 0) { - Object newMapid = callConstructor(mapId.getClass(), -id); - Object c = getDeclaredField(packetPlayOutMap, "c"); - Object d = getDeclaredField(packetPlayOutMap, "d"); - Object e = getDeclaredField(packetPlayOutMap, "e"); - Object f = getDeclaredField(packetPlayOutMap, "f"); - - packetPlayOutMap = callConstructor(packetPlayOutMapClass, newMapid, c, d, e, f); - packet = packetPlayOutMap; - - inv = true; - } - } else { - id = (int) getDeclaredField(packetPlayOutMap, "a"); - - if (id < 0) { - setDeclaredField(packetPlayOutMap, "a", -id); - inv = true; - } - } - - if (!inv) { - boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); - MapCancelEvent event = new MapCancelEvent(player, id, async); - if (MapReflectionAPI.getMapManager().isIdUsedBy(player, id)) event.setCancelled(true); - Bukkit.getPluginManager().callEvent(event); - - if (event.isCancelled()) cancel = true; - } - } - - if (!cancel) super.write(ctx, packet, promise); - } - - @Override - public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { - boolean cancel = false; - - if (packet.getClass().isAssignableFrom(packetPlayInUseEntityClass)) { - Object packetPlayInEntity = packetPlayInUseEntityClass.cast(packet); - - int entityId = (int) getDeclaredField(packetPlayInEntity, supports(20, 4) ? "b" : "a"); - - Enum actionEnum; - Enum hand; - Object pos; - if (supports(17)) { - Object action = getDeclaredField(packetPlayInEntity, supports(20, 4) ? "c" : "b"); - actionEnum = (Enum) callDeclaredMethod(action, "a"); - Class d = getNMSClass("network.protocol.game", "PacketPlayInUseEntity$d"); - Class e = getNMSClass("network.protocol.game", "PacketPlayInUseEntity$e"); - if (action.getClass().isAssignableFrom(e)) { - hand = (Enum) getDeclaredField(action, "a"); - pos = getDeclaredField(action, "b"); - } else { - pos = null; - if (action.getClass().isAssignableFrom(d)) { - hand = (Enum) getDeclaredField(action, "a"); - } else { - hand = null; - } - } - } else { - actionEnum = (Enum) callDeclaredMethod(packetPlayInEntity, supports(13) ? "b" : "a"); //1.13 = b, 1.12 = a - hand = (Enum) callDeclaredMethod(packetPlayInEntity, supports(13) ? "c" : "b"); //1.13 = c, 1.12 = b - pos = callDeclaredMethod(packetPlayInEntity, supports(13) ? "d" : "c"); //1.13 = d, 1.12 = c - } - - if (Bukkit.getScheduler().callSyncMethod(MapReflectionAPI.getInstance(), () -> { - boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); - MapInteractEvent event = new MapInteractEvent(player, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); - if (event.getFrame() != null && event.getMapWrapper() != null) { - Bukkit.getPluginManager().callEvent(event); - return event.isCancelled(); - } - return false; - }).get(1, TimeUnit.SECONDS)) cancel = true; - } else if (packet.getClass().isAssignableFrom(packetPlayInSetCreativeSlotClass)) { - Object packetPlayInSetCreativeSlot = packetPlayInSetCreativeSlotClass.cast(packet); - - int slot; - if (supports(20, 4)) { //1.20.4+ uses short - slot = (short) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, "b"); - } else { //1.20.3 and lower uses int - slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(19, 4) ? "a" : supports(13) ? "b" : "a"); //1.20.4 - 1.19.4 = a, 1.19.3 - 1.13 and 1.20.5 = b, 1.12 = a - } - Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(20, 4) ? "e" : supports(20, 2) ? "d" : supports(18) ? "c" : "getItemStack"); //1.20.5 = e, 1.20.2-1.20.4 = d, >= 1.18 = c, 1.17 = getItemStack - ItemStack craftStack = (ItemStack) ReflectionUtil.callMethod(craftStackClass, "asBukkitCopy", nmsStack); - - boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); - CreativeInventoryMapUpdateEvent event = new CreativeInventoryMapUpdateEvent(player, slot, craftStack, async); - if (event.getMapWrapper() != null) { - Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) cancel = true; - } - } - - if (!cancel) super.channelRead(ctx, packet); - } - }; - - Channel channel = getChannel(player); - channel.pipeline().addBefore("packet_handler", player.getName(), channelDuplexHandler); - } - - private void removePlayer(Player player) { - Channel channel = getChannel(player); - channel.eventLoop().submit(() -> channel.pipeline().remove(player.getName())); - } - - private Channel getChannel(Player player) { - Object networkManager = getDeclaredField(playerCommonConnection, getConnection(player), supports(21) ? "e" : supports(20, 2) ? "c" : supports(19, 4) ? "h" : supports(19) ? "b" : supports(17) ? "a" : "networkManager"); //1.20.2 = ServerCommonPacketListenerImpl#c, 1.20(.1) & 1.19.4 = h, >= 1.19.3 = b, 1.18 - 1.17 = a, 1.16 = networkManager - return (Channel) getDeclaredField(networkManager, supports(20, 2) ? "n" : supports(18) ? "m" : supports(17) ? "k" : "channel"); //1.20.2 = n, 1.20(.1), 1.19 & 1.18 = m, 1.17 = k, 1.16 = channel - } - - private Vector vec3DToVector(Object vec3d) { - if (!(vec3d.getClass().isAssignableFrom(vec3DClass))) return new Vector(0, 0, 0); - - Object vec3dNMS = vec3DClass.cast(vec3d); - double x = (double) getDeclaredField(vec3dNMS, supports(21, 2) ? "d" : supports(19) ? "c" : supports(17) ? "b" : "x"); //1.21.2+ = d, 1.19 = c, 1.18 = b, 1.16 = x - double y = (double) getDeclaredField(vec3dNMS, supports(21, 2) ? "e" : supports(19) ? "d" : supports(17) ? "c" : "y"); //1.21.2+ = e, 1.19 = d, 1.18 = c, 1.16 = y - double z = (double) getDeclaredField(vec3dNMS, supports(21, 2) ? "f" : supports(19) ? "e" : supports(17) ? "d" : "z"); //1.21.2+ = f, 1.19 = e, 1.18 = d, 1.16 = z - - return new Vector(x, y, z); - } -} diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java deleted file mode 100644 index a723ca9..0000000 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * This file is part of MapReflectionAPI. - * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package tech.sbdevelopment.mapreflectionapi.utils; - -import org.bukkit.World; -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.*; - -import static com.cryptomorin.xseries.reflection.XReflection.getCraftClass; -import static com.cryptomorin.xseries.reflection.XReflection.getNMSClass; - -public class ReflectionUtil { - private static final Map> constructorCache = new HashMap<>(); - private static final Map methodCache = new HashMap<>(); - private static final Map fieldCache = new HashMap<>(); - private static final Class craftWorld = getCraftClass("CraftWorld"); - - /** - * Helper class converted to {@link List} - * - * @param The storage type - */ - public static class ListParam extends ArrayList { - } - - /** - * Helper class converted to {@link Collection} - * - * @param The storage type - */ - public static class CollectionParam extends ArrayList { - } - - 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 == CollectionParam.class) return Collection.class; - if (clazz == ListParam.class) return List.class; - if (clazz == ArrayList.class) return Collection.class; //LEGACY! - if (clazz == HashMap.class) return Map.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 Object getHandle(@NotNull World world) {; - return callDeclaredMethod(craftWorld, world, "getHandle"); - } - - @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 { - String cacheKey = "ConstructorNull:" + clazz.getName() + ":" + paramClass.getName(); - - if (constructorCache.containsKey(cacheKey)) { - Constructor cachedConstructor = constructorCache.get(cacheKey); - return cachedConstructor.newInstance(clazz.cast(null)); - } else { - Constructor con = clazz.getConstructor(paramClass); - con.setAccessible(true); - constructorCache.put(cacheKey, con); - 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 { - String cacheKey = "FirstConstructor:" + clazz.getName(); - - if (constructorCache.containsKey(cacheKey)) { - Constructor cachedConstructor = constructorCache.get(cacheKey); - return cachedConstructor.newInstance(params); - } else { - Constructor con = clazz.getConstructors()[0]; - con.setAccessible(true); - constructorCache.put(cacheKey, con); - return con.newInstance(params); - } - } catch (IllegalAccessException | InstantiationException | - InvocationTargetException ex) { - ex.printStackTrace(); - return null; - } - } - - @Nullable - public static Object callConstructor(Class clazz, Object... params) { - try { - String cacheKey = "Constructor:" + clazz.getName() + ":" + Arrays.hashCode(params); - - if (constructorCache.containsKey(cacheKey)) { - Constructor cachedConstructor = constructorCache.get(cacheKey); - return cachedConstructor.newInstance(params); - } else { - Constructor con = clazz.getConstructor(toParamTypes(params)); - con.setAccessible(true); - constructorCache.put(cacheKey, con); - return con.newInstance(params); - } - } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | - InvocationTargetException ex) { - ex.printStackTrace(); - return null; - } - } - - @Nullable - public static Object callDeclaredConstructor(Class clazz, Object... params) { - try { - String cacheKey = "DeclaredConstructor:" + clazz.getName() + ":" + Arrays.hashCode(params); - - if (constructorCache.containsKey(cacheKey)) { - Constructor cachedConstructor = constructorCache.get(cacheKey); - return cachedConstructor.newInstance(params); - } else { - Constructor con = clazz.getDeclaredConstructor(toParamTypes(params)); - con.setAccessible(true); - constructorCache.put(cacheKey, con); - 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 { - String cacheKey = "Method:" + clazz.getName() + ":" + method + ":" + Arrays.hashCode(params); - - if (methodCache.containsKey(cacheKey)) { - Method cachedMethod = methodCache.get(cacheKey); - return cachedMethod.invoke(null, params); - } else { - Method m = clazz.getMethod(method, toParamTypes(params)); - m.setAccessible(true); - methodCache.put(cacheKey, m); - 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 { - String cacheKey = "Method:" + obj.getClass().getName() + ":" + method + ":" + Arrays.hashCode(params); - - if (methodCache.containsKey(cacheKey)) { - Method cachedMethod = methodCache.get(cacheKey); - return cachedMethod.invoke(obj, params); - } else { - Method m = obj.getClass().getMethod(method, toParamTypes(params)); - m.setAccessible(true); - methodCache.put(cacheKey, m); - 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 { - String cacheKey = "DeclaredMethod:" + obj.getClass().getName() + ":" + method + ":" + Arrays.hashCode(params); - - if (methodCache.containsKey(cacheKey)) { - Method cachedMethod = methodCache.get(cacheKey); - return cachedMethod.invoke(obj, params); - } else { - Method m = obj.getClass().getDeclaredMethod(method, toParamTypes(params)); - m.setAccessible(true); - methodCache.put(cacheKey, m); - return m.invoke(obj, params); - } - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { - ex.printStackTrace(); - return null; - } - } - - @Nullable - public static Object callDeclaredMethod(Class clazz, Object obj, String method, Object... params) { - try { - String cacheKey = "DeclaredMethod:" + clazz.getName() + ":" + method + ":" + Arrays.hashCode(params); - - if (methodCache.containsKey(cacheKey)) { - Method cachedMethod = methodCache.get(cacheKey); - return cachedMethod.invoke(obj, params); - } else { - Method m = clazz.getDeclaredMethod(method, toParamTypes(params)); - m.setAccessible(true); - methodCache.put(cacheKey, m); - return m.invoke(obj, params); - } - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { - ex.printStackTrace(); - return null; - } - } - - public static boolean hasField(Object packet, String field) { - try { - String cacheKey = "HasField:" + packet.getClass().getName() + ":" + field; - - if (fieldCache.containsKey(cacheKey)) { - return true; - } else { - packet.getClass().getDeclaredField(field); - fieldCache.put(cacheKey, null); - return true; - } - } catch (NoSuchFieldException ex) { - return false; - } - } - - @Nullable - public static Object getField(Object object, String field) { - try { - String cacheKey = "Field:" + object.getClass().getName() + ":" + field; - - if (fieldCache.containsKey(cacheKey)) { - Field cachedField = fieldCache.get(cacheKey); - return cachedField.get(object); - } else { - Field f = object.getClass().getField(field); - f.setAccessible(true); - fieldCache.put(cacheKey, f); - return f.get(object); - } - } catch (NoSuchFieldException | IllegalAccessException ex) { - ex.printStackTrace(); - return null; - } - } - - @Nullable - public static Object getDeclaredField(Class clazz, String field) { - try { - String cacheKey = "DeclaredField:" + clazz.getName() + ":" + field; - - if (fieldCache.containsKey(cacheKey)) { - Field cachedField = fieldCache.get(cacheKey); - return cachedField.get(null); - } else { - Field f = clazz.getDeclaredField(field); - f.setAccessible(true); - fieldCache.put(cacheKey, f); - return f.get(null); - } - } catch (NoSuchFieldException | IllegalAccessException ex) { - ex.printStackTrace(); - return null; - } - } - - @Nullable - public static Object getDeclaredField(Object object, String field) { - try { - String cacheKey = "DeclaredField:" + object.getClass().getName() + ":" + field; - - if (fieldCache.containsKey(cacheKey)) { - Field cachedField = fieldCache.get(cacheKey); - return cachedField.get(object); - } else { - Field f = object.getClass().getDeclaredField(field); - f.setAccessible(true); - fieldCache.put(cacheKey, f); - return f.get(object); - } - } catch (NoSuchFieldException | IllegalAccessException ex) { - ex.printStackTrace(); - return null; - } - } - - @Nullable - public static Object getDeclaredField(Class clazz, Object object, String field) { - try { - String cacheKey = "DeclaredField:" + clazz.getName() + ":" + field; - - if (fieldCache.containsKey(cacheKey)) { - Field cachedField = fieldCache.get(cacheKey); - return cachedField.get(object); - } else { - Field f = clazz.getDeclaredField(field); - f.setAccessible(true); - fieldCache.put(cacheKey, f); - return f.get(object); - } - } catch (NoSuchFieldException | IllegalAccessException ex) { - ex.printStackTrace(); - return null; - } - } - - public static void setDeclaredField(Object object, String field, Object value) { - try { - String cacheKey = "DeclaredField:" + object.getClass().getName() + ":" + field; - - if (fieldCache.containsKey(cacheKey)) { - Field cachedField = fieldCache.get(cacheKey); - cachedField.set(object, value); - } else { - Field f = object.getClass().getDeclaredField(field); - f.setAccessible(true); - fieldCache.put(cacheKey, f); - f.set(object, value); - } - } catch (NoSuchFieldException | IllegalAccessException ex) { - ex.printStackTrace(); - } - } -} \ No newline at end of file diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/XMaterial.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/XMaterial.java deleted file mode 100644 index 4c993e3..0000000 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/XMaterial.java +++ /dev/null @@ -1,2331 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2018 Hex_27 - * Copyright (c) 2024 Crypto Morin - * - * 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 com.google.common.base.Enums; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.SpawnEggMeta; -import org.jetbrains.annotations.ApiStatus; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -/** - * XMaterial - Data Values/Pre-flattening
- * 1.13 and above as priority. - *

- * This class is mainly designed to support {@link ItemStack}. If you want to use it on blocks, you'll have to use - * XBlock - *

- * Pre-flattening: https://minecraft.wiki/w/Java_Edition_data_values/Pre-flattening - * Materials: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html - * Materials (1.12): https://helpch.at/docs/1.12.2/index.html?org/bukkit/Material.html - * Material IDs: https://minecraft-ids.grahamedgecombe.com/ - * Material Source Code: https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/browse/src/main/java/org/bukkit/Material.java - * XMaterial v1: https://www.spigotmc.org/threads/329630/ - *

- * This class will throw an "unsupported material" error if someone tries to use an item with an invalid data value which can only happen in 1.12 servers and below or when the - * utility is missing a new material in that specific version. - * To get an invalid item, (aka Missing Texture Block) you can use the command - * /give @p minecraft:dirt 1 10 where 1 is the item amount, and 10 is the data value. The material {@link #DIRT} with a data value of {@code 10} doesn't exist. - * - * @author Crypto Morin - * @version 12.0.0 - * @see Material - * @see ItemStack - */ -public enum XMaterial /* implements com.cryptomorin.xseries.abstractions.Material*/ { - ACACIA_BOAT("BOAT_ACACIA"), - ACACIA_BUTTON("WOOD_BUTTON"), - ACACIA_CHEST_BOAT, - ACACIA_DOOR("ACACIA_DOOR", "ACACIA_DOOR_ITEM"), - ACACIA_FENCE, - ACACIA_FENCE_GATE, - ACACIA_HANGING_SIGN, - ACACIA_LEAVES(0, "LEAVES_2"), - ACACIA_LOG(0, "LOG_2"), - ACACIA_PLANKS(4, "WOOD"), - ACACIA_PRESSURE_PLATE("WOOD_PLATE"), - ACACIA_SAPLING(4, "SAPLING"), - ACACIA_SIGN("SIGN_POST", "SIGN"), - ACACIA_SLAB(4, "WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), - ACACIA_STAIRS, - ACACIA_TRAPDOOR("TRAP_DOOR"), - ACACIA_WALL_HANGING_SIGN, - ACACIA_WALL_SIGN("WALL_SIGN"), - ACACIA_WOOD(0, "LOG_2"), - ACTIVATOR_RAIL, - /** - * Air - * {@link Material#isAir()} - * - * @see #VOID_AIR - * @see #CAVE_AIR - */ - AIR, - ALLAY_SPAWN_EGG, - ALLIUM(2, "RED_ROSE"), - AMETHYST_BLOCK, - AMETHYST_CLUSTER, - AMETHYST_SHARD, - ANCIENT_DEBRIS, - ANDESITE(5, "STONE"), - ANDESITE_SLAB, - ANDESITE_STAIRS, - ANDESITE_WALL, - ANGLER_POTTERY_SHERD, - ANVIL, - APPLE, - ARCHER_POTTERY_SHERD, - ARMADILLO_SCUTE, - ARMADILLO_SPAWN_EGG, - ARMOR_STAND, - ARMS_UP_POTTERY_SHERD, - ARROW, - ATTACHED_MELON_STEM(7, "MELON_STEM"), - ATTACHED_PUMPKIN_STEM(7, "PUMPKIN_STEM"), - AXOLOTL_BUCKET, - AXOLOTL_SPAWN_EGG, - AZALEA, - AZALEA_LEAVES, - AZURE_BLUET(3, "RED_ROSE"), - BAKED_POTATO, - BAMBOO, - BAMBOO_BLOCK, - BAMBOO_BUTTON, - BAMBOO_CHEST_RAFT, - BAMBOO_DOOR, - BAMBOO_FENCE, - BAMBOO_FENCE_GATE, - BAMBOO_HANGING_SIGN, - BAMBOO_MOSAIC, - BAMBOO_MOSAIC_SLAB, - BAMBOO_MOSAIC_STAIRS, - BAMBOO_PLANKS, - BAMBOO_PRESSURE_PLATE, - BAMBOO_RAFT, - BAMBOO_SAPLING, - BAMBOO_SIGN, - BAMBOO_SLAB, - BAMBOO_STAIRS, - BAMBOO_TRAPDOOR, - BAMBOO_WALL_HANGING_SIGN, - BAMBOO_WALL_SIGN, - BARREL, - BARRIER, - BASALT, - BAT_SPAWN_EGG(65, "MONSTER_EGG"), - BEACON, - BEDROCK, - BEEF("RAW_BEEF"), - BEEHIVE, - /** - * Beetroot is a known material in pre-1.13 - */ - BEETROOT("BEETROOT_BLOCK"), - BEETROOTS("BEETROOT"), - BEETROOT_SEEDS, - BEETROOT_SOUP, - BEE_NEST, - BEE_SPAWN_EGG, - BELL, - BIG_DRIPLEAF, - BIG_DRIPLEAF_STEM, - BIRCH_BOAT("BOAT_BIRCH"), - BIRCH_BUTTON("WOOD_BUTTON"), - BIRCH_CHEST_BOAT, - BIRCH_DOOR("BIRCH_DOOR", "BIRCH_DOOR_ITEM"), - BIRCH_FENCE, - BIRCH_FENCE_GATE, - BIRCH_HANGING_SIGN, - BIRCH_LEAVES(2, "LEAVES"), - BIRCH_LOG(2, "LOG"), - BIRCH_PLANKS(2, "WOOD"), - BIRCH_PRESSURE_PLATE("WOOD_PLATE"), - BIRCH_SAPLING(2, "SAPLING"), - BIRCH_SIGN("SIGN_POST", "SIGN"), - BIRCH_SLAB(2, "WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), - BIRCH_STAIRS("BIRCH_WOOD_STAIRS"), - BIRCH_TRAPDOOR("TRAP_DOOR"), - BIRCH_WALL_HANGING_SIGN, - BIRCH_WALL_SIGN("WALL_SIGN"), - BIRCH_WOOD(2, "LOG"), - BLACKSTONE, - BLACKSTONE_SLAB, - BLACKSTONE_STAIRS, - BLACKSTONE_WALL, - BLACK_BANNER("STANDING_BANNER", "BANNER"), - /** - * Version 1.12+ interprets "BED" as BLACK_BED due to enum alphabetic ordering. - */ - BLACK_BED(supports(12) ? 15 : 0, "BED_BLOCK", "BED"), - BLACK_CANDLE, - BLACK_CANDLE_CAKE, - BLACK_CARPET(15, "CARPET"), - BLACK_CONCRETE(15, "CONCRETE"), - BLACK_CONCRETE_POWDER(15, "CONCRETE_POWDER"), - BLACK_DYE, - BLACK_GLAZED_TERRACOTTA, - BLACK_SHULKER_BOX, - BLACK_STAINED_GLASS(15, "STAINED_GLASS"), - BLACK_STAINED_GLASS_PANE(15, "STAINED_GLASS_PANE"), - BLACK_TERRACOTTA(15, "STAINED_CLAY"), - BLACK_WALL_BANNER("WALL_BANNER"), - BLACK_WOOL(15, "WOOL"), - BLADE_POTTERY_SHERD, - BLAST_FURNACE, - BLAZE_POWDER, - BLAZE_ROD, - BLAZE_SPAWN_EGG(61, "MONSTER_EGG"), - BLUE_BANNER(4, "STANDING_BANNER", "BANNER"), - BLUE_BED(supports(12) ? 11 : 0, "BED_BLOCK", "BED"), - BLUE_CANDLE, - BLUE_CANDLE_CAKE, - BLUE_CARPET(11, "CARPET"), - BLUE_CONCRETE(11, "CONCRETE"), - BLUE_CONCRETE_POWDER(11, "CONCRETE_POWDER"), - BLUE_DYE(4, "INK_SACK", "LAPIS_LAZULI"), - BLUE_GLAZED_TERRACOTTA, - BLUE_ICE, - BLUE_ORCHID(1, "RED_ROSE"), - BLUE_SHULKER_BOX, - BLUE_STAINED_GLASS(11, "STAINED_GLASS"), - BLUE_STAINED_GLASS_PANE(11, "THIN_GLASS", "STAINED_GLASS_PANE"), - BLUE_TERRACOTTA(11, "STAINED_CLAY"), - BLUE_WALL_BANNER(4, "WALL_BANNER"), - BLUE_WOOL(11, "WOOL"), - BOGGED_SPAWN_EGG, - BOLT_ARMOR_TRIM_SMITHING_TEMPLATE, - BONE, - BONE_BLOCK, - BONE_MEAL(15, "INK_SACK"), - BOOK, - BOOKSHELF, - BOW, - BOWL, - BRAIN_CORAL, - BRAIN_CORAL_BLOCK, - BRAIN_CORAL_FAN, - BRAIN_CORAL_WALL_FAN, - BREAD, - BREEZE_ROD, - BREEZE_SPAWN_EGG, - BREWER_POTTERY_SHERD, - BREWING_STAND("BREWING_STAND", "BREWING_STAND_ITEM"), - BRICK("CLAY_BRICK"), - BRICKS("BRICK"), - BRICK_SLAB(4, "STEP"), - BRICK_STAIRS, - BRICK_WALL, - BROWN_BANNER(3, "STANDING_BANNER", "BANNER"), - BROWN_BED(supports(12) ? 12 : 0, "BED_BLOCK", "BED"), - BROWN_CANDLE, - BROWN_CANDLE_CAKE, - BROWN_CARPET(12, "CARPET"), - BROWN_CONCRETE(12, "CONCRETE"), - BROWN_CONCRETE_POWDER(12, "CONCRETE_POWDER"), - BROWN_DYE(3, "INK_SACK", "DYE", "COCOA_BEANS"), - BROWN_GLAZED_TERRACOTTA, - BROWN_MUSHROOM, - BROWN_MUSHROOM_BLOCK("BROWN_MUSHROOM", "HUGE_MUSHROOM_1"), - BROWN_SHULKER_BOX, - BROWN_STAINED_GLASS(12, "STAINED_GLASS"), - BROWN_STAINED_GLASS_PANE(12, "THIN_GLASS", "STAINED_GLASS_PANE"), - BROWN_TERRACOTTA(12, "STAINED_CLAY"), - BROWN_WALL_BANNER(3, "WALL_BANNER"), - BROWN_WOOL(12, "WOOL"), - BRUSH, - BUBBLE_COLUMN, - BUBBLE_CORAL, - BUBBLE_CORAL_BLOCK, - BUBBLE_CORAL_FAN, - BUBBLE_CORAL_WALL_FAN, - BUCKET, - BUDDING_AMETHYST, - BUNDLE, - BURN_POTTERY_SHERD, - CACTUS, - CAKE("CAKE_BLOCK"), - CALCITE, - CALIBRATED_SCULK_SENSOR, - CAMEL_SPAWN_EGG, - CAMPFIRE, - CANDLE, - CANDLE_CAKE, - CARROT("CARROT_ITEM"), - CARROTS("CARROT"), - CARROT_ON_A_STICK("CARROT_STICK"), - CARTOGRAPHY_TABLE, - CARVED_PUMPKIN, - CAT_SPAWN_EGG, - CAULDRON("CAULDRON", "CAULDRON_ITEM"), - /** - * 1.13 tag is not added because it's the same thing as {@link #AIR} - * - * @see #VOID_AIR - */ - CAVE_AIR("AIR"), - CAVE_SPIDER_SPAWN_EGG(59, "MONSTER_EGG"), - CAVE_VINES, - CAVE_VINES_PLANT, - CHAIN, - CHAINMAIL_BOOTS, - CHAINMAIL_CHESTPLATE, - CHAINMAIL_HELMET, - CHAINMAIL_LEGGINGS, - CHAIN_COMMAND_BLOCK("COMMAND", "COMMAND_CHAIN"), - CHARCOAL(1, "COAL"), - CHERRY_BOAT, - CHERRY_BUTTON, - CHERRY_CHEST_BOAT, - CHERRY_DOOR, - CHERRY_FENCE, - CHERRY_FENCE_GATE, - CHERRY_HANGING_SIGN, - CHERRY_LEAVES, - CHERRY_LOG, - CHERRY_PLANKS, - CHERRY_PRESSURE_PLATE, - CHERRY_SAPLING, - CHERRY_SIGN, - CHERRY_SLAB, - CHERRY_STAIRS, - CHERRY_TRAPDOOR, - CHERRY_WALL_HANGING_SIGN, - CHERRY_WALL_SIGN, - CHERRY_WOOD, - CHEST("LOCKED_CHEST"), - CHEST_MINECART("STORAGE_MINECART"), - CHICKEN("RAW_CHICKEN"), - CHICKEN_SPAWN_EGG(93, "MONSTER_EGG"), - CHIPPED_ANVIL(1, "ANVIL"), - CHISELED_BOOKSHELF, - CHISELED_COPPER, - CHISELED_DEEPSLATE, - CHISELED_NETHER_BRICKS(1, "NETHER_BRICKS"), - CHISELED_POLISHED_BLACKSTONE("POLISHED_BLACKSTONE"), - CHISELED_QUARTZ_BLOCK(1, "QUARTZ_BLOCK"), - CHISELED_RED_SANDSTONE(1, "RED_SANDSTONE"), - CHISELED_SANDSTONE(1, "SANDSTONE"), - CHISELED_STONE_BRICKS(3, "SMOOTH_BRICK"), - CHISELED_TUFF, - CHISELED_TUFF_BRICKS, - CHORUS_FLOWER, - CHORUS_FRUIT, - CHORUS_PLANT, - CLAY, - CLAY_BALL, - CLOCK("WATCH"), - COAL, - COAL_BLOCK, - COAL_ORE, - COARSE_DIRT(1, "DIRT"), - COAST_ARMOR_TRIM_SMITHING_TEMPLATE, - COBBLED_DEEPSLATE, - COBBLED_DEEPSLATE_SLAB, - COBBLED_DEEPSLATE_STAIRS, - COBBLED_DEEPSLATE_WALL, - COBBLESTONE, - COBBLESTONE_SLAB(3, "STEP"), - COBBLESTONE_STAIRS, - COBBLESTONE_WALL("COBBLE_WALL"), - COBWEB("WEB"), - COCOA, - COCOA_BEANS(3, "INK_SACK"), - COD("RAW_FISH"), - COD_BUCKET, - COD_SPAWN_EGG, - COMMAND_BLOCK("COMMAND"), - COMMAND_BLOCK_MINECART("COMMAND_MINECART"), - /** - * Unlike redstone torch and redstone lamp... neither REDTONE_COMPARATOR_OFF nor REDSTONE_COMPARATOR_ON - * are items. REDSTONE_COMPARATOR is. - * - * @see #REDSTONE_TORCH - * @see #REDSTONE_LAMP - */ - COMPARATOR("REDSTONE_COMPARATOR_OFF", "REDSTONE_COMPARATOR_ON", "REDSTONE_COMPARATOR"), - COMPASS, - COMPOSTER, - CONDUIT, - COOKED_BEEF, - COOKED_CHICKEN, - COOKED_COD("COOKED_FISH"), - COOKED_MUTTON, - COOKED_PORKCHOP("GRILLED_PORK"), - COOKED_RABBIT, - COOKED_SALMON(1, "COOKED_FISH"), - COOKIE, - COPPER_BLOCK, - COPPER_BULB, - COPPER_DOOR, - COPPER_GRATE, - COPPER_INGOT, - COPPER_ORE, - COPPER_TRAPDOOR, - CORNFLOWER, - COW_SPAWN_EGG(92, "MONSTER_EGG"), - CRACKED_DEEPSLATE_BRICKS, - CRACKED_DEEPSLATE_TILES, - CRACKED_NETHER_BRICKS(2, "NETHER_BRICKS"), - CRACKED_POLISHED_BLACKSTONE_BRICKS("POLISHED_BLACKSTONE_BRICKS"), - CRACKED_STONE_BRICKS(2, "SMOOTH_BRICK"), - CRAFTER, - CRAFTING_TABLE("WORKBENCH"), - CREEPER_BANNER_PATTERN, - CREEPER_HEAD(4, "SKULL", "SKULL_ITEM"), - CREEPER_SPAWN_EGG(50, "MONSTER_EGG"), - CREEPER_WALL_HEAD(4, "SKULL", "SKULL_ITEM"), - CRIMSON_BUTTON, - CRIMSON_DOOR, - CRIMSON_FENCE, - CRIMSON_FENCE_GATE, - CRIMSON_FUNGUS, - CRIMSON_HANGING_SIGN, - CRIMSON_HYPHAE, - CRIMSON_NYLIUM, - CRIMSON_PLANKS, - CRIMSON_PRESSURE_PLATE, - CRIMSON_ROOTS, - CRIMSON_SIGN("SIGN_POST"), - CRIMSON_SLAB, - CRIMSON_STAIRS, - CRIMSON_STEM, - CRIMSON_TRAPDOOR, - CRIMSON_WALL_HANGING_SIGN, - CRIMSON_WALL_SIGN("WALL_SIGN"), - CROSSBOW, - CRYING_OBSIDIAN, - CUT_COPPER, - CUT_COPPER_SLAB, - CUT_COPPER_STAIRS, - CUT_RED_SANDSTONE, - CUT_RED_SANDSTONE_SLAB("STONE_SLAB2"), - CUT_SANDSTONE, - CUT_SANDSTONE_SLAB(1, "STEP"), - CYAN_BANNER(6, "STANDING_BANNER", "BANNER"), - CYAN_BED(supports(12) ? 9 : 0, "BED_BLOCK", "BED"), - CYAN_CANDLE, - CYAN_CANDLE_CAKE, - CYAN_CARPET(9, "CARPET"), - CYAN_CONCRETE(9, "CONCRETE"), - CYAN_CONCRETE_POWDER(9, "CONCRETE_POWDER"), - CYAN_DYE(6, "INK_SACK"), - CYAN_GLAZED_TERRACOTTA, - CYAN_SHULKER_BOX, - CYAN_STAINED_GLASS(9, "STAINED_GLASS"), - CYAN_STAINED_GLASS_PANE(9, "STAINED_GLASS_PANE"), - CYAN_TERRACOTTA(9, "STAINED_CLAY"), - CYAN_WALL_BANNER(6, "WALL_BANNER"), - CYAN_WOOL(9, "WOOL"), - DAMAGED_ANVIL(2, "ANVIL"), - DANDELION("YELLOW_FLOWER"), - DANGER_POTTERY_SHERD, - DARK_OAK_BOAT("BOAT_DARK_OAK"), - DARK_OAK_BUTTON("WOOD_BUTTON"), - DARK_OAK_CHEST_BOAT, - DARK_OAK_DOOR("DARK_OAK_DOOR", "DARK_OAK_DOOR_ITEM"), - DARK_OAK_FENCE, - DARK_OAK_FENCE_GATE, - DARK_OAK_HANGING_SIGN, - DARK_OAK_LEAVES(1, "LEAVES_2"), - DARK_OAK_LOG(1, "LOG_2"), - DARK_OAK_PLANKS(5, "WOOD"), - DARK_OAK_PRESSURE_PLATE("WOOD_PLATE"), - DARK_OAK_SAPLING(5, "SAPLING"), - DARK_OAK_SIGN("SIGN_POST", "SIGN"), - DARK_OAK_SLAB(5, "WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), - DARK_OAK_STAIRS, - DARK_OAK_TRAPDOOR("TRAP_DOOR"), - DARK_OAK_WALL_HANGING_SIGN, - DARK_OAK_WALL_SIGN("WALL_SIGN"), - DARK_OAK_WOOD(1, "LOG_2"), - DARK_PRISMARINE(2, "PRISMARINE"), - DARK_PRISMARINE_SLAB, - DARK_PRISMARINE_STAIRS, - DAYLIGHT_DETECTOR("DAYLIGHT_DETECTOR_INVERTED"), - DEAD_BRAIN_CORAL, - DEAD_BRAIN_CORAL_BLOCK, - DEAD_BRAIN_CORAL_FAN, - DEAD_BRAIN_CORAL_WALL_FAN, - DEAD_BUBBLE_CORAL, - DEAD_BUBBLE_CORAL_BLOCK, - DEAD_BUBBLE_CORAL_FAN, - DEAD_BUBBLE_CORAL_WALL_FAN, - DEAD_BUSH("LONG_GRASS"), - DEAD_FIRE_CORAL, - DEAD_FIRE_CORAL_BLOCK, - DEAD_FIRE_CORAL_FAN, - DEAD_FIRE_CORAL_WALL_FAN, - DEAD_HORN_CORAL, - DEAD_HORN_CORAL_BLOCK, - DEAD_HORN_CORAL_FAN, - DEAD_HORN_CORAL_WALL_FAN, - DEAD_TUBE_CORAL, - DEAD_TUBE_CORAL_BLOCK, - DEAD_TUBE_CORAL_FAN, - DEAD_TUBE_CORAL_WALL_FAN, - DEBUG_STICK, - DECORATED_POT, - DEEPSLATE, - DEEPSLATE_BRICKS, - DEEPSLATE_BRICK_SLAB, - DEEPSLATE_BRICK_STAIRS, - DEEPSLATE_BRICK_WALL, - DEEPSLATE_COAL_ORE, - DEEPSLATE_COPPER_ORE, - DEEPSLATE_DIAMOND_ORE, - DEEPSLATE_EMERALD_ORE, - DEEPSLATE_GOLD_ORE, - DEEPSLATE_IRON_ORE, - DEEPSLATE_LAPIS_ORE, - DEEPSLATE_REDSTONE_ORE, - DEEPSLATE_TILES, - DEEPSLATE_TILE_SLAB, - DEEPSLATE_TILE_STAIRS, - DEEPSLATE_TILE_WALL, - DETECTOR_RAIL, - DIAMOND, - DIAMOND_AXE, - DIAMOND_BLOCK, - DIAMOND_BOOTS, - DIAMOND_CHESTPLATE, - DIAMOND_HELMET, - DIAMOND_HOE, - DIAMOND_HORSE_ARMOR("DIAMOND_BARDING"), - DIAMOND_LEGGINGS, - DIAMOND_ORE, - DIAMOND_PICKAXE, - DIAMOND_SHOVEL("DIAMOND_SPADE"), - DIAMOND_SWORD, - DIORITE(3, "STONE"), - DIORITE_SLAB, - DIORITE_STAIRS, - DIORITE_WALL, - DIRT, - /** - * Changed in 1.17 - */ - DIRT_PATH("GRASS_PATH"), - DISC_FRAGMENT_5, - DISPENSER, - DOLPHIN_SPAWN_EGG, - DONKEY_SPAWN_EGG(32, "MONSTER_EGG"), - DRAGON_BREATH("DRAGONS_BREATH"), - DRAGON_EGG, - DRAGON_HEAD(5, "SKULL", "SKULL_ITEM"), - DRAGON_WALL_HEAD(5, "SKULL", "SKULL_ITEM"), - DRIED_KELP, - DRIED_KELP_BLOCK, - DRIPSTONE_BLOCK, - DROPPER, - DROWNED_SPAWN_EGG, - DUNE_ARMOR_TRIM_SMITHING_TEMPLATE, - ECHO_SHARD, - EGG, - ELDER_GUARDIAN_SPAWN_EGG(4, "MONSTER_EGG"), - ELYTRA, - EMERALD, - EMERALD_BLOCK, - EMERALD_ORE, - ENCHANTED_BOOK, - ENCHANTED_GOLDEN_APPLE(1, "GOLDEN_APPLE"), - ENCHANTING_TABLE("ENCHANTMENT_TABLE"), - ENDERMAN_SPAWN_EGG(58, "MONSTER_EGG"), - ENDERMITE_SPAWN_EGG(67, "MONSTER_EGG"), - ENDER_CHEST, - ENDER_DRAGON_SPAWN_EGG, - ENDER_EYE("EYE_OF_ENDER"), - ENDER_PEARL, - END_CRYSTAL, - END_GATEWAY, - END_PORTAL("ENDER_PORTAL"), - END_PORTAL_FRAME("ENDER_PORTAL_FRAME"), - END_ROD, - END_STONE("ENDER_STONE"), - END_STONE_BRICKS("END_BRICKS"), - END_STONE_BRICK_SLAB, - END_STONE_BRICK_STAIRS, - END_STONE_BRICK_WALL, - EVOKER_SPAWN_EGG(34, "MONSTER_EGG"), - EXPERIENCE_BOTTLE("EXP_BOTTLE"), - EXPLORER_POTTERY_SHERD, - EXPOSED_CHISELED_COPPER, - EXPOSED_COPPER, - EXPOSED_COPPER_BULB, - EXPOSED_COPPER_DOOR, - EXPOSED_COPPER_GRATE, - EXPOSED_COPPER_TRAPDOOR, - EXPOSED_CUT_COPPER, - EXPOSED_CUT_COPPER_SLAB, - EXPOSED_CUT_COPPER_STAIRS, - EYE_ARMOR_TRIM_SMITHING_TEMPLATE, - FARMLAND("SOIL"), - FEATHER, - FERMENTED_SPIDER_EYE, - FERN(2, "LONG_GRASS"), - /** - * For some reason, filled map items are really special. - * Their data value starts from 0 and every time a player - * creates a new map that maps data value increases. - * GitHub Issue - */ - FILLED_MAP("MAP"), - FIRE, - FIREWORK_ROCKET("FIREWORK"), - FIREWORK_STAR("FIREWORK_CHARGE"), - FIRE_CHARGE("FIREBALL"), - FIRE_CORAL, - FIRE_CORAL_BLOCK, - FIRE_CORAL_FAN, - FIRE_CORAL_WALL_FAN, - FISHING_ROD, - FLETCHING_TABLE, - FLINT, - FLINT_AND_STEEL, - FLOWERING_AZALEA, - FLOWERING_AZALEA_LEAVES, - FLOWER_BANNER_PATTERN, - FLOWER_POT("FLOWER_POT", "FLOWER_POT_ITEM"), - FLOW_ARMOR_TRIM_SMITHING_TEMPLATE, - FLOW_BANNER_PATTERN, - FLOW_POTTERY_SHERD, - FOX_SPAWN_EGG, - FRIEND_POTTERY_SHERD, - FROGSPAWN, - FROG_SPAWN_EGG, - /** - * This special material cannot be obtained as an item. - */ - FROSTED_ICE, - FURNACE("BURNING_FURNACE"), - FURNACE_MINECART("POWERED_MINECART"), - GHAST_SPAWN_EGG(56, "MONSTER_EGG"), - GHAST_TEAR, - GILDED_BLACKSTONE, - GLASS, - GLASS_BOTTLE, - GLASS_PANE("THIN_GLASS"), - GLISTERING_MELON_SLICE("SPECKLED_MELON"), - GLOBE_BANNER_PATTERN, - GLOWSTONE, - GLOWSTONE_DUST, - GLOW_BERRIES, - GLOW_INK_SAC, - GLOW_ITEM_FRAME, - GLOW_LICHEN, - GLOW_SQUID_SPAWN_EGG, - GOAT_HORN, - GOAT_SPAWN_EGG, - GOLDEN_APPLE, - GOLDEN_AXE("GOLD_AXE"), - GOLDEN_BOOTS("GOLD_BOOTS"), - GOLDEN_CARROT, - GOLDEN_CHESTPLATE("GOLD_CHESTPLATE"), - GOLDEN_HELMET("GOLD_HELMET"), - GOLDEN_HOE("GOLD_HOE"), - GOLDEN_HORSE_ARMOR("GOLD_BARDING"), - GOLDEN_LEGGINGS("GOLD_LEGGINGS"), - GOLDEN_PICKAXE("GOLD_PICKAXE"), - GOLDEN_SHOVEL("GOLD_SPADE"), - GOLDEN_SWORD("GOLD_SWORD"), - GOLD_BLOCK, - GOLD_INGOT, - GOLD_NUGGET, - GOLD_ORE, - GRANITE(1, "STONE"), - GRANITE_SLAB, - GRANITE_STAIRS, - GRANITE_WALL, - GRASS_BLOCK("GRASS"), - GRAVEL, - GRAY_BANNER(8, "STANDING_BANNER", "BANNER"), - GRAY_BED(supports(12) ? 7 : 0, "BED_BLOCK", "BED"), - GRAY_CANDLE, - GRAY_CANDLE_CAKE, - GRAY_CARPET(7, "CARPET"), - GRAY_CONCRETE(7, "CONCRETE"), - GRAY_CONCRETE_POWDER(7, "CONCRETE_POWDER"), - GRAY_DYE(8, "INK_SACK"), - GRAY_GLAZED_TERRACOTTA, - GRAY_SHULKER_BOX, - GRAY_STAINED_GLASS(7, "STAINED_GLASS"), - GRAY_STAINED_GLASS_PANE(7, "THIN_GLASS", "STAINED_GLASS_PANE"), - GRAY_TERRACOTTA(7, "STAINED_CLAY"), - GRAY_WALL_BANNER(8, "WALL_BANNER"), - GRAY_WOOL(7, "WOOL"), - GREEN_BANNER(2, "STANDING_BANNER", "BANNER"), - GREEN_BED(supports(12) ? 13 : 0, "BED_BLOCK", "BED"), - GREEN_CANDLE, - GREEN_CANDLE_CAKE, - GREEN_CARPET(13, "CARPET"), - GREEN_CONCRETE(13, "CONCRETE"), - GREEN_CONCRETE_POWDER(13, "CONCRETE_POWDER"), - /** - * 1.13 renamed to CACTUS_GREEN - * 1.14 renamed to GREEN_DYE - */ - GREEN_DYE(2, "INK_SACK", "CACTUS_GREEN"), - GREEN_GLAZED_TERRACOTTA, - GREEN_SHULKER_BOX, - GREEN_STAINED_GLASS(13, "STAINED_GLASS"), - GREEN_STAINED_GLASS_PANE(13, "THIN_GLASS", "STAINED_GLASS_PANE"), - GREEN_TERRACOTTA(13, "STAINED_CLAY"), - GREEN_WALL_BANNER(2, "WALL_BANNER"), - GREEN_WOOL(13, "WOOL"), - GRINDSTONE, - GUARDIAN_SPAWN_EGG(68, "MONSTER_EGG"), - GUNPOWDER("SULPHUR"), - GUSTER_BANNER_PATTERN, - GUSTER_POTTERY_SHERD, - HANGING_ROOTS, - HAY_BLOCK, - HEARTBREAK_POTTERY_SHERD, - HEART_OF_THE_SEA, - HEART_POTTERY_SHERD, - HEAVY_CORE, - HEAVY_WEIGHTED_PRESSURE_PLATE("IRON_PLATE"), - HOGLIN_SPAWN_EGG("MONSTER_EGG"), - HONEYCOMB, - HONEYCOMB_BLOCK, - HONEY_BLOCK, - HONEY_BOTTLE, - HOPPER, - HOPPER_MINECART, - HORN_CORAL, - HORN_CORAL_BLOCK, - HORN_CORAL_FAN, - HORN_CORAL_WALL_FAN, - HORSE_SPAWN_EGG(100, "MONSTER_EGG"), - HOST_ARMOR_TRIM_SMITHING_TEMPLATE, - HOWL_POTTERY_SHERD, - HUSK_SPAWN_EGG(23, "MONSTER_EGG"), - ICE, - INFESTED_CHISELED_STONE_BRICKS(5, "MONSTER_EGGS"), - INFESTED_COBBLESTONE(1, "MONSTER_EGGS"), - INFESTED_CRACKED_STONE_BRICKS(4, "MONSTER_EGGS"), - INFESTED_DEEPSLATE, - INFESTED_MOSSY_STONE_BRICKS(3, "MONSTER_EGGS"), - INFESTED_STONE("MONSTER_EGGS"), - INFESTED_STONE_BRICKS(2, "MONSTER_EGGS"), - /** - * We will only add "INK_SAC" for {@link #BLACK_DYE} since it's - * the only material (linked with this material) that is added - * after 1.13, which means it can use both INK_SACK and INK_SAC. - */ - INK_SAC("INK_SACK"), - IRON_AXE, - IRON_BARS("IRON_FENCE"), - IRON_BLOCK, - IRON_BOOTS, - IRON_CHESTPLATE, - IRON_DOOR("IRON_DOOR_BLOCK"), - IRON_GOLEM_SPAWN_EGG, - IRON_HELMET, - IRON_HOE, - IRON_HORSE_ARMOR("IRON_BARDING"), - IRON_INGOT, - IRON_LEGGINGS, - IRON_NUGGET, - IRON_ORE, - IRON_PICKAXE, - IRON_SHOVEL("IRON_SPADE"), - IRON_SWORD, - IRON_TRAPDOOR, - ITEM_FRAME, - JACK_O_LANTERN, - JIGSAW, - JUKEBOX, - JUNGLE_BOAT("BOAT_JUNGLE"), - JUNGLE_BUTTON("WOOD_BUTTON"), - JUNGLE_CHEST_BOAT, - JUNGLE_DOOR("JUNGLE_DOOR", "JUNGLE_DOOR_ITEM"), - JUNGLE_FENCE, - JUNGLE_FENCE_GATE, - JUNGLE_HANGING_SIGN, - JUNGLE_LEAVES(3, "LEAVES"), - JUNGLE_LOG(3, "LOG"), - JUNGLE_PLANKS(3, "WOOD"), - JUNGLE_PRESSURE_PLATE("WOOD_PLATE"), - JUNGLE_SAPLING(3, "SAPLING"), - JUNGLE_SIGN("SIGN_POST", "SIGN"), - JUNGLE_SLAB(3, "WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), - JUNGLE_STAIRS("JUNGLE_WOOD_STAIRS"), - JUNGLE_TRAPDOOR("TRAP_DOOR"), - JUNGLE_WALL_HANGING_SIGN, - JUNGLE_WALL_SIGN("WALL_SIGN"), - JUNGLE_WOOD(3, "LOG"), - KELP, - KELP_PLANT, - KNOWLEDGE_BOOK("BOOK"), - LADDER, - LANTERN, - LAPIS_BLOCK, - LAPIS_LAZULI(4, "INK_SACK"), - LAPIS_ORE, - LARGE_AMETHYST_BUD, - LARGE_FERN(3, "DOUBLE_PLANT"), - LAVA("STATIONARY_LAVA"), - LAVA_BUCKET, - LAVA_CAULDRON, - LEAD("LEASH"), - LEATHER, - LEATHER_BOOTS, - LEATHER_CHESTPLATE, - LEATHER_HELMET, - LEATHER_HORSE_ARMOR("IRON_HORSE_ARMOR"), - LEATHER_LEGGINGS, - LECTERN, - LEVER, - LIGHT, - LIGHTNING_ROD, - LIGHT_BLUE_BANNER(12, "STANDING_BANNER", "BANNER"), - LIGHT_BLUE_BED(supports(12) ? 3 : 0, "BED_BLOCK", "BED"), - LIGHT_BLUE_CANDLE, - LIGHT_BLUE_CANDLE_CAKE, - LIGHT_BLUE_CARPET(3, "CARPET"), - LIGHT_BLUE_CONCRETE(3, "CONCRETE"), - LIGHT_BLUE_CONCRETE_POWDER(3, "CONCRETE_POWDER"), - LIGHT_BLUE_DYE(12, "INK_SACK"), - LIGHT_BLUE_GLAZED_TERRACOTTA, - LIGHT_BLUE_SHULKER_BOX, - LIGHT_BLUE_STAINED_GLASS(3, "STAINED_GLASS"), - LIGHT_BLUE_STAINED_GLASS_PANE(3, "THIN_GLASS", "STAINED_GLASS_PANE"), - LIGHT_BLUE_TERRACOTTA(3, "STAINED_CLAY"), - LIGHT_BLUE_WALL_BANNER(12, "WALL_BANNER", "STANDING_BANNER", "BANNER"), - LIGHT_BLUE_WOOL(3, "WOOL"), - LIGHT_GRAY_BANNER(7, "STANDING_BANNER", "BANNER"), - LIGHT_GRAY_BED(supports(12) ? 8 : 0, "BED_BLOCK", "BED"), - LIGHT_GRAY_CANDLE, - LIGHT_GRAY_CANDLE_CAKE, - LIGHT_GRAY_CARPET(8, "CARPET"), - LIGHT_GRAY_CONCRETE(8, "CONCRETE"), - LIGHT_GRAY_CONCRETE_POWDER(8, "CONCRETE_POWDER"), - LIGHT_GRAY_DYE(7, "INK_SACK"), - /** - * Renamed to SILVER_GLAZED_TERRACOTTA in 1.12 - * Renamed to LIGHT_GRAY_GLAZED_TERRACOTTA in 1.14 - */ - LIGHT_GRAY_GLAZED_TERRACOTTA("SILVER_GLAZED_TERRACOTTA"), - LIGHT_GRAY_SHULKER_BOX("SILVER_SHULKER_BOX"), - LIGHT_GRAY_STAINED_GLASS(8, "STAINED_GLASS"), - LIGHT_GRAY_STAINED_GLASS_PANE(8, "THIN_GLASS", "STAINED_GLASS_PANE"), - LIGHT_GRAY_TERRACOTTA(8, "STAINED_CLAY"), - LIGHT_GRAY_WALL_BANNER(7, "WALL_BANNER"), - LIGHT_GRAY_WOOL(8, "WOOL"), - LIGHT_WEIGHTED_PRESSURE_PLATE("GOLD_PLATE"), - LILAC(1, "DOUBLE_PLANT"), - LILY_OF_THE_VALLEY, - LILY_PAD("WATER_LILY"), - LIME_BANNER(10, "STANDING_BANNER", "BANNER"), - LIME_BED(supports(12) ? 5 : 0, "BED_BLOCK", "BED"), - LIME_CANDLE, - LIME_CANDLE_CAKE, - LIME_CARPET(5, "CARPET"), - LIME_CONCRETE(5, "CONCRETE"), - LIME_CONCRETE_POWDER(5, "CONCRETE_POWDER"), - LIME_DYE(10, "INK_SACK"), - LIME_GLAZED_TERRACOTTA, - LIME_SHULKER_BOX, - LIME_STAINED_GLASS(5, "STAINED_GLASS"), - LIME_STAINED_GLASS_PANE(5, "STAINED_GLASS_PANE"), - LIME_TERRACOTTA(5, "STAINED_CLAY"), - LIME_WALL_BANNER(10, "WALL_BANNER"), - LIME_WOOL(5, "WOOL"), - LINGERING_POTION, - LLAMA_SPAWN_EGG(103, "MONSTER_EGG"), - LODESTONE, - LOOM, - MACE, - MAGENTA_BANNER(13, "STANDING_BANNER", "BANNER"), - MAGENTA_BED(supports(12) ? 2 : 0, "BED_BLOCK", "BED"), - MAGENTA_CANDLE, - MAGENTA_CANDLE_CAKE, - MAGENTA_CARPET(2, "CARPET"), - MAGENTA_CONCRETE(2, "CONCRETE"), - MAGENTA_CONCRETE_POWDER(2, "CONCRETE_POWDER"), - MAGENTA_DYE(13, "INK_SACK"), - MAGENTA_GLAZED_TERRACOTTA, - MAGENTA_SHULKER_BOX, - MAGENTA_STAINED_GLASS(2, "STAINED_GLASS"), - MAGENTA_STAINED_GLASS_PANE(2, "THIN_GLASS", "STAINED_GLASS_PANE"), - MAGENTA_TERRACOTTA(2, "STAINED_CLAY"), - MAGENTA_WALL_BANNER(13, "WALL_BANNER"), - MAGENTA_WOOL(2, "WOOL"), - MAGMA_BLOCK("MAGMA"), - MAGMA_CREAM, - MAGMA_CUBE_SPAWN_EGG(62, "MONSTER_EGG"), - MANGROVE_BOAT, - MANGROVE_BUTTON, - MANGROVE_CHEST_BOAT, - MANGROVE_DOOR, - MANGROVE_FENCE, - MANGROVE_FENCE_GATE, - MANGROVE_HANGING_SIGN, - MANGROVE_LEAVES, - MANGROVE_LOG, - MANGROVE_PLANKS, - MANGROVE_PRESSURE_PLATE, - MANGROVE_PROPAGULE, - MANGROVE_ROOTS, - MANGROVE_SIGN, - MANGROVE_SLAB, - MANGROVE_STAIRS, - MANGROVE_TRAPDOOR, - MANGROVE_WALL_HANGING_SIGN, - MANGROVE_WALL_SIGN, - MANGROVE_WOOD, - /** - * Adding this to the duplicated list will give you a filled map - * for 1.13+ versions and removing it from duplicated list will - * still give you a filled map in -1.12 versions. - * Since higher versions are our priority I'll keep 1.13+ support - * until I can come up with something to fix it. - */ - MAP("EMPTY_MAP"), - MEDIUM_AMETHYST_BUD, - MELON("MELON_BLOCK"), - MELON_SEEDS, - MELON_SLICE("MELON"), - MELON_STEM, - MILK_BUCKET, - MINECART, - MINER_POTTERY_SHERD, - MOJANG_BANNER_PATTERN, - MOOSHROOM_SPAWN_EGG(96, "MONSTER_EGG"), - MOSSY_COBBLESTONE, - MOSSY_COBBLESTONE_SLAB(), - MOSSY_COBBLESTONE_STAIRS, - MOSSY_COBBLESTONE_WALL(1, "COBBLE_WALL", "COBBLESTONE_WALL"), - MOSSY_STONE_BRICKS(1, "SMOOTH_BRICK"), - MOSSY_STONE_BRICK_SLAB, - MOSSY_STONE_BRICK_STAIRS, - MOSSY_STONE_BRICK_WALL, - MOSS_BLOCK, - MOSS_CARPET, - MOURNER_POTTERY_SHERD, - MOVING_PISTON("PISTON_MOVING_PIECE"), - MUD, - MUDDY_MANGROVE_ROOTS, - MUD_BRICKS, - MUD_BRICK_SLAB, - MUD_BRICK_STAIRS, - MUD_BRICK_WALL, - MULE_SPAWN_EGG(32, "MONSTER_EGG"), - MUSHROOM_STEM("BROWN_MUSHROOM"), - MUSHROOM_STEW("MUSHROOM_SOUP"), - MUSIC_DISC_11("RECORD_11"), - MUSIC_DISC_13("GOLD_RECORD"), - MUSIC_DISC_5, - MUSIC_DISC_BLOCKS("RECORD_3"), - MUSIC_DISC_CAT("GREEN_RECORD"), - MUSIC_DISC_CHIRP("RECORD_4"), - MUSIC_DISC_CREATOR, - MUSIC_DISC_CREATOR_MUSIC_BOX, - MUSIC_DISC_FAR("RECORD_5"), - MUSIC_DISC_MALL("RECORD_6"), - MUSIC_DISC_MELLOHI("RECORD_7"), - MUSIC_DISC_OTHERSIDE, - MUSIC_DISC_PIGSTEP, - MUSIC_DISC_PRECIPICE, - MUSIC_DISC_RELIC, - MUSIC_DISC_STAL("RECORD_8"), - MUSIC_DISC_STRAD("RECORD_9"), - MUSIC_DISC_WAIT("RECORD_12"), - MUSIC_DISC_WARD("RECORD_10"), - MUTTON, - MYCELIUM("MYCEL"), - NAME_TAG, - NAUTILUS_SHELL, - NETHERITE_AXE, - NETHERITE_BLOCK, - NETHERITE_BOOTS, - NETHERITE_CHESTPLATE, - NETHERITE_HELMET, - NETHERITE_HOE, - NETHERITE_INGOT, - NETHERITE_LEGGINGS, - NETHERITE_PICKAXE, - NETHERITE_SCRAP, - NETHERITE_SHOVEL, - NETHERITE_SWORD, - NETHERITE_UPGRADE_SMITHING_TEMPLATE, - NETHERRACK, - NETHER_BRICK("NETHER_BRICK_ITEM"), - NETHER_BRICKS("NETHER_BRICK"), - NETHER_BRICK_FENCE("NETHER_FENCE"), - NETHER_BRICK_SLAB(6, "STEP"), - NETHER_BRICK_STAIRS, - NETHER_BRICK_WALL, - NETHER_GOLD_ORE, - NETHER_PORTAL("PORTAL"), - NETHER_QUARTZ_ORE("QUARTZ_ORE"), - NETHER_SPROUTS, - NETHER_STAR, - /** - * Just like mentioned in Nether Wart - * Nether wart is also known as nether stalk in the code. - * NETHER_STALK is the planted state of nether warts. - */ - NETHER_WART("NETHER_WARTS", "NETHER_STALK"), - NETHER_WART_BLOCK, - NOTE_BLOCK, - OAK_BOAT("BOAT"), - OAK_BUTTON("WOOD_BUTTON"), - OAK_CHEST_BOAT, - OAK_DOOR("WOODEN_DOOR", "WOOD_DOOR"), - OAK_FENCE("FENCE"), - OAK_FENCE_GATE("FENCE_GATE"), - OAK_HANGING_SIGN, - OAK_LEAVES("LEAVES"), - OAK_LOG("LOG"), - OAK_PLANKS("WOOD"), - OAK_PRESSURE_PLATE("WOOD_PLATE"), - OAK_SAPLING("SAPLING"), - OAK_SIGN("SIGN_POST", "SIGN"), - OAK_SLAB("WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), - OAK_STAIRS("WOOD_STAIRS"), - OAK_TRAPDOOR("TRAP_DOOR"), - OAK_WALL_HANGING_SIGN, - OAK_WALL_SIGN("WALL_SIGN"), - OAK_WOOD("LOG"), - OBSERVER, - OBSIDIAN, - OCELOT_SPAWN_EGG(98, "MONSTER_EGG"), - OCHRE_FROGLIGHT, - OMINOUS_BOTTLE, - OMINOUS_TRIAL_KEY, - ORANGE_BANNER(14, "STANDING_BANNER", "BANNER"), - ORANGE_BED(supports(12) ? 1 : 0, "BED_BLOCK", "BED"), - ORANGE_CANDLE, - ORANGE_CANDLE_CAKE, - ORANGE_CARPET(1, "CARPET"), - ORANGE_CONCRETE(1, "CONCRETE"), - ORANGE_CONCRETE_POWDER(1, "CONCRETE_POWDER"), - ORANGE_DYE(14, "INK_SACK"), - ORANGE_GLAZED_TERRACOTTA, - ORANGE_SHULKER_BOX, - ORANGE_STAINED_GLASS(1, "STAINED_GLASS"), - ORANGE_STAINED_GLASS_PANE(1, "STAINED_GLASS_PANE"), - ORANGE_TERRACOTTA(1, "STAINED_CLAY"), - ORANGE_TULIP(5, "RED_ROSE"), - ORANGE_WALL_BANNER(14, "WALL_BANNER"), - ORANGE_WOOL(1, "WOOL"), - OXEYE_DAISY(8, "RED_ROSE"), - OXIDIZED_CHISELED_COPPER, - OXIDIZED_COPPER, - OXIDIZED_COPPER_BULB, - OXIDIZED_COPPER_DOOR, - OXIDIZED_COPPER_GRATE, - OXIDIZED_COPPER_TRAPDOOR, - OXIDIZED_CUT_COPPER, - OXIDIZED_CUT_COPPER_SLAB, - OXIDIZED_CUT_COPPER_STAIRS, - PACKED_ICE, - PACKED_MUD, - PAINTING, - PANDA_SPAWN_EGG, - PAPER, - PARROT_SPAWN_EGG(105, "MONSTER_EGG"), - PEARLESCENT_FROGLIGHT, - PEONY(5, "DOUBLE_PLANT"), - PETRIFIED_OAK_SLAB("WOOD_STEP"), - PHANTOM_MEMBRANE, - PHANTOM_SPAWN_EGG, - PIGLIN_BANNER_PATTERN, - PIGLIN_BRUTE_SPAWN_EGG, - PIGLIN_HEAD, - PIGLIN_SPAWN_EGG(57, "MONSTER_EGG"), - PIGLIN_WALL_HEAD, - PIG_SPAWN_EGG(90, "MONSTER_EGG"), - PILLAGER_SPAWN_EGG, - PINK_BANNER(9, "STANDING_BANNER", "BANNER"), - PINK_BED(supports(12) ? 6 : 0, "BED_BLOCK", "BED"), - PINK_CANDLE, - PINK_CANDLE_CAKE, - PINK_CARPET(6, "CARPET"), - PINK_CONCRETE(6, "CONCRETE"), - PINK_CONCRETE_POWDER(6, "CONCRETE_POWDER"), - PINK_DYE(9, "INK_SACK"), - PINK_GLAZED_TERRACOTTA, - PINK_PETALS, - PINK_SHULKER_BOX, - PINK_STAINED_GLASS(6, "STAINED_GLASS"), - PINK_STAINED_GLASS_PANE(6, "THIN_GLASS", "STAINED_GLASS_PANE"), - PINK_TERRACOTTA(6, "STAINED_CLAY"), - PINK_TULIP(7, "RED_ROSE"), - PINK_WALL_BANNER(9, "WALL_BANNER"), - PINK_WOOL(6, "WOOL"), - PISTON("PISTON_BASE"), - PISTON_HEAD("PISTON_EXTENSION"), - PITCHER_CROP, - PITCHER_PLANT, - PITCHER_POD, - PLAYER_HEAD(3, "SKULL", "SKULL_ITEM"), - PLAYER_WALL_HEAD(3, "SKULL", "SKULL_ITEM"), - PLENTY_POTTERY_SHERD, - PODZOL(2, "DIRT"), - POINTED_DRIPSTONE, - POISONOUS_POTATO, - POLAR_BEAR_SPAWN_EGG(102, "MONSTER_EGG"), - POLISHED_ANDESITE(6, "STONE"), - POLISHED_ANDESITE_SLAB, - POLISHED_ANDESITE_STAIRS, - POLISHED_BASALT, - POLISHED_BLACKSTONE, - POLISHED_BLACKSTONE_BRICKS, - POLISHED_BLACKSTONE_BRICK_SLAB, - POLISHED_BLACKSTONE_BRICK_STAIRS, - POLISHED_BLACKSTONE_BRICK_WALL, - POLISHED_BLACKSTONE_BUTTON, - POLISHED_BLACKSTONE_PRESSURE_PLATE, - POLISHED_BLACKSTONE_SLAB, - POLISHED_BLACKSTONE_STAIRS, - POLISHED_BLACKSTONE_WALL, - POLISHED_DEEPSLATE, - POLISHED_DEEPSLATE_SLAB, - POLISHED_DEEPSLATE_STAIRS, - POLISHED_DEEPSLATE_WALL, - POLISHED_DIORITE(4, "STONE"), - POLISHED_DIORITE_SLAB, - POLISHED_DIORITE_STAIRS, - POLISHED_GRANITE(2, "STONE"), - POLISHED_GRANITE_SLAB, - POLISHED_GRANITE_STAIRS, - POLISHED_TUFF, - POLISHED_TUFF_SLAB, - POLISHED_TUFF_STAIRS, - POLISHED_TUFF_WALL, - POPPED_CHORUS_FRUIT("CHORUS_FRUIT_POPPED"), - POPPY("RED_ROSE"), - PORKCHOP("PORK"), - POTATO("POTATO_ITEM"), - POTATOES("POTATO"), - POTION, - POTTED_ACACIA_SAPLING(4, "FLOWER_POT"), - POTTED_ALLIUM(2, "FLOWER_POT"), - POTTED_AZALEA_BUSH, - POTTED_AZURE_BLUET(3, "FLOWER_POT"), - POTTED_BAMBOO, - POTTED_BIRCH_SAPLING(2, "FLOWER_POT"), - POTTED_BLUE_ORCHID(1, "FLOWER_POT"), - POTTED_BROWN_MUSHROOM("FLOWER_POT"), - POTTED_CACTUS("FLOWER_POT"), - POTTED_CHERRY_SAPLING, - POTTED_CORNFLOWER, - POTTED_CRIMSON_FUNGUS, - POTTED_CRIMSON_ROOTS, - POTTED_DANDELION("FLOWER_POT"), - POTTED_DARK_OAK_SAPLING(5, "FLOWER_POT"), - POTTED_DEAD_BUSH("FLOWER_POT"), - POTTED_FERN(2, "FLOWER_POT"), - POTTED_FLOWERING_AZALEA_BUSH, - POTTED_JUNGLE_SAPLING(3, "FLOWER_POT"), - POTTED_LILY_OF_THE_VALLEY, - POTTED_MANGROVE_PROPAGULE, - POTTED_OAK_SAPLING("FLOWER_POT"), - POTTED_ORANGE_TULIP(5, "FLOWER_POT"), - POTTED_OXEYE_DAISY(8, "FLOWER_POT"), - POTTED_PINK_TULIP(7, "FLOWER_POT"), - POTTED_POPPY("FLOWER_POT"), - POTTED_RED_MUSHROOM("FLOWER_POT"), - POTTED_RED_TULIP(4, "FLOWER_POT"), - POTTED_SPRUCE_SAPLING(1, "FLOWER_POT"), - POTTED_TORCHFLOWER, - POTTED_WARPED_FUNGUS, - POTTED_WARPED_ROOTS, - POTTED_WHITE_TULIP(6, "FLOWER_POT"), - POTTED_WITHER_ROSE, - POTTERY_SHARD_ARCHER, - POTTERY_SHARD_ARMS_UP, - POTTERY_SHARD_PRIZE, - POTTERY_SHARD_SKULL, - POWDER_SNOW, - POWDER_SNOW_BUCKET, - POWDER_SNOW_CAULDRON, - POWERED_RAIL, - PRISMARINE, - PRISMARINE_BRICKS(1, "PRISMARINE"), - PRISMARINE_BRICK_SLAB, - PRISMARINE_BRICK_STAIRS, - PRISMARINE_CRYSTALS, - PRISMARINE_SHARD, - PRISMARINE_SLAB, - PRISMARINE_STAIRS, - PRISMARINE_WALL, - PRIZE_POTTERY_SHERD, - PUFFERFISH(3, "RAW_FISH"), - PUFFERFISH_BUCKET, - PUFFERFISH_SPAWN_EGG, - PUMPKIN, - PUMPKIN_PIE, - PUMPKIN_SEEDS, - PUMPKIN_STEM, - PURPLE_BANNER(5, "STANDING_BANNER", "BANNER"), - PURPLE_BED(supports(12) ? 10 : 0, "BED_BLOCK", "BED"), - PURPLE_CANDLE, - PURPLE_CANDLE_CAKE, - PURPLE_CARPET(10, "CARPET"), - PURPLE_CONCRETE(10, "CONCRETE"), - PURPLE_CONCRETE_POWDER(10, "CONCRETE_POWDER"), - PURPLE_DYE(5, "INK_SACK"), - PURPLE_GLAZED_TERRACOTTA, - PURPLE_SHULKER_BOX, - PURPLE_STAINED_GLASS(10, "STAINED_GLASS"), - PURPLE_STAINED_GLASS_PANE(10, "THIN_GLASS", "STAINED_GLASS_PANE"), - PURPLE_TERRACOTTA(10, "STAINED_CLAY"), - PURPLE_WALL_BANNER(5, "WALL_BANNER"), - PURPLE_WOOL(10, "WOOL"), - PURPUR_BLOCK, - PURPUR_PILLAR, - PURPUR_SLAB("PURPUR_DOUBLE_SLAB"), - PURPUR_STAIRS, - QUARTZ, - QUARTZ_BLOCK, - QUARTZ_BRICKS, - QUARTZ_PILLAR(2, "QUARTZ_BLOCK"), - QUARTZ_SLAB(7, "STEP"), - QUARTZ_STAIRS, - RABBIT, - RABBIT_FOOT, - RABBIT_HIDE, - RABBIT_SPAWN_EGG(101, "MONSTER_EGG"), - RABBIT_STEW, - RAIL("RAILS"), - RAISER_ARMOR_TRIM_SMITHING_TEMPLATE, - RAVAGER_SPAWN_EGG, - RAW_COPPER, - RAW_COPPER_BLOCK, - RAW_GOLD, - RAW_GOLD_BLOCK, - RAW_IRON, - RAW_IRON_BLOCK, - RECOVERY_COMPASS, - REDSTONE, - REDSTONE_BLOCK, - /** - * Unlike redstone torch, REDSTONE_LAMP_ON isn't an item. - * The name is just here on the list for matching. - * - * @see #REDSTONE_TORCH - */ - REDSTONE_LAMP("REDSTONE_LAMP_ON", "REDSTONE_LAMP_OFF"), - REDSTONE_ORE("GLOWING_REDSTONE_ORE"), - /** - * REDSTONE_TORCH_OFF isn't an item, but a block. - * But REDSTONE_TORCH_ON is the item. - * The name is just here on the list for matching. - */ - REDSTONE_TORCH("REDSTONE_TORCH_OFF", "REDSTONE_TORCH_ON"), - REDSTONE_WALL_TORCH, - REDSTONE_WIRE, - RED_BANNER(1, "STANDING_BANNER", "BANNER"), - /** - * Data value 14 or 0 - */ - RED_BED(supports(12) ? 14 : 0, "BED_BLOCK", "BED"), - RED_CANDLE, - RED_CANDLE_CAKE, - RED_CARPET(14, "CARPET"), - RED_CONCRETE(14, "CONCRETE"), - RED_CONCRETE_POWDER(14, "CONCRETE_POWDER"), - /** - * 1.13 renamed to ROSE_RED - * 1.14 renamed to RED_DYE - */ - RED_DYE(1, "INK_SACK", "ROSE_RED"), - RED_GLAZED_TERRACOTTA, - RED_MUSHROOM, - RED_MUSHROOM_BLOCK("RED_MUSHROOM", "HUGE_MUSHROOM_2"), - RED_NETHER_BRICKS("RED_NETHER_BRICK"), - RED_NETHER_BRICK_SLAB, - RED_NETHER_BRICK_STAIRS, - RED_NETHER_BRICK_WALL, - RED_SAND(1, "SAND"), - RED_SANDSTONE, - RED_SANDSTONE_SLAB("DOUBLE_STONE_SLAB2", "STONE_SLAB2"), - RED_SANDSTONE_STAIRS, - RED_SANDSTONE_WALL, - RED_SHULKER_BOX, - RED_STAINED_GLASS(14, "STAINED_GLASS"), - RED_STAINED_GLASS_PANE(14, "THIN_GLASS", "STAINED_GLASS_PANE"), - RED_TERRACOTTA(14, "STAINED_CLAY"), - RED_TULIP(4, "RED_ROSE"), - RED_WALL_BANNER(1, "WALL_BANNER"), - RED_WOOL(14, "WOOL"), - REINFORCED_DEEPSLATE, - REPEATER("DIODE_BLOCK_ON", "DIODE_BLOCK_OFF", "DIODE"), - REPEATING_COMMAND_BLOCK("COMMAND", "COMMAND_REPEATING"), - RESPAWN_ANCHOR, - RIB_ARMOR_TRIM_SMITHING_TEMPLATE, - ROOTED_DIRT, - ROSE_BUSH(4, "DOUBLE_PLANT"), - ROTTEN_FLESH, - SADDLE, - SALMON(1, "RAW_FISH"), - SALMON_BUCKET, - SALMON_SPAWN_EGG, - SAND, - SANDSTONE, - SANDSTONE_SLAB(1, "DOUBLE_STEP", "STEP", "STONE_SLAB"), - SANDSTONE_STAIRS, - SANDSTONE_WALL, - SCAFFOLDING, - SCRAPE_POTTERY_SHERD, - SCULK, - SCULK_CATALYST, - SCULK_SENSOR, - SCULK_SHRIEKER, - SCULK_VEIN, - SCUTE, - SEAGRASS, - SEA_LANTERN, - SEA_PICKLE, - SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE, - SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE, - SHEAF_POTTERY_SHERD, - SHEARS, - SHEEP_SPAWN_EGG(91, "MONSTER_EGG"), - SHELTER_POTTERY_SHERD, - SHIELD, - /** - * 1.13.0: LONG_GRASS:1 - * 1.20.4: GRASS -> SHORT_GRASS - */ - SHORT_GRASS(1, "GRASS", "LONG_GRASS"), - SHROOMLIGHT, - SHULKER_BOX("PURPLE_SHULKER_BOX"), - SHULKER_SHELL, - SHULKER_SPAWN_EGG(69, "MONSTER_EGG"), - SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE, - SILVERFISH_SPAWN_EGG(60, "MONSTER_EGG"), - SKELETON_HORSE_SPAWN_EGG(28, "MONSTER_EGG"), - SKELETON_SKULL("SKULL", "SKULL_ITEM"), - SKELETON_SPAWN_EGG(51, "MONSTER_EGG"), - SKELETON_WALL_SKULL("SKULL", "SKULL_ITEM"), - SKULL_BANNER_PATTERN, - SKULL_POTTERY_SHERD, - SLIME_BALL, - SLIME_BLOCK, - SLIME_SPAWN_EGG(55, "MONSTER_EGG"), - SMALL_AMETHYST_BUD, - SMALL_DRIPLEAF, - SMITHING_TABLE, - SMOKER, - SMOOTH_BASALT, - SMOOTH_QUARTZ, - SMOOTH_QUARTZ_SLAB, - SMOOTH_QUARTZ_STAIRS, - SMOOTH_RED_SANDSTONE(2, "RED_SANDSTONE"), - SMOOTH_RED_SANDSTONE_SLAB("STONE_SLAB2"), - SMOOTH_RED_SANDSTONE_STAIRS, - SMOOTH_SANDSTONE(2, "SANDSTONE"), - SMOOTH_SANDSTONE_SLAB, - SMOOTH_SANDSTONE_STAIRS, - SMOOTH_STONE, - SMOOTH_STONE_SLAB, - SNIFFER_EGG, - SNIFFER_SPAWN_EGG, - SNORT_POTTERY_SHERD, - SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE, - SNOW, - SNOWBALL("SNOW_BALL"), - SNOW_BLOCK, - SNOW_GOLEM_SPAWN_EGG, - SOUL_CAMPFIRE, - SOUL_FIRE, - SOUL_LANTERN, - SOUL_SAND, - SOUL_SOIL, - SOUL_TORCH, - SOUL_WALL_TORCH, - SPAWNER("MOB_SPAWNER"), - SPECTRAL_ARROW, - SPIDER_EYE, - SPIDER_SPAWN_EGG(52, "MONSTER_EGG"), - SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE, - SPLASH_POTION("POTION"), - SPONGE, - SPORE_BLOSSOM, - SPRUCE_BOAT("BOAT_SPRUCE"), - SPRUCE_BUTTON("WOOD_BUTTON"), - SPRUCE_CHEST_BOAT, - SPRUCE_DOOR("SPRUCE_DOOR", "SPRUCE_DOOR_ITEM"), - SPRUCE_FENCE, - SPRUCE_FENCE_GATE, - SPRUCE_HANGING_SIGN, - SPRUCE_LEAVES(1, "LEAVES"), - SPRUCE_LOG(1, "LOG"), - SPRUCE_PLANKS(1, "WOOD"), - SPRUCE_PRESSURE_PLATE("WOOD_PLATE"), - SPRUCE_SAPLING(1, "SAPLING"), - SPRUCE_SIGN("SIGN_POST", "SIGN"), - SPRUCE_SLAB(1, "WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), - SPRUCE_STAIRS("SPRUCE_WOOD_STAIRS"), - SPRUCE_TRAPDOOR("TRAP_DOOR"), - SPRUCE_WALL_HANGING_SIGN, - SPRUCE_WALL_SIGN("WALL_SIGN"), - SPRUCE_WOOD(1, "LOG"), - SPYGLASS, - SQUID_SPAWN_EGG(94, "MONSTER_EGG"), - STICK, - STICKY_PISTON("PISTON_BASE", "PISTON_STICKY_BASE"), - STONE, - STONECUTTER, - STONE_AXE, - STONE_BRICKS("SMOOTH_BRICK"), - STONE_BRICK_SLAB(5, "DOUBLE_STEP", "STEP", "STONE_SLAB"), - STONE_BRICK_STAIRS("SMOOTH_STAIRS"), - STONE_BRICK_WALL, - STONE_BUTTON, - STONE_HOE, - STONE_PICKAXE, - STONE_PRESSURE_PLATE("STONE_PLATE"), - STONE_SHOVEL("STONE_SPADE"), - STONE_SLAB("DOUBLE_STEP", "STEP"), - STONE_STAIRS, - STONE_SWORD, - STRAY_SPAWN_EGG(6, "MONSTER_EGG"), - STRIDER_SPAWN_EGG, - STRING, - STRIPPED_ACACIA_LOG, - STRIPPED_ACACIA_WOOD, - STRIPPED_BAMBOO_BLOCK, - STRIPPED_BIRCH_LOG, - STRIPPED_BIRCH_WOOD, - STRIPPED_CHERRY_LOG, - STRIPPED_CHERRY_WOOD, - STRIPPED_CRIMSON_HYPHAE, - STRIPPED_CRIMSON_STEM, - STRIPPED_DARK_OAK_LOG, - STRIPPED_DARK_OAK_WOOD, - STRIPPED_JUNGLE_LOG, - STRIPPED_JUNGLE_WOOD, - STRIPPED_MANGROVE_LOG, - STRIPPED_MANGROVE_WOOD, - STRIPPED_OAK_LOG, - STRIPPED_OAK_WOOD, - STRIPPED_SPRUCE_LOG, - STRIPPED_SPRUCE_WOOD, - STRIPPED_WARPED_HYPHAE, - STRIPPED_WARPED_STEM, - STRUCTURE_BLOCK, - /** - * Originally developers used barrier blocks for its purpose. - * So technically this isn't really considered as a suggested material. - */ - STRUCTURE_VOID(10, "BARRIER"), - SUGAR, - /** - * Sugar Cane is a known material in pre-1.13 - */ - SUGAR_CANE("SUGAR_CANE_BLOCK"), - SUNFLOWER("DOUBLE_PLANT"), - SUSPICIOUS_GRAVEL, - SUSPICIOUS_SAND, - SUSPICIOUS_STEW, - SWEET_BERRIES, - SWEET_BERRY_BUSH, - TADPOLE_BUCKET, - TADPOLE_SPAWN_EGG, - TALL_GRASS(2, "DOUBLE_PLANT"), - TALL_SEAGRASS, - TARGET, - TERRACOTTA("HARD_CLAY"), - TIDE_ARMOR_TRIM_SMITHING_TEMPLATE, - TINTED_GLASS, - TIPPED_ARROW, - TNT, - TNT_MINECART("EXPLOSIVE_MINECART"), - TORCH, - TORCHFLOWER, - TORCHFLOWER_CROP, - TORCHFLOWER_SEEDS, - TOTEM_OF_UNDYING("TOTEM"), - TRADER_LLAMA_SPAWN_EGG, - TRAPPED_CHEST, - TRIAL_KEY, - TRIAL_SPAWNER, - TRIDENT, - TRIPWIRE, - TRIPWIRE_HOOK, - TROPICAL_FISH(2, "RAW_FISH"), - TROPICAL_FISH_BUCKET("BUCKET", "WATER_BUCKET"), - TROPICAL_FISH_SPAWN_EGG("MONSTER_EGG"), - TUBE_CORAL, - TUBE_CORAL_BLOCK, - TUBE_CORAL_FAN, - TUBE_CORAL_WALL_FAN, - TUFF, - TUFF_BRICKS, - TUFF_BRICK_SLAB, - TUFF_BRICK_STAIRS, - TUFF_BRICK_WALL, - TUFF_SLAB, - TUFF_STAIRS, - TUFF_WALL, - TURTLE_EGG, - TURTLE_HELMET, - TURTLE_SCUTE, - TURTLE_SPAWN_EGG, - TWISTING_VINES, - TWISTING_VINES_PLANT, - VAULT, - VERDANT_FROGLIGHT, - VEX_ARMOR_TRIM_SMITHING_TEMPLATE, - VEX_SPAWN_EGG(35, "MONSTER_EGG"), - VILLAGER_SPAWN_EGG(120, "MONSTER_EGG"), - VINDICATOR_SPAWN_EGG(36, "MONSTER_EGG"), - VINE, - /** - * 1.13 tag is not added because it's the same thing as {@link #AIR} - * - * @see #CAVE_AIR - */ - VOID_AIR("AIR"), - WALL_TORCH("TORCH"), - WANDERING_TRADER_SPAWN_EGG, - WARDEN_SPAWN_EGG, - WARD_ARMOR_TRIM_SMITHING_TEMPLATE, - WARPED_BUTTON, - WARPED_DOOR, - WARPED_FENCE, - WARPED_FENCE_GATE, - WARPED_FUNGUS, - WARPED_FUNGUS_ON_A_STICK, - WARPED_HANGING_SIGN, - WARPED_HYPHAE, - WARPED_NYLIUM, - WARPED_PLANKS, - WARPED_PRESSURE_PLATE, - WARPED_ROOTS, - WARPED_SIGN("SIGN_POST"), - WARPED_SLAB, - WARPED_STAIRS, - WARPED_STEM, - WARPED_TRAPDOOR, - WARPED_WALL_HANGING_SIGN, - WARPED_WALL_SIGN("WALL_SIGN"), - WARPED_WART_BLOCK, - /** - * This is used for blocks only. - * In 1.13- WATER will turn into STATIONARY_WATER after it finished spreading. - * After 1.13+ this uses - * Levelled water flowing system. - */ - WATER("STATIONARY_WATER"), - WATER_BUCKET, - WATER_CAULDRON, - WAXED_CHISELED_COPPER, - WAXED_COPPER_BLOCK, - WAXED_COPPER_BULB, - WAXED_COPPER_DOOR, - WAXED_COPPER_GRATE, - WAXED_COPPER_TRAPDOOR, - WAXED_CUT_COPPER, - WAXED_CUT_COPPER_SLAB, - WAXED_CUT_COPPER_STAIRS, - WAXED_EXPOSED_CHISELED_COPPER, - WAXED_EXPOSED_COPPER, - WAXED_EXPOSED_COPPER_BULB, - WAXED_EXPOSED_COPPER_DOOR, - WAXED_EXPOSED_COPPER_GRATE, - WAXED_EXPOSED_COPPER_TRAPDOOR, - WAXED_EXPOSED_CUT_COPPER, - WAXED_EXPOSED_CUT_COPPER_SLAB, - WAXED_EXPOSED_CUT_COPPER_STAIRS, - WAXED_OXIDIZED_CHISELED_COPPER, - WAXED_OXIDIZED_COPPER, - WAXED_OXIDIZED_COPPER_BULB, - WAXED_OXIDIZED_COPPER_DOOR, - WAXED_OXIDIZED_COPPER_GRATE, - WAXED_OXIDIZED_COPPER_TRAPDOOR, - WAXED_OXIDIZED_CUT_COPPER, - WAXED_OXIDIZED_CUT_COPPER_SLAB, - WAXED_OXIDIZED_CUT_COPPER_STAIRS, - WAXED_WEATHERED_CHISELED_COPPER, - WAXED_WEATHERED_COPPER, - WAXED_WEATHERED_COPPER_BULB, - WAXED_WEATHERED_COPPER_DOOR, - WAXED_WEATHERED_COPPER_GRATE, - WAXED_WEATHERED_COPPER_TRAPDOOR, - WAXED_WEATHERED_CUT_COPPER, - WAXED_WEATHERED_CUT_COPPER_SLAB, - WAXED_WEATHERED_CUT_COPPER_STAIRS, - WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE, - WEATHERED_CHISELED_COPPER, - WEATHERED_COPPER, - WEATHERED_COPPER_BULB, - WEATHERED_COPPER_DOOR, - WEATHERED_COPPER_GRATE, - WEATHERED_COPPER_TRAPDOOR, - WEATHERED_CUT_COPPER, - WEATHERED_CUT_COPPER_SLAB, - WEATHERED_CUT_COPPER_STAIRS, - WEEPING_VINES, - WEEPING_VINES_PLANT, - WET_SPONGE(1, "SPONGE"), - /** - * Wheat is a known material in pre-1.13 - */ - WHEAT("CROPS"), - WHEAT_SEEDS("SEEDS"), - WHITE_BANNER(15, "STANDING_BANNER", "BANNER"), - WHITE_BED("BED_BLOCK", "BED"), - WHITE_CANDLE, - WHITE_CANDLE_CAKE, - WHITE_CARPET("CARPET"), - WHITE_CONCRETE("CONCRETE"), - WHITE_CONCRETE_POWDER("CONCRETE_POWDER"), - WHITE_DYE(15, "INK_SACK", "BONE_MEAL"), - WHITE_GLAZED_TERRACOTTA, - WHITE_SHULKER_BOX, - WHITE_STAINED_GLASS("STAINED_GLASS"), - WHITE_STAINED_GLASS_PANE("THIN_GLASS", "STAINED_GLASS_PANE"), - WHITE_TERRACOTTA("STAINED_CLAY"), - WHITE_TULIP(6, "RED_ROSE"), - WHITE_WALL_BANNER(15, "WALL_BANNER"), - WHITE_WOOL("WOOL"), - WILD_ARMOR_TRIM_SMITHING_TEMPLATE, - WIND_CHARGE, - WITCH_SPAWN_EGG(66, "MONSTER_EGG"), - WITHER_ROSE, - WITHER_SKELETON_SKULL(1, "SKULL", "SKULL_ITEM"), - WITHER_SKELETON_SPAWN_EGG(5, "MONSTER_EGG"), - WITHER_SKELETON_WALL_SKULL(1, "SKULL", "SKULL_ITEM"), - WITHER_SPAWN_EGG, - WOLF_ARMOR, - WOLF_SPAWN_EGG(95, "MONSTER_EGG"), - WOODEN_AXE("WOOD_AXE"), - WOODEN_HOE("WOOD_HOE"), - WOODEN_PICKAXE("WOOD_PICKAXE"), - WOODEN_SHOVEL("WOOD_SPADE"), - WOODEN_SWORD("WOOD_SWORD"), - WRITABLE_BOOK("BOOK_AND_QUILL"), - WRITTEN_BOOK, - YELLOW_BANNER(11, "STANDING_BANNER", "BANNER"), - YELLOW_BED(supports(12) ? 4 : 0, "BED_BLOCK", "BED"), - YELLOW_CANDLE, - YELLOW_CANDLE_CAKE, - YELLOW_CARPET(4, "CARPET"), - YELLOW_CONCRETE(4, "CONCRETE"), - YELLOW_CONCRETE_POWDER(4, "CONCRETE_POWDER"), - /** - * 1.13 renamed to DANDELION_YELLOW - * 1.14 renamed to YELLOW_DYE - */ - YELLOW_DYE(11, "INK_SACK", "DANDELION_YELLOW"), - YELLOW_GLAZED_TERRACOTTA, - YELLOW_SHULKER_BOX, - YELLOW_STAINED_GLASS(4, "STAINED_GLASS"), - YELLOW_STAINED_GLASS_PANE(4, "THIN_GLASS", "STAINED_GLASS_PANE"), - YELLOW_TERRACOTTA(4, "STAINED_CLAY"), - YELLOW_WALL_BANNER(11, "WALL_BANNER"), - YELLOW_WOOL(4, "WOOL"), - ZOGLIN_SPAWN_EGG, - ZOMBIE_HEAD(2, "SKULL", "SKULL_ITEM"), - ZOMBIE_HORSE_SPAWN_EGG(29, "MONSTER_EGG"), - ZOMBIE_SPAWN_EGG(54, "MONSTER_EGG"), - ZOMBIE_VILLAGER_SPAWN_EGG(27, "MONSTER_EGG"), - ZOMBIE_WALL_HEAD(2, "SKULL", "SKULL_ITEM"), - ZOMBIFIED_PIGLIN_SPAWN_EGG(57, "MONSTER_EGG", "ZOMBIE_PIGMAN_SPAWN_EGG"); - - - /** - * Cached array of {@link XMaterial#values()} to avoid allocating memory for - * calling the method every time. - * - * @since 2.0.0 - */ - public static final XMaterial[] VALUES = values(); - - /** - * We don't want to use {@link Enums#getIfPresent(Class, String)} to avoid a few checks. - * - * @since 5.1.0 - */ - private static final Map NAMES = new HashMap<>(); - - /** - * Guava (Google Core Libraries for Java)'s cache for performance and timed caches. - * For strings that match a certain XMaterial. Mostly cached for configs. - * - * @since 1.0.0 - */ - private static final Cache NAME_CACHE = CacheBuilder.newBuilder() - .expireAfterAccess(1, TimeUnit.HOURS) - .build(); - /** - * The maximum data value in the pre-flattening update which belongs to {@link #VILLAGER_SPAWN_EGG}
- * Spawn Eggs - * - * @see #matchXMaterialWithData(String) - * @since 8.0.0 - */ - private static final byte MAX_DATA_VALUE = 120; - /** - * Used to tell the system that the passed object's (name or material) data value - * is not provided or is invalid. - * - * @since 8.0.0 - */ - private static final byte UNKNOWN_DATA_VALUE = -1; - /** - * The maximum material ID before the pre-flattening update which belongs to {@link #MUSIC_DISC_WAIT} - * - * @since 8.1.0 - */ - private static final short MAX_ID = 2267; - /** - * XMaterial Paradox (Duplication Check) - *

- * A set of duplicated material names in 1.13 and 1.12 that will conflict with the legacy names. - * Values are the new material names. This map also contains illegal elements. Check the static initializer for more info. - *

- * Duplications are not useful at all in versions above the flattening update {@link Data#ISFLAT} - * This set is only used for matching materials, for parsing refer to {@link #isDuplicated()} - * - * @since 3.0.0 - */ - private static final Set DUPLICATED; - - static { - for (XMaterial material : VALUES) NAMES.put(material.name(), material); - } - - static { - if (Data.ISFLAT) { - // It's not needed at all if it's the newer version. We can save some memory. - DUPLICATED = null; - } else { - // MELON_SLICE, CARROTS, POTATOES, BEETROOTS, GRASS_BLOCK, BRICKS, NETHER_BRICKS, BROWN_MUSHROOM - // Using the constructor to add elements will decide to allocate more size which we don't need. - DUPLICATED = new HashSet<>(4); - DUPLICATED.add("GRASS"); - DUPLICATED.add(MELON.name()); - DUPLICATED.add(BRICK.name()); - DUPLICATED.add(NETHER_BRICK.name()); - } - } - - /** - * The data value of this material Pre-flattening - * It's never a negative number. - * - * @see #getData() - */ - private final byte data; - /** - * A list of material names that was being used for older verions. - * - * @see #getLegacy() - */ - @Nonnull - private final String[] legacy; - /** - * The cached Bukkit parsed material. - * - * @see #parseMaterial() - * @since 9.0.0 - */ - @Nullable - private final Material material; - - XMaterial(int data, @Nonnull String... legacy) { - this.data = (byte) data; - this.legacy = legacy; - - Material mat = null; - if ((!Data.ISFLAT && this.isDuplicated()) || (mat = Material.getMaterial(this.name())) == null) { - for (int i = legacy.length - 1; i >= 0; i--) { - mat = Material.getMaterial(legacy[i]); - if (mat != null) break; - } - } - this.material = mat; - } - - XMaterial(String... legacy) { - this(0, legacy); - } - - /** - * Gets the XMaterial with this name similar to {@link #valueOf(String)} - * without throwing an exception. - * - * @param name the name of the material. - * @return an optional that can be empty. - * @since 5.1.0 - */ - @Nonnull - private static Optional getIfPresent(@Nonnull String name) { - return Optional.ofNullable(NAMES.get(name)); - } - - /** - * The current version of the server. - * - * @return the current server version minor number. - * @see #supports(int) - * @since 2.0.0 - */ - public static int getVersion() { - return Data.VERSION; - } - - /** - * When using 1.13+, this helps to find the old material name - * with its data value using a cached search for optimization. - * - * @see #matchDefinedXMaterial(String, byte) - * @since 1.0.0 - */ - @Nullable - private static XMaterial requestOldXMaterial(@Nonnull String name, byte data) { - String holder = name + data; - XMaterial cache = NAME_CACHE.getIfPresent(holder); - if (cache != null) return cache; - - for (XMaterial material : VALUES) { - // Not using material.name().equals(name) check is intended. - if ((data == UNKNOWN_DATA_VALUE || data == material.data) && material.anyMatchLegacy(name)) { - NAME_CACHE.put(holder, material); - return material; - } - } - - return null; - } - - /** - * Parses material name and data value from the specified string. - * The separator for the material name and its data value is {@code :} - * Spaces are allowed. Mostly used when getting materials from config for old school minecrafters. - *

- * Examples - *

-     *     {@code INK_SACK:1 -> RED_DYE}
-     *     {@code WOOL: 14  -> RED_WOOL}
-     * 
- * - * @param name the material string that consists of the material name, data and separator character. - * @return the parsed XMaterial. - * @see #matchXMaterial(String) - * @since 3.0.0 - */ - @Nonnull - private static Optional matchXMaterialWithData(@Nonnull String name) { - int index = name.indexOf(':'); - if (index != -1) { - String mat = format(name.substring(0, index)); - try { - // We don't use Byte.parseByte because we have our own range check. - byte data = (byte) Integer.parseInt(name.substring(index + 1).replace(" ", "")); - return data >= 0 && data < MAX_DATA_VALUE ? matchDefinedXMaterial(mat, data) : matchDefinedXMaterial(mat, UNKNOWN_DATA_VALUE); - } catch (NumberFormatException ignored) { - return matchDefinedXMaterial(mat, UNKNOWN_DATA_VALUE); - } - } - - return Optional.empty(); - } - - /** - * Parses the given material name as an XMaterial with a given data - * value in the string if attached. Check {@link #matchXMaterialWithData(String)} for more info. - * - * @see #matchXMaterialWithData(String) - * @see #matchDefinedXMaterial(String, byte) - * @since 2.0.0 - */ - @Nonnull - public static Optional matchXMaterial(@Nonnull String name) { - if (name == null || name.isEmpty()) - throw new IllegalArgumentException("Cannot match a material with null or empty material name"); - Optional oldMatch = matchXMaterialWithData(name); - return oldMatch.isPresent() ? oldMatch : matchDefinedXMaterial(format(name), UNKNOWN_DATA_VALUE); - } - - /** - * Parses the given material as an XMaterial. - * - * @throws IllegalArgumentException may be thrown as an unexpected exception. - * @see #matchDefinedXMaterial(String, byte) - * @see #matchXMaterial(ItemStack) - * @since 2.0.0 - */ - @Nonnull - public static XMaterial matchXMaterial(@Nonnull Material material) { - Objects.requireNonNull(material, "Cannot match null material"); - return matchDefinedXMaterial(material.name(), UNKNOWN_DATA_VALUE) - .orElseThrow(() -> new IllegalArgumentException("Unsupported material with no data value: " + material.name())); - } - - /** - * Parses the given item as an XMaterial using its material and data value (durability) - * if not a damageable item {@link ItemStack#getDurability()}. - * - * @param item the ItemStack to match. - * @return an XMaterial if matched any. - * @throws IllegalArgumentException may be thrown as an unexpected exception. - * @see #matchXMaterial(Material) - * @since 2.0.0 - */ - @Nonnull - @SuppressWarnings("deprecation") - public static XMaterial matchXMaterial(@Nonnull ItemStack item) { - Objects.requireNonNull(item, "Cannot match null ItemStack"); - String material = item.getType().name(); - - // 1.13+ doesn't use data values at all. - // Maps are given different data values for different parts of the map also some plugins use negative values for custom images. - // Items that have durability, such as armor and tools don't use the data value to distinguish their material. - byte data = (byte) (Data.ISFLAT || material.equals("MAP") || item.getType().getMaxDurability() > 0 ? 0 : item.getDurability()); - - // Versions 1.9-1.12 didn't really use the items data value. - if (supports(9) && !supports(13) && item.hasItemMeta() && material.equals("MONSTER_EGG")) { - ItemMeta meta = item.getItemMeta(); - if (meta instanceof SpawnEggMeta) { - SpawnEggMeta egg = (SpawnEggMeta) meta; - material = egg.getSpawnedType().name() + "_SPAWN_EGG"; - } - } - - // Potions used the items data value to store - // information about the type of potion in 1.8 - if (!supports(9) && material.equals("POTION")) { - // Source: v1.8.8 org.bukkit.potion.Potion.fromDamage(int damage) - int damage = item.getDurability(); - return ((damage & 16384) > 0) ? SPLASH_POTION : POTION; - } - - // Refer to the enum for info. - // Currently, these are the only materials with a non-zero data value - // that has been renamed after the flattening update. - // If this happens to more materials in the future, I might have to change the system. - if (supports(13) && !supports(14)) { - // https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/diff/src/main/java/org/bukkit/Material.java?until=67d908a9830c71267ee740f5bddd728ce9c64cc7 - switch (material) { - case "CACTUS_GREEN": - return GREEN_DYE; - case "ROSE_RED": - return RED_DYE; - case "DANDELION_YELLOW": - return YELLOW_DYE; - } - } - - // Check FILLED_MAP enum for more info. - // if (!Data.ISFLAT && item.hasItemMeta() && item.getItemMeta() instanceof org.bukkit.inventory.meta.MapMeta) return FILLED_MAP; - - // No orElseThrow, I don't want to deal with Java's final variable bullshit. - Optional result = matchDefinedXMaterial(material, data); - if (result.isPresent()) return result.get(); - throw new IllegalArgumentException("Unsupported material from item: " + material + " (" + data + ')'); - } - - /** - * The main method that parses the given material name and data value as an XMaterial. - * All the values passed to this method will not be null or empty and are formatted correctly. - * - * @param name the formatted name of the material. - * @param data the data value of the material. Is always 0 or {@link #UNKNOWN_DATA_VALUE} when {@link Data#ISFLAT} - * @return an XMaterial (with the same data value if specified) - * @see #matchXMaterial(Material) - * @see #matchXMaterial(ItemStack) - * @since 3.0.0 - */ - @SuppressWarnings({"DanglingJavadoc", "JavadocBlankLines"}) - @Nonnull - protected static Optional matchDefinedXMaterial(@Nonnull String name, byte data) { - // if (!Boolean.valueOf(Boolean.getBoolean(Boolean.TRUE.toString())).equals(Boolean.FALSE.booleanValue())) return null; - Boolean duplicated = null; - boolean isAMap = name.equalsIgnoreCase("MAP"); - - // Do basic number and boolean checks before accessing more complex enum stuff. - if (Data.ISFLAT || (!isAMap && data <= 0 && !(duplicated = isDuplicated(name)))) { - Optional xMaterial = getIfPresent(name); - if (xMaterial.isPresent()) return xMaterial; - } - // Usually flat versions wouldn't pass this point, but some special materials do. - - XMaterial oldXMaterial = requestOldXMaterial(name, data); - if (oldXMaterial == null) { - // Special case. Refer to FILLED_MAP for more info. - return (data >= 0 && isAMap) ? Optional.of(FILLED_MAP) : Optional.empty(); - } - - /** - * XMaterial Paradox (Duplication Check) - * I've concluded that this is just an infinite loop that keeps - * going around the Singular Form and the Plural Form materials. A waste of brain cells and a waste of time. - * This solution works just fine anyway. - * - * A solution for XMaterial Paradox. - * Manually parses the duplicated materials to find the exact material based on the server version. - * If the name ends with "S" -> Plural Form Material. - * Plural methods are only plural if they're also {@link #DUPLICATED} - * - * The only special exceptions are {@link #BRICKS} (??) and {@link #NETHER_BRICKS} - * Note: BRICKS was added because - * {@code XMaterial.matchXMaterial("BRICK")} would match {@link #BRICKS} instead in 1.8. - */ - boolean isPlural = oldXMaterial == CARROTS || oldXMaterial == POTATOES || oldXMaterial == BRICKS; - - if (!Data.ISFLAT && isPlural && (duplicated == null ? isDuplicated(name) : duplicated)) - return getIfPresent(name); - return Optional.of(oldXMaterial); - } - - /** - * Attempts to build the string like an enum name. - * Removes all the spaces, and extra non-English characters. Also removes some config/in-game based strings. - * While this method is hard to maintain, it's extremely efficient. It's approximately more than x5 times faster than - * the normal RegEx + String Methods approach for both formatted and unformatted material names. - * - * @param name the material name to modify. - * @return an enum name. - * @since 2.0.0 - */ - @Nonnull - protected static String format(@Nonnull String name) { - int len = name.length(); - char[] chs = new char[len]; - int count = 0; - boolean appendUnderline = false; - - for (int i = 0; i < len; i++) { - char ch = name.charAt(i); - - if (!appendUnderline && count != 0 && (ch == '-' || ch == ' ' || ch == '_') && chs[count] != '_') - appendUnderline = true; - else { - boolean number = false; - // Old materials have numbers in them. - if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (number = (ch >= '0' && ch <= '9'))) { - if (appendUnderline) { - chs[count++] = '_'; - appendUnderline = false; - } - - if (number) chs[count++] = ch; - else chs[count++] = (char) (ch & 0x5f); - } - } - } - - return new String(chs, 0, count); - } - - /** - * This is an internal API. Use {@link com.cryptomorin.xseries.reflection.XReflection#supports(int)} instead. - * Checks if the specified version is the same version or higher than the current server version. - * - * @param version the major version to be checked. "1." is ignored. E.g. 1.12 = 12 | 1.9 = 9 - * @return true of the version is equal or higher than the current version. - * @since 2.0.0 - */ - @ApiStatus.Internal - public static boolean supports(int version) { - return Data.VERSION >= version; - } - - public String[] getLegacy() { - return this.legacy; - } - - /** - * Sets the {@link Material} (and data value on older versions) of an item. - * Damageable materials will not have their durability changed. - *

- * Use {@link #parseItem()} instead when creating new ItemStacks. - * - * @param item the item to change its type. - * @see #parseItem() - * @since 3.0.0 - */ - @Nonnull - @SuppressWarnings("deprecation") - public ItemStack setType(@Nonnull ItemStack item) { - Objects.requireNonNull(item, "Cannot set material for null ItemStack"); - Material material = this.parseMaterial(); - Objects.requireNonNull(material, () -> "Unsupported material: " + this.name()); - - item.setType(material); - if (!Data.ISFLAT && material.getMaxDurability() <= 0) item.setDurability(this.data); - // Splash Potions weren't an official material pre-flattening. - if (!Data.ISFLAT && this == SPLASH_POTION) { - item.setDurability((short) 16384); // Hard-coded as 'data' is only a byte. - } - return item; - } - - /** - * Checks if the given material name matches any of this xmaterial's legacy material names. - * All the values passed to this method will not be null or empty and are formatted correctly. - * - * @param name the material name to check. - * @return true if it's one of the legacy names, otherwise false. - * @since 2.0.0 - */ - private boolean anyMatchLegacy(@Nonnull String name) { - for (int i = this.legacy.length - 1; i >= 0; i--) { - if (name.equals(this.legacy[i])) return true; - } - return false; - } - - /** - * Parses an enum name to a user-friendly name. - * These names will have underlines removed and with each word capitalized. - *

- * Examples: - *

-     *     {@literal EMERALD                 -> Emerald}
-     *     {@literal EMERALD_BLOCK           -> Emerald Block}
-     *     {@literal ENCHANTED_GOLDEN_APPLE  -> Enchanted Golden Apple}
-     * 
- * - * @return a more user-friendly enum name. - * @since 3.0.0 - */ - @Override - @Nonnull - public String toString() { - return Arrays.stream(name().split("_")) - .map(t -> t.charAt(0) + t.substring(1).toLowerCase()) - .collect(Collectors.joining(" ")); - } - - /** - * Gets the ID (Magic value) of the material. - * ID List - *

- * Spigot added material ID support back in 1.16+ - * - * @return the ID of the material or -1 if it's not a legacy material or the server doesn't support the material. - * @since 2.2.0 - */ - @SuppressWarnings("deprecation") - public int getId() { - // https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/diff/src/main/java/org/bukkit/Material.java?until=1cb03826ebde4ef887519ce37b0a2a341494a183 - // Should start working again in 1.16+ - Material material = this.parseMaterial(); - if (material == null) return -1; - try { - return material.getId(); - } catch (IllegalArgumentException ignored) { - return -1; - } - } - - /** - * The data value of this material pre-flattening. - *

- * Can be accessed with {@link ItemStack#getData()} then {@code MaterialData#getData()} - * or {@link ItemStack#getDurability()} if not damageable. - * - * @return data of this material, or 0 if none. - * @since 1.0.0 - */ - @SuppressWarnings("deprecation") - public byte getData() { - return data; - } - - /** - * Parses an item from this XMaterial. - * Uses data values on older versions. - * - * @return an ItemStack with the same material (and data value if in older versions.) - * @see #setType(ItemStack) - * @since 2.0.0 - */ - @Nullable - @SuppressWarnings("deprecation") - public ItemStack parseItem() { - Material material = this.parseMaterial(); - if (material == null) return null; - ItemStack base = Data.ISFLAT ? new ItemStack(material) : new ItemStack(material, 1, this.data); - // Splash Potions weren't an official material pre-flattening. - if (!Data.ISFLAT && this == SPLASH_POTION) { - base.setDurability((short) 16384); // Hard-coded as 'data' is only a byte. - } - return base; - } - - /** - * Parses the material of this XMaterial. - * - * @return the material related to this XMaterial based on the server version. - * @since 1.0.0 - */ - @Nullable - public Material parseMaterial() { - return this.material; - } - - /** - * Checks if an item has the same material (and data value on older versions). - * - * @param item item to check. - * @return true if the material is the same as the item's material (and data value if on older versions), otherwise false. - * @since 1.0.0 - */ - @SuppressWarnings("deprecation") - public boolean isSimilar(@Nonnull ItemStack item) { - Objects.requireNonNull(item, "Cannot compare with null ItemStack"); - if (item.getType() != this.parseMaterial()) return false; - // Special case for splash potions. - if (this == SPLASH_POTION) { - return Data.ISFLAT || item.getDurability() == (short) 16384; - } - return Data.ISFLAT || item.getDurability() == this.data || item.getType().getMaxDurability() > 0; - } - - /** - * Checks if this material is supported in the current version. - * Suggested materials will be ignored. - *

- * Note that you should use {@link #parseMaterial()} or {@link #parseItem()} and check if it's null - * if you're going to parse and use the material/item later. - * - * @return true if the material exists in {@link Material} list. - * @since 2.0.0 - */ - public boolean isSupported() { - return this.material != null; - } - - /** - * Checks if this material is supported in the current version and - * returns itself if yes. - *

- * In the other case, the alternate material will get returned, - * no matter if it is supported or not. - * - * @param alternateMaterial the material to get if this one is not supported. - * @return this material or the {@code alternateMaterial} if not supported. - */ - @Nullable - public XMaterial or(@Nullable XMaterial alternateMaterial) { - return isSupported() ? this : alternateMaterial; - } - - /** - * XMaterial Paradox (Duplication Check) - * Checks if the material has any duplicates. - *

- * Example: - *

{@code MELON, CARROT, POTATO, BEETROOT -> true} - * - * @param name the name of the material to check. - * @return true if there's a duplicated material for this material, otherwise false. - * @since 2.0.0 - */ - private static boolean isDuplicated(@Nonnull String name) { - // Don't use matchXMaterial() since this method is being called from matchXMaterial() itself and will cause a StackOverflowError. - return DUPLICATED.contains(name); - } - - /** - * This method is needed due to Java enum initialization limitations. - * It's really inefficient yes, but it's only used for initialization. - *

- * Yes there are many other ways like comparing the hardcoded ordinal or using a boolean in the enum constructor, - * but it's not really a big deal. - *

- * This method should not be called if the version is after the flattening update {@link Data#ISFLAT} - * and is only used for parsing materials, not matching, for matching check {@link #DUPLICATED} - */ - private boolean isDuplicated() { - switch (this.name()) { - case "MELON": - case "CARROT": - case "POTATO": - case "GRASS": - case "BRICK": - case "NETHER_BRICK": - - // Illegal Elements - // Since both 1.12 and 1.13 have _DOOR XMaterial will use it - // for 1.12 to parse the material, but it needs _DOOR_ITEM. - // We'll trick XMaterial into thinking this needs to be parsed - // using the old methods. - // Some of these materials have their enum name added to the legacy list as well. - case "DARK_OAK_DOOR": - case "ACACIA_DOOR": - case "BIRCH_DOOR": - case "JUNGLE_DOOR": - case "SPRUCE_DOOR": - case "MAP": - case "CAULDRON": - case "BREWING_STAND": - case "FLOWER_POT": - return true; - default: - return false; - } - } - - /** - * Used for data that need to be accessed during enum initialization. - * - * @since 9.0.0 - */ - @ApiStatus.Internal - private static final class Data { - /** - * The current version of the server in the form of a major version. - * If the static initialization for this fails, you know something's wrong with the server software. - * - * @since 1.0.0 - */ - private static final int VERSION; - - static { // This needs to be right below VERSION because of initialization order. - String version = Bukkit.getVersion(); - Matcher matcher = Pattern.compile("MC: \\d\\.(\\d+)").matcher(version); - - if (matcher.find()) VERSION = Integer.parseInt(matcher.group(1)); - else throw new IllegalArgumentException("Failed to parse server version from: " + version); - } - - /** - * Cached result if the server version is after the v1.13 flattening update. - * - * @since 3.0.0 - */ - private static final boolean ISFLAT = supports(13); - } -} \ No newline at end of file