From 960827981518e9359afab7864f8920fd5d76a2d9 Mon Sep 17 00:00:00 2001 From: DerFrZocker Date: Wed, 29 May 2024 06:48:55 +1000 Subject: [PATCH] #1403, SPIGOT-4288, SPIGOT-6202: Add material rerouting in preparation for the switch to ItemType and BlockType This also moves the conversion from and to legacy material to the method calls of legacy plugins, and no longer allows them directly in the server. This has the side effect of fixing some legacy plugin issues, such as SPIGOT-4288, SPIGOT-6161. Also fixes legacy items sometimes not stacking in inventory when using addItem, a client disconnect when using legacy items in recipes and probably some more. --- .../craftbukkit/inventory/CraftItemStack.java | 2 +- .../bukkit/craftbukkit/legacy/CraftEvil.java | 18 +- .../craftbukkit/legacy/CraftLegacy.java | 15 +- .../craftbukkit/legacy/MaterialRerouting.java | 581 ++++++++++++++++++ .../bukkit/craftbukkit/util/Commodore.java | 20 +- .../legacy/MaterialReroutingTest.java | 117 ++++ .../legacy/RerouteValidationTest.java | 1 + 7 files changed, 729 insertions(+), 25 deletions(-) create mode 100644 src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java create mode 100644 src/test/java/org/bukkit/craftbukkit/legacy/MaterialReroutingTest.java diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java index 306397e55e5a..2240ef38da05 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java @@ -31,7 +31,7 @@ public static net.minecraft.world.item.ItemStack asNMSCopy(ItemStack original) { return net.minecraft.world.item.ItemStack.EMPTY; } - Item item = CraftMagicNumbers.getItem(original.getType(), original.getDurability()); + Item item = CraftItemType.bukkitToMinecraft(original.getType()); if (item == null) { return net.minecraft.world.item.ItemStack.EMPTY; diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/CraftEvil.java b/src/main/java/org/bukkit/craftbukkit/legacy/CraftEvil.java index c96d9940a1ce..00eac826194b 100644 --- a/src/main/java/org/bukkit/craftbukkit/legacy/CraftEvil.java +++ b/src/main/java/org/bukkit/craftbukkit/legacy/CraftEvil.java @@ -11,7 +11,6 @@ import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.block.CraftBlock; -import org.bukkit.craftbukkit.block.CraftBlockState; import org.bukkit.inventory.ItemStack; import org.bukkit.material.MaterialData; @@ -38,6 +37,15 @@ private CraftEvil() { // } + public static void setDurability(ItemStack itemStack, short durability) { + itemStack.setDurability(durability); + MaterialData materialData = CraftLegacy.toLegacyData(itemStack.getType(), true); + + if (materialData.getItemType().getMaxDurability() <= 0) { + itemStack.setType(CraftLegacy.fromLegacy(new MaterialData(materialData.getItemType(), (byte) itemStack.getDurability()), true)); + } + } + public static int getBlockTypeIdAt(World world, int x, int y, int z) { return getId(world.getBlockAt(x, y, z).getType()); } @@ -46,14 +54,6 @@ public static int getBlockTypeIdAt(World world, Location location) { return getId(world.getBlockAt(location).getType()); } - public static Material getType(Block block) { - return CraftLegacy.toLegacyMaterial(((CraftBlock) block).getNMS()); - } - - public static Material getType(BlockState block) { - return CraftLegacy.toLegacyMaterial(((CraftBlockState) block).getHandle()); - } - public static int getTypeId(Block block) { return getId(block.getType()); } diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java index e9172ec6b04d..bf30ca8784bc 100644 --- a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java +++ b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java @@ -67,10 +67,19 @@ public static Material toLegacy(Material material) { } public static MaterialData toLegacyData(Material material) { + return toLegacyData(material, false); + } + + public static MaterialData toLegacyData(Material material, boolean itemPriority) { Preconditions.checkArgument(!material.isLegacy(), "toLegacy on legacy Material"); - MaterialData mappedData; + MaterialData mappedData = null; - if (material.isBlock()) { + if (itemPriority) { + Item item = CraftMagicNumbers.getItem(material); + mappedData = itemToMaterial.get(item); + } + + if (mappedData == null && material.isBlock()) { Block block = CraftMagicNumbers.getBlock(material); IBlockData blockData = block.defaultBlockState(); @@ -84,7 +93,7 @@ public static MaterialData toLegacyData(Material material) { mappedData = itemToMaterial.get(block.asItem()); } } - } else { + } else if (!itemPriority) { Item item = CraftMagicNumbers.getItem(material); mappedData = itemToMaterial.get(item); } diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java b/src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java new file mode 100644 index 000000000000..8f70fb876712 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java @@ -0,0 +1,581 @@ +package org.bukkit.craftbukkit.legacy; + +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import org.bukkit.Bukkit; +import org.bukkit.ChunkSnapshot; +import org.bukkit.Keyed; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.RegionAccessor; +import org.bukkit.Server; +import org.bukkit.Statistic; +import org.bukkit.Tag; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.DecoratedPot; +import org.bukkit.block.Jukebox; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.craftbukkit.inventory.CraftItemType; +import org.bukkit.craftbukkit.legacy.reroute.InjectPluginVersion; +import org.bukkit.craftbukkit.legacy.reroute.RerouteStatic; +import org.bukkit.craftbukkit.tag.CraftBlockTag; +import org.bukkit.craftbukkit.tag.CraftItemTag; +import org.bukkit.craftbukkit.util.ApiVersion; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Animals; +import org.bukkit.entity.Boat; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.FallingBlock; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Piglin; +import org.bukkit.entity.Player; +import org.bukkit.entity.Steerable; +import org.bukkit.event.block.BlockCanBuildEvent; +import org.bukkit.event.block.BlockPhysicsEvent; +import org.bukkit.event.entity.EntityChangeBlockEvent; +import org.bukkit.event.inventory.FurnaceExtractEvent; +import org.bukkit.event.player.PlayerBucketEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerStatisticIncrementEvent; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.inventory.CookingRecipe; +import org.bukkit.inventory.FurnaceRecipe; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.RecipeChoice; +import org.bukkit.inventory.ShapedRecipe; +import org.bukkit.inventory.ShapelessRecipe; +import org.bukkit.inventory.StonecuttingRecipe; +import org.bukkit.inventory.meta.BlockDataMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.material.MaterialData; +import org.bukkit.packs.DataPackManager; +import org.bukkit.scoreboard.Criteria; + +public class MaterialRerouting { + + private static Material transformFromBlockType(Material blockType, ApiVersion version) { + if (blockType == null) { + return null; + } + + if (version.isOlderThan(ApiVersion.FLATTENING)) { + return CraftLegacy.toLegacyData(blockType, false).getItemType(); + } + + return blockType; + } + + private static Material transformFromItemType(Material itemType, ApiVersion version) { + if (itemType == null) { + return null; + } + + if (version.isOlderThan(ApiVersion.FLATTENING)) { + return CraftLegacy.toLegacyData(itemType, true).getItemType(); + } + + return itemType; + } + + private static Material transformToBlockType(Material material) { + if (material == null) { + return null; + } + + if (material.isLegacy()) { + return CraftLegacy.fromLegacy(new MaterialData(material), false); + } + + return material; + } + + private static Material transformToItemType(Material material) { + if (material == null) { + return null; + } + + if (material.isLegacy()) { + return CraftLegacy.fromLegacy(new MaterialData(material), true); + } + + return material; + } + + public static Material getMaterial(BlockData blockData, @InjectPluginVersion ApiVersion version) { + return transformFromBlockType(blockData.getMaterial(), version); + } + + public static Material getPlacementMaterial(BlockData blockData, @InjectPluginVersion ApiVersion version) { + return transformFromItemType(blockData.getPlacementMaterial(), version); + } + + public static Material getType(Block block, @InjectPluginVersion ApiVersion version) { + return transformFromBlockType(block.getType(), version); + } + + public static void setType(Block block, Material type) { + block.setType(transformToBlockType(type)); + } + + public static void setType(Block block, Material type, boolean applyPhysics) { + block.setType(transformToBlockType(type), applyPhysics); + } + + public static Material getType(BlockState blockState, @InjectPluginVersion ApiVersion version) { + return transformFromBlockType(blockState.getType(), version); + } + + public static void setType(BlockState blockState, Material type) { + blockState.setType(transformToBlockType(type)); + } + + public static void setSherd(DecoratedPot decoratedPot, DecoratedPot.Side side, Material sherd) { + decoratedPot.setSherd(side, transformToItemType(sherd)); + } + + public static Material getSherd(DecoratedPot decoratedPot, DecoratedPot.Side side, @InjectPluginVersion ApiVersion version) { + return transformFromItemType(decoratedPot.getSherd(side), version); + } + + public static Map getSherds(DecoratedPot decoratedPot, @InjectPluginVersion ApiVersion version) { + Map result = new EnumMap<>(DecoratedPot.Side.class); + for (Map.Entry entry : decoratedPot.getSherds().entrySet()) { + result.put(entry.getKey(), transformFromItemType(entry.getValue(), version)); + } + + return result; + } + + @Deprecated + public static List getShards(DecoratedPot decoratedPot, @InjectPluginVersion ApiVersion version) { + return decoratedPot.getSherds().values().stream().map(shard -> transformFromItemType(shard, version)).toList(); + } + + public static void setPlaying(Jukebox jukebox, Material record) { + jukebox.setPlaying(transformToItemType(record)); + } + + public static Material getPlaying(Jukebox jukebox, @InjectPluginVersion ApiVersion version) { + return transformFromItemType(jukebox.getPlaying(), version); + } + + public static boolean includes(EnchantmentTarget enchantmentTarget, Material item) { + return enchantmentTarget.includes(transformToItemType(item)); + } + + public static boolean isBreedItem(Animals animals, Material material) { + return animals.isBreedItem(transformToItemType(material)); + } + + public static Material getMaterial(Boat.Type type, @InjectPluginVersion ApiVersion version) { + return transformFromItemType(type.getMaterial(), version); + } + + @Deprecated + public static Material getMaterial(FallingBlock fallingBlock, @InjectPluginVersion ApiVersion version) { + return transformFromBlockType(fallingBlock.getBlockData().getMaterial(), version); + } + + public static boolean hasCooldown(HumanEntity humanEntity, Material material) { + return humanEntity.hasCooldown(transformToItemType(material)); + } + + public static int getCooldown(HumanEntity humanEntity, Material material) { + return humanEntity.getCooldown(transformToItemType(material)); + } + + public static void setCooldown(HumanEntity humanEntity, Material material, int ticks) { + humanEntity.setCooldown(transformToItemType(material), ticks); + } + + public static List getLineOfSight(LivingEntity livingEntity, Set transparent, int maxDistance) { + if (transparent == null) { + return livingEntity.getLineOfSight(null, maxDistance); + } + + return livingEntity.getLineOfSight(transparent.stream().map(MaterialRerouting::transformToBlockType).collect(Collectors.toSet()), maxDistance); + } + + public static Block getTargetBlock(LivingEntity livingEntity, Set transparent, int maxDistance) { + if (transparent == null) { + return livingEntity.getTargetBlock(null, maxDistance); + } + + return livingEntity.getTargetBlock(transparent.stream().map(MaterialRerouting::transformToBlockType).collect(Collectors.toSet()), maxDistance); + } + + public static List getLastTwoTargetBlocks(LivingEntity livingEntity, Set transparent, int maxDistance) { + if (transparent == null) { + return livingEntity.getLastTwoTargetBlocks(null, maxDistance); + } + + return livingEntity.getLastTwoTargetBlocks(transparent.stream().map(MaterialRerouting::transformToBlockType).collect(Collectors.toSet()), maxDistance); + } + + public static boolean addBarterMaterial(Piglin piglin, Material material) { + return piglin.addBarterMaterial(transformToItemType(material)); + } + + public static boolean removeBarterMaterial(Piglin piglin, Material material) { + return piglin.removeBarterMaterial(transformToItemType(material)); + } + + public static boolean addMaterialOfInterest(Piglin piglin, Material material) { + return piglin.addMaterialOfInterest(transformToItemType(material)); + } + + public static boolean removeMaterialOfInterest(Piglin piglin, Material material) { + return piglin.removeMaterialOfInterest(transformToItemType(material)); + } + + public static Set getInterestList(Piglin piglin, @InjectPluginVersion ApiVersion version) { + return piglin.getInterestList().stream().map(item -> transformFromItemType(item, version)).collect(Collectors.toSet()); + } + + public static Set getBarterList(Piglin piglin, @InjectPluginVersion ApiVersion version) { + return piglin.getBarterList().stream().map(item -> transformFromItemType(item, version)).collect(Collectors.toSet()); + } + + @Deprecated + public static void sendBlockChange(Player player, Location location, Material material, byte data) { + player.sendBlockChange(location, CraftBlockData.fromData(CraftMagicNumbers.getBlock(material, data))); + } + + public static Material getSteerMaterial(Steerable steerable, @InjectPluginVersion ApiVersion version) { + return transformFromItemType(steerable.getSteerMaterial(), version); + } + + public static Material getMaterial(BlockCanBuildEvent blockCanBuildEvent, @InjectPluginVersion ApiVersion version) { + return transformFromBlockType(blockCanBuildEvent.getMaterial(), version); + } + + public static Material getChangedType(BlockPhysicsEvent blockPhysicsEvent, @InjectPluginVersion ApiVersion version) { + return transformFromBlockType(blockPhysicsEvent.getChangedType(), version); + } + + public static Material getTo(EntityChangeBlockEvent entityChangeBlockEvent, @InjectPluginVersion ApiVersion version) { + return transformFromBlockType(entityChangeBlockEvent.getTo(), version); + } + + public static Material getItemType(FurnaceExtractEvent furnaceExtractEvent, @InjectPluginVersion ApiVersion version) { + return transformFromItemType(furnaceExtractEvent.getItemType(), version); + } + + public static Material getBucket(PlayerBucketEvent playerBucketEvent, @InjectPluginVersion ApiVersion version) { + return transformFromItemType(playerBucketEvent.getBucket(), version); + } + + public static Material getMaterial(PlayerInteractEvent playerInteractEvent, @InjectPluginVersion ApiVersion version) { + return transformFromItemType(playerInteractEvent.getMaterial(), version); + } + + public static Material getMaterial(PlayerStatisticIncrementEvent playerStatisticIncrementEvent, @InjectPluginVersion ApiVersion version) { + if (playerStatisticIncrementEvent.getStatistic().getType() == Statistic.Type.BLOCK) { + return transformFromBlockType(playerStatisticIncrementEvent.getMaterial(), version); + } else if (playerStatisticIncrementEvent.getStatistic().getType() == Statistic.Type.ITEM) { + return transformFromItemType(playerStatisticIncrementEvent.getMaterial(), version); + } else { + // Theoretically this should be null, but just in case convert from block type + // Can probably check if it is not null and print a warning, but for now it should be fine without the check. + return transformFromBlockType(playerStatisticIncrementEvent.getMaterial(), version); + } + } + + public static void setBlock(ChunkGenerator.ChunkData chunkData, int x, int y, int z, Material material) { + chunkData.setBlock(x, y, z, transformToBlockType(material)); + } + + public static void setRegion(ChunkGenerator.ChunkData chunkData, int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) { + chunkData.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, transformToBlockType(material)); + } + + public static Material getType(ChunkGenerator.ChunkData chunkData, int x, int y, int z, @InjectPluginVersion ApiVersion version) { + return transformFromBlockType(chunkData.getType(x, y, z), version); + } + + public static BlockData getBlockData(BlockDataMeta blockDataMeta, Material material) { + return blockDataMeta.getBlockData(transformToBlockType(material)); + } + + public static CookingRecipe setInput(CookingRecipe cookingRecipe, Material material) { + return cookingRecipe.setInput(transformToItemType(material)); + } + + public static FurnaceRecipe setInput(FurnaceRecipe furnaceRecipe, Material material) { + return furnaceRecipe.setInput(transformToItemType(material)); + } + + @Deprecated + public static FurnaceRecipe setInput(FurnaceRecipe furnaceRecipe, Material material, int data) { + return furnaceRecipe.setInput(CraftItemType.minecraftToBukkit(CraftMagicNumbers.getItem(material, (short) data))); + } + + public static boolean contains(Inventory inventory, Material material) { + return inventory.contains(transformToItemType(material)); + } + + public static boolean contains(Inventory inventory, Material material, int amount) { + return inventory.contains(transformToItemType(material), amount); + } + + public static HashMap all(Inventory inventory, Material material) { + return inventory.all(transformToItemType(material)); + } + + public static int first(Inventory inventory, Material material) { + return inventory.first(transformToItemType(material)); + } + + public static void remove(Inventory inventory, Material material) { + inventory.remove(transformToItemType(material)); + } + + public static ItemMeta getItemMeta(ItemFactory itemFactory, Material material) { + return itemFactory.getItemMeta(transformToItemType(material)); + } + + public static boolean isApplicable(ItemFactory itemFactory, ItemMeta itemMeta, Material material) { + return itemFactory.isApplicable(itemMeta, transformToItemType(material)); + } + + public static ItemMeta asMetaFor(ItemFactory itemFactory, ItemMeta itemMeta, Material material) { + return itemFactory.asMetaFor(itemMeta, transformToItemType(material)); + } + + public static Material getSpawnEgg(ItemFactory itemFactory, EntityType entityType, @InjectPluginVersion ApiVersion version) { + return transformFromItemType(itemFactory.getSpawnEgg(entityType), version); + } + + public static Material getType(ItemStack itemStack, @InjectPluginVersion ApiVersion version) { + return transformFromItemType(itemStack.getType(), version); + } + + public static void setType(ItemStack itemStack, Material material) { + itemStack.setType(transformToItemType(material)); + } + + public static List getChoices(RecipeChoice.MaterialChoice materialChoice, @InjectPluginVersion ApiVersion version) { + return materialChoice.getChoices().stream().map(m -> transformFromItemType(m, version)).toList(); + } + + public static ShapedRecipe setIngredient(ShapedRecipe shapedRecipe, char key, Material material) { + return shapedRecipe.setIngredient(key, transformToItemType(material)); + } + + @Deprecated + public static ShapedRecipe setIngredient(ShapedRecipe shapedRecipe, char key, Material material, int raw) { + return shapedRecipe.setIngredient(key, CraftItemType.minecraftToBukkit(CraftMagicNumbers.getItem(material, (short) raw))); + } + + public static ShapelessRecipe addIngredient(ShapelessRecipe shapelessRecipe, Material material) { + return shapelessRecipe.addIngredient(transformToItemType(material)); + } + + @Deprecated + public static ShapelessRecipe addIngredient(ShapelessRecipe shapelessRecipe, Material material, int raw) { + return shapelessRecipe.addIngredient(CraftItemType.minecraftToBukkit(CraftMagicNumbers.getItem(material, (short) raw))); + } + + public static ShapelessRecipe addIngredient(ShapelessRecipe shapelessRecipe, int count, Material material) { + return shapelessRecipe.addIngredient(count, transformToItemType(material)); + } + + @Deprecated + public static ShapelessRecipe addIngredient(ShapelessRecipe shapelessRecipe, int count, Material material, int raw) { + return shapelessRecipe.addIngredient(count, CraftItemType.minecraftToBukkit(CraftMagicNumbers.getItem(material, (short) raw))); + } + + public static ShapelessRecipe removeIngredient(ShapelessRecipe shapelessRecipe, Material material) { + return shapelessRecipe.removeIngredient(transformToItemType(material)); + } + + public static ShapelessRecipe removeIngredient(ShapelessRecipe shapelessRecipe, int count, Material material) { + return shapelessRecipe.removeIngredient(count, transformToItemType(material)); + } + + @Deprecated + public static ShapelessRecipe removeIngredient(ShapelessRecipe shapelessRecipe, Material material, int raw) { + return shapelessRecipe.removeIngredient(CraftItemType.minecraftToBukkit(CraftMagicNumbers.getItem(material, (short) raw))); + } + + @Deprecated + public static ShapelessRecipe removeIngredient(ShapelessRecipe shapelessRecipe, int count, Material material, int raw) { + return shapelessRecipe.removeIngredient(count, CraftItemType.minecraftToBukkit(CraftMagicNumbers.getItem(material, (short) raw))); + } + + public static StonecuttingRecipe setInput(StonecuttingRecipe stonecuttingRecipe, Material material) { + return stonecuttingRecipe.setInput(transformToItemType(material)); + } + + public static boolean isEnabledByFeature(DataPackManager dataPackManager, Material material, World world) { + return dataPackManager.isEnabledByFeature(transformToItemType(material), world); + } + + @RerouteStatic("org/bukkit/scoreboard/Criteria") + public static Criteria statistic(Statistic statistic, Material material) { + if (statistic.getType() == Statistic.Type.BLOCK) { + return Criteria.statistic(statistic, transformToBlockType(material)); + } else if (statistic.getType() == Statistic.Type.ITEM) { + return Criteria.statistic(statistic, transformToItemType(material)); + } else { + // This is not allowed, the method will throw an error + return Criteria.statistic(statistic, transformToBlockType(material)); + } + } + + @RerouteStatic("org/bukkit/Bukkit") + public static BlockData createBlockData(Material material) { + return Bukkit.createBlockData(transformToBlockType(material)); + } + + @RerouteStatic("org/bukkit/Bukkit") + public static BlockData createBlockData(Material material, Consumer consumer) { + return Bukkit.createBlockData(transformToBlockType(material), consumer); + } + + @RerouteStatic("org/bukkit/Bukkit") + public static BlockData createBlockData(Material material, String data) { + return Bukkit.createBlockData(transformToBlockType(material), data); + } + + public static Material getBlockType(ChunkSnapshot chunkSnapshot, int x, int y, int z, @InjectPluginVersion ApiVersion version) { + return transformFromBlockType(chunkSnapshot.getBlockType(x, y, z), version); + } + + public static void incrementStatistic(OfflinePlayer offlinePlayer, Statistic statistic, Material material) { + if (statistic.getType() == Statistic.Type.BLOCK) { + offlinePlayer.incrementStatistic(statistic, transformToBlockType(material)); + } else if (statistic.getType() == Statistic.Type.ITEM) { + offlinePlayer.incrementStatistic(statistic, transformToItemType(material)); + } else { + // This is not allowed, the method will throw an error + offlinePlayer.incrementStatistic(statistic, transformToBlockType(material)); + } + } + + public static void decrementStatistic(OfflinePlayer offlinePlayer, Statistic statistic, Material material) { + if (statistic.getType() == Statistic.Type.BLOCK) { + offlinePlayer.decrementStatistic(statistic, transformToBlockType(material)); + } else if (statistic.getType() == Statistic.Type.ITEM) { + offlinePlayer.decrementStatistic(statistic, transformToItemType(material)); + } else { + // This is not allowed, the method will throw an error + offlinePlayer.decrementStatistic(statistic, transformToBlockType(material)); + } + } + + public static int getStatistic(OfflinePlayer offlinePlayer, Statistic statistic, Material material) { + if (statistic.getType() == Statistic.Type.BLOCK) { + return offlinePlayer.getStatistic(statistic, transformToBlockType(material)); + } else if (statistic.getType() == Statistic.Type.ITEM) { + return offlinePlayer.getStatistic(statistic, transformToItemType(material)); + } else { + // This is not allowed, the method will throw an error + return offlinePlayer.getStatistic(statistic, transformToBlockType(material)); + } + } + + public static void incrementStatistic(OfflinePlayer offlinePlayer, Statistic statistic, Material material, int amount) { + if (statistic.getType() == Statistic.Type.BLOCK) { + offlinePlayer.incrementStatistic(statistic, transformToBlockType(material), amount); + } else if (statistic.getType() == Statistic.Type.ITEM) { + offlinePlayer.incrementStatistic(statistic, transformToItemType(material), amount); + } else { + // This is not allowed, the method will throw an error + offlinePlayer.incrementStatistic(statistic, transformToBlockType(material), amount); + } + } + + public static void decrementStatistic(OfflinePlayer offlinePlayer, Statistic statistic, Material material, int amount) { + if (statistic.getType() == Statistic.Type.BLOCK) { + offlinePlayer.decrementStatistic(statistic, transformToBlockType(material), amount); + } else if (statistic.getType() == Statistic.Type.ITEM) { + offlinePlayer.decrementStatistic(statistic, transformToItemType(material), amount); + } else { + // This is not allowed, the method will throw an error + offlinePlayer.decrementStatistic(statistic, transformToBlockType(material), amount); + } + } + + public static void setStatistic(OfflinePlayer offlinePlayer, Statistic statistic, Material material, int newValue) { + if (statistic.getType() == Statistic.Type.BLOCK) { + offlinePlayer.setStatistic(statistic, transformToBlockType(material), newValue); + } else if (statistic.getType() == Statistic.Type.ITEM) { + offlinePlayer.setStatistic(statistic, transformToItemType(material), newValue); + } else { + // This is not allowed, the method will throw an error + offlinePlayer.setStatistic(statistic, transformToBlockType(material), newValue); + } + } + + public static Material getType(RegionAccessor regionAccessor, Location location, @InjectPluginVersion ApiVersion version) { + return transformFromBlockType(regionAccessor.getType(location), version); + } + + public static Material getType(RegionAccessor regionAccessor, int x, int y, int z, @InjectPluginVersion ApiVersion version) { + return transformFromBlockType(regionAccessor.getType(x, y, z), version); + } + + public static void setType(RegionAccessor regionAccessor, Location location, Material material) { + regionAccessor.setType(location, transformToBlockType(material)); + } + + public static void setType(RegionAccessor regionAccessor, int x, int y, int z, Material material) { + regionAccessor.setType(x, y, z, transformToBlockType(material)); + } + + public static BlockData createBlockData(Server server, Material material) { + return server.createBlockData(transformToBlockType(material)); + } + + public static BlockData createBlockData(Server server, Material material, Consumer consumer) { + return server.createBlockData(transformToBlockType(material), consumer); + } + + public static BlockData createBlockData(Server server, Material material, String data) { + return server.createBlockData(transformToBlockType(material), data); + } + + public static boolean isTagged(Tag tag, T item) { + if (tag instanceof CraftBlockTag) { + return tag.isTagged((T) transformToBlockType((Material) item)); + } else if (tag instanceof CraftItemTag) { + return tag.isTagged((T) transformToItemType((Material) item)); + } + + return tag.isTagged(item); + } + + public static Set getValues(Tag tag, @InjectPluginVersion ApiVersion version) { + Set values = tag.getValues(); + if (values.isEmpty()) { + return values; + } + + if (tag instanceof CraftBlockTag) { + return values.stream().map(val -> (Material) val).map(val -> transformFromBlockType(val, version)).map(val -> (T) val).collect(Collectors.toSet()); + } else if (tag instanceof CraftItemTag) { + return values.stream().map(val -> (Material) val).map(val -> transformFromItemType(val, version)).map(val -> (T) val).collect(Collectors.toSet()); + } + + return values; + } + + @Deprecated + public static FallingBlock spawnFallingBlock(World world, Location location, Material material, byte data) { + return world.spawnFallingBlock(location, CraftBlockData.fromData(CraftMagicNumbers.getBlock(material, data))); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java index 4f06d72c8ddd..7e51bfa6037f 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java @@ -22,6 +22,7 @@ import joptsimple.OptionSpec; import org.bukkit.Material; import org.bukkit.craftbukkit.legacy.FieldRename; +import org.bukkit.craftbukkit.legacy.MaterialRerouting; import org.bukkit.craftbukkit.legacy.reroute.RerouteArgument; import org.bukkit.craftbukkit.legacy.reroute.RerouteBuilder; import org.bukkit.craftbukkit.legacy.reroute.RerouteMethodData; @@ -55,7 +56,8 @@ public class Commodore { "org/bukkit/block/Block (B)V setData", "org/bukkit/block/Block (BZ)V setData", "org/bukkit/inventory/ItemStack ()I getTypeId", - "org/bukkit/inventory/ItemStack (I)V setTypeId" + "org/bukkit/inventory/ItemStack (I)V setTypeId", + "org/bukkit/inventory/ItemStack (S)V setDurability" )); private static final Map RENAMES = Map.of( @@ -73,6 +75,7 @@ private static Map createReroutes(Class clazz) { @VisibleForTesting public static final List> REROUTES = new ArrayList<>(); // Only used for testing private static final Map FIELD_RENAME_METHOD_REROUTE = createReroutes(FieldRename.class); + private static final Map MATERIAL_METHOD_REROUTE = createReroutes(MaterialRerouting.class); public static void main(String[] args) { OptionParser parser = new OptionParser(); @@ -327,10 +330,10 @@ private void handleMethod(MethodPrinter visitor, int opcode, String owner, Strin Type retType = Type.getReturnType(desc); + // TODO 2024-05-22: This can be moved over to use the reroute api if (EVIL.contains(owner + " " + desc + " " + name) || (owner.startsWith("org/bukkit/block/") && (desc + " " + name).equals("()I getTypeId")) - || (owner.startsWith("org/bukkit/block/") && (desc + " " + name).equals("(I)Z setTypeId")) - || (owner.startsWith("org/bukkit/block/") && (desc + " " + name).equals("()Lorg/bukkit/Material; getType"))) { + || (owner.startsWith("org/bukkit/block/") && (desc + " " + name).equals("(I)Z setTypeId"))) { Type[] args = Type.getArgumentTypes(desc); Type[] newArgs = new Type[args.length + 1]; newArgs[0] = Type.getObjectType(owner); @@ -370,15 +373,8 @@ private void handleMethod(MethodPrinter visitor, int opcode, String owner, Strin } } - // TODO: 4/23/23 Handle for InvokeDynamicInsn, does not directly work, since it adds a new method call which InvokeDynamicInsn does not like - // The time required to fixe this is probably higher than the return, - // One possible way could be to write a custom method and delegate the dynamic call to it, - // the method would be needed to be written with asm, to account for different amount of arguments and which normally should be visited - // Or a custom factory is created, this would be a very fancy (but probably overkill) solution - // Anyway, I encourage everyone who is reading this to to give it a shot - if (instantiatedMethodType == null && retType.getSort() == Type.OBJECT && retType.getInternalName().equals("org/bukkit/Material") && owner.startsWith("org/bukkit")) { - visitor.visit(opcode, owner, name, desc, itf, samMethodType, instantiatedMethodType); - visitor.visit(Opcodes.INVOKESTATIC, "org/bukkit/craftbukkit/legacy/CraftLegacy", "toLegacy", "(Lorg/bukkit/Material;)Lorg/bukkit/Material;", false, samMethodType, instantiatedMethodType); + // TODO 2024-05-21: Move this up, when material gets fully replaced with ItemType and BlockType + if (owner.startsWith("org/bukkit") && checkReroute(visitor, MATERIAL_METHOD_REROUTE, opcode, owner, name, desc, samMethodType, instantiatedMethodType)) { return; } diff --git a/src/test/java/org/bukkit/craftbukkit/legacy/MaterialReroutingTest.java b/src/test/java/org/bukkit/craftbukkit/legacy/MaterialReroutingTest.java new file mode 100644 index 000000000000..fa4bd5aba18f --- /dev/null +++ b/src/test/java/org/bukkit/craftbukkit/legacy/MaterialReroutingTest.java @@ -0,0 +1,117 @@ +package org.bukkit.craftbukkit.legacy; + +import static org.junit.jupiter.api.Assertions.*; +import com.google.common.base.Joiner; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.jar.JarFile; +import java.util.stream.Stream; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.legacy.reroute.RerouteBuilder; +import org.bukkit.craftbukkit.legacy.reroute.RerouteMethodData; +import org.bukkit.craftbukkit.util.Commodore; +import org.bukkit.support.AbstractTestingBase; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +public class MaterialReroutingTest extends AbstractTestingBase { + + // Needs to be a bukkit class + private static final URI BUKKIT_CLASSES; + private static final Map MATERIAL_METHOD_REROUTE = RerouteBuilder.buildFromClass(MaterialRerouting.class); + + static { + try { + BUKKIT_CLASSES = Bukkit.class.getProtectionDomain().getCodeSource().getLocation().toURI(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + private static JarFile jarFile = null; + + public static Stream bukkitData() { + return jarFile + .stream() + .filter(entry -> entry.getName().endsWith(".class")) + // Add class exceptions here + .filter(entry -> !entry.getName().endsWith("Material.class")) + .filter(entry -> !entry.getName().endsWith("UnsafeValues.class")) + .filter(entry -> !entry.getName().endsWith("BlockType.class")) + .filter(entry -> !entry.getName().endsWith("ItemType.class")) + .filter(entry -> !entry.getName().endsWith("Registry.class")) + .filter(entry -> !entry.getName().startsWith("org/bukkit/material")) + .map(entry -> { + try { + return jarFile.getInputStream(entry); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).map(Arguments::arguments); + } + + @BeforeAll + public static void beforeAll() throws IOException { + jarFile = new JarFile(new File(BUKKIT_CLASSES)); + } + + @ParameterizedTest + @MethodSource("bukkitData") + public void testBukkitClasses(InputStream inputStream) throws IOException { + List missingReroute = new ArrayList<>(); + + try (inputStream) { + ClassReader classReader = new ClassReader(inputStream); + ClassNode classNode = new ClassNode(Opcodes.ASM9); + + classReader.accept(classNode, Opcodes.ASM9); + + for (MethodNode methodNode : classNode.methods) { + String signature = methodNode.signature == null ? "" : methodNode.signature; + // Add signature exceptions here + signature = signature.replace("Lorg/bukkit/Tag;", ""); // Gets handled specially + + if (methodNode.desc.contains("Lorg/bukkit/Material;") || signature.contains("Lorg/bukkit/Material;")) { + // Add method exceptions here + switch (methodNode.name) { + case "", "updateMaterial", "setItemMeta0" -> { + continue; + } + } + + if (!Commodore.rerouteMethods(Collections.emptySet(), MATERIAL_METHOD_REROUTE, (methodNode.access & Opcodes.ACC_STATIC) != 0, classNode.name, methodNode.name, methodNode.desc, a -> { })) { + missingReroute.add(methodNode.name + " " + methodNode.desc + " " + methodNode.signature); + } + } + } + + assertTrue(missingReroute.isEmpty(), String.format(""" + The class %s has methods with a Material as return or parameter in it, which does not have a reroute added to MaterialRerouting. + Please add it to MaterialRerouting or add an exception for it, if it should not be rerouted. + + Following missing methods where found: + %s""", classNode.name, Joiner.on('\n').join(missingReroute))); + } + } + + @AfterAll + public static void clear() throws IOException { + if (jarFile != null) { + jarFile.close(); + } + } +} diff --git a/src/test/java/org/bukkit/craftbukkit/legacy/RerouteValidationTest.java b/src/test/java/org/bukkit/craftbukkit/legacy/RerouteValidationTest.java index 89b030390a56..728f07af341f 100644 --- a/src/test/java/org/bukkit/craftbukkit/legacy/RerouteValidationTest.java +++ b/src/test/java/org/bukkit/craftbukkit/legacy/RerouteValidationTest.java @@ -93,6 +93,7 @@ private Class toClass(Type type) throws ClassNotFoundException { return Class.forName(type.getDescriptor().replace('/', '.'), false, getClass().getClassLoader()); } else { return switch (type.getSort()) { + case Type.VOID -> void.class; case Type.BOOLEAN -> boolean.class; case Type.CHAR -> char.class; case Type.BYTE -> byte.class;