From 622a2976d97775df8650d92be44cdb67ca4012ca Mon Sep 17 00:00:00 2001 From: t00thpick1 Date: Tue, 14 Jan 2014 23:42:40 -0500 Subject: [PATCH] [Bleeding] Fix Achievements and Statistics API. Fixes BUKKIT-5305 --- .../minecraft/server/StatisticManager.java | 6 + .../bukkit/craftbukkit/CraftAchievement.java | 44 ------ .../bukkit/craftbukkit/CraftStatistic.java | 142 ++++++++++++++++++ .../craftbukkit/entity/CraftPlayer.java | 138 ++++++++++++++++- .../craftbukkit/event/CraftEventFactory.java | 44 ++++++ src/test/java/org/bukkit/AchievementTest.java | 37 ----- src/test/java/org/bukkit/ArtTest.java | 2 +- .../bukkit/StatisticsAndAchievementsTest.java | 61 ++++++++ 8 files changed, 386 insertions(+), 88 deletions(-) delete mode 100644 src/main/java/org/bukkit/craftbukkit/CraftAchievement.java create mode 100644 src/main/java/org/bukkit/craftbukkit/CraftStatistic.java delete mode 100644 src/test/java/org/bukkit/AchievementTest.java create mode 100644 src/test/java/org/bukkit/StatisticsAndAchievementsTest.java diff --git a/src/main/java/net/minecraft/server/StatisticManager.java b/src/main/java/net/minecraft/server/StatisticManager.java index ba8e2134e7..86d186a569 100644 --- a/src/main/java/net/minecraft/server/StatisticManager.java +++ b/src/main/java/net/minecraft/server/StatisticManager.java @@ -20,6 +20,12 @@ public class StatisticManager { public void b(EntityHuman entityhuman, Statistic statistic, int i) { if (!statistic.d() || this.b((Achievement) statistic)) { + // CraftBukkit start + org.bukkit.event.Cancellable cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.handleStatisticsIncrease(entityhuman, statistic, a(statistic), i); + if (cancellable != null && cancellable.isCancelled()) { + return; + } + // CraftBukkit end this.a(entityhuman, statistic, this.a(statistic) + i); } } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftAchievement.java b/src/main/java/org/bukkit/craftbukkit/CraftAchievement.java deleted file mode 100644 index f5897e5835..0000000000 --- a/src/main/java/org/bukkit/craftbukkit/CraftAchievement.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.bukkit.craftbukkit; - -import org.bukkit.Achievement; - -import com.google.common.base.CaseFormat; -import com.google.common.collect.BiMap; -import com.google.common.collect.ImmutableBiMap; -import com.google.common.collect.ImmutableMap; - -public class CraftAchievement { - private static final BiMap achievements; - static { - ImmutableMap specialCases = ImmutableMap.builder() - .put("achievement.buildWorkBench", Achievement.BUILD_WORKBENCH) - .put("achievement.diamonds", Achievement.GET_DIAMONDS) - .put("achievement.portal", Achievement.NETHER_PORTAL) - .put("achievement.ghast", Achievement.GHAST_RETURN) - .put("achievement.theEnd", Achievement.END_PORTAL) - .put("achievement.theEnd2", Achievement.THE_END) - .put("achievement.blazeRod", Achievement.GET_BLAZE_ROD) - .put("achievement.potion", Achievement.BREW_POTION) - .build(); - - ImmutableBiMap.Builder builder = ImmutableBiMap.builder(); - for (Achievement achievement : Achievement.values()) { - if (specialCases.values().contains(achievement)) { - continue; - } - builder.put("achievement."+CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, achievement.name()), achievement); - } - - builder.putAll(specialCases); - - achievements = builder.build(); - } - - public static String getAchievementName(Achievement material) { - return achievements.inverse().get(material); - } - - public static Achievement getAchievement(String name) { - return achievements.get(name); - } -} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftStatistic.java b/src/main/java/org/bukkit/craftbukkit/CraftStatistic.java new file mode 100644 index 0000000000..7190c5702f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftStatistic.java @@ -0,0 +1,142 @@ +package org.bukkit.craftbukkit; + +import net.minecraft.server.EntityTypes; +import net.minecraft.server.MonsterEggInfo; +import net.minecraft.server.StatisticList; + +import org.bukkit.Achievement; +import org.bukkit.Statistic; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; + +import com.google.common.base.CaseFormat; +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableMap; + +public class CraftStatistic { + private static final BiMap statistics; + private static final BiMap achievements; + + static { + ImmutableMap specialCases = ImmutableMap. builder() + .put("achievement.buildWorkBench", Achievement.BUILD_WORKBENCH) + .put("achievement.diamonds", Achievement.GET_DIAMONDS) + .put("achievement.portal", Achievement.NETHER_PORTAL) + .put("achievement.ghast", Achievement.GHAST_RETURN) + .put("achievement.theEnd", Achievement.END_PORTAL) + .put("achievement.theEnd2", Achievement.THE_END) + .put("achievement.blazeRod", Achievement.GET_BLAZE_ROD) + .put("achievement.potion", Achievement.BREW_POTION) + .build(); + ImmutableBiMap.Builder statisticBuilder = ImmutableBiMap.builder(); + ImmutableBiMap.Builder achievementBuilder = ImmutableBiMap.builder(); + for (Statistic statistic : Statistic.values()) { + if (statistic == Statistic.PLAY_ONE_TICK) { + statisticBuilder.put("stat.playOneMinute", statistic); + } else { + statisticBuilder.put("stat." + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, statistic.name()), statistic); + } + } + for (Achievement achievement : Achievement.values()) { + if (specialCases.values().contains(achievement)) { + continue; + } + achievementBuilder.put("achievement." + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, achievement.name()), achievement); + } + + achievementBuilder.putAll(specialCases); + + statistics = statisticBuilder.build(); + achievements = achievementBuilder.build(); + } + + private CraftStatistic() {} + + public static org.bukkit.Achievement getBukkitAchievement(net.minecraft.server.Achievement achievement) { + return getBukkitAchievementByName(achievement.e); + } + + public static org.bukkit.Achievement getBukkitAchievementByName(String name) { + return achievements.get(name); + } + + public static org.bukkit.Statistic getBukkitStatistic(net.minecraft.server.Statistic statistic) { + return getBukkitStatisticByName(statistic.e); + } + + public static org.bukkit.Statistic getBukkitStatisticByName(String name) { + if (name.startsWith("stat.killEntity")) { + name = "stat.killEntity"; + } + if (name.startsWith("stat.entityKilledBy")) { + name = "stat.entityKilledBy"; + } + if (name.startsWith("stat.breakItem")) { + name = "stat.breakItem"; + } + if (name.startsWith("stat.useItem")) { + name = "stat.useItem"; + } + if (name.startsWith("stat.mineBlock")) { + name = "stat.mineBlock"; + } + if (name.startsWith("stat.craftItem")) { + name = "stat.craftItem"; + } + return statistics.get(name); + } + + public static net.minecraft.server.Statistic getNMSStatistic(org.bukkit.Statistic statistic) { + return StatisticList.a(statistics.inverse().get(statistic)); + } + + public static net.minecraft.server.Achievement getNMSAchievement(org.bukkit.Achievement achievement) { + return (net.minecraft.server.Achievement) StatisticList.a(achievements.inverse().get(achievement)); + } + + public static net.minecraft.server.Statistic getMaterialStatistic(org.bukkit.Statistic stat, Material material) { + try { + if (stat == Statistic.MINE_BLOCK) { + return StatisticList.C[material.getId()]; + } + if (stat == Statistic.CRAFT_ITEM) { + return StatisticList.D[material.getId()]; + } + if (stat == Statistic.USE_ITEM) { + return StatisticList.E[material.getId()]; + } + if (stat == Statistic.BREAK_ITEM) { + return StatisticList.F[material.getId()]; + } + } catch (ArrayIndexOutOfBoundsException e) { + return null; + } + return null; + } + + public static net.minecraft.server.Statistic getEntityStatistic(org.bukkit.Statistic stat, EntityType entity) { + MonsterEggInfo monsteregginfo = (MonsterEggInfo) EntityTypes.a.get(Integer.valueOf(entity.getTypeId())); + + if (monsteregginfo != null) { + return monsteregginfo.d; + } + return null; + } + + public static EntityType getEntityTypeFromStatistic(net.minecraft.server.Statistic statistic) { + String statisticString = statistic.e; + return EntityType.fromName(statisticString.substring(statisticString.lastIndexOf(".") + 1)); + } + + public static Material getMaterialFromStatistic(net.minecraft.server.Statistic statistic) { + String statisticString = statistic.e; + int id; + try { + id = Integer.valueOf(statisticString.substring(statisticString.lastIndexOf(".") + 1)); + } catch (NumberFormatException e) { + return null; + } + return Material.getMaterial(id); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 33169e2978..9935e7a4c2 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -20,11 +20,11 @@ import net.minecraft.server.*; import org.apache.commons.lang.Validate; import org.apache.commons.lang.NotImplementedException; - import org.bukkit.*; import org.bukkit.Achievement; -import org.bukkit.Material; import org.bukkit.Statistic; +import org.bukkit.Material; +import org.bukkit.Statistic.Type; import org.bukkit.World; import org.bukkit.configuration.serialization.DelegateDeserialization; import org.bukkit.conversations.Conversation; @@ -35,6 +35,7 @@ import org.bukkit.craftbukkit.CraftEffect; import org.bukkit.craftbukkit.CraftOfflinePlayer; import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.CraftSound; +import org.bukkit.craftbukkit.CraftStatistic; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.map.CraftMapView; import org.bukkit.craftbukkit.map.RenderData; @@ -51,7 +52,6 @@ import org.bukkit.inventory.InventoryView.Property; import org.bukkit.map.MapView; import org.bukkit.metadata.MetadataValue; import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.messaging.Messenger; import org.bukkit.plugin.messaging.StandardMessenger; import org.bukkit.scoreboard.Scoreboard; @@ -517,7 +517,29 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public void awardAchievement(Achievement achievement) { - // TODO - non-functional as of ID purge + Validate.notNull(achievement, "Achievement cannot be null"); + if (achievement.hasParent() && !hasAchievement(achievement.getParent())) { + awardAchievement(achievement.getParent()); + } + getHandle().x().a(getHandle(), CraftStatistic.getNMSAchievement(achievement), 1); + getHandle().x().b(getHandle()); + } + + @Override + public void removeAchievement(Achievement achievement) { + Validate.notNull(achievement, "Achievement cannot be null"); + for (Achievement achieve : Achievement.values()) { + if (achieve.getParent() == achievement && hasAchievement(achieve)) { + removeAchievement(achieve); + } + } + getHandle().x().a(getHandle(), CraftStatistic.getNMSAchievement(achievement), 0); + } + + @Override + public boolean hasAchievement(Achievement achievement) { + Validate.notNull(achievement, "Achievement cannot be null"); + return getHandle().x().a(CraftStatistic.getNMSAchievement(achievement)); } @Override @@ -525,9 +547,37 @@ public class CraftPlayer extends CraftHumanEntity implements Player { incrementStatistic(statistic, 1); } + @Override + public void decrementStatistic(Statistic statistic) { + decrementStatistic(statistic, 1); + } + + @Override + public int getStatistic(Statistic statistic) { + Validate.notNull(statistic, "Statistic cannot be null"); + Validate.isTrue(statistic.getType() == Type.UNTYPED, "Must supply additional paramater for this statistic"); + return getHandle().x().a(CraftStatistic.getNMSStatistic(statistic)); + } + @Override public void incrementStatistic(Statistic statistic, int amount) { - // TODO - non-functional as of ID purge + Validate.isTrue(amount > 0, "Amount must be greater than 0"); + setStatistic(statistic, getStatistic(statistic) + amount); + } + + @Override + public void decrementStatistic(Statistic statistic, int amount) { + Validate.isTrue(amount > 0, "Amount must be greater than 0"); + setStatistic(statistic, getStatistic(statistic) - amount); + } + + @Override + public void setStatistic(Statistic statistic, int newValue) { + Validate.notNull(statistic, "Statistic cannot be null"); + Validate.isTrue(statistic.getType() == Type.UNTYPED, "Must supply additional paramater for this statistic"); + Validate.isTrue(newValue >= 0, "Value must be greater than or equal to 0"); + net.minecraft.server.Statistic nmsStatistic = CraftStatistic.getNMSStatistic(statistic); + getHandle().x().a(getHandle(), nmsStatistic, newValue); } @Override @@ -535,9 +585,85 @@ public class CraftPlayer extends CraftHumanEntity implements Player { incrementStatistic(statistic, material, 1); } + @Override + public void decrementStatistic(Statistic statistic, Material material) { + decrementStatistic(statistic, material, 1); + } + + @Override + public int getStatistic(Statistic statistic, Material material) { + Validate.notNull(statistic, "Statistic cannot be null"); + Validate.notNull(material, "Material cannot be null"); + Validate.isTrue(statistic.getType() == Type.BLOCK || statistic.getType() == Type.ITEM, "This statistic does not take a Material parameter"); + net.minecraft.server.Statistic nmsStatistic = CraftStatistic.getMaterialStatistic(statistic, material); + Validate.notNull(nmsStatistic, "The supplied Material does not have a corresponding statistic"); + return getHandle().x().a(nmsStatistic); + } + @Override public void incrementStatistic(Statistic statistic, Material material, int amount) { - // TODO - non-functional as of ID purge + Validate.isTrue(amount > 0, "Amount must be greater than 0"); + setStatistic(statistic, material, getStatistic(statistic, material) + amount); + } + + @Override + public void decrementStatistic(Statistic statistic, Material material, int amount) { + Validate.isTrue(amount > 0, "Amount must be greater than 0"); + setStatistic(statistic, material, getStatistic(statistic, material) - amount); + } + + @Override + public void setStatistic(Statistic statistic, Material material, int newValue) { + Validate.notNull(statistic, "Statistic cannot be null"); + Validate.notNull(material, "Material cannot be null"); + Validate.isTrue(newValue >= 0, "Value must be greater than or equal to 0"); + Validate.isTrue(statistic.getType() == Type.BLOCK || statistic.getType() == Type.ITEM, "This statistic does not take a Material parameter"); + net.minecraft.server.Statistic nmsStatistic = CraftStatistic.getMaterialStatistic(statistic, material); + Validate.notNull(nmsStatistic, "The supplied Material does not have a corresponding statistic"); + getHandle().x().a(getHandle(), nmsStatistic, newValue); + } + + @Override + public void incrementStatistic(Statistic statistic, EntityType entityType) { + incrementStatistic(statistic, entityType, 1); + } + + @Override + public void decrementStatistic(Statistic statistic, EntityType entityType) { + decrementStatistic(statistic, entityType, 1); + } + + @Override + public int getStatistic(Statistic statistic, EntityType entityType) { + Validate.notNull(statistic, "Statistic cannot be null"); + Validate.notNull(entityType, "EntityType cannot be null"); + Validate.isTrue(statistic.getType() == Type.ENTITY, "This statistic does not take an EntityType parameter"); + net.minecraft.server.Statistic nmsStatistic = CraftStatistic.getEntityStatistic(statistic, entityType); + Validate.notNull(nmsStatistic, "The supplied EntityType does not have a corresponding statistic"); + return getHandle().x().a(nmsStatistic); + } + + @Override + public void incrementStatistic(Statistic statistic, EntityType entityType, int amount) { + Validate.isTrue(amount > 0, "Amount must be greater than 0"); + setStatistic(statistic, entityType, getStatistic(statistic, entityType) + amount); + } + + @Override + public void decrementStatistic(Statistic statistic, EntityType entityType, int amount) { + Validate.isTrue(amount > 0, "Amount must be greater than 0"); + setStatistic(statistic, entityType, getStatistic(statistic, entityType) - amount); + } + + @Override + public void setStatistic(Statistic statistic, EntityType entityType, int newValue) { + Validate.notNull(statistic, "Statistic cannot be null"); + Validate.notNull(entityType, "EntityType cannot be null"); + Validate.isTrue(newValue >= 0, "Value must be greater than or equal to 0"); + Validate.isTrue(statistic.getType() == Type.ENTITY, "This statistic does not take an EntityType parameter"); + net.minecraft.server.Statistic nmsStatistic = CraftStatistic.getEntityStatistic(statistic, entityType); + Validate.notNull(nmsStatistic, "The supplied EntityType does not have a corresponding statistic"); + getHandle().x().a(getHandle(), nmsStatistic, newValue); } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index a654a3e605..721215475c 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -32,10 +32,12 @@ import net.minecraft.server.WorldServer; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.Server; +import org.bukkit.Statistic.Type; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftStatistic; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.CraftBlock; import org.bukkit.craftbukkit.block.CraftBlockState; @@ -47,6 +49,7 @@ import org.bukkit.craftbukkit.util.CraftDamageSource; import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.entity.Arrow; import org.bukkit.entity.Creeper; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Horse; import org.bukkit.entity.LightningStrike; import org.bukkit.entity.LivingEntity; @@ -56,6 +59,7 @@ import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; import org.bukkit.entity.ThrownExpBottle; import org.bukkit.entity.ThrownPotion; +import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.block.*; import org.bukkit.event.block.BlockIgniteEvent.IgniteCause; @@ -729,4 +733,44 @@ public class CraftEventFactory { entity.world.getServer().getPluginManager().callEvent(event); return event; } + + public static Cancellable handleStatisticsIncrease(EntityHuman entityHuman, net.minecraft.server.Statistic statistic, int current, int incrementation) { + Player player = ((EntityPlayer) entityHuman).getBukkitEntity(); + Event event; + if (statistic instanceof net.minecraft.server.Achievement) { + if (current != 0) { + return null; + } + event = new PlayerAchievementAwardedEvent(player, CraftStatistic.getBukkitAchievement((net.minecraft.server.Achievement) statistic)); + } else { + org.bukkit.Statistic stat = CraftStatistic.getBukkitStatistic(statistic); + switch (stat) { + case FALL_ONE_CM: + case BOAT_ONE_CM: + case CLIMB_ONE_CM: + case DIVE_ONE_CM: + case FLY_ONE_CM: + case HORSE_ONE_CM: + case MINECART_ONE_CM: + case PIG_ONE_CM: + case PLAY_ONE_TICK: + case SWIM_ONE_CM: + case WALK_ONE_CM: + // Do not process event for these - too spammy + return null; + default: + } + if (stat.getType() == Type.UNTYPED) { + event = new PlayerStatisticIncrementEvent(player, stat, current, current + incrementation); + } else if (stat.getType() == Type.ENTITY) { + EntityType entityType = CraftStatistic.getEntityTypeFromStatistic(statistic); + event = new PlayerStatisticIncrementEvent(player, stat, current, current + incrementation, entityType); + } else { + Material material = CraftStatistic.getMaterialFromStatistic(statistic); + event = new PlayerStatisticIncrementEvent(player, stat, current, current + incrementation, material); + } + } + entityHuman.world.getServer().getPluginManager().callEvent(event); + return (Cancellable) event; + } } diff --git a/src/test/java/org/bukkit/AchievementTest.java b/src/test/java/org/bukkit/AchievementTest.java deleted file mode 100644 index 813568e3bb..0000000000 --- a/src/test/java/org/bukkit/AchievementTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.bukkit; - -import static org.junit.Assert.*; -import static org.hamcrest.Matchers.*; - -import java.util.Collections; -import java.util.List; - -import net.minecraft.server.AchievementList; - -import org.bukkit.craftbukkit.CraftAchievement; -import org.bukkit.support.AbstractTestingBase; -import org.bukkit.support.Util; -import org.junit.Test; - -import com.google.common.collect.Lists; - -public class AchievementTest extends AbstractTestingBase { - @Test - @SuppressWarnings("unchecked") - public void verifyMapping() throws Throwable { - List achievements = Lists.newArrayList(Achievement.values()); - - for (net.minecraft.server.Achievement statistic : (List) AchievementList.e) { - String name = statistic.e; - - String message = String.format("org.bukkit.Achievement is missing: '%s'", name); - - Achievement subject = CraftAchievement.getAchievement(name); - assertNotNull(message, subject); - - assertTrue(name, achievements.remove(subject)); - } - - assertThat("org.bukkit.Achievement has too many achievements", achievements, is(Collections.EMPTY_LIST)); - } -} diff --git a/src/test/java/org/bukkit/ArtTest.java b/src/test/java/org/bukkit/ArtTest.java index 46804f7ed4..c8e11d86bc 100644 --- a/src/test/java/org/bukkit/ArtTest.java +++ b/src/test/java/org/bukkit/ArtTest.java @@ -30,7 +30,7 @@ public class ArtTest { Art subject = Art.getById(id); - String message = String.format("org.bukkit.Art is missing id: %d named: '%s'", id - Achievement.STATISTIC_OFFSET, name); + String message = String.format("org.bukkit.Art is missing id: %d named: '%s'", id, name); assertNotNull(message, subject); assertThat(Art.getByName(name), is(subject)); diff --git a/src/test/java/org/bukkit/StatisticsAndAchievementsTest.java b/src/test/java/org/bukkit/StatisticsAndAchievementsTest.java new file mode 100644 index 0000000000..306c2a41e6 --- /dev/null +++ b/src/test/java/org/bukkit/StatisticsAndAchievementsTest.java @@ -0,0 +1,61 @@ +package org.bukkit; + +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; + +import java.util.Collections; +import java.util.List; + +import net.minecraft.server.AchievementList; +import net.minecraft.server.StatisticList; + +import org.bukkit.craftbukkit.CraftStatistic; +import org.bukkit.support.AbstractTestingBase; +import org.junit.Test; + +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Lists; + +public class StatisticsAndAchievementsTest extends AbstractTestingBase { + @Test + @SuppressWarnings("unchecked") + public void verifyAchievementMapping() throws Throwable { + List achievements = Lists.newArrayList(Achievement.values()); + for (net.minecraft.server.Achievement achievement : (List) AchievementList.e) { + String name = achievement.e; + + String message = String.format("org.bukkit.Achievement is missing: '%s'", name); + + Achievement subject = CraftStatistic.getBukkitAchievement(achievement); + assertThat(message, subject, is(not(nullValue()))); + + assertThat(name, achievements.remove(subject), is(true)); + } + + assertThat("org.bukkit.Achievement has too many achievements", achievements, is(empty())); + } + + @Test + @SuppressWarnings("unchecked") + public void verifyStatisticMapping() throws Throwable { + HashMultiset statistics = HashMultiset.create(); + for (net.minecraft.server.Statistic statistic : (List) StatisticList.b) { + if (statistic instanceof net.minecraft.server.Achievement) { + continue; + } + String name = statistic.e; + + String message = String.format("org.bukkit.Statistic is missing: '%s'", name); + + Statistic subject = CraftStatistic.getBukkitStatistic(statistic); + assertThat(message, subject, is(not(nullValue()))); + + statistics.add(subject); + } + + for (Statistic statistic : Statistic.values()) { + String message = String.format("org.bukkit.Statistic.%s does not have a corresponding minecraft statistic", statistic.name()); + assertThat(message, statistics.remove(statistic, statistics.count(statistic)), is(greaterThan(0))); + } + } +}