diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java index 306397e55..2240ef38d 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 final class CraftItemStack extends ItemStack { 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 c96d9940a..00eac8261 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.Block; 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 @@ public final class 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 final class CraftEvil { 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 e9172ec6b..bf30ca878 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 final class CraftLegacy { } public static MaterialData toLegacyData(Material material) { - Preconditions.checkArgument(!material.isLegacy(), "toLegacy on legacy Material"); - MaterialData mappedData; + return toLegacyData(material, false); + } - if (material.isBlock()) { + public static MaterialData toLegacyData(Material material, boolean itemPriority) { + Preconditions.checkArgument(!material.isLegacy(), "toLegacy on legacy Material"); + MaterialData mappedData = null; + + 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 final class CraftLegacy { 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 000000000..8f70fb876 --- /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 4f06d72c8..7e51bfa60 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.OptionSet; 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 @@ public class Commodore { @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 @@ public class Commodore { 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 @@ public class Commodore { } } - // 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 000000000..fa4bd5aba --- /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 89b030390..728f07af3 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 @@ public class RerouteValidationTest { 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;