diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 0e4d943ec..a0fe9dd31 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -195,6 +195,7 @@ import org.bukkit.craftbukkit.tag.CraftBlockTag; import org.bukkit.craftbukkit.tag.CraftEntityTag; import org.bukkit.craftbukkit.tag.CraftFluidTag; import org.bukkit.craftbukkit.tag.CraftItemTag; +import org.bukkit.craftbukkit.util.ApiVersion; import org.bukkit.craftbukkit.util.CraftChatMessage; import org.bukkit.craftbukkit.util.CraftIconCache; import org.bukkit.craftbukkit.util.CraftLocation; @@ -288,7 +289,7 @@ public final class CraftServer implements Server { private final Object2IntOpenHashMap spawnCategoryLimit = new Object2IntOpenHashMap<>(); private File container; private WarningState warningState = WarningState.DEFAULT; - public String minimumAPI; + public ApiVersion minimumAPI; public CraftScoreboardManager scoreboardManager; public CraftDataPackManager dataPackManager; private CraftServerTickManager serverTickManager; @@ -371,7 +372,7 @@ public final class CraftServer implements Server { console.autosavePeriod = configuration.getInt("ticks-per.autosave"); warningState = WarningState.value(configuration.getString("settings.deprecated-verbose")); TicketType.PLUGIN.timeout = configuration.getInt("chunk-gc.period-in-ticks"); - minimumAPI = configuration.getString("settings.minimum-api"); + minimumAPI = ApiVersion.getOrCreateVersion(configuration.getString("settings.minimum-api")); loadIcon(); // Set map color cache @@ -888,7 +889,7 @@ public final class CraftServer implements Server { overrideSpawnLimits(); warningState = WarningState.value(configuration.getString("settings.deprecated-verbose")); TicketType.PLUGIN.timeout = configuration.getInt("chunk-gc.period-in-ticks"); - minimumAPI = configuration.getString("settings.minimum-api"); + minimumAPI = ApiVersion.getOrCreateVersion(configuration.getString("settings.minimum-api")); printSaveWarning = false; console.autosavePeriod = configuration.getInt("ticks-per.autosave"); loadIcon(); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java index ff1d6455a..9630a59a4 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java @@ -274,8 +274,8 @@ public final class CraftEntityTypes { register(new EntityTypeData<>(EntityType.CHICKEN, Chicken.class, CraftChicken::new, createLiving(EntityTypes.CHICKEN))); register(new EntityTypeData<>(EntityType.SQUID, Squid.class, CraftSquid::new, createLiving(EntityTypes.SQUID))); register(new EntityTypeData<>(EntityType.WOLF, Wolf.class, CraftWolf::new, createLiving(EntityTypes.WOLF))); - register(new EntityTypeData<>(EntityType.MUSHROOM_COW, MushroomCow.class, CraftMushroomCow::new, createLiving(EntityTypes.MOOSHROOM))); - register(new EntityTypeData<>(EntityType.SNOWMAN, Snowman.class, CraftSnowman::new, createLiving(EntityTypes.SNOW_GOLEM))); + register(new EntityTypeData<>(EntityType.MOOSHROOM, MushroomCow.class, CraftMushroomCow::new, createLiving(EntityTypes.MOOSHROOM))); + register(new EntityTypeData<>(EntityType.SNOW_GOLEM, Snowman.class, CraftSnowman::new, createLiving(EntityTypes.SNOW_GOLEM))); register(new EntityTypeData<>(EntityType.OCELOT, Ocelot.class, CraftOcelot::new, createLiving(EntityTypes.OCELOT))); register(new EntityTypeData<>(EntityType.IRON_GOLEM, IronGolem.class, CraftIronGolem::new, createLiving(EntityTypes.IRON_GOLEM))); register(new EntityTypeData<>(EntityType.HORSE, Horse.class, CraftHorse::new, createLiving(EntityTypes.HORSE))); @@ -349,11 +349,11 @@ public final class CraftEntityTypes { // Move no rotation register(new EntityTypeData<>(EntityType.ARROW, Arrow.class, CraftArrow::new, createAndMoveEmptyRot(EntityTypes.ARROW))); register(new EntityTypeData<>(EntityType.ENDER_PEARL, EnderPearl.class, CraftEnderPearl::new, createAndMoveEmptyRot(EntityTypes.ENDER_PEARL))); - register(new EntityTypeData<>(EntityType.THROWN_EXP_BOTTLE, ThrownExpBottle.class, CraftThrownExpBottle::new, createAndMoveEmptyRot(EntityTypes.EXPERIENCE_BOTTLE))); + register(new EntityTypeData<>(EntityType.EXPERIENCE_BOTTLE, ThrownExpBottle.class, CraftThrownExpBottle::new, createAndMoveEmptyRot(EntityTypes.EXPERIENCE_BOTTLE))); register(new EntityTypeData<>(EntityType.SPECTRAL_ARROW, SpectralArrow.class, CraftSpectralArrow::new, createAndMoveEmptyRot(EntityTypes.SPECTRAL_ARROW))); - register(new EntityTypeData<>(EntityType.ENDER_CRYSTAL, EnderCrystal.class, CraftEnderCrystal::new, createAndMoveEmptyRot(EntityTypes.END_CRYSTAL))); + register(new EntityTypeData<>(EntityType.END_CRYSTAL, EnderCrystal.class, CraftEnderCrystal::new, createAndMoveEmptyRot(EntityTypes.END_CRYSTAL))); register(new EntityTypeData<>(EntityType.TRIDENT, Trident.class, CraftTrident::new, createAndMoveEmptyRot(EntityTypes.TRIDENT))); - register(new EntityTypeData<>(EntityType.LIGHTNING, LightningStrike.class, CraftLightningStrike::new, createAndMoveEmptyRot(EntityTypes.LIGHTNING_BOLT))); + register(new EntityTypeData<>(EntityType.LIGHTNING_BOLT, LightningStrike.class, CraftLightningStrike::new, createAndMoveEmptyRot(EntityTypes.LIGHTNING_BOLT))); // Move register(new EntityTypeData<>(EntityType.SHULKER_BULLET, ShulkerBullet.class, CraftShulkerBullet::new, createAndMove(EntityTypes.SHULKER_BULLET))); @@ -370,7 +370,7 @@ public final class CraftEntityTypes { register(new EntityTypeData<>(EntityType.TEXT_DISPLAY, TextDisplay.class, CraftTextDisplay::new, createAndSetPos(EntityTypes.TEXT_DISPLAY))); // MISC - register(new EntityTypeData<>(EntityType.DROPPED_ITEM, Item.class, CraftItem::new, spawnData -> { + register(new EntityTypeData<>(EntityType.ITEM, Item.class, CraftItem::new, spawnData -> { // We use stone instead of empty, to give the plugin developer a visual clue, that the spawn method is working, // and that the item stack should probably be changed. net.minecraft.world.item.ItemStack itemStack = new net.minecraft.world.item.ItemStack(Items.STONE); @@ -384,31 +384,31 @@ public final class CraftEntityTypes { )); register(new EntityTypeData<>(EntityType.AREA_EFFECT_CLOUD, AreaEffectCloud.class, CraftAreaEffectCloud::new, spawnData -> new EntityAreaEffectCloud(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); register(new EntityTypeData<>(EntityType.EGG, Egg.class, CraftEgg::new, spawnData -> new EntityEgg(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); - register(new EntityTypeData<>(EntityType.LEASH_HITCH, LeashHitch.class, CraftLeash::new, spawnData -> new EntityLeash(spawnData.minecraftWorld(), BlockPosition.containing(spawnData.x(), spawnData.y(), spawnData.z())))); // SPIGOT-5732: LeashHitch has no direction and is always centered at a block + register(new EntityTypeData<>(EntityType.LEASH_KNOT, LeashHitch.class, CraftLeash::new, spawnData -> new EntityLeash(spawnData.minecraftWorld(), BlockPosition.containing(spawnData.x(), spawnData.y(), spawnData.z())))); // SPIGOT-5732: LeashHitch has no direction and is always centered at a block register(new EntityTypeData<>(EntityType.SNOWBALL, Snowball.class, CraftSnowball::new, spawnData -> new EntitySnowball(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); - register(new EntityTypeData<>(EntityType.ENDER_SIGNAL, EnderSignal.class, CraftEnderSignal::new, spawnData -> new EntityEnderSignal(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); - register(new EntityTypeData<>(EntityType.SPLASH_POTION, ThrownPotion.class, CraftThrownPotion::new, spawnData -> { + register(new EntityTypeData<>(EntityType.EYE_OF_ENDER, EnderSignal.class, CraftEnderSignal::new, spawnData -> new EntityEnderSignal(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); + register(new EntityTypeData<>(EntityType.POTION, ThrownPotion.class, CraftThrownPotion::new, spawnData -> { EntityPotion entity = new EntityPotion(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()); entity.setItem(CraftItemStack.asNMSCopy(new ItemStack(Material.SPLASH_POTION, 1))); return entity; })); - register(new EntityTypeData<>(EntityType.PRIMED_TNT, TNTPrimed.class, CraftTNTPrimed::new, spawnData -> new EntityTNTPrimed(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), null))); + register(new EntityTypeData<>(EntityType.TNT, TNTPrimed.class, CraftTNTPrimed::new, spawnData -> new EntityTNTPrimed(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), null))); register(new EntityTypeData<>(EntityType.FALLING_BLOCK, FallingBlock.class, CraftFallingBlock::new, spawnData -> { BlockPosition pos = BlockPosition.containing(spawnData.x(), spawnData.y(), spawnData.z()); return EntityFallingBlock.fall(spawnData.minecraftWorld(), pos, spawnData.world().getBlockState(pos)); })); - register(new EntityTypeData<>(EntityType.FIREWORK, Firework.class, CraftFirework::new, spawnData -> new EntityFireworks(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), net.minecraft.world.item.ItemStack.EMPTY))); + register(new EntityTypeData<>(EntityType.FIREWORK_ROCKET, Firework.class, CraftFirework::new, spawnData -> new EntityFireworks(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), net.minecraft.world.item.ItemStack.EMPTY))); register(new EntityTypeData<>(EntityType.EVOKER_FANGS, EvokerFangs.class, CraftEvokerFangs::new, spawnData -> new EntityEvokerFangs(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), (float) Math.toRadians(spawnData.yaw()), 0, null))); - register(new EntityTypeData<>(EntityType.MINECART_COMMAND, CommandMinecart.class, CraftMinecartCommand::new, spawnData -> new EntityMinecartCommandBlock(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); + register(new EntityTypeData<>(EntityType.COMMAND_BLOCK_MINECART, CommandMinecart.class, CraftMinecartCommand::new, spawnData -> new EntityMinecartCommandBlock(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); register(new EntityTypeData<>(EntityType.MINECART, RideableMinecart.class, CraftMinecartRideable::new, spawnData -> new EntityMinecartRideable(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); - register(new EntityTypeData<>(EntityType.MINECART_CHEST, StorageMinecart.class, CraftMinecartChest::new, spawnData -> new EntityMinecartChest(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); - register(new EntityTypeData<>(EntityType.MINECART_FURNACE, PoweredMinecart.class, CraftMinecartFurnace::new, spawnData -> new EntityMinecartFurnace(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); - register(new EntityTypeData<>(EntityType.MINECART_TNT, ExplosiveMinecart.class, CraftMinecartTNT::new, spawnData -> new EntityMinecartTNT(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); - register(new EntityTypeData<>(EntityType.MINECART_HOPPER, HopperMinecart.class, CraftMinecartHopper::new, spawnData -> new EntityMinecartHopper(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); - register(new EntityTypeData<>(EntityType.MINECART_MOB_SPAWNER, SpawnerMinecart.class, CraftMinecartMobSpawner::new, spawnData -> new EntityMinecartMobSpawner(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); + register(new EntityTypeData<>(EntityType.CHEST_MINECART, StorageMinecart.class, CraftMinecartChest::new, spawnData -> new EntityMinecartChest(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); + register(new EntityTypeData<>(EntityType.FURNACE_MINECART, PoweredMinecart.class, CraftMinecartFurnace::new, spawnData -> new EntityMinecartFurnace(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); + register(new EntityTypeData<>(EntityType.TNT_MINECART, ExplosiveMinecart.class, CraftMinecartTNT::new, spawnData -> new EntityMinecartTNT(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); + register(new EntityTypeData<>(EntityType.HOPPER_MINECART, HopperMinecart.class, CraftMinecartHopper::new, spawnData -> new EntityMinecartHopper(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); + register(new EntityTypeData<>(EntityType.SPAWNER_MINECART, SpawnerMinecart.class, CraftMinecartMobSpawner::new, spawnData -> new EntityMinecartMobSpawner(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); // None spawn able - register(new EntityTypeData<>(EntityType.FISHING_HOOK, FishHook.class, CraftFishHook::new, null)); // Cannot spawn a fish hook + register(new EntityTypeData<>(EntityType.FISHING_BOBBER, FishHook.class, CraftFishHook::new, null)); // Cannot spawn a fish hook register(new EntityTypeData<>(EntityType.PLAYER, Player.class, CraftPlayer::new, null)); // Cannot spawn a player } diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 875427404..c8fc7b848 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -1362,10 +1362,10 @@ public class CraftEventFactory { org.bukkit.entity.Entity bukkitIgniter = igniter.getBukkitEntity(); IgniteCause cause; switch (bukkitIgniter.getType()) { - case ENDER_CRYSTAL: + case END_CRYSTAL: cause = IgniteCause.ENDER_CRYSTAL; break; - case LIGHTNING: + case LIGHTNING_BOLT: cause = IgniteCause.LIGHTNING; break; case SMALL_FIREBALL: diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java index 019899001..4a318cb3d 100644 --- a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java +++ b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java @@ -281,7 +281,7 @@ public final class CraftLegacy { SPAWN_EGGS.put((byte) EntityType.HUSK.getTypeId(), Material.HUSK_SPAWN_EGG); SPAWN_EGGS.put((byte) EntityType.LLAMA.getTypeId(), Material.LLAMA_SPAWN_EGG); SPAWN_EGGS.put((byte) EntityType.MAGMA_CUBE.getTypeId(), Material.MAGMA_CUBE_SPAWN_EGG); - SPAWN_EGGS.put((byte) EntityType.MUSHROOM_COW.getTypeId(), Material.MOOSHROOM_SPAWN_EGG); + SPAWN_EGGS.put((byte) EntityType.MOOSHROOM.getTypeId(), Material.MOOSHROOM_SPAWN_EGG); SPAWN_EGGS.put((byte) EntityType.MULE.getTypeId(), Material.MULE_SPAWN_EGG); SPAWN_EGGS.put((byte) EntityType.OCELOT.getTypeId(), Material.OCELOT_SPAWN_EGG); SPAWN_EGGS.put((byte) EntityType.PARROT.getTypeId(), Material.PARROT_SPAWN_EGG); diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java b/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java new file mode 100644 index 000000000..68078d888 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java @@ -0,0 +1,340 @@ +package org.bukkit.craftbukkit.legacy; + +import org.bukkit.Particle; +import org.bukkit.block.Biome; +import org.bukkit.block.banner.PatternType; +import org.bukkit.craftbukkit.legacy.fieldrename.FieldRenameData; +import org.bukkit.craftbukkit.legacy.reroute.DoNotReroute; +import org.bukkit.craftbukkit.legacy.reroute.InjectPluginVersion; +import org.bukkit.craftbukkit.legacy.reroute.RerouteMethodName; +import org.bukkit.craftbukkit.legacy.reroute.RerouteStatic; +import org.bukkit.craftbukkit.util.ApiVersion; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.EntityType; +import org.bukkit.loot.LootTables; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; + +public class FieldRename { + + @DoNotReroute + public static String rename(ApiVersion apiVersion, String owner, String from) { + if (owner == null) { + return from; + } + + return switch (owner) { + case "org/bukkit/block/banner/PatternType" -> convertPatternTypeName(apiVersion, from); + case "org/bukkit/enchantments/Enchantment" -> convertEnchantmentName(apiVersion, from); + case "org/bukkit/block/Biome" -> convertBiomeName(apiVersion, from); + case "org/bukkit/entity/EntityType" -> convertEntityTypeName(apiVersion, from); + case "org/bukkit/potion/PotionEffectType" -> convertPotionEffectTypeName(apiVersion, from); + case "org/bukkit/potion/PotionType" -> convertPotionTypeName(apiVersion, from); + case "org/bukkit/MusicInstrument" -> convertMusicInstrumentName(apiVersion, from); + case "org/bukkit/Particle" -> convertParticleName(apiVersion, from); + case "org/bukkit/loot/LootTables" -> convertLootTablesName(apiVersion, from); + case "org/bukkit/attribute/Attribute" -> convertAttributeName(apiVersion, from); + default -> from; + }; + } + + // PatternType + private static final FieldRenameData PATTERN_TYPE_DATA = FieldRenameData.Builder.newBuilder() + .forVersionsBefore(ApiVersion.FIELD_NAME_PARITY) + .change("DIAGONAL_RIGHT", "DIAGONAL_UP_RIGHT") + .forAllVersions() + .change("STRIPE_SMALL", "SMALL_STRIPES") + .change("DIAGONAL_LEFT_MIRROR", "DIAGONAL_UP_LEFT") + .change("DIAGONAL_RIGHT_MIRROR", "DIAGONAL_RIGHT") + .change("CIRCLE_MIDDLE", "CIRCLE") + .change("RHOMBUS_MIDDLE", "RHOMBUS") + .change("HALF_VERTICAL_MIRROR", "HALF_VERTICAL_RIGHT") + .change("HALF_HORIZONTAL_MIRROR", "HALF_HORIZONTAL_BOTTOM") + .build(); + + @DoNotReroute + public static String convertPatternTypeName(ApiVersion version, String from) { + return PATTERN_TYPE_DATA.getReplacement(version, from); + } + + @RerouteMethodName("valueOf") + @RerouteStatic("org/bukkit/block/banner/PatternType") + public static PatternType valueOf_PatternType(String value, @InjectPluginVersion ApiVersion version) { + return PatternType.valueOf(convertPatternTypeName(version, value)); + } + + // Enchantment + private static final FieldRenameData ENCHANTMENT_DATA = FieldRenameData.Builder.newBuilder() + .forAllVersions() + .change("PROTECTION_ENVIRONMENTAL", "PROTECTION") + .change("PROTECTION_FIRE", "FIRE_PROTECTION") + .change("PROTECTION_FALL", "FEATHER_FALLING") + .change("PROTECTION_EXPLOSIONS", "BLAST_PROTECTION") + .change("PROTECTION_PROJECTILE", "PROJECTILE_PROTECTION") + .change("OXYGEN", "RESPIRATION") + .change("WATER_WORKER", "AQUA_AFFINITY") + .change("DAMAGE_ALL", "SHARPNESS") + .change("DAMAGE_UNDEAD", "SMITE") + .change("DAMAGE_ARTHROPODS", "BANE_OF_ARTHROPODS") + .change("LOOT_BONUS_MOBS", "LOOTING") + .change("SWEEPING_EDGE", "SWEEPING") + .change("DIG_SPEED", "EFFICIENCY") + .change("DURABILITY", "UNBREAKING") + .change("LOOT_BONUS_BLOCKS", "FORTUNE") + .change("ARROW_DAMAGE", "POWER") + .change("ARROW_KNOCKBACK", "PUNCH") + .change("ARROW_FIRE", "FLAME") + .change("ARROW_INFINITY", "INFINITY") + .change("LUCK", "LUCK_OF_THE_SEA") + .build(); + + @DoNotReroute + public static String convertEnchantmentName(ApiVersion version, String from) { + return ENCHANTMENT_DATA.getReplacement(version, from); + } + + @RerouteMethodName("getByName") + @RerouteStatic("org/bukkit/enchantments/Enchantment") + public static Enchantment getByName_Enchantment(String name) { + // We don't have version-specific changes, so just use current, and don't inject a version + return Enchantment.getByName(convertEnchantmentName(ApiVersion.CURRENT, name)); + } + + // Biome + private static final FieldRenameData BIOME_DATA = FieldRenameData.Builder.newBuilder() + .forAllVersions() + .change("NETHER", "NETHER_WASTES") + .change("TALL_BIRCH_FOREST", "OLD_GROWTH_BIRCH_FOREST") + .change("GIANT_TREE_TAIGA", "OLD_GROWTH_PINE_TAIGA") + .change("GIANT_SPRUCE_TAIGA", "OLD_GROWTH_SPRUCE_TAIGA") + .change("SNOWY_TUNDRA", "SNOWY_PLAINS") + .change("JUNGLE_EDGE", "SPARSE_JUNGLE") + .change("STONE_SHORE", "STONY_SHORE") + .change("MOUNTAINS", "WINDSWEPT_HILLS") + .change("WOODED_MOUNTAINS", "WINDSWEPT_FOREST") + .change("GRAVELLY_MOUNTAINS", "WINDSWEPT_GRAVELLY_HILLS") + .change("SHATTERED_SAVANNA", "WINDSWEPT_SAVANNA") + .change("WOODED_BADLANDS_PLATEAU", "WOODED_BADLANDS") + .build(); + + @DoNotReroute + public static String convertBiomeName(ApiVersion version, String from) { + return BIOME_DATA.getReplacement(version, from); + } + + @RerouteMethodName("valueOf") + @RerouteStatic("org/bukkit/block/Biome") + public static Biome valueOf_Biome(String name) { + // We don't have version-specific changes, so just use current, and don't inject a version + return Biome.valueOf(convertBiomeName(ApiVersion.CURRENT, name)); + } + + + // EntityType + private static final FieldRenameData ENTITY_TYPE_DATA = FieldRenameData.Builder.newBuilder() + .forAllVersions() + .change("XP_ORB", "EXPERIENCE_ORB") + .change("EYE_OF_ENDER_SIGNAL", "EYE_OF_ENDER") + .change("XP_BOTTLE", "EXPERIENCE_BOTTLE") + .change("FIREWORKS_ROCKET", "FIREWORK_ROCKET") + .change("EVOCATION_FANGS", "EVOKER_FANGS") + .change("EVOCATION_ILLAGER", "EVOKER") + .change("VINDICATION_ILLAGER", "VINDICATOR") + .change("ILLUSION_ILLAGER", "ILLUSIONER") + .change("COMMANDBLOCK_MINECART", "COMMAND_BLOCK_MINECART") + .change("SNOWMAN", "SNOW_GOLEM") + .change("VILLAGER_GOLEM", "IRON_GOLEM") + .change("ENDER_CRYSTAL", "END_CRYSTAL") + .change("ZOMBIE_PIGMAN", "ZOMBIFIED_PIGLIN") + .change("PIG_ZOMBIE", "ZOMBIFIED_PIGLIN") + .change("DROPPED_ITEM", "ITEM") + .change("LEASH_HITCH", "LEASH_KNOT") + .change("ENDER_SIGNAL", "EYE_OF_ENDER") + .change("SPLASH_POTION", "POTION") + .change("THROWN_EXP_BOTTLE", "EXPERIENCE_BOTTLE") + .change("PRIMED_TNT", "TNT") + .change("FIREWORK", "FIREWORK_ROCKET") + .change("MINECART_COMMAND", "COMMAND_BLOCK_MINECART") + .change("MINECART_CHEST", "CHEST_MINECART") + .change("MINECART_FURNACE", "FURNACE_MINECART") + .change("MINECART_TNT", "TNT_MINECART") + .change("MINECART_HOPPER", "HOPPER_MINECART") + .change("MINECART_MOB_SPAWNER", "SPAWNER_MINECART") + .change("MUSHROOM_COW", "MOOSHROOM") + .change("SNOWMAN", "SNOW_GOLEM") + .change("ENDER_CRYSTAL", "END_CRYSTAL") + .change("FISHING_HOOK", "FISHING_BOBBER") + .change("LIGHTNING", "LIGHTNING_BOLT") + .build(); + + @DoNotReroute + public static String convertEntityTypeName(ApiVersion version, String from) { + return ENTITY_TYPE_DATA.getReplacement(version, from); + } + + @RerouteMethodName("valueOf") + @RerouteStatic("org/bukkit/entity/EntityType") + public static EntityType valueOf_EntityType(String name) { + // We don't have version-specific changes, so just use current, and don't inject a version + return EntityType.valueOf(convertEntityTypeName(ApiVersion.CURRENT, name)); + } + + @RerouteMethodName("fromName") + @RerouteStatic("org/bukkit/entity/EntityType") + public static EntityType fromName_EntityType(String name) { + // We don't have version-specific changes, so just use current, and don't inject a version + return EntityType.fromName(convertEntityTypeName(ApiVersion.CURRENT, name)); + } + + // PotionEffectType + private static final FieldRenameData POTION_EFFECT_TYPE_DATA = FieldRenameData.Builder.newBuilder() + .forAllVersions() + .change("SLOW", "SLOWNESS") + .change("FAST_DIGGING", "HASTE") + .change("SLOW_DIGGING", "MINING_FATIGUE") + .change("INCREASE_DAMAGE", "STRENGTH") + .change("HEAL", "INSTANT_HEALTH") + .change("HARM", "INSTANT_DAMAGE") + .change("JUMP", "JUMP_BOOST") + .change("CONFUSION", "NAUSEA") + .change("DAMAGE_RESISTANCE", "RESISTANCE") + .build(); + + @DoNotReroute + public static String convertPotionEffectTypeName(ApiVersion version, String from) { + return POTION_EFFECT_TYPE_DATA.getReplacement(version, from); + } + + @RerouteMethodName("getByName") + @RerouteStatic("org/bukkit/potion/PotionEffectType") + public static PotionEffectType getByName_PotionEffectType(String name) { + // We don't have version-specific changes, so just use current, and don't inject a version + return PotionEffectType.getByName(convertPotionEffectTypeName(ApiVersion.CURRENT, name)); + } + + // PotionType + private static final FieldRenameData POTION_TYPE_DATA = FieldRenameData.Builder.newBuilder() + .forAllVersions() + .change("UNCRAFTABLE", "EMPTY") + .change("JUMP", "LEAPING") + .change("SPEED", "SWIFTNESS") + .change("INSTANT_HEAL", "HEALING") + .change("INSTANT_DAMAGE", "HARMING") + .change("REGEN", "REGENERATION") + .build(); + + @DoNotReroute + public static String convertPotionTypeName(ApiVersion version, String from) { + return POTION_TYPE_DATA.getReplacement(version, from); + } + + @RerouteMethodName("valueOf") + @RerouteStatic("org/bukkit/potion/PotionType") + public static PotionType valueOf_PotionType(String name) { + // We don't have version-specific changes, so just use current, and don't inject a version + return PotionType.valueOf(convertPotionTypeName(ApiVersion.CURRENT, name)); + } + + // MusicInstrument + private static final FieldRenameData MUSIC_INSTRUMENT_DATA = FieldRenameData.Builder.newBuilder() + .forAllVersions() + .change("PONDER", "PONDER_GOAT_HORN") + .change("SING", "SING_GOAT_HORN") + .change("SEEK", "SEEK_GOAT_HORN") + .change("ADMIRE", "ADMIRE_GOAT_HORN") + .change("CALL", "CALL_GOAT_HORN") + .change("YEARN", "YEARN_GOAT_HORN") + .change("DREAM", "DREAM_GOAT_HORN") + .build(); + + @DoNotReroute + public static String convertMusicInstrumentName(ApiVersion version, String from) { + return MUSIC_INSTRUMENT_DATA.getReplacement(version, from); + } + + // Particle + private static final FieldRenameData PARTICLE_DATA = FieldRenameData.Builder.newBuilder() + .forAllVersions() + .change("EXPLOSION_NORMAL", "POOF") + .change("EXPLOSION_LARGE", "EXPLOSION") + .change("EXPLOSION_HUGE", "EXPLOSION_EMITTER") + .change("FIREWORKS_SPARK", "FIREWORK") + .change("WATER_BUBBLE", "BUBBLE") + .change("WATER_SPLASH", "SPLASH") + .change("WATER_WAKE", "FISHING") + .change("SUSPENDED", "UNDERWATER") + .change("SUSPENDED_DEPTH", "UNDERWATER") + .change("CRIT_MAGIC", "ENCHANTED_HIT") + .change("SMOKE_NORMAL", "SMOKE") + .change("SMOKE_LARGE", "LARGE_SMOKE") + .change("SPELL", "EFFECT") + .change("SPELL_INSTANT", "INSTANT_EFFECT") + .change("SPELL_MOB", "ENTITY_EFFECT") + .change("SPELL_MOB_AMBIENT", "AMBIENT_ENTITY_EFFECT") + .change("SPELL_WITCH", "WITCH") + .change("DRIP_WATER", "DRIPPING_WATER") + .change("DRIP_LAVA", "DRIPPING_LAVA") + .change("VILLAGER_ANGRY", "ANGRY_VILLAGER") + .change("VILLAGER_HAPPY", "HAPPY_VILLAGER") + .change("TOWN_AURA", "MYCELIUM") + .change("ENCHANTMENT_TABLE", "ENCHANT") + .change("REDSTONE", "DUST") + .change("SNOWBALL", "ITEM_SNOWBALL") + .change("SNOW_SHOVEL", "ITEM_SNOWBALL") + .change("SLIME", "ITEM_SLIME") + .change("ITEM_CRACK", "ITEM") + .change("BLOCK_CRACK", "BLOCK") + .change("BLOCK_DUST", "BLOCK") + .change("WATER_DROP", "RAIN") + .change("MOB_APPEARANCE", "ELDER_GUARDIAN") + .change("TOTEM", "TOTEM_OF_UNDYING") + .build(); + + @DoNotReroute + public static String convertParticleName(ApiVersion version, String from) { + return PARTICLE_DATA.getReplacement(version, from); + } + + @RerouteMethodName("valueOf") + @RerouteStatic("org/bukkit/Particle") + public static Particle valueOf_Particle(String name) { + // We don't have version-specific changes, so just use current, and don't inject a version + return Particle.valueOf(convertParticleName(ApiVersion.CURRENT, name)); + } + + // LootTables + private static final FieldRenameData LOOT_TABLES_DATA = FieldRenameData.Builder.newBuilder() + .forAllVersions() + .change("ZOMBIE_PIGMAN", "ZOMBIFIED_PIGLIN") + .build(); + + @DoNotReroute + public static String convertLootTablesName(ApiVersion version, String from) { + return LOOT_TABLES_DATA.getReplacement(version, from); + } + + @RerouteMethodName("valueOf") + @RerouteStatic("org/bukkit/loot/LootTables") + public static LootTables valueOf_LootTables(String name) { + // We don't have version-specific changes, so just use current, and don't inject a version + return LootTables.valueOf(convertLootTablesName(ApiVersion.CURRENT, name)); + } + + // LootTables + private static final FieldRenameData ATTRIBUTE_DATA = FieldRenameData.Builder.newBuilder() + .forAllVersions() + .change("HORSE_JUMP_STRENGTH", "GENERIC_JUMP_STRENGTH") + .build(); + + @DoNotReroute + public static String convertAttributeName(ApiVersion version, String from) { + return ATTRIBUTE_DATA.getReplacement(version, from); + } + + @RerouteMethodName("valueOf") + @RerouteStatic("org/bukkit/attribute/Attribute") + public static LootTables valueOf_Attribute(String name) { + // We don't have version-specific changes, so just use current, and don't inject a version + return LootTables.valueOf(convertAttributeName(ApiVersion.CURRENT, name)); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/fieldrename/FieldRenameData.java b/src/main/java/org/bukkit/craftbukkit/legacy/fieldrename/FieldRenameData.java new file mode 100644 index 000000000..c6922fd33 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/legacy/fieldrename/FieldRenameData.java @@ -0,0 +1,64 @@ +package org.bukkit.craftbukkit.legacy.fieldrename; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; +import org.bukkit.craftbukkit.util.ApiVersion; + +public record FieldRenameData(NavigableMap> versionData, Map data) { + + public String getReplacement(ApiVersion apiVersion, String from) { + if (from == null) { + return null; + } + + from = from.toUpperCase(Locale.ROOT); + from = data.getOrDefault(from, from); + + for (Map.Entry> entry : versionData.entrySet()) { + if (apiVersion.isNewerThanOrSameAs(entry.getKey())) { + continue; + } + + from = entry.getValue().getOrDefault(from, from); + } + + return from; + } + + public static class Builder { + + private final Map data = new HashMap<>(); + private final NavigableMap> versionData = new TreeMap<>(); + private ApiVersion currentVersion; + + public static Builder newBuilder() { + return new Builder(); + } + + public Builder forVersionsBefore(ApiVersion apiVersion) { + this.currentVersion = apiVersion; + return this; + } + + public Builder forAllVersions() { + this.currentVersion = null; + return this; + } + + public Builder change(String from, String to) { + if (currentVersion != null) { + versionData.computeIfAbsent(currentVersion, d -> new HashMap<>()).put(from, to); + } else { + data.put(from, to); + } + return this; + } + + public FieldRenameData build() { + return new FieldRenameData(versionData, data); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/reroute/DoNotReroute.java b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/DoNotReroute.java new file mode 100644 index 000000000..f5badbbfe --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/DoNotReroute.java @@ -0,0 +1,11 @@ +package org.bukkit.craftbukkit.legacy.reroute; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface DoNotReroute { +} diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/reroute/InjectPluginName.java b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/InjectPluginName.java new file mode 100644 index 000000000..85a6f885f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/InjectPluginName.java @@ -0,0 +1,11 @@ +package org.bukkit.craftbukkit.legacy.reroute; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface InjectPluginName { +} diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/reroute/InjectPluginVersion.java b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/InjectPluginVersion.java new file mode 100644 index 000000000..895d4d821 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/InjectPluginVersion.java @@ -0,0 +1,11 @@ +package org.bukkit.craftbukkit.legacy.reroute; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface InjectPluginVersion { +} diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteArgument.java b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteArgument.java new file mode 100644 index 000000000..c1a7bb31e --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteArgument.java @@ -0,0 +1,39 @@ +package org.bukkit.craftbukkit.legacy.reroute; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +public record RerouteArgument(Type type, boolean injectPluginName, boolean injectPluginVersion) { + + /** + * Converts the type string to the correct load opcode. + *
+ * References: + *
+ * Interpretation of field descriptors + *
+ * iload Opcode / + * {@literal iload_ Opcode} + *
+ * lload Opcode / + * {@literal lload_ Opcode} + *
+ * fload Opcode / + * {@literal fload_ Opcode} + *
+ * dload Opcode / + * {@literal dload_ Opcode} + *
+ * aload Opcode / + * {@literal aload_ Opcode} + * + * @return the opcode of the type + */ + public int instruction() { + if (injectPluginName() || injectPluginVersion()) { + throw new IllegalStateException(String.format("Cannot get instruction for plugin name / version argument: %s", this)); + } + + return type.getOpcode(Opcodes.ILOAD); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteBuilder.java b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteBuilder.java new file mode 100644 index 000000000..232507e01 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteBuilder.java @@ -0,0 +1,115 @@ +package org.bukkit.craftbukkit.legacy.reroute; + +import com.google.common.base.Preconditions; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.bukkit.craftbukkit.util.ApiVersion; +import org.objectweb.asm.Type; + +public class RerouteBuilder { + + public static Map buildFromClass(Class clazz) { + Preconditions.checkArgument(!clazz.isInterface(), "Interface Classes are currently not supported"); + + Map result = new HashMap<>(); + + for (Method method : clazz.getDeclaredMethods()) { + if (method.isBridge()) { + continue; + } + + if (method.isSynthetic()) { + continue; + } + + if (!Modifier.isPublic(method.getModifiers())) { + continue; + } + + if (!Modifier.isStatic(method.getModifiers())) { + continue; + } + + if (method.isAnnotationPresent(DoNotReroute.class)) { + continue; + } + + RerouteMethodData rerouteMethodData = buildFromMethod(method); + result.put(rerouteMethodData.source(), rerouteMethodData); + } + + return Collections.unmodifiableMap(result); + } + + public static RerouteMethodData buildFromMethod(Method method) { + RerouteReturn rerouteReturn = new RerouteReturn(Type.getReturnType(method)); + List arguments = new ArrayList<>(); + List sourceArguments = new ArrayList<>(); + + for (Parameter parameter : method.getParameters()) { + Type type = Type.getType(parameter.getType()); + boolean injectPluginName = false; + boolean injectPluginVersion = false; + if (parameter.isAnnotationPresent(InjectPluginName.class)) { + if (parameter.getType() != String.class) { + throw new RuntimeException("Plugin name argument must be of type name, but got " + parameter.getType()); + } + injectPluginName = true; + } + + if (parameter.isAnnotationPresent(InjectPluginVersion.class)) { + if (parameter.getType() != ApiVersion.class) { + throw new RuntimeException("Plugin version argument must be of type ApiVersion, but got " + parameter.getType()); + } + injectPluginVersion = true; + } + + if (injectPluginName && injectPluginVersion) { + // This should not happen, since we check types, + // and those two have different types -> it would already have failed + throw new RuntimeException("Wtf?"); + } + + RerouteArgument argument = new RerouteArgument(type, injectPluginName, injectPluginVersion); + arguments.add(argument); + if (!injectPluginName && !injectPluginVersion) { + sourceArguments.add(argument); + } + } + + RerouteStatic rerouteStatic = method.getAnnotation(RerouteStatic.class); + Type sourceOwner; + if (rerouteStatic != null) { + sourceOwner = Type.getObjectType(rerouteStatic.value()); + } else { + RerouteArgument argument = sourceArguments.get(0); + sourceOwner = argument.type(); + sourceArguments.remove(argument); + } + Type sourceDesc = Type.getMethodType(rerouteReturn.type(), sourceArguments.stream().map(RerouteArgument::type).toArray(Type[]::new)); + + RerouteMethodName rerouteMethodName = method.getAnnotation(RerouteMethodName.class); + String methodName; + if (rerouteMethodName != null) { + methodName = rerouteMethodName.value(); + } else { + methodName = method.getName(); + } + + String methodKey = sourceOwner.getInternalName() + + " " + + sourceDesc.getDescriptor() + + " " + + methodName; + + Type targetType = Type.getType(method); + + return new RerouteMethodData(methodKey, sourceDesc, sourceOwner, methodName, rerouteStatic != null, targetType, Type.getInternalName(method.getDeclaringClass()), method.getName(), arguments, rerouteReturn); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteMethodData.java b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteMethodData.java new file mode 100644 index 000000000..0c39bc279 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteMethodData.java @@ -0,0 +1,9 @@ +package org.bukkit.craftbukkit.legacy.reroute; + +import java.util.List; +import org.objectweb.asm.Type; + +public record RerouteMethodData(String source, Type sourceDesc, Type sourceOwner, String sourceName, + boolean staticReroute, Type targetType, String targetOwner, String targetName, + List arguments, RerouteReturn rerouteReturn) { +} diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteMethodName.java b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteMethodName.java new file mode 100644 index 000000000..0a3fdd886 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteMethodName.java @@ -0,0 +1,13 @@ +package org.bukkit.craftbukkit.legacy.reroute; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface RerouteMethodName { + + String value(); +} diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteReturn.java b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteReturn.java new file mode 100644 index 000000000..7e8cdcd2e --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteReturn.java @@ -0,0 +1,32 @@ +package org.bukkit.craftbukkit.legacy.reroute; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +public record RerouteReturn(Type type) { + + /** + * Converts the type string to the correct return opcode. + *
+ * References: + *
+ * Interpretation of field descriptors + *
+ * return Opcode + *
+ * ireturn Opcode + *
+ * lreturn Opcode + *
+ * freturn Opcode + *
+ * dreturn Opcode + *
+ * areturn Opcode + * + * @return the opcode of the type + */ + public int instruction() { + return type.getOpcode(Opcodes.IRETURN); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteStatic.java b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteStatic.java new file mode 100644 index 000000000..66279697f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/legacy/reroute/RerouteStatic.java @@ -0,0 +1,13 @@ +package org.bukkit.craftbukkit.legacy.reroute; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface RerouteStatic { + + String value(); +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/ApiVersion.java b/src/main/java/org/bukkit/craftbukkit/util/ApiVersion.java new file mode 100644 index 000000000..689fdd4c5 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/ApiVersion.java @@ -0,0 +1,125 @@ +package org.bukkit.craftbukkit.util; + +import java.util.HashMap; +import java.util.Map; +import org.jetbrains.annotations.NotNull; + +public final class ApiVersion implements Comparable { + + public static final ApiVersion CURRENT; + public static final ApiVersion FLATTENING; + public static final ApiVersion FIELD_NAME_PARITY; + public static final ApiVersion NONE; + + private static final Map versions; + + static { + versions = new HashMap<>(); + CURRENT = getOrCreateVersion("1.20.5"); + FLATTENING = getOrCreateVersion("1.13"); + FIELD_NAME_PARITY = getOrCreateVersion("1.20.5"); + NONE = getOrCreateVersion("none"); + } + + private final boolean none; + private final int major; + private final int minor; + private final int patch; + + private ApiVersion() { + this.none = true; + this.major = Integer.MIN_VALUE; + this.minor = Integer.MIN_VALUE; + this.patch = Integer.MIN_VALUE; + } + + private ApiVersion(int major, int minor, int patch) { + this.none = false; + this.major = major; + this.minor = minor; + this.patch = patch; + } + + public static ApiVersion getOrCreateVersion(String versionString) { + if (versionString == null || versionString.trim().isEmpty() || versionString.equalsIgnoreCase("none")) { + return versions.computeIfAbsent("none", s -> new ApiVersion()); + } + + ApiVersion version = versions.get(versionString); + + if (version != null) { + return version; + } + + String[] versionParts = versionString.split("\\."); + + if (versionParts.length != 2 && versionParts.length != 3) { + throw new IllegalArgumentException(String.format("API version string should be of format \"major.minor.patch\" or \"major.minor\", where \"major\", \"minor\" and \"patch\" are numbers. For example \"1.18.2\" or \"1.13\", but got '%s' instead.", versionString)); + } + + int major = parseNumber(versionParts[0]); + int minor = parseNumber(versionParts[1]); + + int patch; + if (versionParts.length == 3) { + patch = parseNumber(versionParts[2]); + } else { + patch = 0; + } + + versionString = toVersionString(major, minor, patch); + return versions.computeIfAbsent(versionString, s -> new ApiVersion(major, minor, patch)); + } + + private static int parseNumber(String number) { + return Integer.parseInt(number); + } + + private static String toVersionString(int major, int minor, int patch) { + return major + "." + minor + "." + patch; + } + + @Override + public int compareTo(@NotNull ApiVersion other) { + int result = Integer.compare(major, other.major); + + if (result == 0) { + result = Integer.compare(minor, other.minor); + } + + if (result == 0) { + result = Integer.compare(patch, other.patch); + } + + return result; + } + + public String getVersionString() { + if (none) { + return "none"; + } + + return toVersionString(major, minor, patch); + } + + public boolean isNewerThan(ApiVersion apiVersion) { + return compareTo(apiVersion) > 0; + } + + public boolean isOlderThan(ApiVersion apiVersion) { + return compareTo(apiVersion) < 0; + } + + public boolean isNewerThanOrSameAs(ApiVersion apiVersion) { + return compareTo(apiVersion) >= 0; + } + + public boolean isOlderThanOrSameAs(ApiVersion apiVersion) { + return compareTo(apiVersion) <= 0; + } + + @Override + public String toString() { + return getVersionString(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/ClassTraverser.java b/src/main/java/org/bukkit/craftbukkit/util/ClassTraverser.java new file mode 100644 index 000000000..2e118c417 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/ClassTraverser.java @@ -0,0 +1,46 @@ +package org.bukkit.craftbukkit.util; + +import com.google.common.collect.Sets; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public class ClassTraverser implements Iterator> { + + private final Set> visit = new HashSet<>(); + private final Set> toVisit = new HashSet<>(); + + private Class next; + + public ClassTraverser(Class next) { + this.next = next; + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public Class next() { + Class clazz = next; + + visit.add(next); + + Set> classes = Sets.newHashSet(clazz.getInterfaces()); + classes.add(clazz.getSuperclass()); + classes.remove(null); // Super class can be null, remove it if this is the case + classes.removeAll(visit); + toVisit.addAll(classes); + + if (toVisit.isEmpty()) { + next = null; + return clazz; + } + + next = toVisit.iterator().next(); + toVisit.remove(next); + + return clazz; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java index 4d7718eba..93c989b6d 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java @@ -11,6 +11,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; @@ -19,18 +20,28 @@ import joptsimple.OptionParser; import joptsimple.OptionSet; import joptsimple.OptionSpec; import org.bukkit.Material; +import org.bukkit.craftbukkit.legacy.FieldRename; +import org.bukkit.craftbukkit.legacy.reroute.RerouteArgument; +import org.bukkit.craftbukkit.legacy.reroute.RerouteBuilder; +import org.bukkit.craftbukkit.legacy.reroute.RerouteMethodData; import org.bukkit.plugin.AuthorNagException; +import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; +import org.objectweb.asm.RecordComponentVisitor; import org.objectweb.asm.Type; +import org.objectweb.asm.TypePath; import org.objectweb.asm.commons.ClassRemapper; import org.objectweb.asm.commons.SimpleRemapper; public class Commodore { + private static final String BUKKIT_GENERATED_METHOD_PREFIX = "BUKKIT_CUSTOM_METHOD_"; private static final Set EVIL = new HashSet<>(Arrays.asList( "org/bukkit/World (III)I getBlockTypeIdAt", @@ -51,6 +62,8 @@ public class Commodore { "org/spigotmc/event/entity/EntityDismountEvent", "org/bukkit/event/entity/EntityDismountEvent" ); + private static final Map FIELD_RENAME_METHOD_REROUTE = RerouteBuilder.buildFromClass(FieldRename.class); + public static void main(String[] args) { OptionParser parser = new OptionParser(); OptionSpec inputFlag = parser.acceptsAll(Arrays.asList("i", "input")).withRequiredArg().ofType(File.class).required(); @@ -95,7 +108,7 @@ public class Commodore { byte[] b = ByteStreams.toByteArray(is); if (entry.getName().endsWith(".class")) { - b = convert(b, false); + b = convert(b, "dummy", ApiVersion.NONE); entry = new JarEntry(entry.getName()); } @@ -113,81 +126,74 @@ public class Commodore { } } - public static byte[] convert(byte[] b, final boolean modern) { + public static byte[] convert(byte[] b, final String pluginName, final ApiVersion pluginVersion) { + final boolean modern = pluginVersion.isNewerThanOrSameAs(ApiVersion.FLATTENING); ClassReader cr = new ClassReader(b); ClassWriter cw = new ClassWriter(cr, 0); cr.accept(new ClassRemapper(new ClassVisitor(Opcodes.ASM9, cw) { + final Set rerouteMethodData = new HashSet<>(); + String className; + boolean isInterface; + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + className = name; + isInterface = (access & Opcodes.ACC_INTERFACE) != 0; + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public void visitEnd() { + for (RerouteMethodData rerouteMethodData : rerouteMethodData) { + MethodVisitor methodVisitor = super.visitMethod(Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PUBLIC, buildMethodName(rerouteMethodData), buildMethodDesc(rerouteMethodData), null, null); + methodVisitor.visitCode(); + int index = 0; + int extraSize = 0; + for (RerouteArgument argument : rerouteMethodData.arguments()) { + if (argument.injectPluginName()) { + methodVisitor.visitLdcInsn(pluginName); + } else if (argument.injectPluginVersion()) { + methodVisitor.visitLdcInsn(pluginVersion.getVersionString()); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(ApiVersion.class), "getOrCreateVersion", "(Ljava/lang/String;)L" + Type.getInternalName(ApiVersion.class) + ";", false); + } else { + methodVisitor.visitIntInsn(argument.instruction(), index); + index++; + + // Long and double need two space + // https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-4.html#jvms-4.7.3 + // https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-2.html#jvms-2.6.1 + // https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-2.html#jvms-2.6.2 + extraSize += argument.type().getSize() - 1; + } + } + + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, rerouteMethodData.targetOwner(), rerouteMethodData.targetName(), rerouteMethodData.targetType().getDescriptor(), false); + methodVisitor.visitInsn(rerouteMethodData.rerouteReturn().instruction()); + methodVisitor.visitMaxs(rerouteMethodData.arguments().size() + extraSize, index + extraSize); + methodVisitor.visitEnd(); + } + + super.visitEnd(); + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return createAnnotationVisitor(pluginVersion, api, super.visitAnnotation(descriptor, visible)); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return createAnnotationVisitor(pluginVersion, api, super.visitTypeAnnotation(typeRef, typePath, descriptor, visible)); + } + @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { return new MethodVisitor(api, super.visitMethod(access, name, desc, signature, exceptions)) { @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { - if (owner.equals("org/bukkit/block/Biome")) { - switch (name) { - case "NETHER": - super.visitFieldInsn(opcode, owner, "NETHER_WASTES", desc); - return; - case "TALL_BIRCH_FOREST": - super.visitFieldInsn(opcode, owner, "OLD_GROWTH_BIRCH_FOREST", desc); - return; - case "GIANT_TREE_TAIGA": - super.visitFieldInsn(opcode, owner, "OLD_GROWTH_PINE_TAIGA", desc); - return; - case "GIANT_SPRUCE_TAIGA": - super.visitFieldInsn(opcode, owner, "OLD_GROWTH_SPRUCE_TAIGA", desc); - return; - case "SNOWY_TUNDRA": - super.visitFieldInsn(opcode, owner, "SNOWY_PLAINS", desc); - return; - case "JUNGLE_EDGE": - super.visitFieldInsn(opcode, owner, "SPARSE_JUNGLE", desc); - return; - case "STONE_SHORE": - super.visitFieldInsn(opcode, owner, "STONY_SHORE", desc); - return; - case "MOUNTAINS": - super.visitFieldInsn(opcode, owner, "WINDSWEPT_HILLS", desc); - return; - case "WOODED_MOUNTAINS": - super.visitFieldInsn(opcode, owner, "WINDSWEPT_FOREST", desc); - return; - case "GRAVELLY_MOUNTAINS": - super.visitFieldInsn(opcode, owner, "WINDSWEPT_GRAVELLY_HILLS", desc); - return; - case "SHATTERED_SAVANNA": - super.visitFieldInsn(opcode, owner, "WINDSWEPT_SAVANNA", desc); - return; - case "WOODED_BADLANDS_PLATEAU": - super.visitFieldInsn(opcode, owner, "WOODED_BADLANDS", desc); - return; - } - } - - if (owner.equals("org/bukkit/entity/EntityType")) { - switch (name) { - case "PIG_ZOMBIE": - super.visitFieldInsn(opcode, owner, "ZOMBIFIED_PIGLIN", desc); - return; - } - } - - if (owner.equals("org/bukkit/attribute/Attribute")) { - switch (name) { - case "HORSE_JUMP_STRENGTH": - super.visitFieldInsn(opcode, owner, "GENERIC_JUMP_STRENGTH", desc); - return; - } - } - - if (owner.equals("org/bukkit/loot/LootTables")) { - switch (name) { - case "ZOMBIE_PIGMAN": - super.visitFieldInsn(opcode, owner, "ZOMBIFIED_PIGLIN", desc); - return; - } - } + name = FieldRename.rename(pluginVersion, owner, name); if (modern) { if (owner.equals("org/bukkit/Material")) { @@ -268,6 +274,10 @@ public class Commodore { } private void handleMethod(MethodPrinter visitor, int opcode, String owner, String name, String desc, boolean itf, Type samMethodType, Type instantiatedMethodType) { + if (checkReroute(visitor, FIELD_RENAME_METHOD_REROUTE, opcode, owner, name, desc, samMethodType, instantiatedMethodType)) { + return; + } + // SPIGOT-4496 if (owner.equals("org/bukkit/map/MapView") && name.equals("getId") && desc.equals("()S")) { // Should be same size on stack so just call other method @@ -373,6 +383,13 @@ public class Commodore { visitor.visit(opcode, owner, name, desc, itf, samMethodType, instantiatedMethodType); } + private boolean checkReroute(MethodPrinter visitor, Map rerouteMethodDataMap, int opcode, String owner, String name, String desc, Type samMethodType, Type instantiatedMethodType) { + return rerouteMethods(rerouteMethodDataMap, opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.H_INVOKESTATIC, owner, name, desc, data -> { + visitor.visit(Opcodes.INVOKESTATIC, className, buildMethodName(data), buildMethodDesc(data), isInterface, samMethodType, instantiatedMethodType); + rerouteMethodData.add(data); + }); + } + @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { handleMethod((newOpcode, newOwner, newName, newDescription, newItf, newSam, newInstantiated) -> { @@ -419,6 +436,71 @@ public class Commodore { // But as with the todo above, I encourage everyone who is reading this to to give it a shot super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return createAnnotationVisitor(pluginVersion, api, super.visitAnnotation(descriptor, visible)); + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + return createAnnotationVisitor(pluginVersion, api, super.visitAnnotationDefault()); + } + + @Override + public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return createAnnotationVisitor(pluginVersion, api, super.visitInsnAnnotation(typeRef, typePath, descriptor, visible)); + } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String descriptor, boolean visible) { + return createAnnotationVisitor(pluginVersion, api, super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible)); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, boolean visible) { + return createAnnotationVisitor(pluginVersion, api, super.visitParameterAnnotation(parameter, descriptor, visible)); + } + + @Override + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return createAnnotationVisitor(pluginVersion, api, super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible)); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return createAnnotationVisitor(pluginVersion, api, super.visitTypeAnnotation(typeRef, typePath, descriptor, visible)); + } + }; + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + return new FieldVisitor(api, super.visitField(access, name, descriptor, signature, value)) { + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return createAnnotationVisitor(pluginVersion, api, super.visitAnnotation(descriptor, visible)); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return createAnnotationVisitor(pluginVersion, api, super.visitTypeAnnotation(typeRef, typePath, descriptor, visible)); + } + }; + } + + @Override + public RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) { + return new RecordComponentVisitor(api, super.visitRecordComponent(name, descriptor, signature)) { + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return createAnnotationVisitor(pluginVersion, api, super.visitAnnotation(descriptor, visible)); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return createAnnotationVisitor(pluginVersion, api, super.visitTypeAnnotation(typeRef, typePath, descriptor, visible)); + } }; } }, new SimpleRemapper(RENAMES)), 0); @@ -426,6 +508,90 @@ public class Commodore { return cw.toByteArray(); } + private static AnnotationVisitor createAnnotationVisitor(ApiVersion apiVersion, int api, AnnotationVisitor delegate) { + return new AnnotationVisitor(api, delegate) { + @Override + public void visitEnum(String name, String descriptor, String value) { + super.visitEnum(name, descriptor, FieldRename.rename(apiVersion, Type.getType(descriptor).getInternalName(), value)); + } + + @Override + public AnnotationVisitor visitArray(String name) { + return createAnnotationVisitor(apiVersion, api, super.visitArray(name)); + } + + @Override + public AnnotationVisitor visitAnnotation(String name, String descriptor) { + return createAnnotationVisitor(apiVersion, api, super.visitAnnotation(name, descriptor)); + } + }; + } + + /* + This method looks (and probably is) overengineered, but it gives the most flexible when it comes to remapping normal methods to static one. + The problem with normal owner and desc replacement is that child classes have them as an owner, instead there parents for there parents methods + + For example, if we have following two interfaces org.bukkit.BlockData and org.bukkit.Orientable extents BlockData + and BlockData has the method org.bukkit.Material getType which we want to reroute to the static method + org.bukkit.Material org.bukkit.craftbukkit.legacy.EnumEvil#getType(org.bukkit.BlockData) + + If we now call BlockData#getType we get as the owner org/bukkit/BlockData and as desc ()Lorg/bukkit/Material; + Which we can nicely reroute by checking if the owner is BlockData and the name getType + The problem, starts if we use Orientable#getType no we get as owner org/bukkit/Orientable and as desc ()Lorg/bukkit/Material; + Now we can now longer safely say to which getType method we need to reroute (assume there are multiple getType methods from different classes, + which are not related to BlockData), simple using the owner class will not work, since would reroute to + EnumEvil#getType(org.bukkit.Orientable) which is not EnumEvil#getType(org.bukkit.BlockData) and will throw a method not found error + at runtime. + + Meaning we would need to add checks for each subclass, which would be pur insanity. + + To solve this, we go through each super class and interfaces (and their super class and interfaces etc.) and try to get an owner + which matches with one of our replacement methods. Based on how inheritance works in java, this method should be safe to use. + + As a site note: This method could also be used for the other method reroute, e.g. legacy method rerouting, where only the replacement + method needs to be written, and this method figures out the rest, which could reduce the size and complexity of the Commodore class. + The question then becomes one about performance (since this is not the most performance way) and convenience. + But since it is only applied for each class and method call once when they get first loaded, it should not be that bad. + (Although some load time testing could be done) + */ + public static boolean rerouteMethods(Map rerouteMethodDataMap, boolean staticCall, String owner, String name, String desc, Consumer consumer) { + Type ownerType = Type.getObjectType(owner); + Class ownerClass; + try { + ownerClass = Class.forName(ownerType.getClassName()); + } catch (ClassNotFoundException e) { + return false; + } + + ClassTraverser it = new ClassTraverser(ownerClass); + while (it.hasNext()) { + Class clazz = it.next(); + + String methodKey = Type.getInternalName(clazz) + " " + desc + " " + name; + + RerouteMethodData data = rerouteMethodDataMap.get(methodKey); + if (data == null) { + if (staticCall) { + return false; + } + continue; + } + + consumer.accept(data); + return true; + } + + return false; + } + + private static String buildMethodName(RerouteMethodData rerouteMethodData) { + return BUKKIT_GENERATED_METHOD_PREFIX + rerouteMethodData.targetOwner().replace('/', '_') + "_" + rerouteMethodData.targetName(); + } + + private static String buildMethodDesc(RerouteMethodData rerouteMethodData) { + return Type.getMethodDescriptor(rerouteMethodData.sourceDesc().getReturnType(), rerouteMethodData.arguments().stream().filter(a -> !a.injectPluginName()).filter(a -> !a.injectPluginVersion()).map(RerouteArgument::type).toArray(Type[]::new)); + } + @FunctionalInterface private interface MethodPrinter { diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java index 9fa0c0458..f28615452 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -13,9 +13,7 @@ import com.mojang.serialization.Dynamic; import com.mojang.serialization.JsonOps; import java.io.File; import java.io.IOException; -import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Locale; import java.util.Map; import java.util.logging.Level; @@ -290,30 +288,27 @@ public final class CraftMagicNumbers implements UnsafeValues { return file.delete(); } - private static final List SUPPORTED_API = Arrays.asList("1.13", "1.14", "1.15", "1.16", "1.17", "1.18", "1.19", "1.20"); - @Override public void checkSupported(PluginDescriptionFile pdf) throws InvalidPluginException { - String minimumVersion = MinecraftServer.getServer().server.minimumAPI; - int minimumIndex = SUPPORTED_API.indexOf(minimumVersion); + ApiVersion toCheck = ApiVersion.getOrCreateVersion(pdf.getAPIVersion()); + ApiVersion minimumVersion = MinecraftServer.getServer().server.minimumAPI; - if (pdf.getAPIVersion() != null) { - int pluginIndex = SUPPORTED_API.indexOf(pdf.getAPIVersion()); + if (toCheck.isNewerThan(ApiVersion.CURRENT)) { + // Newer than supported + throw new InvalidPluginException("Unsupported API version " + pdf.getAPIVersion()); + } - if (pluginIndex == -1) { - throw new InvalidPluginException("Unsupported API version " + pdf.getAPIVersion()); - } + if (toCheck.isOlderThan(minimumVersion)) { + // Older than supported + throw new InvalidPluginException("Plugin API version " + pdf.getAPIVersion() + " is lower than the minimum allowed version. Please update or replace it."); + } - if (pluginIndex < minimumIndex) { - throw new InvalidPluginException("Plugin API version " + pdf.getAPIVersion() + " is lower than the minimum allowed version. Please update or replace it."); - } - } else { - if (minimumIndex == -1) { - CraftLegacy.init(); - Bukkit.getLogger().log(Level.WARNING, "Legacy plugin " + pdf.getFullName() + " does not specify an api-version."); - } else { - throw new InvalidPluginException("Plugin API version " + pdf.getAPIVersion() + " is lower than the minimum allowed version. Please update or replace it."); - } + if (toCheck.isOlderThan(ApiVersion.FLATTENING)) { + CraftLegacy.init(); + } + + if (toCheck == ApiVersion.NONE) { + Bukkit.getLogger().log(Level.WARNING, "Legacy plugin " + pdf.getFullName() + " does not specify an api-version."); } } @@ -324,7 +319,7 @@ public final class CraftMagicNumbers implements UnsafeValues { @Override public byte[] processClass(PluginDescriptionFile pdf, String path, byte[] clazz) { try { - clazz = Commodore.convert(clazz, !isLegacy(pdf)); + clazz = Commodore.convert(clazz, pdf.getName(), ApiVersion.getOrCreateVersion(pdf.getAPIVersion())); } catch (Exception ex) { Bukkit.getLogger().log(Level.SEVERE, "Fatal error trying to convert " + pdf.getFullName() + ":" + path, ex); } diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/CompositeSerialization.java b/src/test/java/org/bukkit/craftbukkit/inventory/CompositeSerialization.java index 3f57b2a2b..e7e74c24b 100644 --- a/src/test/java/org/bukkit/craftbukkit/inventory/CompositeSerialization.java +++ b/src/test/java/org/bukkit/craftbukkit/inventory/CompositeSerialization.java @@ -35,14 +35,14 @@ public class CompositeSerialization extends AbstractTestingBase { stacks.add(new ItemStack(Material.OAK_LEAVES, 32, (short) 2)); ItemStack item7 = new ItemStack(Material.IRON_SHOVEL); - item7.addUnsafeEnchantment(Enchantment.PROTECTION_FIRE, 1); + item7.addUnsafeEnchantment(Enchantment.FIRE_PROTECTION, 1); stacks.add(item7); ItemStack item8 = new ItemStack(Material.IRON_PICKAXE); - item8.addUnsafeEnchantment(Enchantment.PROTECTION_FALL, 2); - item8.addUnsafeEnchantment(Enchantment.PROTECTION_EXPLOSIONS, 1); - item8.addUnsafeEnchantment(Enchantment.PROTECTION_PROJECTILE, 5); - item8.addUnsafeEnchantment(Enchantment.OXYGEN, 4); + item8.addUnsafeEnchantment(Enchantment.FEATHER_FALLING, 2); + item8.addUnsafeEnchantment(Enchantment.BLAST_PROTECTION, 1); + item8.addUnsafeEnchantment(Enchantment.PROJECTILE_PROTECTION, 5); + item8.addUnsafeEnchantment(Enchantment.RESPIRATION, 4); stacks.add(item8); ItemStack item9 = new ItemStack(Material.APPLE); diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java index 7e5cee8ec..c491b9933 100644 --- a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java +++ b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java @@ -98,46 +98,46 @@ public class ItemMetaTest extends AbstractTestingBase { @Test public void testConflictingEnchantment() { ItemMeta itemMeta = Bukkit.getItemFactory().getItemMeta(Material.DIAMOND_PICKAXE); - assertThat(itemMeta.hasConflictingEnchant(Enchantment.DURABILITY), is(false)); + assertThat(itemMeta.hasConflictingEnchant(Enchantment.UNBREAKING), is(false)); itemMeta.addEnchant(Enchantment.SILK_TOUCH, 1, false); - assertThat(itemMeta.hasConflictingEnchant(Enchantment.DURABILITY), is(false)); - assertThat(itemMeta.hasConflictingEnchant(Enchantment.LOOT_BONUS_BLOCKS), is(true)); + assertThat(itemMeta.hasConflictingEnchant(Enchantment.UNBREAKING), is(false)); + assertThat(itemMeta.hasConflictingEnchant(Enchantment.FORTUNE), is(true)); assertThat(itemMeta.hasConflictingEnchant(null), is(false)); } @Test public void testConflictingStoredEnchantment() { EnchantmentStorageMeta itemMeta = (EnchantmentStorageMeta) Bukkit.getItemFactory().getItemMeta(Material.ENCHANTED_BOOK); - assertThat(itemMeta.hasConflictingStoredEnchant(Enchantment.DURABILITY), is(false)); + assertThat(itemMeta.hasConflictingStoredEnchant(Enchantment.UNBREAKING), is(false)); itemMeta.addStoredEnchant(Enchantment.SILK_TOUCH, 1, false); - assertThat(itemMeta.hasConflictingStoredEnchant(Enchantment.DURABILITY), is(false)); - assertThat(itemMeta.hasConflictingStoredEnchant(Enchantment.LOOT_BONUS_BLOCKS), is(true)); + assertThat(itemMeta.hasConflictingStoredEnchant(Enchantment.UNBREAKING), is(false)); + assertThat(itemMeta.hasConflictingStoredEnchant(Enchantment.FORTUNE), is(true)); assertThat(itemMeta.hasConflictingStoredEnchant(null), is(false)); } @Test public void testConflictingEnchantments() { ItemMeta itemMeta = Bukkit.getItemFactory().getItemMeta(Material.DIAMOND_PICKAXE); - itemMeta.addEnchant(Enchantment.DURABILITY, 6, true); - itemMeta.addEnchant(Enchantment.DIG_SPEED, 6, true); - assertThat(itemMeta.hasConflictingEnchant(Enchantment.LOOT_BONUS_BLOCKS), is(false)); + itemMeta.addEnchant(Enchantment.UNBREAKING, 6, true); + itemMeta.addEnchant(Enchantment.EFFICIENCY, 6, true); + assertThat(itemMeta.hasConflictingEnchant(Enchantment.FORTUNE), is(false)); itemMeta.addEnchant(Enchantment.SILK_TOUCH, 1, false); - assertThat(itemMeta.hasConflictingEnchant(Enchantment.LOOT_BONUS_BLOCKS), is(true)); + assertThat(itemMeta.hasConflictingEnchant(Enchantment.FORTUNE), is(true)); assertThat(itemMeta.hasConflictingEnchant(null), is(false)); } @Test public void testConflictingStoredEnchantments() { EnchantmentStorageMeta itemMeta = (EnchantmentStorageMeta) Bukkit.getItemFactory().getItemMeta(Material.ENCHANTED_BOOK); - itemMeta.addStoredEnchant(Enchantment.DURABILITY, 6, true); - itemMeta.addStoredEnchant(Enchantment.DIG_SPEED, 6, true); - assertThat(itemMeta.hasConflictingStoredEnchant(Enchantment.LOOT_BONUS_BLOCKS), is(false)); + itemMeta.addStoredEnchant(Enchantment.UNBREAKING, 6, true); + itemMeta.addStoredEnchant(Enchantment.EFFICIENCY, 6, true); + assertThat(itemMeta.hasConflictingStoredEnchant(Enchantment.FORTUNE), is(false)); itemMeta.addStoredEnchant(Enchantment.SILK_TOUCH, 1, false); - assertThat(itemMeta.hasConflictingStoredEnchant(Enchantment.LOOT_BONUS_BLOCKS), is(true)); + assertThat(itemMeta.hasConflictingStoredEnchant(Enchantment.FORTUNE), is(true)); assertThat(itemMeta.hasConflictingStoredEnchant(null), is(false)); } @@ -276,7 +276,7 @@ public class ItemMetaTest extends AbstractTestingBase { @Override ItemStack operate(final ItemStack cleanStack) { final PotionMeta meta = (PotionMeta) cleanStack.getItemMeta(); meta.setBasePotionType(PotionType.WATER); - meta.addCustomEffect(PotionEffectType.CONFUSION.createEffect(1, 1), false); + meta.addCustomEffect(PotionEffectType.NAUSEA.createEffect(1, 1), false); cleanStack.setItemMeta(meta); return cleanStack; } @@ -292,7 +292,7 @@ public class ItemMetaTest extends AbstractTestingBase { new StackProvider(Material.ENCHANTED_BOOK) { @Override ItemStack operate(final ItemStack cleanStack) { final EnchantmentStorageMeta meta = (EnchantmentStorageMeta) cleanStack.getItemMeta(); - meta.addStoredEnchant(Enchantment.ARROW_FIRE, 1, true); + meta.addStoredEnchant(Enchantment.FLAME, 1, true); cleanStack.setItemMeta(meta); return cleanStack; } @@ -369,7 +369,7 @@ public class ItemMetaTest extends AbstractTestingBase { new StackProvider(Material.SUSPICIOUS_STEW) { @Override ItemStack operate(ItemStack cleanStack) { final CraftMetaSuspiciousStew meta = ((CraftMetaSuspiciousStew) cleanStack.getItemMeta()); - meta.addCustomEffect(PotionEffectType.CONFUSION.createEffect(1, 0), false); + meta.addCustomEffect(PotionEffectType.NAUSEA.createEffect(1, 0), false); cleanStack.setItemMeta(meta); return cleanStack; } @@ -402,7 +402,7 @@ public class ItemMetaTest extends AbstractTestingBase { new StackProvider(Material.GOAT_HORN) { @Override ItemStack operate(ItemStack cleanStack) { final CraftMetaMusicInstrument meta = (CraftMetaMusicInstrument) cleanStack.getItemMeta(); - meta.setInstrument(MusicInstrument.ADMIRE); + meta.setInstrument(MusicInstrument.ADMIRE_GOAT_HORN); cleanStack.setItemMeta(meta); return cleanStack; } diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/ItemStackEnchantStorageTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/ItemStackEnchantStorageTest.java index 0e42ae68f..a8fc0a40b 100644 --- a/src/test/java/org/bukkit/craftbukkit/inventory/ItemStackEnchantStorageTest.java +++ b/src/test/java/org/bukkit/craftbukkit/inventory/ItemStackEnchantStorageTest.java @@ -29,7 +29,7 @@ public class ItemStackEnchantStorageTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { EnchantmentStorageMeta meta = (EnchantmentStorageMeta) cleanStack.getItemMeta(); - meta.addStoredEnchant(Enchantment.DURABILITY, 1, true); + meta.addStoredEnchant(Enchantment.UNBREAKING, 1, true); cleanStack.setItemMeta(meta); return cleanStack; } @@ -67,7 +67,7 @@ public class ItemStackEnchantStorageTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { EnchantmentStorageMeta meta = (EnchantmentStorageMeta) cleanStack.getItemMeta(); - meta.addStoredEnchant(Enchantment.DAMAGE_UNDEAD, 1, true); + meta.addStoredEnchant(Enchantment.SMITE, 1, true); cleanStack.setItemMeta(meta); return cleanStack; } @@ -76,7 +76,7 @@ public class ItemStackEnchantStorageTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { EnchantmentStorageMeta meta = (EnchantmentStorageMeta) cleanStack.getItemMeta(); - meta.addStoredEnchant(Enchantment.DAMAGE_UNDEAD, 1, true); + meta.addStoredEnchant(Enchantment.SMITE, 1, true); meta.addStoredEnchant(Enchantment.FIRE_ASPECT, 1, true); cleanStack.setItemMeta(meta); return cleanStack; @@ -89,7 +89,7 @@ public class ItemStackEnchantStorageTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { EnchantmentStorageMeta meta = (EnchantmentStorageMeta) cleanStack.getItemMeta(); - meta.addStoredEnchant(Enchantment.PROTECTION_FIRE, 1, true); + meta.addStoredEnchant(Enchantment.FIRE_PROTECTION, 1, true); cleanStack.setItemMeta(meta); return cleanStack; } @@ -98,7 +98,7 @@ public class ItemStackEnchantStorageTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { EnchantmentStorageMeta meta = (EnchantmentStorageMeta) cleanStack.getItemMeta(); - meta.addEnchant(Enchantment.PROTECTION_FIRE, 2, true); + meta.addEnchant(Enchantment.FIRE_PROTECTION, 2, true); cleanStack.setItemMeta(meta); return cleanStack; } diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/ItemStackLoreEnchantmentTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/ItemStackLoreEnchantmentTest.java index ded1d1663..c9c42a764 100644 --- a/src/test/java/org/bukkit/craftbukkit/inventory/ItemStackLoreEnchantmentTest.java +++ b/src/test/java/org/bukkit/craftbukkit/inventory/ItemStackLoreEnchantmentTest.java @@ -149,7 +149,7 @@ public class ItemStackLoreEnchantmentTest extends ItemStackTest { new Operator() { @Override public ItemStack operate(ItemStack cleanStack) { - cleanStack.addUnsafeEnchantment(Enchantment.DIG_SPEED, 2); + cleanStack.addUnsafeEnchantment(Enchantment.EFFICIENCY, 2); return cleanStack; } }, @@ -165,7 +165,7 @@ public class ItemStackLoreEnchantmentTest extends ItemStackTest { new Operator() { @Override public ItemStack operate(ItemStack cleanStack) { - cleanStack.addUnsafeEnchantment(Enchantment.OXYGEN, 1); + cleanStack.addUnsafeEnchantment(Enchantment.RESPIRATION, 1); return cleanStack; } }, @@ -183,14 +183,14 @@ public class ItemStackLoreEnchantmentTest extends ItemStackTest { new Operator() { @Override public ItemStack operate(ItemStack cleanStack) { - cleanStack.addUnsafeEnchantment(Enchantment.ARROW_DAMAGE, 1); + cleanStack.addUnsafeEnchantment(Enchantment.POWER, 1); return cleanStack; } }, new Operator() { @Override public ItemStack operate(ItemStack cleanStack) { - cleanStack.addUnsafeEnchantment(Enchantment.ARROW_FIRE, 1); + cleanStack.addUnsafeEnchantment(Enchantment.FLAME, 1); return cleanStack; } }, @@ -201,7 +201,7 @@ public class ItemStackLoreEnchantmentTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { ItemMeta meta = cleanStack.getItemMeta(); - meta.addEnchant(Enchantment.DURABILITY, 1, true); + meta.addEnchant(Enchantment.UNBREAKING, 1, true); cleanStack.setItemMeta(meta); return cleanStack; } @@ -239,7 +239,7 @@ public class ItemStackLoreEnchantmentTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { ItemMeta meta = cleanStack.getItemMeta(); - meta.addEnchant(Enchantment.PROTECTION_FIRE, 1, true); + meta.addEnchant(Enchantment.FIRE_PROTECTION, 1, true); cleanStack.setItemMeta(meta); return cleanStack; } @@ -248,7 +248,7 @@ public class ItemStackLoreEnchantmentTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { ItemMeta meta = cleanStack.getItemMeta(); - meta.addEnchant(Enchantment.PROTECTION_FIRE, 2, true); + meta.addEnchant(Enchantment.FIRE_PROTECTION, 2, true); cleanStack.setItemMeta(meta); return cleanStack; } diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/ItemStackPotionsTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/ItemStackPotionsTest.java index fe6a2ed8a..549ac3558 100644 --- a/src/test/java/org/bukkit/craftbukkit/inventory/ItemStackPotionsTest.java +++ b/src/test/java/org/bukkit/craftbukkit/inventory/ItemStackPotionsTest.java @@ -29,7 +29,7 @@ public class ItemStackPotionsTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { final PotionMeta meta = (PotionMeta) cleanStack.getItemMeta(); - meta.addCustomEffect(PotionEffectType.CONFUSION.createEffect(1, 1), false); + meta.addCustomEffect(PotionEffectType.NAUSEA.createEffect(1, 1), false); cleanStack.setItemMeta(meta); return cleanStack; } @@ -47,7 +47,7 @@ public class ItemStackPotionsTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { final PotionMeta meta = (PotionMeta) cleanStack.getItemMeta(); - meta.addCustomEffect(PotionEffectType.HARM.createEffect(2, 1), false); + meta.addCustomEffect(PotionEffectType.INSTANT_DAMAGE.createEffect(2, 1), false); cleanStack.setItemMeta(meta); return cleanStack; } @@ -67,7 +67,7 @@ public class ItemStackPotionsTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { final PotionMeta meta = (PotionMeta) cleanStack.getItemMeta(); - meta.addCustomEffect(PotionEffectType.SLOW_DIGGING.createEffect(1, 1), false); + meta.addCustomEffect(PotionEffectType.MINING_FATIGUE.createEffect(1, 1), false); cleanStack.setItemMeta(meta); return cleanStack; } @@ -76,7 +76,7 @@ public class ItemStackPotionsTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { final PotionMeta meta = (PotionMeta) cleanStack.getItemMeta(); - meta.addCustomEffect(PotionEffectType.FAST_DIGGING.createEffect(1, 1), false); + meta.addCustomEffect(PotionEffectType.HASTE.createEffect(1, 1), false); cleanStack.setItemMeta(meta); return cleanStack; } @@ -88,7 +88,7 @@ public class ItemStackPotionsTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { final PotionMeta meta = (PotionMeta) cleanStack.getItemMeta(); - meta.addCustomEffect(PotionEffectType.JUMP.createEffect(1, 1), false); + meta.addCustomEffect(PotionEffectType.JUMP_BOOST.createEffect(1, 1), false); cleanStack.setItemMeta(meta); return cleanStack; } @@ -97,7 +97,7 @@ public class ItemStackPotionsTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { final PotionMeta meta = (PotionMeta) cleanStack.getItemMeta(); - meta.addCustomEffect(PotionEffectType.JUMP.createEffect(1, 1), false); + meta.addCustomEffect(PotionEffectType.JUMP_BOOST.createEffect(1, 1), false); meta.addCustomEffect(PotionEffectType.REGENERATION.createEffect(1, 1), false); cleanStack.setItemMeta(meta); return cleanStack; @@ -131,7 +131,7 @@ public class ItemStackPotionsTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { final PotionMeta meta = (PotionMeta) cleanStack.getItemMeta(); - meta.addCustomEffect(PotionEffectType.INCREASE_DAMAGE.createEffect(1, 1), false); + meta.addCustomEffect(PotionEffectType.STRENGTH.createEffect(1, 1), false); cleanStack.setItemMeta(meta); return cleanStack; } @@ -140,7 +140,7 @@ public class ItemStackPotionsTest extends ItemStackTest { @Override public ItemStack operate(ItemStack cleanStack) { final PotionMeta meta = (PotionMeta) cleanStack.getItemMeta(); - meta.addCustomEffect(PotionEffectType.INCREASE_DAMAGE.createEffect(1, 2), false); + meta.addCustomEffect(PotionEffectType.STRENGTH.createEffect(1, 2), false); cleanStack.setItemMeta(meta); return cleanStack; } diff --git a/src/test/java/org/bukkit/craftbukkit/util/ApiVersionTest.java b/src/test/java/org/bukkit/craftbukkit/util/ApiVersionTest.java new file mode 100644 index 000000000..0aa3ec035 --- /dev/null +++ b/src/test/java/org/bukkit/craftbukkit/util/ApiVersionTest.java @@ -0,0 +1,158 @@ +package org.bukkit.craftbukkit.util; + +import static org.junit.jupiter.api.Assertions.*; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ApiVersionTest { + + public static Stream parseData() { + return Stream.of( + Arguments.of(null, "none"), + Arguments.of("", "none"), + Arguments.of("none", "none"), + Arguments.of("1.12", "1.12.0"), + Arguments.of("1.13.3", "1.13.3"), + Arguments.of("1.+20.3", "1.20.3") + ); + } + + public static Stream compareData() { + return Stream.of( + Arguments.of("none", "none", CompareResult.SAME), + Arguments.of("none", "1.20", CompareResult.SMALLER), + Arguments.of("2.20.3", "1.30.4", CompareResult.BIGGER), + Arguments.of("1.13", "1.12", CompareResult.BIGGER), + Arguments.of("1.13.2", "1.13.3", CompareResult.SMALLER) + ); + } + + public static Stream newerData() { + return Stream.of( + Arguments.of("1.12", "1.12", false), + Arguments.of("1.12", "1.12.2", false), + Arguments.of("1.12.2", "1.12", true) + ); + } + + public static Stream olderData() { + return Stream.of( + Arguments.of("1.12", "1.12", false), + Arguments.of("1.12", "1.12.2", true), + Arguments.of("1.12.2", "1.12", false) + ); + } + + public static Stream newerOrSameData() { + return Stream.of( + Arguments.of("1.12", "1.12", true), + Arguments.of("1.12", "1.12.2", false), + Arguments.of("1.12.2", "1.12", true) + ); + } + + public static Stream olderOrSameData() { + return Stream.of( + Arguments.of("1.12", "1.12", true), + Arguments.of("1.12", "1.12.2", true), + Arguments.of("1.12.2", "1.12", false) + ); + } + + @ParameterizedTest + @MethodSource("parseData") + public void testParsing(String parse, String expected) { + ApiVersion apiVersion = ApiVersion.getOrCreateVersion(parse); + + assertEquals(expected, apiVersion.getVersionString()); + } + + @Test + public void testSameInstance() { + ApiVersion one = ApiVersion.getOrCreateVersion("1.23.3"); + ApiVersion second = ApiVersion.getOrCreateVersion("1.+23.3"); + + assertSame(one, second); + } + + @ParameterizedTest + @MethodSource("compareData") + public void testCompareTo(String first, String second, CompareResult compareResult) { + ApiVersion firstApi = ApiVersion.getOrCreateVersion(first); + ApiVersion secondApi = ApiVersion.getOrCreateVersion(second); + + + int result = firstApi.compareTo(secondApi); + + assertSame(compareResult, CompareResult.toCompareResult(result)); + } + + @ParameterizedTest + @MethodSource("newerData") + public void testNewerThan(String first, String second, boolean newer) { + ApiVersion firstApi = ApiVersion.getOrCreateVersion(first); + ApiVersion secondApi = ApiVersion.getOrCreateVersion(second); + + + boolean result = firstApi.isNewerThan(secondApi); + + assertSame(newer, result); + } + + @ParameterizedTest + @MethodSource("olderData") + public void testOlderThan(String first, String second, boolean older) { + ApiVersion firstApi = ApiVersion.getOrCreateVersion(first); + ApiVersion secondApi = ApiVersion.getOrCreateVersion(second); + + + boolean result = firstApi.isOlderThan(secondApi); + + assertSame(older, result); + } + + @ParameterizedTest + @MethodSource("newerOrSameData") + public void testNewerOrSame(String first, String second, boolean newerOrSame) { + ApiVersion firstApi = ApiVersion.getOrCreateVersion(first); + ApiVersion secondApi = ApiVersion.getOrCreateVersion(second); + + + boolean result = firstApi.isNewerThanOrSameAs(secondApi); + + assertSame(newerOrSame, result); + } + + @ParameterizedTest + @MethodSource("olderOrSameData") + public void testOlderOrSame(String first, String second, boolean olderOrSame) { + ApiVersion firstApi = ApiVersion.getOrCreateVersion(first); + ApiVersion secondApi = ApiVersion.getOrCreateVersion(second); + + + boolean result = firstApi.isOlderThanOrSameAs(secondApi); + + assertSame(olderOrSame, result); + } + + public enum CompareResult { + SMALLER, + BIGGER, + SAME; + + public static CompareResult toCompareResult(int i) { + if (i == 0) { + return CompareResult.SAME; + } + + if (i < 0) { + return CompareResult.SMALLER; + } + + return CompareResult.BIGGER; + } + } +} diff --git a/src/test/java/org/bukkit/potion/PotionTest.java b/src/test/java/org/bukkit/potion/PotionTest.java index 40fdce5f4..1438ff952 100644 --- a/src/test/java/org/bukkit/potion/PotionTest.java +++ b/src/test/java/org/bukkit/potion/PotionTest.java @@ -9,6 +9,7 @@ import net.minecraft.resources.MinecraftKey; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectList; import net.minecraft.world.item.alchemy.PotionRegistry; +import org.bukkit.craftbukkit.legacy.FieldRename; import org.bukkit.craftbukkit.potion.CraftPotionEffectType; import org.bukkit.support.AbstractTestingBase; import org.junit.jupiter.api.Test; @@ -42,7 +43,7 @@ public class PotionTest extends AbstractTestingBase { assertNotNull(bukkit, "No Bukkit type for " + key); assertFalse(bukkit.getName().contains("UNKNOWN"), "No name for " + key); - PotionEffectType byName = PotionEffectType.getByName(bukkit.getName()); + PotionEffectType byName = FieldRename.getByName_PotionEffectType(bukkit.getName()); assertEquals(bukkit, byName, "Same type not returned by name " + key); } }