From 51ac7911dec36073829515db38e066049805d009 Mon Sep 17 00:00:00 2001 From: Christopher White Date: Sun, 6 Aug 2023 18:43:18 -1000 Subject: [PATCH] Support 1.9 using ProtocolLib (#1199) Signed-off-by: Christopher White <18whitechristop@gmail.com> --- core/pom.xml | 1 + core/src/main/java/tc/oc/pgm/PGMConfig.java | 7 + core/src/main/java/tc/oc/pgm/PGMPlugin.java | 13 + core/src/main/java/tc/oc/pgm/api/Config.java | 7 + .../pgm/flag/LegacyFlagBeamMatchModule.java | 4 +- .../tc/oc/pgm/shield/ShieldPlayerModule.java | 12 +- core/src/main/resources/config.yml | 1 + core/src/main/resources/plugin.yml | 2 +- pom.xml | 22 + .../tc/oc/pgm/util/attribute/Attribute.java | 8 + .../tc/oc/pgm/util/bukkit/BukkitUtils.java | 2 +- .../java/tc/oc/pgm/util/bukkit/Platform.java | 95 +++ .../java/tc/oc/pgm/util/nms/NMSHacks.java | 34 +- .../java/tc/oc/pgm/util/nms/NMSHacksNoOp.java | 127 +++- .../tc/oc/pgm/util/nms/NMSHacksPlatform.java | 8 +- .../attribute/AttributeInstanceBukkit.java | 66 ++ .../nms/attribute/AttributeMapBukkit.java | 23 + .../nms/attribute/AttributeUtilBukkit.java | 76 +++ .../pgm/util/nms/entity/fake/FakeEntity.java | 7 +- .../nms/entity/fake/FakeEntityImpl1_8.java | 6 - .../util/nms/entity/fake/FakeEntityNoOp.java | 6 - .../entity/fake/FakeEntityProtocolLib.java | 12 + .../armorstand/FakeArmorStandProtocolLib.java | 23 + .../wither/FakeWitherSkullProtocolLib.java | 15 + .../java/tc/oc/pgm/util/nms/reflect/Refl.java | 165 +++++ .../tc/oc/pgm/util/nms/reflect/Reflect.java | 52 ++ .../pgm/util/nms/reflect/ReflectionProxy.java | 352 ++++++++++ .../pgm/util/nms/{ => v1_8}/NMSHacks1_8.java | 114 ++-- .../nms/{ => v1_8}/NMSHacksSportPaper.java | 60 +- .../tc/oc/pgm/util/nms/v1_9/NMSHacks1_9.java | 154 +++++ .../util/nms/v1_9/NMSHacksProtocolLib.java | 642 ++++++++++++++++++ .../reflect/MinecraftReflectionUtils.java | 32 +- .../oc/pgm/util/tablist/TablistResizer.java | 29 + 33 files changed, 2027 insertions(+), 150 deletions(-) create mode 100644 util/src/main/java/tc/oc/pgm/util/bukkit/Platform.java create mode 100644 util/src/main/java/tc/oc/pgm/util/nms/attribute/AttributeInstanceBukkit.java create mode 100644 util/src/main/java/tc/oc/pgm/util/nms/attribute/AttributeMapBukkit.java create mode 100644 util/src/main/java/tc/oc/pgm/util/nms/attribute/AttributeUtilBukkit.java create mode 100644 util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntityProtocolLib.java create mode 100644 util/src/main/java/tc/oc/pgm/util/nms/entity/fake/armorstand/FakeArmorStandProtocolLib.java create mode 100644 util/src/main/java/tc/oc/pgm/util/nms/entity/fake/wither/FakeWitherSkullProtocolLib.java create mode 100644 util/src/main/java/tc/oc/pgm/util/nms/reflect/Refl.java create mode 100644 util/src/main/java/tc/oc/pgm/util/nms/reflect/Reflect.java create mode 100644 util/src/main/java/tc/oc/pgm/util/nms/reflect/ReflectionProxy.java rename util/src/main/java/tc/oc/pgm/util/nms/{ => v1_8}/NMSHacks1_8.java (92%) rename util/src/main/java/tc/oc/pgm/util/nms/{ => v1_8}/NMSHacksSportPaper.java (86%) create mode 100644 util/src/main/java/tc/oc/pgm/util/nms/v1_9/NMSHacks1_9.java create mode 100644 util/src/main/java/tc/oc/pgm/util/nms/v1_9/NMSHacksProtocolLib.java create mode 100644 util/src/main/java/tc/oc/pgm/util/tablist/TablistResizer.java diff --git a/core/pom.xml b/core/pom.xml index 4483c1ed84..13522ae302 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -69,6 +69,7 @@ org.slf4j:slf4j-api:* fr.minuskube.inv:smart-invs net.objecthunter:exp4j:* + io.github.bananapuncher714:nbteditor:* diff --git a/core/src/main/java/tc/oc/pgm/PGMConfig.java b/core/src/main/java/tc/oc/pgm/PGMConfig.java index 0f7ecf365b..8cd8b94efe 100644 --- a/core/src/main/java/tc/oc/pgm/PGMConfig.java +++ b/core/src/main/java/tc/oc/pgm/PGMConfig.java @@ -90,6 +90,7 @@ public final class PGMConfig implements Config { // ui.* private final boolean showSideBar; private final boolean showTabList; + private final boolean resizeTabList; private final boolean showTabListPing; private final boolean showProximity; private final boolean showFireworks; @@ -183,6 +184,7 @@ public final class PGMConfig implements Config { this.showProximity = parseBoolean(config.getString("ui.proximity", "false")); this.showSideBar = parseBoolean(config.getString("ui.sidebar", "true")); this.showTabList = parseBoolean(config.getString("ui.tablist", "true")); + this.resizeTabList = parseBoolean(config.getString("ui.tablist-resize", "false")); this.showTabListPing = parseBoolean(config.getString("ui.ping", "true")); this.participantsSeeObservers = parseBoolean(config.getString("ui.participants-see-observers", "true")); @@ -573,6 +575,11 @@ public boolean showTabList() { return showTabList; } + @Override + public boolean resizeTabList() { + return resizeTabList; + } + @Override public boolean showTabListPing() { return showTabListPing; diff --git a/core/src/main/java/tc/oc/pgm/PGMPlugin.java b/core/src/main/java/tc/oc/pgm/PGMPlugin.java index c36b9fd373..5475d3698b 100644 --- a/core/src/main/java/tc/oc/pgm/PGMPlugin.java +++ b/core/src/main/java/tc/oc/pgm/PGMPlugin.java @@ -73,6 +73,8 @@ import tc.oc.pgm.util.listener.ItemTransferListener; import tc.oc.pgm.util.listener.PlayerBlockListener; import tc.oc.pgm.util.listener.PlayerMoveListener; +import tc.oc.pgm.util.nms.NMSHacks; +import tc.oc.pgm.util.tablist.TablistResizer; import tc.oc.pgm.util.text.TextException; import tc.oc.pgm.util.text.TextTranslations; import tc.oc.pgm.util.xml.InvalidXMLException; @@ -114,6 +116,9 @@ public void onEnable() { return; // Indicates the plugin failed to load, so exit early } + // Sanity test PGM is running on a supported version before doing any work + NMSHacks.allocateEntityId(); + Permissions.registerAll(); final CommandSender console = getServer().getConsoleSender(); @@ -214,6 +219,14 @@ public void onEnable() { if (config.showTabList()) { matchTabManager = new MatchTabManager(this); + + if (config.resizeTabList()) { + if (this.getServer().getPluginManager().isPluginEnabled("ProtocolLib")) { + TablistResizer.registerAdapter(this); + } else { + logger.warning("ProtocolLib is required when 'ui.resize' is enabled"); + } + } } if (!config.getUptimeLimit().isNegative()) { diff --git a/core/src/main/java/tc/oc/pgm/api/Config.java b/core/src/main/java/tc/oc/pgm/api/Config.java index 7773225963..61af12f148 100644 --- a/core/src/main/java/tc/oc/pgm/api/Config.java +++ b/core/src/main/java/tc/oc/pgm/api/Config.java @@ -208,6 +208,13 @@ public interface Config { */ boolean showTabList(); + /** + * Gets whether the tab list should be resized to 4 rows for 1.7 players. + * + * @return If the tab list will be resized. + */ + boolean resizeTabList(); + /** * Gets whether the tab list is should show real ping. * diff --git a/core/src/main/java/tc/oc/pgm/flag/LegacyFlagBeamMatchModule.java b/core/src/main/java/tc/oc/pgm/flag/LegacyFlagBeamMatchModule.java index c6bfd52a44..d618d8978a 100644 --- a/core/src/main/java/tc/oc/pgm/flag/LegacyFlagBeamMatchModule.java +++ b/core/src/main/java/tc/oc/pgm/flag/LegacyFlagBeamMatchModule.java @@ -189,8 +189,8 @@ public void show(MatchPlayer player) { spawn(bukkit, base(player)); segments.forEach(segment -> spawn(bukkit, segment)); range(1, segments.size()) - .forEachOrdered(i -> segments.get(i - 1).ride(bukkit, segments.get(i).entity())); - base(player).ride(bukkit, segments.get(0).entity()); + .forEachOrdered(i -> segments.get(i - 1).ride(bukkit, segments.get(i).entityId())); + base(player).ride(bukkit, segments.get(0).entityId()); update(player); } diff --git a/core/src/main/java/tc/oc/pgm/shield/ShieldPlayerModule.java b/core/src/main/java/tc/oc/pgm/shield/ShieldPlayerModule.java index 3529efdf72..89048a40af 100644 --- a/core/src/main/java/tc/oc/pgm/shield/ShieldPlayerModule.java +++ b/core/src/main/java/tc/oc/pgm/shield/ShieldPlayerModule.java @@ -56,6 +56,16 @@ public void remove() { addAbsorption(-shieldHealth); } + static Sound RECHARGE_SOUND = resolveRechaseSound(); + + static Sound resolveRechaseSound() { + try { + return Sound.ORB_PICKUP; + } catch (Throwable t) { + return Sound.valueOf("ENTITY_EXPERIENCE_ORB_PICKUP"); + } + } + /** * Recharge the shield to its maximum health. If the player has more absorption than the current * shield strength, the excess is preserved. @@ -66,7 +76,7 @@ void recharge() { logger.fine("Recharging shield: shield=" + shieldHealth + " delta=" + delta); shieldHealth = parameters.maxHealth; addAbsorption(delta); - bukkit.playSound(bukkit.getLocation(), Sound.ORB_PICKUP, 1, 2); + bukkit.playSound(bukkit.getLocation(), RECHARGE_SOUND, 1, 2); } } diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index d083bb805e..8b0b6f1b05 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -74,6 +74,7 @@ gameplay: ui: sidebar: true # Enable the side bar? tablist: true # Enable the tab list? + tablist-resize: false # Resize the tab list for 1.7 players? Requires ProtocolLib ping: false # Should tab list show real ping? proximity: false # Should the proximity of objectives be visible? fireworks: true # Spawn fireworks after objectives are completed? diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index c33343788e..5536ab8551 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: PGM -softdepend: [ViaVersion] +softdepend: [ViaVersion, ProtocolLib] description: ${project.parent.description} main: ${project.mainClass} version: ${project.version} (git-${git.commit.id.abbrev}) diff --git a/pom.xml b/pom.xml index 43a7bed1f4..a1960c56eb 100644 --- a/pom.xml +++ b/pom.xml @@ -63,6 +63,14 @@ pgm-fyi https://repo.pgm.fyi/snapshots + + dmulloy2-repo + https://repo.dmulloy2.net/repository/public/ + + + CodeMC + https://repo.codemc.org/repository/maven-public/ + @@ -98,6 +106,13 @@ + + + com.comphenix.protocol + ProtocolLib + 5.0.0 + + org.jdom @@ -173,6 +188,13 @@ 1.2.1 compile + + + + io.github.bananapuncher714 + nbteditor + 7.18.5 + diff --git a/util/src/main/java/tc/oc/pgm/util/attribute/Attribute.java b/util/src/main/java/tc/oc/pgm/util/attribute/Attribute.java index 97f5412a66..58f81cdbb9 100644 --- a/util/src/main/java/tc/oc/pgm/util/attribute/Attribute.java +++ b/util/src/main/java/tc/oc/pgm/util/attribute/Attribute.java @@ -15,6 +15,14 @@ public enum Attribute { GENERIC_MOVEMENT_SPEED("generic.movementSpeed"), /** Attack damage of an Entity. */ GENERIC_ATTACK_DAMAGE("generic.attackDamage"), + /** Attack speed of an Entity */ + GENERIC_ATTACK_SPEED("generic.attackSpeed"), + /** Armor of an Entity */ + GENERIC_ARMOR("generic.armor"), + /** Armor Toughness of an Entity */ + GENERIC_ARMOR_TOUGHNESS("generic.armorToughness"), + /** Luck */ + GENERIC_LUCK("generic.luck"), /** Strength with which a horse will jump. */ HORSE_JUMP_STRENGTH("horse.jumpStrength"), /** Chance of a zombie to spawn reinforcements. */ diff --git a/util/src/main/java/tc/oc/pgm/util/bukkit/BukkitUtils.java b/util/src/main/java/tc/oc/pgm/util/bukkit/BukkitUtils.java index 6175fd5614..55dc31cbd4 100644 --- a/util/src/main/java/tc/oc/pgm/util/bukkit/BukkitUtils.java +++ b/util/src/main/java/tc/oc/pgm/util/bukkit/BukkitUtils.java @@ -23,7 +23,7 @@ static Plugin getPlugin() { return PLUGIN.get(); } - Boolean isSportPaper = Bukkit.getServer().getVersion().contains("SportPaper"); + Boolean isSportPaper = Platform.SERVER_PLATFORM == Platform.SPORTPAPER_1_8; static boolean isSportPaper() { return isSportPaper; diff --git a/util/src/main/java/tc/oc/pgm/util/bukkit/Platform.java b/util/src/main/java/tc/oc/pgm/util/bukkit/Platform.java new file mode 100644 index 0000000000..5774f56fd4 --- /dev/null +++ b/util/src/main/java/tc/oc/pgm/util/bukkit/Platform.java @@ -0,0 +1,95 @@ +package tc.oc.pgm.util.bukkit; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import org.bukkit.Bukkit; +import tc.oc.pgm.util.ClassLogger; +import tc.oc.pgm.util.nms.NMSHacksNoOp; +import tc.oc.pgm.util.nms.NMSHacksPlatform; +import tc.oc.pgm.util.nms.v1_8.NMSHacksSportPaper; +import tc.oc.pgm.util.nms.v1_9.NMSHacks1_9; +import tc.oc.pgm.util.reflect.ReflectionUtils; + +public enum Platform { + UNKNOWN("UNKNOWN", "UNKNOWN", NMSHacksNoOp.class, false), + SPORTPAPER_1_8("SportPaper", "1.8", NMSHacksSportPaper.class, false), + SPIGOT_1_8( + "Spigot", + "1.8", // NMSHacks1_8 causes issues with other versions, get it dynamically + (Class) + ReflectionUtils.getClassFromName("tc.oc.pgm.util.nms.v1_8.NMSHacks1_8"), + false), + PAPER_1_8( + "Paper", + "1.8", // NMSHacks1_8 causes issues with other versions, get it dynamically + (Class) + ReflectionUtils.getClassFromName("tc.oc.pgm.util.nms.v1_8.NMSHacks1_8"), + false), + SPIGOT_1_9("Spigot", "1.9", NMSHacks1_9.class, true), + PAPER_1_9("Paper", "1.9", NMSHacks1_9.class, true); + + private static ClassLogger logger = ClassLogger.get(Platform.class);; + public static Platform SERVER_PLATFORM = computeServerPlatform(); + + private static Platform computeServerPlatform() { + String versionString = Bukkit.getServer().getVersion(); + for (Platform platform : Platform.values()) { + if (versionString.contains(platform.variant) + && versionString.contains("MC: " + platform.majorVersion)) { + return platform; + } + } + return UNKNOWN; + } + + private final String variant; + private final String majorVersion; + private final Class nmsHacksClass; + private final boolean requiresProtocolLib; + + Platform( + String variant, + String majorVersion, + Class nmsHacksClass, + boolean requiresProtocolLib) { + this.variant = variant; + this.majorVersion = majorVersion; + this.nmsHacksClass = nmsHacksClass; + this.requiresProtocolLib = requiresProtocolLib; + } + + public NMSHacksPlatform getNMSHacks() { + if (this == UNKNOWN) { + Bukkit.getServer().getPluginManager().disablePlugin(BukkitUtils.getPlugin()); + throw new UnsupportedOperationException("UNKNOWN Platform!"); + } + if (this.requiresProtocolLib + && !Bukkit.getServer().getPluginManager().isPluginEnabled("ProtocolLib")) { + Bukkit.getServer().getPluginManager().disablePlugin(BukkitUtils.getPlugin()); + throw new UnsupportedOperationException( + "ProtocolLib is required for PGM to run on " + this.toString()); + } + if (this == SERVER_PLATFORM) { + try { + logger.info("Detected server: " + this.toString()); + Constructor constructor = nmsHacksClass.getConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } catch (InvocationTargetException + | InstantiationException + | IllegalAccessException + | NoSuchMethodException e) { + Bukkit.getServer().getPluginManager().disablePlugin(BukkitUtils.getPlugin()); + throw new RuntimeException(e); + } + } else { + Bukkit.getServer().getPluginManager().disablePlugin(BukkitUtils.getPlugin()); + throw new UnsupportedOperationException("getNMSHacks called for incorrect platform!"); + } + } + + @Override + public String toString() { + return this.variant + " (" + this.majorVersion + ")"; + } +} diff --git a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java b/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java index a4edf6303a..2cbe12a538 100644 --- a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java +++ b/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java @@ -6,7 +6,6 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Logger; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.ChunkSnapshot; @@ -34,37 +33,17 @@ import org.bukkit.scoreboard.NameTagVisibility; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; -import tc.oc.pgm.util.ClassLogger; import tc.oc.pgm.util.attribute.AttributeMap; import tc.oc.pgm.util.attribute.AttributeModifier; import tc.oc.pgm.util.block.RayBlockIntersection; -import tc.oc.pgm.util.bukkit.BukkitUtils; +import tc.oc.pgm.util.bukkit.Platform; import tc.oc.pgm.util.nms.entity.fake.FakeEntity; import tc.oc.pgm.util.nms.entity.potion.EntityPotion; import tc.oc.pgm.util.skin.Skin; public interface NMSHacks { - NMSHacksPlatform INSTANCE = chooseNMSHacks(); - - static NMSHacksPlatform chooseNMSHacks() { - NMSHacksPlatform choice; - Logger logger = ClassLogger.get(NMSHacks.class); - - try { - if (BukkitUtils.isSportPaper()) { - choice = new NMSHacksSportPaper(); - logger.info("Using NMSHacksSportPaper"); - } else { - choice = new NMSHacks1_8(); - logger.info("Using NMSHacks1_8"); - } - } catch (Throwable throwable) { - logger.severe("You are trying to run PGM on an unsupported version!"); - throw throwable; - } - return choice; - } + NMSHacksPlatform INSTANCE = Platform.SERVER_PLATFORM.getNMSHacks(); AtomicInteger ENTITY_IDS = new AtomicInteger(Integer.MAX_VALUE); @@ -317,10 +296,19 @@ static boolean teleportRelative( return INSTANCE.teleportRelative(player, deltaPos, deltaYaw, deltaPitch, cause); } + static void sendSpawnEntityPacket( + Player player, int entityId, Location location, Vector velocity) { + INSTANCE.sendSpawnEntityPacket(player, entityId, location, velocity); + } + static void spawnFreezeEntity(Player player, int entityId, boolean legacy) { INSTANCE.spawnFreezeEntity(player, entityId, legacy); } + static void spawnFakeArmorStand(Player player, int entityId, Location location, Vector velocity) { + INSTANCE.spawnFakeArmorStand(player, entityId, location, velocity); + } + /** * Test if the given tool is capable of "efficiently" mining the given block. * diff --git a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacksNoOp.java b/util/src/main/java/tc/oc/pgm/util/nms/NMSHacksNoOp.java index 0a354e57b8..d8cf92f7e5 100644 --- a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacksNoOp.java +++ b/util/src/main/java/tc/oc/pgm/util/nms/NMSHacksNoOp.java @@ -1,33 +1,49 @@ package tc.oc.pgm.util.nms; -import java.lang.reflect.Method; +import com.google.common.collect.SetMultimap; +import com.mojang.authlib.GameProfile; +import java.util.Collection; import java.util.HashSet; import java.util.Set; +import java.util.UUID; import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.ChunkSnapshot; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.WorldCreator; +import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.EntityType; import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.inventory.DoubleChestInventory; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; import org.bukkit.material.MaterialData; import org.bukkit.plugin.Plugin; import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; import tc.oc.pgm.util.attribute.AttributeMap; +import tc.oc.pgm.util.attribute.AttributeModifier; import tc.oc.pgm.util.nms.attribute.AttributeMapNoOp; import tc.oc.pgm.util.nms.entity.fake.FakeEntity; import tc.oc.pgm.util.nms.entity.fake.FakeEntityNoOp; import tc.oc.pgm.util.nms.entity.potion.EntityPotion; import tc.oc.pgm.util.nms.entity.potion.EntityPotionBukkit; -import tc.oc.pgm.util.reflect.MinecraftReflectionUtils; -import tc.oc.pgm.util.reflect.ReflectionUtils; +import tc.oc.pgm.util.nms.reflect.Refl; +import tc.oc.pgm.util.nms.reflect.ReflectionProxy; +import tc.oc.pgm.util.skin.Skin; +import tc.oc.pgm.util.skin.Skins; public abstract class NMSHacksNoOp implements NMSHacksPlatform { + static Refl refl = ReflectionProxy.getProxy(Refl.class); @Override public boolean isCraftItemArrowEntity(Item item) { @@ -128,20 +144,14 @@ public Enchantment getEnchantment(String key) { } } - static Class craftWorldClass = MinecraftReflectionUtils.getCraftBukkitClass("CraftWorld"); - static Method getHandleMethod = ReflectionUtils.getMethod(craftWorldClass, "getHandle"); - static Class nmsWorldClass = MinecraftReflectionUtils.getNMSClass("World"); - static Method getTimeMethod = ReflectionUtils.getMethod(nmsWorldClass, "getTime"); - @Override public long getMonotonicTime(World world) { - Object handle = ReflectionUtils.callMethod(getHandleMethod, world); - return (long) ReflectionUtils.callMethod(getTimeMethod, handle); + return refl.getWorldTime(refl.getWorldHandle(world)); } @Override public int getPing(Player player) { - return 100; + return refl.getPlayerPing(refl.getPlayerHandle(player)); } @Override @@ -166,6 +176,52 @@ public ItemStack craftItemCopy(ItemStack item) { return item.clone(); } + @Override + public Set getBlocks(Chunk bukkitChunk, Material material) { + Set blockSet = new HashSet<>(); + ChunkSnapshot chunkSnapshot = bukkitChunk.getChunkSnapshot(); + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int highestBlockY = chunkSnapshot.getHighestBlockYAt(x, z); + for (int y = 0; y < highestBlockY; y++) { + Block block = bukkitChunk.getBlock(x, y, z); + if (block.getType() == material) { + blockSet.add(block); + } + } + } + } + + return blockSet; + } + + @Override + public void setSkullMetaOwner(SkullMeta meta, String name, UUID uuid, Skin skin) { + GameProfile gameProfile = new GameProfile(uuid, name); + Skins.setProperties(skin, gameProfile.getProperties()); + refl.setSkullProfile(meta, gameProfile); + } + + @Override + public WorldCreator detectWorld(String worldName) { + return null; // Usage handles this nicely + } + + @Override + public void setAbsorption(LivingEntity entity, double health) { + refl.setAbsorptionHearts(refl.getCraftEntityHandle(entity), (float) health); + } + + @Override + public double getAbsorption(LivingEntity entity) { + return refl.getAbsorptionHearts(refl.getCraftEntityHandle(entity)); + } + + @Override + public Skin getPlayerSkinForViewer(Player player, Player viewer) { + return getPlayerSkin(player); // not possible here outside of sportpaper + } + @Override public Set getBlockStates(Material material) { // TODO: MaterialData is not version compatible @@ -176,6 +232,48 @@ public Set getBlockStates(Material material) { return materialDataSet; } + @Override + public boolean teleportRelative( + Player player, + Vector deltaPos, + float deltaYaw, + float deltaPitch, + PlayerTeleportEvent.TeleportCause cause) { + // From = Players current Location + Location from = player.getLocation(); + // To = Players new Location if Teleport is Successful + Location to = from.clone().add(deltaPos); + to.setYaw(to.getYaw() + deltaYaw); + to.setPitch(to.getPitch() + deltaPitch); + + return player.teleport(to, cause); + } + + @Override + public void resetDimension(World world) { + // NoOp Other dimensions dont currently work in PGM + } + + @Override + public void setCanDestroy(ItemMeta itemMeta, Collection materials) { + setMaterialCollection(itemMeta, materials, "CanDestroy"); + } + + @Override + public Set getCanDestroy(ItemMeta itemMeta) { + return getMaterialCollection(itemMeta, "CanDestroy"); + } + + @Override + public void setCanPlaceOn(ItemMeta itemMeta, Collection materials) { + setMaterialCollection(itemMeta, materials, "CanPlaceOn"); + } + + @Override + public Set getCanPlaceOn(ItemMeta itemMeta) { + return getMaterialCollection(itemMeta, "CanPlaceOn"); + } + @Override public void setBlockStateData(BlockState state, MaterialData materialData) { state.setType(materialData.getItemType()); @@ -187,6 +285,13 @@ public double getTPS() { return 20.0; } + @Override + public void copyAttributeModifiers(ItemMeta destination, ItemMeta source) { + SetMultimap attributeModifiers = getAttributeModifiers(source); + attributeModifiers.putAll(getAttributeModifiers(destination)); + applyAttributeModifiers(attributeModifiers, destination); + } + @Override public AttributeMap buildAttributeMap(Player player) { return new AttributeMapNoOp(); diff --git a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacksPlatform.java b/util/src/main/java/tc/oc/pgm/util/nms/NMSHacksPlatform.java index d110e32ae0..4894d88f30 100644 --- a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacksPlatform.java +++ b/util/src/main/java/tc/oc/pgm/util/nms/NMSHacksPlatform.java @@ -154,10 +154,16 @@ boolean teleportRelative( float deltaPitch, PlayerTeleportEvent.TeleportCause cause); - void sendSpawnEntityPacket(Player player, int entityId, Location location); + void sendSpawnEntityPacket(Player player, int entityId, Location location, Vector velocity); + + default void sendSpawnEntityPacket(Player player, int entityId, Location location) { + sendSpawnEntityPacket(player, entityId, location, new Vector()); + } void spawnFreezeEntity(Player player, int entityId, boolean legacy); + void spawnFakeArmorStand(Player player, int entityId, Location location, Vector velocity); + /** * Test if the given tool is capable of "efficiently" mining the given block. * diff --git a/util/src/main/java/tc/oc/pgm/util/nms/attribute/AttributeInstanceBukkit.java b/util/src/main/java/tc/oc/pgm/util/nms/attribute/AttributeInstanceBukkit.java new file mode 100644 index 0000000000..55f2cd5b5d --- /dev/null +++ b/util/src/main/java/tc/oc/pgm/util/nms/attribute/AttributeInstanceBukkit.java @@ -0,0 +1,66 @@ +package tc.oc.pgm.util.nms.attribute; + +import java.util.ArrayList; +import java.util.Collection; +import tc.oc.pgm.util.attribute.Attribute; +import tc.oc.pgm.util.attribute.AttributeInstance; +import tc.oc.pgm.util.attribute.AttributeModifier; + +/** Attribute Instance for 1.9+ */ +public class AttributeInstanceBukkit implements AttributeInstance { + + private final org.bukkit.attribute.AttributeInstance bukkitAttributeInstance; + + public AttributeInstanceBukkit(org.bukkit.attribute.AttributeInstance bukkitAttributeInstance) { + this.bukkitAttributeInstance = bukkitAttributeInstance; + } + + @Override + public Attribute getAttribute() { + return AttributeUtilBukkit.convertAttribute(bukkitAttributeInstance.getAttribute()); + } + + @Override + public double getBaseValue() { + return bukkitAttributeInstance.getBaseValue(); + } + + @Override + public void setBaseValue(double d) { + bukkitAttributeInstance.setBaseValue(d); + } + + @Override + public Collection getModifiers() { + Collection convertedModifiers = new ArrayList<>(); + + for (org.bukkit.attribute.AttributeModifier modifier : bukkitAttributeInstance.getModifiers()) { + + AttributeModifier attributeModifier = AttributeUtilBukkit.convertAttributeModifier(modifier); + + convertedModifiers.add(attributeModifier); + } + + return convertedModifiers; + } + + @Override + public void addModifier(AttributeModifier modifier) { + bukkitAttributeInstance.addModifier(AttributeUtilBukkit.convertAttributeModifier(modifier)); + } + + @Override + public void removeModifier(AttributeModifier modifier) { + bukkitAttributeInstance.removeModifier(AttributeUtilBukkit.convertAttributeModifier(modifier)); + } + + @Override + public double getValue() { + return bukkitAttributeInstance.getValue(); + } + + @Override + public double getDefaultValue() { + return bukkitAttributeInstance.getDefaultValue(); + } +} diff --git a/util/src/main/java/tc/oc/pgm/util/nms/attribute/AttributeMapBukkit.java b/util/src/main/java/tc/oc/pgm/util/nms/attribute/AttributeMapBukkit.java new file mode 100644 index 0000000000..64e3ecfde0 --- /dev/null +++ b/util/src/main/java/tc/oc/pgm/util/nms/attribute/AttributeMapBukkit.java @@ -0,0 +1,23 @@ +package tc.oc.pgm.util.nms.attribute; + +import org.bukkit.entity.Player; +import tc.oc.pgm.util.attribute.Attribute; +import tc.oc.pgm.util.attribute.AttributeInstance; +import tc.oc.pgm.util.attribute.AttributeMap; + +public class AttributeMapBukkit implements AttributeMap { + private Player player; + + public AttributeMapBukkit(Player player) { + this.player = player; + } + + @Override + public AttributeInstance getAttribute(Attribute attribute) { + org.bukkit.attribute.Attribute bukkitAttribute = + AttributeUtilBukkit.convertAttribute(attribute); + if (bukkitAttribute == null) return null; + org.bukkit.attribute.AttributeInstance attributeInstance = player.getAttribute(bukkitAttribute); + return attributeInstance == null ? null : new AttributeInstanceBukkit(attributeInstance); + } +} diff --git a/util/src/main/java/tc/oc/pgm/util/nms/attribute/AttributeUtilBukkit.java b/util/src/main/java/tc/oc/pgm/util/nms/attribute/AttributeUtilBukkit.java new file mode 100644 index 0000000000..169a7b41ef --- /dev/null +++ b/util/src/main/java/tc/oc/pgm/util/nms/attribute/AttributeUtilBukkit.java @@ -0,0 +1,76 @@ +package tc.oc.pgm.util.nms.attribute; + +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.jetbrains.annotations.NotNull; +import tc.oc.pgm.util.attribute.Attribute; +import tc.oc.pgm.util.attribute.AttributeModifier; + +/** Util to improve performance converting attributes between bukkit and PGM */ +public interface AttributeUtilBukkit { + + Map attributeCacheToBukkit = + new EnumMap<>(Attribute.class); + Map attributeCacheToPGM = + new EnumMap<>(org.bukkit.attribute.Attribute.class); + + EnumSet missingAttributes = findMissingAttributes(); + + static EnumSet findMissingAttributes() { + Set missingAttributes = new HashSet<>(); + + for (Attribute attribute : Attribute.values()) { + try { + org.bukkit.attribute.Attribute.valueOf(attribute.name()); + } catch (Exception e) { + missingAttributes.add(attribute); + } + } + + return EnumSet.copyOf(missingAttributes); + } + + static org.bukkit.attribute.Attribute convertAttribute(Attribute attribute) { + if (missingAttributes.contains(attribute)) return null; + org.bukkit.attribute.Attribute cachedAttribute = attributeCacheToBukkit.get(attribute); + if (cachedAttribute == null) { + cachedAttribute = org.bukkit.attribute.Attribute.valueOf(attribute.name()); + attributeCacheToBukkit.put(attribute, cachedAttribute); + } + return cachedAttribute; + } + + static Attribute convertAttribute(org.bukkit.attribute.Attribute attribute) { + Attribute cachedAttribute = attributeCacheToPGM.get(attribute); + if (cachedAttribute == null) { + cachedAttribute = Attribute.valueOf(attribute.name()); + attributeCacheToPGM.put(attribute, cachedAttribute); + } + return cachedAttribute; + } + + @NotNull + static AttributeModifier convertAttributeModifier( + org.bukkit.attribute.AttributeModifier modifier) { + return new AttributeModifier( + modifier.getUniqueId(), + modifier.getName(), + modifier.getAmount(), + AttributeModifier.Operation.values()[modifier.getOperation().ordinal()]); + } + + @NotNull + static org.bukkit.attribute.AttributeModifier convertAttributeModifier( + AttributeModifier modifier) { + + return new org.bukkit.attribute.AttributeModifier( + modifier.getUniqueId(), + modifier.getName(), + modifier.getAmount(), + org.bukkit.attribute.AttributeModifier.Operation.values()[ + modifier.getOperation().ordinal()]); + } +} diff --git a/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntity.java b/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntity.java index 1cebe9ea13..27228aacc2 100644 --- a/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntity.java +++ b/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntity.java @@ -9,8 +9,6 @@ public interface FakeEntity { int entityId(); - Entity entity(); - void spawn(Player viewer, Location location, org.bukkit.util.Vector velocity); default void spawn(Player viewer, Location location) { @@ -26,10 +24,9 @@ default void teleport(Player viewer, Location location) { NMSHacks.sendPacket(viewer, NMSHacks.teleportEntityPacket(entityId(), location)); } - default void ride(Player viewer, Entity rider) { - int entityID = rider.getEntityId(); + default void ride(Player viewer, int riderID) { int vehicleID = entityId(); - NMSHacks.entityAttach(viewer, entityID, vehicleID, false); + NMSHacks.entityAttach(viewer, riderID, vehicleID, false); } default void mount(Player viewer, Entity vehicle) { diff --git a/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntityImpl1_8.java b/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntityImpl1_8.java index 080655afab..1f8e818a64 100644 --- a/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntityImpl1_8.java +++ b/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntityImpl1_8.java @@ -1,7 +1,6 @@ package tc.oc.pgm.util.nms.entity.fake; import org.bukkit.Location; -import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.util.Vector; import tc.oc.pgm.util.nms.NMSHacks; @@ -14,11 +13,6 @@ protected FakeEntityImpl1_8(T entity) { this.entity = entity; } - @Override - public Entity entity() { - return entity.getBukkitEntity(); - } - @Override public void spawn(Player viewer, Location location, Vector velocity) { entity.setPositionRotation( diff --git a/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntityNoOp.java b/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntityNoOp.java index dfce6f7d8f..ef033de924 100644 --- a/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntityNoOp.java +++ b/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntityNoOp.java @@ -1,7 +1,6 @@ package tc.oc.pgm.util.nms.entity.fake; import org.bukkit.Location; -import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.util.Vector; @@ -12,11 +11,6 @@ public int entityId() { return 0; } - @Override - public Entity entity() { - return null; - } - @Override public void spawn(Player viewer, Location location, Vector velocity) {} } diff --git a/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntityProtocolLib.java b/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntityProtocolLib.java new file mode 100644 index 0000000000..7fdcf17291 --- /dev/null +++ b/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/FakeEntityProtocolLib.java @@ -0,0 +1,12 @@ +package tc.oc.pgm.util.nms.entity.fake; + +import tc.oc.pgm.util.nms.NMSHacks; + +public abstract class FakeEntityProtocolLib implements FakeEntity { + private final int entityID = NMSHacks.allocateEntityId(); + + @Override + public int entityId() { + return entityID; + } +} diff --git a/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/armorstand/FakeArmorStandProtocolLib.java b/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/armorstand/FakeArmorStandProtocolLib.java new file mode 100644 index 0000000000..eafc30c7b0 --- /dev/null +++ b/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/armorstand/FakeArmorStandProtocolLib.java @@ -0,0 +1,23 @@ +package tc.oc.pgm.util.nms.entity.fake.armorstand; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; +import tc.oc.pgm.util.nms.NMSHacks; +import tc.oc.pgm.util.nms.entity.fake.FakeEntityProtocolLib; + +public class FakeArmorStandProtocolLib extends FakeEntityProtocolLib { + + ItemStack head; + + public FakeArmorStandProtocolLib(ItemStack head) { + this.head = head; + } + + @Override + public void spawn(Player viewer, Location location, Vector velocity) { + NMSHacks.spawnFakeArmorStand(viewer.getPlayer(), entityId(), location, velocity); + wear(viewer, 4, head); + } +} diff --git a/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/wither/FakeWitherSkullProtocolLib.java b/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/wither/FakeWitherSkullProtocolLib.java new file mode 100644 index 0000000000..8be4d34149 --- /dev/null +++ b/util/src/main/java/tc/oc/pgm/util/nms/entity/fake/wither/FakeWitherSkullProtocolLib.java @@ -0,0 +1,15 @@ +package tc.oc.pgm.util.nms.entity.fake.wither; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tc.oc.pgm.util.nms.NMSHacks; +import tc.oc.pgm.util.nms.entity.fake.FakeEntityProtocolLib; + +public class FakeWitherSkullProtocolLib extends FakeEntityProtocolLib { + + @Override + public void spawn(Player viewer, Location location, Vector velocity) { + NMSHacks.sendSpawnEntityPacket(viewer.getPlayer(), entityId(), location, velocity); + } +} diff --git a/util/src/main/java/tc/oc/pgm/util/nms/reflect/Refl.java b/util/src/main/java/tc/oc/pgm/util/nms/reflect/Refl.java new file mode 100644 index 0000000000..90f4c9b388 --- /dev/null +++ b/util/src/main/java/tc/oc/pgm/util/nms/reflect/Refl.java @@ -0,0 +1,165 @@ +package tc.oc.pgm.util.nms.reflect; + +import java.util.Map; +import java.util.concurrent.Callable; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; + +public interface Refl { + + @Reflect.CB("inventory.CraftMetaItem.unhandledTags") + Map getUnhandledTags(ItemMeta item); + + @Reflect.CB("inventory.CraftMetaSkull.profile") + void setSkullProfile(SkullMeta meta, Object profile); + + @Reflect.CB("entity.CraftPlayer.getHandle()") + Object getPlayerHandle(Player player); + + @Reflect.NMS("EntityPlayer.ping") + int getPlayerPing(Object handle); + + @Reflect.CB("CraftWorld.getHandle()") + Object getWorldHandle(World world); + + @Reflect.NMS("World.getTime()") + long getWorldTime(Object handle); + + @Reflect.CB("CraftServer.getHandle()") + Object getCraftServerHandle(Server server); + + @Reflect.CB("entity.CraftLivingEntity.getHandle()") + Object getCraftEntityHandle(LivingEntity entity); + + @Reflect.NMS("EntityLiving.getAbsorptionHearts()") + float getAbsorptionHearts(Object entity); + + @Reflect.NMS(value = "EntityLiving.setAbsorptionHearts()", parameters = float.class) + void setAbsorptionHearts(Object entity, float hearts); + + @Reflect.NMS("DedicatedPlayerList.getServer()") + Object getNMSServer(Object handle); + + @Reflect.NMS(value = "MinecraftServer.a()", parameters = Callable.class) + void addCallableToMainThread(Object nmsServer, Callable callable); + + @Reflect.NMS(value = "Item.canDestroySpecialBlock()", parameters = IBlockData.class) + boolean canDestroySpecialBlock(Object nmsItem, Object blockData); + + @Reflect.NMS("Material.isAlwaysDestroyable") + boolean isAlwaysDestroyable(Object material); + + @Reflect.CB("inventory.CraftItemStack") + interface CraftItemStack { + @Reflect.StaticMethod(value = "asCraftCopy", parameters = ItemStack.class) + ItemStack asCraftCopy(ItemStack itemStack); + } + + @Reflect.NMS("NBTBase") + interface NBTBase {} + + @Reflect.NMS("NBTTagString") + interface NBTTagString { + + @Reflect.Constructor(String.class) + Object build(String string); + + @Reflect.Method("a_") + String getString(Object item); + }; + + @Reflect.NMS("NBTTagList") + interface NBTTagList { + + @Reflect.Field("list") + Object getListField(Object self); + + @Reflect.Constructor + Object build(); + + @Reflect.Method("size") + int size(Object self); + + @Reflect.Method(value = "get", parameters = int.class) + Object get(Object self, int index); + + @Reflect.Method(value = "add", parameters = NBTBase.class) + void add(Object self, Object value); + + @Reflect.Method("isEmpty") + boolean isEmpty(Object self); + } + + @Reflect.NMS("NBTTagCompound") + interface NBTTagCompound { + + @Reflect.Constructor + Object build(); + + @Reflect.Method(value = "getString", parameters = String.class) + String getString(Object self, String parameter); + + @Reflect.Method(value = "getLong", parameters = String.class) + long getLong(Object self, String parameter); + + @Reflect.Method(value = "getDouble", parameters = String.class) + double getDouble(Object self, String parameter); + + @Reflect.Method(value = "getInt", parameters = String.class) + int getInt(Object self, String parameter); + + @Reflect.Method( + value = "setString", + parameters = {String.class, String.class}) + void setString(Object self, String parameter, String value); + + @Reflect.Method( + value = "setLong", + parameters = {String.class, long.class}) + void setLong(Object self, String parameter, long value); + + @Reflect.Method( + value = "setDouble", + parameters = {String.class, double.class}) + void setDouble(Object self, String parameter, double value); + + @Reflect.Method( + value = "setInt", + parameters = {String.class, int.class}) + void setInt(Object self, String parameter, int value); + } + + @Reflect.CB("util.CraftMagicNumbers") + interface CraftMagicNumbers { + @Reflect.StaticMethod(value = "getItem", parameters = Material.class) + Object getItem(Material material); + + @Reflect.StaticMethod(value = "getBlock", parameters = Material.class) + Object getBlock(Material material); + } + + @Reflect.NMS("Block") + interface Block { + + @Reflect.StaticMethod(value = "getByName", parameters = String.class) + Object getBlockByName(String name); + + @Reflect.StaticMethod(value = "getId", parameters = Block.class) + int getId(Object self); + + @Reflect.Method("getBlockData") + Object getBlockData(Object self); + + @Reflect.Method("q") + Object getMaterial(Object self); + } + + @Reflect.NMS("IBlockData") + interface IBlockData {} +} diff --git a/util/src/main/java/tc/oc/pgm/util/nms/reflect/Reflect.java b/util/src/main/java/tc/oc/pgm/util/nms/reflect/Reflect.java new file mode 100644 index 0000000000..459ff3bcf1 --- /dev/null +++ b/util/src/main/java/tc/oc/pgm/util/nms/reflect/Reflect.java @@ -0,0 +1,52 @@ +package tc.oc.pgm.util.nms.reflect; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Reflect { + @Retention(RetentionPolicy.RUNTIME) + @interface NMS { + String value(); + + Class[] parameters() default {}; + } + + @Retention(RetentionPolicy.RUNTIME) + @interface CB { + String value(); + + Class[] parameters() default {}; + } + + @Retention(RetentionPolicy.RUNTIME) + @interface B { + String value(); + + Class[] parameters() default {}; + } + + @Retention(RetentionPolicy.RUNTIME) + @interface StaticMethod { + String value(); + + Class[] parameters() default {}; + } + + @Retention(RetentionPolicy.RUNTIME) + @interface Method { + String value(); + + Class[] parameters() default {}; + } + + @Retention(RetentionPolicy.RUNTIME) + @interface Field { + String value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @interface Constructor { + Class[] value() default {}; + } +} diff --git a/util/src/main/java/tc/oc/pgm/util/nms/reflect/ReflectionProxy.java b/util/src/main/java/tc/oc/pgm/util/nms/reflect/ReflectionProxy.java new file mode 100644 index 0000000000..00b96721e8 --- /dev/null +++ b/util/src/main/java/tc/oc/pgm/util/nms/reflect/ReflectionProxy.java @@ -0,0 +1,352 @@ +package tc.oc.pgm.util.nms.reflect; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import tc.oc.pgm.util.reflect.MinecraftReflectionUtils; +import tc.oc.pgm.util.reflect.ReflectionUtils; + +public class ReflectionProxy implements InvocationHandler { + + static Map> functionMap = new ConcurrentHashMap<>(); + + static ReflectionProxy INSTANCE = new ReflectionProxy(); + + public static T getProxy(Class classType) { + return (T) + Proxy.newProxyInstance( + ReflectionProxy.class.getClassLoader(), new Class[] {classType}, INSTANCE); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return functionMap + .computeIfAbsent( + method, + (input) -> { + Class declaringClass = method.getDeclaringClass(); + Class parentClass = getAnnotatedClass(declaringClass); + if (parentClass == null) { + return processComponent(method, args); + } else { + return processComponent(method, args, parentClass); + } + }) + .apply(args); + } + + @Nullable + private static Function processComponent(Method method, Object[] args) { + if (method.isAnnotationPresent(Reflect.NMS.class)) { + return processNMSComponent(method, args); + } else if (method.isAnnotationPresent(Reflect.CB.class)) { + return processCBComponent(method, args); + } else if (method.isAnnotationPresent(Reflect.B.class)) { + return processBukkitComponent(method, args); + } + throw new RuntimeException("Error processing Method: " + method); + } + + @Nullable + private static Function processComponent( + Method method, Object[] args, Class parentClass) { + if (method.isAnnotationPresent(Reflect.StaticMethod.class)) { + return processStaticMethod(method, parentClass); + } else if (method.isAnnotationPresent(Reflect.Method.class)) { + return processMethod(method, args, parentClass); + } else if (method.isAnnotationPresent(Reflect.Field.class)) { + return processField(method, args, parentClass); + } else if (method.isAnnotationPresent(Reflect.Constructor.class)) { + return processConstructor(method, parentClass); + } + throw new RuntimeException("Error processing method: " + method); + } + + private static Function processStaticMethod( + Method method, Class parentClass) { + for (Reflect.StaticMethod staticMethod : + method.getDeclaredAnnotationsByType(Reflect.StaticMethod.class)) { + try { + Method reflectedMethod = + parentClass.getMethod( + staticMethod.value(), processParameters(staticMethod.parameters())); + return (input) -> ReflectionUtils.callMethod(reflectedMethod, null, input); + } catch (NoSuchMethodException e) { + } + } + throw new RuntimeException("Method not found for " + method); + } + + private static Function processMethod( + Method method, Object[] args, Class parentClass) { + for (Reflect.Method staticMethod : method.getDeclaredAnnotationsByType(Reflect.Method.class)) { + try { + Method reflectedMethod = + parentClass.getMethod( + staticMethod.value(), processParameters(staticMethod.parameters())); + return callMethod(args, reflectedMethod); + } catch (NoSuchMethodException ignored) { + } + } + throw new RuntimeException("Method not found for " + method); + } + + @Nullable + private static Function processField( + Method method, Object[] args, Class parentClass) { + for (Reflect.Field field : method.getDeclaredAnnotationsByType(Reflect.Field.class)) { + try { + Field reflectedField = parentClass.getDeclaredField(field.value()); + reflectedField.setAccessible(true); + return performActionOnField(args, reflectedField); + } catch (NoSuchFieldException ignored) { + } + } + throw new RuntimeException("Field not found for " + method); + } + + @NotNull + private static Function processConstructor( + Method method, Class parentClass) { + for (Reflect.Constructor constructor : + method.getDeclaredAnnotationsByType(Reflect.Constructor.class)) { + try { + Constructor reflectedConstructor = + parentClass.getConstructor(processParameters(constructor.value())); + return (input) -> ReflectionUtils.callConstructor(reflectedConstructor, input); + } catch (NoSuchMethodException ignored) { + } + } + throw new RuntimeException("Constructor not found for " + method); + } + + @Nullable + private static Function processBukkitComponent(Method method, Object[] args) { + for (Reflect.B bukkit : method.getDeclaredAnnotationsByType(Reflect.B.class)) { + String methodPath = bukkit.value(); + Class[] parameters = bukkit.parameters(); + + String[] split = splitClassAndMethod(methodPath); + + String className = split[0]; + String methodName = split[1]; + + Class parentClass; + try { + parentClass = MinecraftReflectionUtils.getBukkitClass(className); + } catch (RuntimeException ignored) { + continue; + } + + if (methodName.endsWith("()")) { + Method reflectedMethod = parseStandaloneMethod(parentClass, methodName, parameters); + if (reflectedMethod != null) { + return callMethod(args, reflectedMethod); + } + } else { + Field reflectedField = parseStandaloneField(parentClass, methodName); + if (reflectedField != null) { + return performActionOnField(args, reflectedField); + } + } + } + throw new RuntimeException("Error processing Method: " + method); + } + + @Nullable + private static Function processCBComponent(Method method, Object[] args) { + for (Reflect.CB cb : method.getDeclaredAnnotationsByType(Reflect.CB.class)) { + String methodPath = cb.value(); + Class[] parameters = cb.parameters(); + + String[] split = splitClassAndMethod(methodPath); + + String className = split[0]; + String methodName = split[1]; + + Class parentClass; + try { + parentClass = MinecraftReflectionUtils.getCraftBukkitClass(className); + } catch (RuntimeException ignored) { + continue; + } + + if (methodName.endsWith("()")) { + Method reflectedMethod = parseStandaloneMethod(parentClass, methodName, parameters); + if (reflectedMethod != null) { + return callMethod(args, reflectedMethod); + } + } else { + Field reflectedField = parseStandaloneField(parentClass, methodName); + if (reflectedField != null) { + return performActionOnField(args, reflectedField); + } + } + } + throw new RuntimeException("Error processing Method: " + method); + } + + @Nullable + private static Function processNMSComponent(Method method, Object[] args) { + for (Reflect.NMS nms : method.getDeclaredAnnotationsByType(Reflect.NMS.class)) { + String methodPath = nms.value(); + Class[] parameters = nms.parameters(); + + String[] split = splitClassAndMethod(methodPath); + + String className = split[0]; + String methodName = split[1]; + + Class parentClass; + parentClass = findNMSClass(className); + if (parentClass == null) continue; + + if (methodName.endsWith("()")) { + Method reflectedMethod = parseStandaloneMethod(parentClass, methodName, parameters); + if (reflectedMethod != null) { + return callMethod(args, reflectedMethod); + } + } else { + Field reflectedField = parseStandaloneField(parentClass, methodName); + if (reflectedField != null) { + return performActionOnField(args, reflectedField); + } + } + } + throw new RuntimeException("Error processing Method: " + method); + } + + @Nullable + private static Function performActionOnField( + Object[] args, Field reflectedField) { + if (args.length == 0) { + throw new UnsupportedOperationException("Annotated fields must have more than 0 parameters!"); + } else if (args.length == 1) { + return (input) -> ReflectionUtils.readField(input[0], reflectedField); + } else if (args.length == 2) { + return (input) -> { + ReflectionUtils.setField(input[0], input[1], reflectedField); + return null; + }; + } else { + throw new UnsupportedOperationException("Annotated fields must have less than 3 parameters!"); + } + } + + private static Function callMethod(Object[] args, Method reflectedMethod) { + if (args.length > 1) { + return (input) -> + ReflectionUtils.callMethod( + reflectedMethod, input[0], Arrays.copyOfRange(input, 1, input.length)); + } else { + return (input) -> ReflectionUtils.callMethod(reflectedMethod, input[0]); + } + } + + @Nullable + private static Field parseStandaloneField(Class parentClass, String methodName) { + Field reflectedField; + try { + reflectedField = parentClass.getDeclaredField(methodName); + reflectedField.setAccessible(true); + } catch (NoSuchFieldException e) { + return null; + } + return reflectedField; + } + + @Nullable + private static Method parseStandaloneMethod( + Class parentClass, String methodName, Class[] parameters) { + Method reflectedMethod; + methodName = methodName.substring(0, methodName.length() - 2); + try { + if (parameters.length > 0) { + reflectedMethod = parentClass.getMethod(methodName, processParameters(parameters)); + } else { + reflectedMethod = parentClass.getMethod(methodName); + } + reflectedMethod.setAccessible(true); + } catch (NoSuchMethodException e) { + return null; + } + return reflectedMethod; + } + + private static Class[] processParameters(Class[] givenParameters) { + Class[] parameters = new Class[givenParameters.length]; + + for (int i = 0; i < givenParameters.length; i++) { + try { + Class annotatedClass = getAnnotatedClass(givenParameters[i]); + if (annotatedClass != null) { + parameters[i] = annotatedClass; + break; + } + } catch (RuntimeException ignored) { + } + parameters[i] = givenParameters[i]; + } + + return parameters; + } + + @NotNull + private static String[] splitClassAndMethod(String fieldMethodPath) { + String[] split = new String[2]; + int index = fieldMethodPath.lastIndexOf("."); + split[0] = fieldMethodPath.substring(0, index); + split[1] = fieldMethodPath.substring(index + 1); + return split; + } + + private static Class getAnnotatedClass(Class declaringClass) { + if (declaringClass.isAnnotationPresent(Reflect.NMS.class)) { + for (Reflect.NMS nms : declaringClass.getDeclaredAnnotationsByType(Reflect.NMS.class)) { + Class parentClass = findNMSClass(nms.value()); + if (parentClass != null) { + return parentClass; + } + } + throw new RuntimeException("Class not found for " + declaringClass); + } else if (declaringClass.isAnnotationPresent(Reflect.CB.class)) { + for (Reflect.CB cb : declaringClass.getDeclaredAnnotationsByType(Reflect.CB.class)) { + try { + return MinecraftReflectionUtils.getCraftBukkitClass(cb.value()); + } catch (RuntimeException ignored) { + } + } + throw new RuntimeException("Class not found for " + declaringClass); + } else if (declaringClass.isAnnotationPresent(Reflect.B.class)) { + for (Reflect.B bukkit : declaringClass.getDeclaredAnnotationsByType(Reflect.B.class)) { + try { + return MinecraftReflectionUtils.getBukkitClass(bukkit.value()); + } catch (RuntimeException ignored) { + } + } + throw new RuntimeException("Class not found for " + declaringClass); + } + return null; + } + + private static Class findNMSClass(String className) { + Class parentClass = null; + try { + parentClass = MinecraftReflectionUtils.getNMSClassLegacy(className); + } catch (RuntimeException ignored) { + } + try { + parentClass = MinecraftReflectionUtils.getNMSClassNew(className); + } catch (RuntimeException ignored) { + } + return parentClass; + } +} diff --git a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks1_8.java b/util/src/main/java/tc/oc/pgm/util/nms/v1_8/NMSHacks1_8.java similarity index 92% rename from util/src/main/java/tc/oc/pgm/util/nms/NMSHacks1_8.java rename to util/src/main/java/tc/oc/pgm/util/nms/v1_8/NMSHacks1_8.java index b9e3886b75..657681fc29 100644 --- a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks1_8.java +++ b/util/src/main/java/tc/oc/pgm/util/nms/v1_8/NMSHacks1_8.java @@ -1,9 +1,8 @@ -package tc.oc.pgm.util.nms; +package tc.oc.pgm.util.nms.v1_8; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.SetMultimap; -import com.google.common.util.concurrent.ListenableFutureTask; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; import java.lang.reflect.Constructor; @@ -109,17 +108,23 @@ import tc.oc.pgm.util.block.RayBlockIntersection; import tc.oc.pgm.util.bukkit.BukkitUtils; import tc.oc.pgm.util.bukkit.ViaUtils; +import tc.oc.pgm.util.nms.EnumPlayerInfoAction; +import tc.oc.pgm.util.nms.NMSHacksNoOp; import tc.oc.pgm.util.nms.attribute.AttributeMap1_8; import tc.oc.pgm.util.nms.entity.fake.FakeEntity; import tc.oc.pgm.util.nms.entity.fake.armorstand.FakeArmorStand1_8; import tc.oc.pgm.util.nms.entity.fake.wither.FakeWitherSkull1_8; import tc.oc.pgm.util.nms.entity.potion.EntityPotion; import tc.oc.pgm.util.nms.entity.potion.EntityPotion1_8; +import tc.oc.pgm.util.reflect.MinecraftReflectionUtils; import tc.oc.pgm.util.reflect.ReflectionUtils; import tc.oc.pgm.util.skin.Skin; import tc.oc.pgm.util.skin.Skins; -public class NMSHacks1_8 extends NMSHacksNoOp { +class NMSHacks1_8 extends NMSHacksNoOp { + + public NMSHacks1_8() {} + @Override public void sendPacket(Player bukkitPlayer, Object packet) { if (bukkitPlayer.isOnline()) { @@ -654,11 +659,6 @@ public Skin getPlayerSkin(Player player) { return Skins.fromProperties(craftPlayer.getProfile().getProperties()); } - @Override - public Skin getPlayerSkinForViewer(Player player, Player viewer) { - return getPlayerSkin(player); - } - @Override public void updateVelocity(Player player) { EntityPlayer handle = ((CraftPlayer) player).getHandle(); @@ -743,7 +743,8 @@ public Field getField() { } @Override - public void sendSpawnEntityPacket(Player player, int entityId, Location location) { + public void sendSpawnEntityPacket( + Player player, int entityId, Location location, Vector velocity) { PacketPlayOutSpawnEntity packet = new PacketPlayOutSpawnEntity(); ReflectionUtils.setField(packet, entityId, EntitySpawnFields.a.getField()); @@ -779,42 +780,47 @@ public void spawnFreezeEntity(Player player, int entityId, boolean legacy) { } else { Location loc = player.getLocation().subtract(0, 1.1, 0); - DataWatcher dataWatcher = new DataWatcher(null); - int flags = 0; - flags |= 0x20; - dataWatcher.a(0, (byte) flags); - dataWatcher.a(1, (short) 0); - int flags1 = 0; - dataWatcher.a(10, (byte) flags1); - PacketPlayOutSpawnEntityLiving packet = new PacketPlayOutSpawnEntityLiving(); - - ReflectionUtils.setField(packet, entityId, LivingEntitySpawnFields.a.getField()); - ReflectionUtils.setField( - packet, (byte) EntityType.ARMOR_STAND.getTypeId(), LivingEntitySpawnFields.b.getField()); - ReflectionUtils.setField( - packet, MathHelper.floor(loc.getX() * 32.0D), LivingEntitySpawnFields.c.getField()); - ReflectionUtils.setField( - packet, MathHelper.floor(loc.getY() * 32.0D), LivingEntitySpawnFields.d.getField()); - ReflectionUtils.setField( - packet, MathHelper.floor(loc.getZ() * 32.0D), LivingEntitySpawnFields.e.getField()); - ReflectionUtils.setField( - packet, - (byte) ((int) (((byte) loc.getYaw()) * 256.0F / 360.0F)), - LivingEntitySpawnFields.i.getField()); - ReflectionUtils.setField( - packet, - (byte) ((int) (((byte) loc.getPitch()) * 256.0F / 360.0F)), - LivingEntitySpawnFields.j.getField()); - ReflectionUtils.setField( - packet, - (byte) ((int) (((byte) loc.getPitch()) * 256.0F / 360.0F)), - LivingEntitySpawnFields.k.getField()); - ReflectionUtils.setField(packet, dataWatcher, LivingEntitySpawnFields.l.getField()); - - sendPacket(player, packet); + spawnFakeArmorStand(player, entityId, loc, new Vector()); } } + @Override + public void spawnFakeArmorStand(Player player, int entityId, Location location, Vector velocity) { + DataWatcher dataWatcher = new DataWatcher(null); + int flags = 0; + flags |= 0x20; + dataWatcher.a(0, (byte) flags); + dataWatcher.a(1, (short) 0); + int flags1 = 0; + dataWatcher.a(10, (byte) flags1); + PacketPlayOutSpawnEntityLiving packet = new PacketPlayOutSpawnEntityLiving(); + + ReflectionUtils.setField(packet, entityId, LivingEntitySpawnFields.a.getField()); + ReflectionUtils.setField( + packet, (byte) EntityType.ARMOR_STAND.getTypeId(), LivingEntitySpawnFields.b.getField()); + ReflectionUtils.setField( + packet, MathHelper.floor(location.getX() * 32.0D), LivingEntitySpawnFields.c.getField()); + ReflectionUtils.setField( + packet, MathHelper.floor(location.getY() * 32.0D), LivingEntitySpawnFields.d.getField()); + ReflectionUtils.setField( + packet, MathHelper.floor(location.getZ() * 32.0D), LivingEntitySpawnFields.e.getField()); + ReflectionUtils.setField( + packet, + (byte) ((int) (((byte) location.getYaw()) * 256.0F / 360.0F)), + LivingEntitySpawnFields.i.getField()); + ReflectionUtils.setField( + packet, + (byte) ((int) (((byte) location.getPitch()) * 256.0F / 360.0F)), + LivingEntitySpawnFields.j.getField()); + ReflectionUtils.setField( + packet, + (byte) ((int) (((byte) location.getPitch()) * 256.0F / 360.0F)), + LivingEntitySpawnFields.k.getField()); + ReflectionUtils.setField(packet, dataWatcher, LivingEntitySpawnFields.l.getField()); + + sendPacket(player, packet); + } + @Override public boolean canMineBlock(MaterialData blockMaterial, ItemStack tool) { if (!blockMaterial.getItemType().isBlock()) { @@ -848,7 +854,7 @@ public void resetDimension(World world) { Field unhandledTagsField = ReflectionUtils.getField( - "org.bukkit.craftbukkit.v1_8_R3.inventory.CraftMetaItem", "unhandledTags"); + MinecraftReflectionUtils.getCraftBukkitClass("inventory.CraftMetaItem"), "unhandledTags"); static Field nbtListField = ReflectionUtils.getField(NBTTagList.class, "list"); @Override @@ -911,14 +917,6 @@ public Set getCanPlaceOn(ItemMeta itemMeta) { return getMaterialCollection(itemMeta, "CanPlaceOn"); } - @Override - public void copyAttributeModifiers(ItemMeta destination, ItemMeta source) { - SetMultimap attributeModifiers = - getAttributeModifiers(source); - attributeModifiers.putAll(getAttributeModifiers(destination)); - applyAttributeModifiers(attributeModifiers, destination); - } - @Override public void applyAttributeModifiers( SetMultimap attributeModifiers, ItemMeta meta) { @@ -1048,24 +1046,10 @@ public AttributeMap buildAttributeMap(Player player) { return new AttributeMap1_8(player); } - public ListenableFutureTask constructFutureTask(Runnable task) { - final ListenableFutureTask future = ListenableFutureTask.create(task, null); - return future; - } - - static Field TASK_QUEUE = ReflectionUtils.getField(MinecraftServer.class, "j"); - @Override public void postToMainThread(Plugin plugin, boolean priority, Runnable task) { MinecraftServer server = ((CraftServer) plugin.getServer()).getHandle().getServer(); server.a(Executors.callable(task)); - - // ListenableFutureTask futureTask = constructFutureTask(task); - // Queue> taskQueue = (Queue>) ReflectionUtils.readField(server, - // TASK_QUEUE); - // synchronized (taskQueue) { - // taskQueue.add(futureTask); - // } } @NotNull diff --git a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacksSportPaper.java b/util/src/main/java/tc/oc/pgm/util/nms/v1_8/NMSHacksSportPaper.java similarity index 86% rename from util/src/main/java/tc/oc/pgm/util/nms/NMSHacksSportPaper.java rename to util/src/main/java/tc/oc/pgm/util/nms/v1_8/NMSHacksSportPaper.java index 0869a7bb58..1221040eee 100644 --- a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacksSportPaper.java +++ b/util/src/main/java/tc/oc/pgm/util/nms/v1_8/NMSHacksSportPaper.java @@ -1,4 +1,4 @@ -package tc.oc.pgm.util.nms; +package tc.oc.pgm.util.nms.v1_8; import com.google.common.collect.SetMultimap; import java.util.Collection; @@ -27,7 +27,9 @@ import org.bukkit.material.MaterialData; import org.bukkit.plugin.Plugin; import org.bukkit.scoreboard.NameTagVisibility; +import org.bukkit.util.Vector; import tc.oc.pgm.util.attribute.AttributeModifier; +import tc.oc.pgm.util.nms.EnumPlayerInfoAction; import tc.oc.pgm.util.skin.Skin; public class NMSHacksSportPaper extends NMSHacks1_8 { @@ -118,7 +120,8 @@ public void addPlayerInfoToPacket(Object packet, Object playerInfoData) { } @Override - public void sendSpawnEntityPacket(Player player, int entityId, Location location) { + public void sendSpawnEntityPacket( + Player player, int entityId, Location location, Vector velocity) { sendPacket( player, new PacketPlayOutSpawnEntity( @@ -126,9 +129,9 @@ public void sendSpawnEntityPacket(Player player, int entityId, Location location location.getX(), location.getY(), location.getZ(), - 0, - 0, - 0, + (int) (velocity.getX() * 8000), + (int) (velocity.getY() * 8000), + (int) (velocity.getZ() * 8000), (int) location.getPitch(), (int) location.getYaw(), 66, @@ -147,31 +150,34 @@ public void spawnFreezeEntity(Player player, int entityId, boolean legacy) { sendSpawnEntityPacket(player, entityId, location); } else { Location loc = player.getLocation().subtract(0, 1.1, 0); - int flags = 0; - flags |= 0x20; - DataWatcher dataWatcher = new DataWatcher(null); - dataWatcher.a(0, (byte) (byte) flags); - dataWatcher.a(1, (short) (short) 0); - int flags1 = 0; - dataWatcher.a(10, (byte) flags1); - sendPacket( - player, - new PacketPlayOutSpawnEntityLiving( - entityId, - (byte) EntityType.ARMOR_STAND.getTypeId(), - loc.getX(), - loc.getY(), - loc.getZ(), - loc.getYaw(), - loc.getPitch(), - loc.getPitch(), - 0, - 0, - 0, - dataWatcher)); + Vector velocity = new Vector(); + spawnFakeArmorStand(player, entityId, loc, velocity); } } + @Override + public void spawnFakeArmorStand(Player player, int entityId, Location loc, Vector velocity) { + DataWatcher dataWatcher = new DataWatcher(null); + dataWatcher.a(0, (byte) 0x20); + dataWatcher.a(1, (short) 0); + dataWatcher.a(10, (byte) 0); + sendPacket( + player, + new PacketPlayOutSpawnEntityLiving( + entityId, + (byte) EntityType.ARMOR_STAND.getTypeId(), + loc.getX(), + loc.getY(), + loc.getZ(), + loc.getYaw(), + loc.getPitch(), + loc.getPitch(), + (int) (velocity.getX() * 8000), + (int) (velocity.getY() * 8000), + (int) (velocity.getZ() * 8000), + dataWatcher)); + } + @Override public Skin getPlayerSkinForViewer(Player player, Player viewer) { return player.hasFakeSkin(viewer) diff --git a/util/src/main/java/tc/oc/pgm/util/nms/v1_9/NMSHacks1_9.java b/util/src/main/java/tc/oc/pgm/util/nms/v1_9/NMSHacks1_9.java new file mode 100644 index 0000000000..1143fb92d6 --- /dev/null +++ b/util/src/main/java/tc/oc/pgm/util/nms/v1_9/NMSHacks1_9.java @@ -0,0 +1,154 @@ +package tc.oc.pgm.util.nms.v1_9; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.SetMultimap; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Executors; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.material.MaterialData; +import org.bukkit.plugin.Plugin; +import tc.oc.pgm.util.attribute.AttributeMap; +import tc.oc.pgm.util.attribute.AttributeModifier; +import tc.oc.pgm.util.nms.attribute.AttributeMapBukkit; +import tc.oc.pgm.util.nms.reflect.Refl; +import tc.oc.pgm.util.nms.reflect.ReflectionProxy; + +public class NMSHacks1_9 extends NMSHacksProtocolLib { + static Refl refl = ReflectionProxy.getProxy(Refl.class); + static Refl.NBTTagString nbtTagString = ReflectionProxy.getProxy(Refl.NBTTagString.class); + static Refl.NBTTagList nbtTagList = ReflectionProxy.getProxy(Refl.NBTTagList.class); + static Refl.NBTTagCompound nbtTagCompound = ReflectionProxy.getProxy(Refl.NBTTagCompound.class); + static Refl.CraftMagicNumbers craftMagicNumbers = + ReflectionProxy.getProxy(Refl.CraftMagicNumbers.class); + static Refl.Block reflBlock = ReflectionProxy.getProxy(Refl.Block.class); + static Refl.CraftItemStack craftItemStack = ReflectionProxy.getProxy(Refl.CraftItemStack.class); + + @Override + public Set getMaterialCollection(ItemMeta itemMeta, String key) { + Map unhandledTags = refl.getUnhandledTags(itemMeta); + if (!unhandledTags.containsKey(key)) return EnumSet.noneOf(Material.class); + EnumSet materialSet = EnumSet.noneOf(Material.class); + Object canDestroyList = unhandledTags.get(key); + + for (Object item : (List) nbtTagList.getListField(canDestroyList)) { + String blockString = nbtTagString.getString(item); + Object nmsBlock = reflBlock.getBlockByName(blockString); + int id = reflBlock.getId(nmsBlock); + Material material = Material.getMaterial(id); + materialSet.add(material); + } + + return materialSet; + } + + @Override + public void setMaterialCollection( + ItemMeta itemMeta, Collection materials, String collectionName) { + Map unhandledTags = refl.getUnhandledTags(itemMeta); + Object canDestroyList = + unhandledTags.containsKey(collectionName) + ? unhandledTags.get(collectionName) + : nbtTagList.build(); + for (Material material : materials) { + Object block = craftMagicNumbers.getBlock(material); + + if (block != null) { + String blockString = block.toString(); // Format: Block{what we want} + blockString = blockString.substring(6, blockString.length() - 1); + Object nbtString = nbtTagString.build(blockString); + nbtTagList.add(canDestroyList, nbtString); + } + } + if (!nbtTagList.isEmpty(canDestroyList)) unhandledTags.put(collectionName, canDestroyList); + } + + @Override + public boolean canMineBlock(MaterialData blockMaterial, ItemStack tool) { + if (!blockMaterial.getItemType().isBlock()) { + throw new IllegalArgumentException("Material '" + blockMaterial + "' is not a block"); + } + + Object nmsBlock = craftMagicNumbers.getBlock(blockMaterial.getItemType()); + Object nmsTool = tool == null ? null : craftMagicNumbers.getItem(tool.getType()); + + Object iBlockData = reflBlock.getBlockData(nmsBlock); + + boolean alwaysDestroyable = refl.isAlwaysDestroyable(reflBlock.getMaterial(nmsBlock)); + boolean toolCanDestroy = nmsTool != null && refl.canDestroySpecialBlock(nmsTool, iBlockData); + return nmsBlock != null && (alwaysDestroyable || toolCanDestroy); + } + + @Override + public AttributeMap buildAttributeMap(Player player) { + return new AttributeMapBukkit(player); + } + + @Override + public void applyAttributeModifiers( + SetMultimap attributeModifiers, ItemMeta meta) { + Object list = nbtTagList.build(); + if (!attributeModifiers.isEmpty()) { + for (Map.Entry entry : attributeModifiers.entries()) { + AttributeModifier modifier = entry.getValue(); + Object tag = nbtTagCompound.build(); + nbtTagCompound.setString(tag, "Name", modifier.getName()); + nbtTagCompound.setDouble(tag, "Amount", modifier.getAmount()); + nbtTagCompound.setInt(tag, "Operation", modifier.getOperation().ordinal()); + nbtTagCompound.setLong(tag, "UUIDMost", modifier.getUniqueId().getMostSignificantBits()); + nbtTagCompound.setLong(tag, "UUIDLeast", modifier.getUniqueId().getLeastSignificantBits()); + nbtTagCompound.setString(tag, "AttributeName", entry.getKey()); + nbtTagList.add(list, tag); + } + + Map unhandledTags = refl.getUnhandledTags(meta); + + unhandledTags.put("AttributeModifiers", list); + } + } + + @Override + public SetMultimap getAttributeModifiers(ItemMeta meta) { + Map unhandledTags = refl.getUnhandledTags(meta); + if (unhandledTags.containsKey("AttributeModifiers")) { + final SetMultimap attributeModifiers = HashMultimap.create(); + final Object modTags = unhandledTags.get("AttributeModifiers"); + for (int i = 0; i < nbtTagList.size(modTags); i++) { + final Object modTag = nbtTagList.get(modTags, i); + attributeModifiers.put( + nbtTagCompound.getString(modTag, "AttributeName"), + new AttributeModifier( + new UUID( + nbtTagCompound.getLong(modTag, "UUIDMost"), + nbtTagCompound.getLong(modTag, "UUIDLeast")), + nbtTagCompound.getString(modTag, "Name"), + nbtTagCompound.getDouble(modTag, "Amount"), + AttributeModifier.Operation.fromOpcode( + nbtTagCompound.getInt(modTag, "Operation")))); + } + return attributeModifiers; + } else { + return HashMultimap.create(); + } + } + + @Override + public ItemStack craftItemCopy(ItemStack item) { + return craftItemStack.asCraftCopy(item); + } + + @Override + public void postToMainThread(Plugin plugin, boolean priority, Runnable task) { + Server bukkitServer = plugin.getServer(); + Object nmsServer = refl.getNMSServer(refl.getCraftServerHandle(bukkitServer)); + refl.addCallableToMainThread(nmsServer, Executors.callable(task)); + } +} diff --git a/util/src/main/java/tc/oc/pgm/util/nms/v1_9/NMSHacksProtocolLib.java b/util/src/main/java/tc/oc/pgm/util/nms/v1_9/NMSHacksProtocolLib.java new file mode 100644 index 0000000000..ba95bc4596 --- /dev/null +++ b/util/src/main/java/tc/oc/pgm/util/nms/v1_9/NMSHacksProtocolLib.java @@ -0,0 +1,642 @@ +package tc.oc.pgm.util.nms.v1_9; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.wrappers.BlockPosition; +import com.comphenix.protocol.wrappers.EnumWrappers; +import com.comphenix.protocol.wrappers.PlayerInfoData; +import com.comphenix.protocol.wrappers.WrappedBlockData; +import com.comphenix.protocol.wrappers.WrappedChatComponent; +import com.comphenix.protocol.wrappers.WrappedDataWatcher; +import com.comphenix.protocol.wrappers.WrappedGameProfile; +import com.comphenix.protocol.wrappers.WrappedSignedProperty; +import com.google.common.collect.Sets; +import io.github.bananapuncher714.nbteditor.NBTEditor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Fireball; +import org.bukkit.entity.Firework; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.scoreboard.NameTagVisibility; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import tc.oc.pgm.util.block.RayBlockIntersection; +import tc.oc.pgm.util.bukkit.ViaUtils; +import tc.oc.pgm.util.nms.EnumPlayerInfoAction; +import tc.oc.pgm.util.nms.NMSHacksNoOp; +import tc.oc.pgm.util.nms.entity.fake.FakeEntity; +import tc.oc.pgm.util.nms.entity.fake.armorstand.FakeArmorStandProtocolLib; +import tc.oc.pgm.util.nms.entity.fake.wither.FakeWitherSkullProtocolLib; +import tc.oc.pgm.util.skin.Skin; + +public abstract class NMSHacksProtocolLib extends NMSHacksNoOp { + + static ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager(); + + @Override + public void sendPacket(Player bukkitPlayer, Object packet) { + if (packet != null && bukkitPlayer != null) { + protocolManager.sendServerPacket(bukkitPlayer, (PacketContainer) packet); + } + } + + private List getViewingPlayers(Entity entity) { + // TODO: use radius defined in spigot.yml / sportpaper.yml + List players = new ArrayList<>(); + for (Entity nearbyEntity : + entity.getWorld().getNearbyEntities(entity.getLocation(), 64, 64, 64)) { + if (nearbyEntity instanceof Player && nearbyEntity.getEntityId() != entity.getEntityId()) { + players.add((Player) nearbyEntity); + } + } + return players; + } + + @Override + public void sendPacketToViewers(Entity entity, Object packet, boolean excludeSpectators) { + for (Player nearbyPlayer : getViewingPlayers(entity)) { + if (excludeSpectators) { + Entity spectatorTarget = nearbyPlayer.getSpectatorTarget(); + if (spectatorTarget != null && spectatorTarget.getUniqueId().equals(entity.getUniqueId())) + continue; + } + sendPacket(nearbyPlayer, packet); + } + } + + @Override + public void playDeathAnimation(Player player) { + float health = 0.0f; + PacketContainer metadataPacket = getMetadataPacket(player, health); + + sendPacketToViewers(player, metadataPacket, true); + + metadataPacket = getMetadataPacket(player, 1.0f); + sendPacket(player, metadataPacket); + + // Reimplement using Poses in 1.13+ + Location location = player.getLocation(); + PacketContainer useBedPacket = new PacketContainer(PacketType.Play.Server.USE_BED); + useBedPacket.getEntityModifier(player.getWorld()).write(0, player); + BlockPosition blockPosition = + new BlockPosition(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + useBedPacket.getBlockPositionModifier().write(0, blockPosition); + sendPacketToViewers(player, useBedPacket, true); + + Object teleport = teleportEntityPacket(player.getEntityId(), location); + sendPacketToViewers(player, teleport, true); + } + + @NotNull + private static PacketContainer getMetadataPacket(Player player, float health) { + PacketContainer metadataPacket = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); + + metadataPacket.getIntegers().write(0, player.getEntityId()); + + WrappedDataWatcher wrappedDataWatcher = WrappedDataWatcher.getEntityWatcher(player).deepClone(); + + WrappedDataWatcher.WrappedDataWatcherObject watcherObject = + new WrappedDataWatcher.WrappedDataWatcherObject( + 6, WrappedDataWatcher.Registry.get(Float.class)); + wrappedDataWatcher.setObject(watcherObject, health); + + metadataPacket + .getWatchableCollectionModifier() + .write(0, wrappedDataWatcher.getWatchableObjects()); + return metadataPacket; + } + + @Override + public Object teleportEntityPacket(int entityId, Location location) { + PacketContainer entityTeleportPacket = + new PacketContainer(PacketType.Play.Server.ENTITY_TELEPORT); + entityTeleportPacket.getIntegers().write(0, entityId); + + entityTeleportPacket + .getDoubles() + .write(0, location.getX()) + .write(1, location.getY()) + .write(2, location.getZ()); + + entityTeleportPacket + .getBytes() + .write(0, (byte) (location.getYaw() * 256 / 360)) + .write(1, (byte) (location.getPitch() * 256 / 360)); + + entityTeleportPacket.getBooleans().write(0, true); + + return entityTeleportPacket; + } + + @Override + public Object entityMetadataPacket(int entityId, Entity entity, boolean complete) { + PacketContainer packetContainer = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); + + packetContainer.getIntegers().write(0, entityId); + + WrappedDataWatcher wrappedDataWatcher = WrappedDataWatcher.getEntityWatcher(entity).deepClone(); + packetContainer.getDataWatcherModifier().write(0, wrappedDataWatcher); + + return packetContainer; + } + + @Override + public void skipFireworksLaunch(Firework firework) { + NBTEditor.set(firework, "Life", 2); + NBTEditor.set(firework, "LifeTime", 2); + sendPacketToViewers( + firework, entityMetadataPacket(firework.getEntityId(), firework, false), false); + } + + static Sound itemPickupSound = Sound.valueOf("ENTITY_ITEM_PICKUP"); + + @Override + public void fakePlayerItemPickup(Player player, Item item) { + float pitch = (((float) (Math.random() - Math.random()) * 0.7F + 1.0F) * 2.0F); + item.getWorld().playSound(item.getLocation(), itemPickupSound, 0.2F, pitch); + + PacketContainer packet = new PacketContainer(PacketType.Play.Server.COLLECT); + packet.getIntegers().write(0, item.getEntityId()); + packet.getIntegers().write(1, player.getEntityId()); + + sendPacketToViewers(item, packet, false); + + item.remove(); + } + + @Override + public void freezeEntity(Entity entity) { + NBTEditor.set(entity, true, "NoAI"); + NBTEditor.set(entity, true, "NoGravity"); + } + + @Override + public void setFireballDirection(Fireball entity, Vector direction) { + List doubles = new ArrayList<>(); + doubles.add(direction.getX() * 0.1D); + doubles.add(direction.getY() * 0.1D); + doubles.add(direction.getZ() * 0.1D); + NBTEditor.set(entity, doubles, "power"); + } + + @Override + public void removeAndAddAllTabPlayers(Player viewer) { + List playerInfoDataList = new ArrayList<>(); + + for (Player player : Bukkit.getOnlinePlayers()) { + if (viewer.canSee(player) || player == viewer) { + playerInfoDataList.add( + new PlayerInfoData( + WrappedGameProfile.fromPlayer(player), + getPing(player), + EnumWrappers.NativeGameMode.fromBukkit(viewer.getGameMode()), + WrappedChatComponent.fromLegacyText(player.getPlayerListName()))); + } + } + + PacketContainer removePlayerPacket = new PacketContainer(PacketType.Play.Server.PLAYER_INFO); + removePlayerPacket.getPlayerInfoAction().write(0, EnumWrappers.PlayerInfoAction.REMOVE_PLAYER); + + removePlayerPacket.getPlayerInfoDataLists().write(0, playerInfoDataList); + sendPacket(viewer, removePlayerPacket); + + PacketContainer addPlayerPacket = new PacketContainer(PacketType.Play.Server.PLAYER_INFO); + addPlayerPacket.getPlayerInfoAction().write(0, EnumWrappers.PlayerInfoAction.ADD_PLAYER); + + addPlayerPacket.getPlayerInfoDataLists().write(0, playerInfoDataList); + sendPacket(viewer, addPlayerPacket); + } + + @Override + public void sendLegacyWearing(Player player, int slot, ItemStack item) { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.ENTITY_EQUIPMENT); + + packet.getIntegers().write(0, player.getEntityId()); + + if (slot != 4) { + throw new UnsupportedOperationException( + "NMSHacks.entityEquipmentPacket needs to be refactored to support this!"); + } + packet.getItemSlots().write(0, EnumWrappers.ItemSlot.HEAD); + + packet.getItemModifier().write(0, item); + + for (Player viewingPlayer : getViewingPlayers(player)) { + if (ViaUtils.getProtocolVersion(viewingPlayer) <= ViaUtils.VERSION_1_7) { + sendPacket(viewingPlayer, packet); + } + } + } + + @Override + public void sendBlockChange(Location loc, Player player, @Nullable Material material) { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.BLOCK_CHANGE); + + BlockPosition blockPosition = new BlockPosition(loc.toVector()); + + packet.getBlockPositionModifier().write(0, blockPosition); + + WrappedBlockData data; + + if (material == null) { + Block block = loc.getBlock(); + data = WrappedBlockData.createData(block.getType(), block.getData()); + } else { + data = WrappedBlockData.createData(material); + } + + packet.getBlockData().write(0, data); + + sendPacket(player, packet); + } + + @Override + public void clearArrowsInPlayer(Player player) { + WrappedDataWatcher entityWatcher = WrappedDataWatcher.getEntityWatcher(player); + entityWatcher.setObject(9, (int) 0, true); + } + + @Override + public void showBorderWarning(Player player, boolean show) { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.WORLD_BORDER); + packet.getWorldBorderActions().write(0, EnumWrappers.WorldBorderAction.SET_WARNING_BLOCKS); + + packet.getIntegers().write(0, 29999984).write(1, 15).write(2, show ? Integer.MAX_VALUE : 0); + + packet.getDoubles().write(0, 0.0).write(1, 0.0).write(2, 6.0E7D).write(3, 6.0E7D); + packet.getLongs().write(0, 0L); + + sendPacket(player, packet); + } + + @Override + public Object entityEquipmentPacket(int entityId, int slot, ItemStack armor) { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.ENTITY_EQUIPMENT); + + packet.getIntegers().write(0, entityId); + + if (slot != 4) { + throw new UnsupportedOperationException( + "NMSHacks.entityEquipmentPacket needs to be refactored to support this!"); + } else { + packet.getItemSlots().write(0, EnumWrappers.ItemSlot.HEAD); + } + + packet.getItemModifier().write(0, armor); + + return packet; + } + + @Override + public void entityAttach(Player player, int entityID, int vehicleID, boolean leash) { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.MOUNT); + + packet.getIntegers().write(0, vehicleID); + + int[] ridingEntities = {entityID}; + + packet.getIntegerArrays().write(0, ridingEntities); + + sendPacket(player, packet); + } + + @Override + public FakeEntity fakeWitherSkull(World world) { + return new FakeWitherSkullProtocolLib(); + } + + @Override + public FakeEntity fakeArmorStand(World world, ItemStack head) { + return new FakeArmorStandProtocolLib(head); + } + + @Override + public Object spawnPlayerPacket(int entityId, UUID uuid, Location location, Player player) { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.NAMED_ENTITY_SPAWN); + + packet.getIntegers().write(0, entityId); + + packet + .getDoubles() + .write(0, location.getX()) + .write(1, location.getY()) + .write(2, location.getZ()); + + packet.getUUIDs().write(0, uuid); + + packet + .getBytes() + .write(0, (byte) (int) (location.getYaw() * 256.0F / 360.0F)) + .write(1, (byte) (int) (location.getPitch() * 256.0F / 360.0F)); + + WrappedDataWatcher dataWatcher = + player == null + ? new WrappedDataWatcher() + : WrappedDataWatcher.getEntityWatcher(player).deepClone(); + + packet.getDataWatcherModifier().write(0, dataWatcher); + + return packet; + } + + @Override + public Object destroyEntitiesPacket(int... entityIds) { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.ENTITY_DESTROY); + + packet.getIntegerArrays().write(0, entityIds); + + return packet; + } + + static EnumWrappers.PlayerInfoAction convertPlayerInfoAction( + EnumPlayerInfoAction enumPlayerInfoAction) { + switch (enumPlayerInfoAction) { + case ADD_PLAYER: + return EnumWrappers.PlayerInfoAction.ADD_PLAYER; + case UPDATE_GAME_MODE: + return EnumWrappers.PlayerInfoAction.UPDATE_GAME_MODE; + case UPDATE_LATENCY: + return EnumWrappers.PlayerInfoAction.UPDATE_LATENCY; + case UPDATE_DISPLAY_NAME: + return EnumWrappers.PlayerInfoAction.UPDATE_DISPLAY_NAME; + case REMOVE_PLAYER: + default: + return EnumWrappers.PlayerInfoAction.REMOVE_PLAYER; + } + } + + @Override + public Object createPlayerInfoPacket(EnumPlayerInfoAction action) { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.PLAYER_INFO); + + packet.getPlayerInfoAction().write(0, convertPlayerInfoAction(action)); + + return packet; + } + + @Override + public void setPotionParticles(Player player, boolean enabled) { + WrappedDataWatcher dataWatcher = WrappedDataWatcher.getEntityWatcher(player); + + if (enabled) { + Collection activePotionEffects = player.getActivePotionEffects(); + for (PotionEffect potionEffect : activePotionEffects) { + if (!potionEffect.isAmbient()) { + dataWatcher.setObject(8, false, true); + dataWatcher.setObject(7, potionEffect.getType().getId(), true); + } + } + } + dataWatcher.setObject(7, (int) 0, true); + dataWatcher.setObject(8, true, true); + } + + @Override + public RayBlockIntersection getTargetedBLock(Player player) { + Block targetBlock = player.getTargetBlock(Sets.newHashSet(Material.AIR), 4); + + // this hit location will cause some particles to hide inside the block in adventure mode maps + // with blockdrops + Vector hitLocation = targetBlock.getLocation().toVector().add(new Vector(0.5, 0.5, 0.5)); + + // BlockFace is unused, default up + return new RayBlockIntersection(targetBlock, BlockFace.UP, hitLocation); + } + + @Override + public boolean playerInfoDataListNotEmpty(Object packet) { + PacketContainer packetContainer = (PacketContainer) packet; + + StructureModifier> playerInfoDataListsModifier = + packetContainer.getPlayerInfoDataLists(); + return !playerInfoDataListsModifier.read(0).isEmpty(); + } + + @Override + public Object playerListPacketData( + Object packetPlayOutPlayerInfo, + UUID uuid, + String name, + GameMode gamemode, + int ping, + @Nullable Skin skin, + @Nullable String renderedDisplayName) { + + WrappedGameProfile wrappedGameProfile = new WrappedGameProfile(uuid, name); + + if (skin != null) { + WrappedSignedProperty property = + WrappedSignedProperty.fromValues("textures", skin.getData(), skin.getSignature()); + wrappedGameProfile.getProperties().put("textures", property); + } + + EnumWrappers.NativeGameMode nativeGameMode = + gamemode == null + ? EnumWrappers.NativeGameMode.CREATIVE + : EnumWrappers.NativeGameMode.fromBukkit(gamemode); + + WrappedChatComponent wrappedChatComponent = + renderedDisplayName == null + ? WrappedChatComponent.fromText("") + : WrappedChatComponent.fromJson(renderedDisplayName); + + return new PlayerInfoData(wrappedGameProfile, ping, nativeGameMode, wrappedChatComponent); + } + + @Override + public void addPlayerInfoToPacket(Object packet, Object playerInfoData) { + PacketContainer packetContainer = (PacketContainer) packet; + + StructureModifier> playerInfoDataListsModifier = + packetContainer.getPlayerInfoDataLists(); + List playerInfoDataList = playerInfoDataListsModifier.read(0); + playerInfoDataList.add((PlayerInfoData) playerInfoData); + playerInfoDataListsModifier.write(0, playerInfoDataList); + } + + @Override + public Skin getPlayerSkin(Player player) { + for (WrappedSignedProperty wrappedSignedProperty : + WrappedGameProfile.fromPlayer(player).getProperties().get("textures")) { + // return the first texture + return new Skin(wrappedSignedProperty.getValue(), wrappedSignedProperty.getSignature()); + } + + return null; + } + + @Override + public void updateVelocity(Player player) { + Vector velocity = player.getVelocity(); + PacketContainer packet = new PacketContainer(PacketType.Play.Server.ENTITY_VELOCITY); + packet + .getIntegers() + .write(0, player.getEntityId()) + .write(1, (int) (velocity.getX() * 8000)) + .write(2, (int) (velocity.getY() * 8000)) + .write(3, (int) (velocity.getZ() * 8000)); + sendPacket(player, packet); + } + + @Override + public void sendSpawnEntityPacket( + Player player, int entityId, Location location, Vector velocity) { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY); + packet + .getIntegers() + .write(0, entityId) + .write(1, (int) (velocity.getX() * 8000)) + .write(2, (int) (velocity.getY() * 8000)) + .write(3, (int) (velocity.getZ() * 8000)) + .write(4, (int) (location.getPitch() * 256.0F / 360.0F)) + .write(5, (int) (location.getYaw() * 256.0F / 360.0F)) + .write(6, 66); + + packet.getUUIDs().write(0, UUID.randomUUID()); + + packet + .getDoubles() + .write(0, location.getX()) + .write(1, location.getY()) + .write(2, location.getZ()); + + sendPacket(player, packet); + } + + @Override + public void spawnFreezeEntity(Player player, int entityId, boolean legacy) { + if (legacy) { + Location location = player.getLocation().add(0, 0.286, 0); + if (location.getY() < -64) { + location.setY(-64); + player.teleport(location); + } + + sendSpawnEntityPacket(player, entityId, location); + } else { + Location location = player.getLocation().subtract(0, 1.1, 0); + Vector velocity = new Vector(); + + spawnFakeArmorStand(player, entityId, location, velocity); + } + } + + @Override + public void spawnFakeArmorStand(Player player, int entityId, Location location, Vector velocity) { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY_LIVING); + packet + .getIntegers() + .write(0, entityId) + .write(1, 30) // armor stand + .write(2, (int) (velocity.getX() * 8000)) + .write(3, (int) (velocity.getY() * 8000)) + .write(4, (int) (velocity.getZ() * 8000)); + packet + .getDoubles() + .write(0, location.getX()) + .write(1, location.getY()) + .write(2, location.getZ()); + + packet + .getBytes() + .write(0, (byte) (int) (location.getYaw() * 256.0F / 360.0F)) + .write(1, (byte) (int) (location.getPitch() * 256.0F / 360.0F)) + .write(2, (byte) (int) (location.getPitch() * 256.0F / 360.0F)); + + packet.getUUIDs().write(0, UUID.randomUUID()); + + WrappedDataWatcher dataWatcher = new WrappedDataWatcher(); + dataWatcher.setObject( + new WrappedDataWatcher.WrappedDataWatcherObject( + 0, WrappedDataWatcher.Registry.get(Byte.class)), + (byte) 0x20); + dataWatcher.setObject( + new WrappedDataWatcher.WrappedDataWatcherObject( + 4, WrappedDataWatcher.Registry.get(Boolean.class)), + true); + dataWatcher.setObject( + new WrappedDataWatcher.WrappedDataWatcherObject( + 10, WrappedDataWatcher.Registry.get(Byte.class)), + (byte) 0x8); + + packet.getDataWatcherModifier().write(0, dataWatcher); + + sendPacket(player, packet); + } + + @Override + public Object teamPacket( + int operation, + String name, + String displayName, + String prefix, + String suffix, + boolean friendlyFire, + boolean seeFriendlyInvisibles, + NameTagVisibility nameTagVisibility, + Collection players) { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM); + + String nameTagVisString = null; + if (nameTagVisibility != null) { + switch (nameTagVisibility) { + case ALWAYS: + nameTagVisString = "always"; + break; + case NEVER: + nameTagVisString = "never"; + break; + case HIDE_FOR_OTHER_TEAMS: + nameTagVisString = "hideForOtherTeams"; + break; + case HIDE_FOR_OWN_TEAM: + nameTagVisString = "hideForOwnTeam"; + break; + } + } + + packet + .getStrings() + .write(0, name) + .write(1, displayName) + .write(2, prefix) + .write(3, suffix) + .write(4, nameTagVisString); + + int flags = 0; + if (friendlyFire) { + flags |= 1; + } + if (seeFriendlyInvisibles) { + flags |= 2; + } + + packet + .getIntegers() + .write(0, -1) // color + .write(1, operation) + .write(2, flags); + + packet.getSpecificModifier(Collection.class).write(0, players); + + return packet; + } +} diff --git a/util/src/main/java/tc/oc/pgm/util/reflect/MinecraftReflectionUtils.java b/util/src/main/java/tc/oc/pgm/util/reflect/MinecraftReflectionUtils.java index 985c02d311..2fbfe3a3d8 100644 --- a/util/src/main/java/tc/oc/pgm/util/reflect/MinecraftReflectionUtils.java +++ b/util/src/main/java/tc/oc/pgm/util/reflect/MinecraftReflectionUtils.java @@ -1,10 +1,28 @@ package tc.oc.pgm.util.reflect; +import io.github.bananapuncher714.nbteditor.NBTEditor; import org.bukkit.Bukkit; public interface MinecraftReflectionUtils { String VERSION = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; + NBTEditor.MinecraftVersion MINECRAFT_VERSION = NBTEditor.getMinecraftVersion(); + + double VERSION_NUMBER = parseVersionNumber(); + + static double parseVersionNumber() { + String[] split = VERSION.split("_"); + + double version = Double.parseDouble(split[1]); + ; + + if (split.length > 2) { + version += 0.1 * Double.parseDouble(split[2].substring(1)); + } + + return version; + } + static Class getBukkitClass(String classPath) { return ReflectionUtils.getClassFromName("org.bukkit." + classPath); } @@ -13,7 +31,19 @@ static Class getCraftBukkitClass(String classPath) { return ReflectionUtils.getClassFromName("org.bukkit.craftbukkit." + VERSION + "." + classPath); } - static Class getNMSClass(String classPath) { + static Class getNMSClassOriginal(String classPath, String newPath) { + if (MINECRAFT_VERSION.lessThanOrEqualTo(NBTEditor.MinecraftVersion.v1_16)) { + return ReflectionUtils.getClassFromName("net.minecraft.server." + VERSION + "." + classPath); + } else { + return ReflectionUtils.getClassFromName("net.minecraft." + newPath); + } + } + + static Class getNMSClassLegacy(String classPath) { return ReflectionUtils.getClassFromName("net.minecraft.server." + VERSION + "." + classPath); } + + static Class getNMSClassNew(String classPath) { + return ReflectionUtils.getClassFromName("net.minecraft." + classPath); + } } diff --git a/util/src/main/java/tc/oc/pgm/util/tablist/TablistResizer.java b/util/src/main/java/tc/oc/pgm/util/tablist/TablistResizer.java new file mode 100644 index 0000000000..50ea08cc6b --- /dev/null +++ b/util/src/main/java/tc/oc/pgm/util/tablist/TablistResizer.java @@ -0,0 +1,29 @@ +package tc.oc.pgm.util.tablist; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.events.ListenerPriority; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketEvent; +import org.bukkit.plugin.Plugin; + +public class TablistResizer { + private static final int TAB_SIZE = 80; + + public static void registerAdapter(Plugin plugin) { + ProtocolLibrary.getProtocolManager().addPacketListener(new TablistResizePacketAdapter(plugin)); + } + + private static class TablistResizePacketAdapter extends PacketAdapter { + + public TablistResizePacketAdapter(Plugin plugin) { + super(plugin, ListenerPriority.LOWEST, PacketType.Play.Server.LOGIN); + } + + @Override + public void onPacketSending(PacketEvent event) { + if (event.getPacketType() == PacketType.Play.Server.LOGIN) + event.getPacket().getIntegers().write(2, TAB_SIZE); + } + } +}