#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.
This commit is contained in:
DerFrZocker 2024-05-29 06:48:55 +10:00 committed by md_5
parent 94e44ec934
commit 9608279815
No known key found for this signature in database
GPG Key ID: E8E901AC7C617C11
7 changed files with 730 additions and 26 deletions

View File

@ -31,7 +31,7 @@ public final class CraftItemStack extends ItemStack {
return net.minecraft.world.item.ItemStack.EMPTY; return net.minecraft.world.item.ItemStack.EMPTY;
} }
Item item = CraftMagicNumbers.getItem(original.getType(), original.getDurability()); Item item = CraftItemType.bukkitToMinecraft(original.getType());
if (item == null) { if (item == null) {
return net.minecraft.world.item.ItemStack.EMPTY; return net.minecraft.world.item.ItemStack.EMPTY;

View File

@ -11,7 +11,6 @@ import org.bukkit.block.Block;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.block.CraftBlock; import org.bukkit.craftbukkit.block.CraftBlock;
import org.bukkit.craftbukkit.block.CraftBlockState;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData; 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) { public static int getBlockTypeIdAt(World world, int x, int y, int z) {
return getId(world.getBlockAt(x, y, z).getType()); return getId(world.getBlockAt(x, y, z).getType());
} }
@ -46,14 +54,6 @@ public final class CraftEvil {
return getId(world.getBlockAt(location).getType()); 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) { public static int getTypeId(Block block) {
return getId(block.getType()); return getId(block.getType());
} }

View File

@ -67,10 +67,19 @@ public final class CraftLegacy {
} }
public static MaterialData toLegacyData(Material material) { public static MaterialData toLegacyData(Material material) {
Preconditions.checkArgument(!material.isLegacy(), "toLegacy on legacy Material"); return toLegacyData(material, false);
MaterialData mappedData; }
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); Block block = CraftMagicNumbers.getBlock(material);
IBlockData blockData = block.defaultBlockState(); IBlockData blockData = block.defaultBlockState();
@ -84,7 +93,7 @@ public final class CraftLegacy {
mappedData = itemToMaterial.get(block.asItem()); mappedData = itemToMaterial.get(block.asItem());
} }
} }
} else { } else if (!itemPriority) {
Item item = CraftMagicNumbers.getItem(material); Item item = CraftMagicNumbers.getItem(material);
mappedData = itemToMaterial.get(item); mappedData = itemToMaterial.get(item);
} }

View File

@ -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<DecoratedPot.Side, Material> getSherds(DecoratedPot decoratedPot, @InjectPluginVersion ApiVersion version) {
Map<DecoratedPot.Side, Material> result = new EnumMap<>(DecoratedPot.Side.class);
for (Map.Entry<DecoratedPot.Side, Material> entry : decoratedPot.getSherds().entrySet()) {
result.put(entry.getKey(), transformFromItemType(entry.getValue(), version));
}
return result;
}
@Deprecated
public static List<Material> 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<Block> getLineOfSight(LivingEntity livingEntity, Set<Material> 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<Material> 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<Block> getLastTwoTargetBlocks(LivingEntity livingEntity, Set<Material> 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<Material> getInterestList(Piglin piglin, @InjectPluginVersion ApiVersion version) {
return piglin.getInterestList().stream().map(item -> transformFromItemType(item, version)).collect(Collectors.toSet());
}
public static Set<Material> 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<Integer, ? extends ItemStack> 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<Material> 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<? super BlockData> 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<? super BlockData> 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 <T extends Keyed> boolean isTagged(Tag<T> 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 <T extends Keyed> Set<T> getValues(Tag<T> tag, @InjectPluginVersion ApiVersion version) {
Set<T> 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)));
}
}

View File

@ -22,6 +22,7 @@ import joptsimple.OptionSet;
import joptsimple.OptionSpec; import joptsimple.OptionSpec;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.craftbukkit.legacy.FieldRename; 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.RerouteArgument;
import org.bukkit.craftbukkit.legacy.reroute.RerouteBuilder; import org.bukkit.craftbukkit.legacy.reroute.RerouteBuilder;
import org.bukkit.craftbukkit.legacy.reroute.RerouteMethodData; 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 (B)V setData",
"org/bukkit/block/Block (BZ)V setData", "org/bukkit/block/Block (BZ)V setData",
"org/bukkit/inventory/ItemStack ()I getTypeId", "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<String, String> RENAMES = Map.of( private static final Map<String, String> RENAMES = Map.of(
@ -73,6 +75,7 @@ public class Commodore {
@VisibleForTesting @VisibleForTesting
public static final List<Map<String, RerouteMethodData>> REROUTES = new ArrayList<>(); // Only used for testing public static final List<Map<String, RerouteMethodData>> REROUTES = new ArrayList<>(); // Only used for testing
private static final Map<String, RerouteMethodData> FIELD_RENAME_METHOD_REROUTE = createReroutes(FieldRename.class); private static final Map<String, RerouteMethodData> FIELD_RENAME_METHOD_REROUTE = createReroutes(FieldRename.class);
private static final Map<String, RerouteMethodData> MATERIAL_METHOD_REROUTE = createReroutes(MaterialRerouting.class);
public static void main(String[] args) { public static void main(String[] args) {
OptionParser parser = new OptionParser(); OptionParser parser = new OptionParser();
@ -327,10 +330,10 @@ public class Commodore {
Type retType = Type.getReturnType(desc); 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) 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 getTypeId"))
|| (owner.startsWith("org/bukkit/block/") && (desc + " " + name).equals("(I)Z setTypeId")) || (owner.startsWith("org/bukkit/block/") && (desc + " " + name).equals("(I)Z setTypeId"))) {
|| (owner.startsWith("org/bukkit/block/") && (desc + " " + name).equals("()Lorg/bukkit/Material; getType"))) {
Type[] args = Type.getArgumentTypes(desc); Type[] args = Type.getArgumentTypes(desc);
Type[] newArgs = new Type[args.length + 1]; Type[] newArgs = new Type[args.length + 1];
newArgs[0] = Type.getObjectType(owner); 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 // TODO 2024-05-21: Move this up, when material gets fully replaced with ItemType and BlockType
// The time required to fixe this is probably higher than the return, if (owner.startsWith("org/bukkit") && checkReroute(visitor, MATERIAL_METHOD_REROUTE, opcode, owner, name, desc, samMethodType, instantiatedMethodType)) {
// 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);
return; return;
} }

View File

@ -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<String, RerouteMethodData> 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<Arguments> 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<String> 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<Lorg/bukkit/Material;>;", ""); // Gets handled specially
if (methodNode.desc.contains("Lorg/bukkit/Material;") || signature.contains("Lorg/bukkit/Material;")) {
// Add method exceptions here
switch (methodNode.name) {
case "<init>", "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();
}
}
}

View File

@ -93,6 +93,7 @@ public class RerouteValidationTest {
return Class.forName(type.getDescriptor().replace('/', '.'), false, getClass().getClassLoader()); return Class.forName(type.getDescriptor().replace('/', '.'), false, getClass().getClassLoader());
} else { } else {
return switch (type.getSort()) { return switch (type.getSort()) {
case Type.VOID -> void.class;
case Type.BOOLEAN -> boolean.class; case Type.BOOLEAN -> boolean.class;
case Type.CHAR -> char.class; case Type.CHAR -> char.class;
case Type.BYTE -> byte.class; case Type.BYTE -> byte.class;