diff --git a/Changelog.txt b/Changelog.txt index 4641b529d..4515641b0 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,11 @@ +Version 2.2.013 + (API) Added new methods to com.gmail.nossr50.util.blockmeta.UserBlockTracker for easier readability + (API) Deprecated the old poorly named methods in UserBlockTracker + (Codebase) Cleaned up and organized unit tests relating to UserBlockTracker + + NOTES: + Not planning to delete the deprecated methods in UserBlockTracker anytime soon, as nothing has really changed other than the names + Version 2.2.012 Fixed a bug where Daze would cause an exception in older game versions (1.20.4 and older) diff --git a/pom.xml b/pom.xml index a8ce46efc..dbfe0175c 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.gmail.nossr50.mcMMO mcMMO - 2.2.012 + 2.2.013-SNAPSHOT mcMMO https://github.com/mcMMO-Dev/mcMMO diff --git a/src/main/java/com/gmail/nossr50/config/skills/alchemy/PotionConfig.java b/src/main/java/com/gmail/nossr50/config/skills/alchemy/PotionConfig.java index ecc16e66e..d0cad88b5 100644 --- a/src/main/java/com/gmail/nossr50/config/skills/alchemy/PotionConfig.java +++ b/src/main/java/com/gmail/nossr50/config/skills/alchemy/PotionConfig.java @@ -4,7 +4,6 @@ import com.gmail.nossr50.config.LegacyConfigLoader; import com.gmail.nossr50.datatypes.skills.alchemy.AlchemyPotion; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.util.ItemUtils; -import com.gmail.nossr50.util.PotionUtil; import org.bukkit.ChatColor; import org.bukkit.Color; import org.bukkit.Material; diff --git a/src/main/java/com/gmail/nossr50/datatypes/skills/alchemy/PotionStage.java b/src/main/java/com/gmail/nossr50/datatypes/skills/alchemy/PotionStage.java index 64bf65009..7825c717f 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/skills/alchemy/PotionStage.java +++ b/src/main/java/com/gmail/nossr50/datatypes/skills/alchemy/PotionStage.java @@ -1,7 +1,6 @@ package com.gmail.nossr50.datatypes.skills.alchemy; import com.gmail.nossr50.util.PotionUtil; -import org.bukkit.Bukkit; import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.potion.PotionEffect; diff --git a/src/main/java/com/gmail/nossr50/listeners/BlockListener.java b/src/main/java/com/gmail/nossr50/listeners/BlockListener.java index 5e6ea6a78..23e17af53 100644 --- a/src/main/java/com/gmail/nossr50/listeners/BlockListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/BlockListener.java @@ -310,7 +310,7 @@ public class BlockListener implements Listener { // Minecraft is dumb, the events still throw when a plant "grows" higher than the max block height. Even though no new block is created if (BlockUtils.isWithinWorldBounds(block)) { - mcMMO.getPlaceStore().setFalse(block); + mcMMO.getPlaceStore().setEligible(block); } } @@ -400,14 +400,14 @@ public class BlockListener implements Listener { else if (BlockUtils.affectedBySuperBreaker(blockState) && (ItemUtils.isPickaxe(heldItem) || ItemUtils.isHoe(heldItem)) && mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.MINING) - && !mcMMO.getPlaceStore().isTrue(blockState)) { + && !mcMMO.getPlaceStore().isIneligible(blockState)) { MiningManager miningManager = mcMMOPlayer.getMiningManager(); miningManager.miningBlockCheck(blockState); } /* WOOD CUTTING */ else if (BlockUtils.hasWoodcuttingXP(blockState) && ItemUtils.isAxe(heldItem) - && mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.WOODCUTTING) && !mcMMO.getPlaceStore().isTrue(blockState)) { + && mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.WOODCUTTING) && !mcMMO.getPlaceStore().isIneligible(blockState)) { WoodcuttingManager woodcuttingManager = mcMMOPlayer.getWoodcuttingManager(); if (woodcuttingManager.canUseTreeFeller(heldItem)) { woodcuttingManager.processTreeFeller(blockState); @@ -422,7 +422,7 @@ public class BlockListener implements Listener { } /* EXCAVATION */ - else if (BlockUtils.affectedByGigaDrillBreaker(blockState) && ItemUtils.isShovel(heldItem) && mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.EXCAVATION) && !mcMMO.getPlaceStore().isTrue(blockState)) { + else if (BlockUtils.affectedByGigaDrillBreaker(blockState) && ItemUtils.isShovel(heldItem) && mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.EXCAVATION) && !mcMMO.getPlaceStore().isIneligible(blockState)) { ExcavationManager excavationManager = mcMMOPlayer.getExcavationManager(); excavationManager.excavationBlockCheck(blockState); @@ -687,7 +687,7 @@ public class BlockListener implements Listener { if (UserManager.getPlayer(player).isDebugMode()) { - if (mcMMO.getPlaceStore().isTrue(blockState)) + if (mcMMO.getPlaceStore().isIneligible(blockState)) player.sendMessage("[mcMMO DEBUG] This block is not natural and does not reward treasures/XP"); else { diff --git a/src/main/java/com/gmail/nossr50/listeners/EntityListener.java b/src/main/java/com/gmail/nossr50/listeners/EntityListener.java index 5e3002a15..fb36629de 100644 --- a/src/main/java/com/gmail/nossr50/listeners/EntityListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/EntityListener.java @@ -44,7 +44,6 @@ import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.projectiles.ProjectileSource; -import org.jetbrains.annotations.NotNull; import static com.gmail.nossr50.util.MobMetadataUtils.*; @@ -207,8 +206,8 @@ public class EntityListener implements Listener { if (entity instanceof FallingBlock || entity instanceof Enderman) { boolean isTracked = entity.hasMetadata(MetadataConstants.METADATA_KEY_TRAVELING_BLOCK); - if (mcMMO.getPlaceStore().isTrue(block) && !isTracked) { - mcMMO.getPlaceStore().setFalse(block); + if (mcMMO.getPlaceStore().isIneligible(block) && !isTracked) { + mcMMO.getPlaceStore().setEligible(block); entity.setMetadata(MetadataConstants.METADATA_KEY_TRAVELING_BLOCK, MetadataConstants.MCMMO_METADATA_VALUE); TravelingBlockMetaCleanup metaCleanupTask = new TravelingBlockMetaCleanup(entity, pluginRef); @@ -222,8 +221,8 @@ public class EntityListener implements Listener { //Redstone ore fire this event and should be ignored } else { - if (mcMMO.getPlaceStore().isTrue(block)) { - mcMMO.getPlaceStore().setFalse(block); + if (mcMMO.getPlaceStore().isIneligible(block)) { + mcMMO.getPlaceStore().setEligible(block); } } } diff --git a/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java b/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java index 28a321f88..e9b8daf62 100644 --- a/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java @@ -852,7 +852,7 @@ public class PlayerListener implements Listener { case "NETHER_WART_BLOCK": case "POTATO": case "MANGROVE_PROPAGULE": - mcMMO.getPlaceStore().setFalse(blockState); + mcMMO.getPlaceStore().setEligible(blockState); break; } } diff --git a/src/main/java/com/gmail/nossr50/listeners/WorldListener.java b/src/main/java/com/gmail/nossr50/listeners/WorldListener.java index aedecfea1..010185e9e 100644 --- a/src/main/java/com/gmail/nossr50/listeners/WorldListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/WorldListener.java @@ -32,7 +32,7 @@ public class WorldListener implements Listener { // Using 50 ms later as I do not know of a way to run one tick later (safely) plugin.getFoliaLib().getImpl().runLater(() -> { for (BlockState blockState : event.getBlocks()) { - mcMMO.getPlaceStore().setFalse(blockState); + mcMMO.getPlaceStore().setEligible(blockState); } }, 1); } diff --git a/src/main/java/com/gmail/nossr50/runnables/PistonTrackerTask.java b/src/main/java/com/gmail/nossr50/runnables/PistonTrackerTask.java deleted file mode 100644 index d4e69c5bc..000000000 --- a/src/main/java/com/gmail/nossr50/runnables/PistonTrackerTask.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.gmail.nossr50.runnables; - -import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.util.BlockUtils; -import com.gmail.nossr50.util.CancellableRunnable; -import com.gmail.nossr50.util.MetadataConstants; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; - -import java.util.List; - -public class PistonTrackerTask extends CancellableRunnable { - private final List blocks; - private final BlockFace direction; - private final Block futureEmptyBlock; - - public PistonTrackerTask(List blocks, BlockFace direction, Block futureEmptyBlock) { - this.blocks = blocks; - this.direction = direction; - this.futureEmptyBlock = futureEmptyBlock; - } - - @Override - public void run() { - // Check to see if futureEmptyBlock is empty - if it isn't; the blocks didn't move - if (!BlockUtils.isPistonPiece(futureEmptyBlock.getState())) { - return; - } - - if (mcMMO.getPlaceStore().isTrue(futureEmptyBlock)) { - mcMMO.getPlaceStore().setFalse(futureEmptyBlock); - } - - for (Block b : blocks) { - Block nextBlock = b.getRelative(direction); - - if (nextBlock.hasMetadata(MetadataConstants.METADATA_KEY_PISTON_TRACKING)) { - BlockUtils.setUnnaturalBlock(nextBlock); - nextBlock.removeMetadata(MetadataConstants.METADATA_KEY_PISTON_TRACKING, mcMMO.p); - } - else if (mcMMO.getPlaceStore().isTrue(nextBlock)) { - // Block doesn't have metadatakey but isTrue - set it to false - mcMMO.getPlaceStore().setFalse(nextBlock); - } - } - } -} diff --git a/src/main/java/com/gmail/nossr50/runnables/StickyPistonTrackerTask.java b/src/main/java/com/gmail/nossr50/runnables/StickyPistonTrackerTask.java index d8f52ae30..02590c53f 100644 --- a/src/main/java/com/gmail/nossr50/runnables/StickyPistonTrackerTask.java +++ b/src/main/java/com/gmail/nossr50/runnables/StickyPistonTrackerTask.java @@ -19,7 +19,7 @@ public class StickyPistonTrackerTask extends CancellableRunnable { @Override public void run() { - if (!mcMMO.getPlaceStore().isTrue(movedBlock.getRelative(direction))) { + if (!mcMMO.getPlaceStore().isIneligible(movedBlock.getRelative(direction))) { return; } @@ -29,7 +29,7 @@ public class StickyPistonTrackerTask extends CancellableRunnable { } // The sticky piston actually pulled the block so move the PlaceStore data - mcMMO.getPlaceStore().setFalse(movedBlock.getRelative(direction)); + mcMMO.getPlaceStore().setEligible(movedBlock.getRelative(direction)); BlockUtils.setUnnaturalBlock(movedBlock); } } diff --git a/src/main/java/com/gmail/nossr50/runnables/skills/AlchemyBrewCheckTask.java b/src/main/java/com/gmail/nossr50/runnables/skills/AlchemyBrewCheckTask.java index 11618147e..640b498e8 100644 --- a/src/main/java/com/gmail/nossr50/runnables/skills/AlchemyBrewCheckTask.java +++ b/src/main/java/com/gmail/nossr50/runnables/skills/AlchemyBrewCheckTask.java @@ -2,7 +2,6 @@ package com.gmail.nossr50.runnables.skills; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.skills.alchemy.Alchemy; -import com.gmail.nossr50.skills.alchemy.AlchemyPotionBrewer; import com.gmail.nossr50.util.CancellableRunnable; import com.gmail.nossr50.util.ContainerMetadataUtils; import com.gmail.nossr50.util.player.UserManager; @@ -17,7 +16,6 @@ import org.jetbrains.annotations.Nullable; import java.util.Arrays; import static com.gmail.nossr50.skills.alchemy.AlchemyPotionBrewer.isValidBrew; -import static com.gmail.nossr50.util.EventUtils.getMcMMOPlayer; public class AlchemyBrewCheckTask extends CancellableRunnable { private final BrewingStand brewingStand; diff --git a/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java b/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java index b60c33b7f..a93f6b3f7 100644 --- a/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java +++ b/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java @@ -282,7 +282,7 @@ public class HerbalismManager extends SkillManager { if (brokenPlant.getLocation().equals(originalBreak.getBlock().getLocation())) { //If its the same block as the original, we are going to directly check it for being a valid XP gain and add it to the nonChorusBlocks list even if its a chorus block //This stops a delay from happening when bringing up the XP bar for chorus trees - if (!mcMMO.getPlaceStore().isTrue(originalBreak)) { + if (!mcMMO.getPlaceStore().isIneligible(originalBreak)) { //Even if its a chorus block, the original break will be moved to nonChorusBlocks for immediate XP rewards noDelayPlantBlocks.add(brokenPlant); } else { @@ -335,7 +335,7 @@ public class HerbalismManager extends SkillManager { BlockData plantData = brokenPlantState.getBlockData(); //Check for double drops - if (!mcMMO.getPlaceStore().isTrue(brokenPlant)) { + if (!mcMMO.getPlaceStore().isIneligible(brokenPlant)) { /* * @@ -413,7 +413,7 @@ public class HerbalismManager extends SkillManager { BlockState brokenBlockNewState = brokenPlantBlock.getState(); BlockData plantData = brokenBlockNewState.getBlockData(); - if (mcMMO.getPlaceStore().isTrue(brokenBlockNewState)) { + if (mcMMO.getPlaceStore().isIneligible(brokenBlockNewState)) { /* * * Unnatural Blocks @@ -427,7 +427,7 @@ public class HerbalismManager extends SkillManager { } //Mark it as natural again as it is being broken - mcMMO.getPlaceStore().setFalse(brokenBlockNewState); + mcMMO.getPlaceStore().setEligible(brokenBlockNewState); } else { /* * @@ -489,9 +489,9 @@ public class HerbalismManager extends SkillManager { continue; } - if (mcMMO.getPlaceStore().isTrue(brokenBlockNewState)) { + if (mcMMO.getPlaceStore().isIneligible(brokenBlockNewState)) { //Mark it as natural again as it is being broken - mcMMO.getPlaceStore().setFalse(brokenBlockNewState); + mcMMO.getPlaceStore().setEligible(brokenBlockNewState); } else { //TODO: Do we care about chorus flower age? //Calculate XP for the old type diff --git a/src/main/java/com/gmail/nossr50/skills/mining/MiningManager.java b/src/main/java/com/gmail/nossr50/skills/mining/MiningManager.java index 1d8738de2..a4c8eade0 100644 --- a/src/main/java/com/gmail/nossr50/skills/mining/MiningManager.java +++ b/src/main/java/com/gmail/nossr50/skills/mining/MiningManager.java @@ -181,7 +181,7 @@ public class MiningManager extends SkillManager { //Containers usually have 0 XP unless someone edited their config in a very strange way if (ExperienceConfig.getInstance().getXp(PrimarySkillType.MINING, targetBlock) != 0 && !(targetBlock instanceof Container) - && !mcMMO.getPlaceStore().isTrue(targetBlock)) { + && !mcMMO.getPlaceStore().isIneligible(targetBlock)) { if (BlockUtils.isOre(blockState)) { ores.add(blockState); } else { @@ -216,7 +216,7 @@ public class MiningManager extends SkillManager { Misc.spawnItem(getPlayer(), Misc.getBlockCenter(blockState), new ItemStack(blockState.getType()), ItemSpawnReason.BLAST_MINING_ORES); // Initial block that would have been dropped - if (mcMMO.p.getAdvancedConfig().isBlastMiningBonusDropsEnabled() && !mcMMO.getPlaceStore().isTrue(blockState)) { + if (mcMMO.p.getAdvancedConfig().isBlastMiningBonusDropsEnabled() && !mcMMO.getPlaceStore().isIneligible(blockState)) { for (int i = 1; i < dropMultiplier; i++) { // Bukkit.broadcastMessage("Bonus Drop on Ore: "+blockState.getType().toString()); Misc.spawnItem(getPlayer(), Misc.getBlockCenter(blockState), new ItemStack(blockState.getType()), ItemSpawnReason.BLAST_MINING_ORES_BONUS_DROP); // Initial block that would have been dropped diff --git a/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java b/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java index 877f0cc24..b68cedad7 100644 --- a/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java +++ b/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java @@ -113,7 +113,7 @@ public class WoodcuttingManager extends SkillManager { } public void processWoodcuttingBlockXP(@NotNull BlockState blockState) { - if (mcMMO.getPlaceStore().isTrue(blockState)) + if (mcMMO.getPlaceStore().isIneligible(blockState)) return; int xp = getExperienceFromLog(blockState); @@ -269,7 +269,7 @@ public class WoodcuttingManager extends SkillManager { * in treeFellerBlocks. */ private boolean processTreeFellerTargetBlock(@NotNull BlockState blockState, @NotNull List futureCenterBlocks, @NotNull Set treeFellerBlocks) { - if (treeFellerBlocks.contains(blockState) || mcMMO.getPlaceStore().isTrue(blockState)) { + if (treeFellerBlocks.contains(blockState) || mcMMO.getPlaceStore().isIneligible(blockState)) { return false; } @@ -373,7 +373,7 @@ public class WoodcuttingManager extends SkillManager { * @return Amount of experience */ private static int processTreeFellerXPGains(BlockState blockState, int woodCount) { - if (mcMMO.getPlaceStore().isTrue(blockState)) + if (mcMMO.getPlaceStore().isIneligible(blockState)) return 0; int rawXP = ExperienceConfig.getInstance().getXp(PrimarySkillType.WOODCUTTING, blockState.getType()); diff --git a/src/main/java/com/gmail/nossr50/util/BlockUtils.java b/src/main/java/com/gmail/nossr50/util/BlockUtils.java index 56d1d698d..59245fc9d 100644 --- a/src/main/java/com/gmail/nossr50/util/BlockUtils.java +++ b/src/main/java/com/gmail/nossr50/util/BlockUtils.java @@ -65,7 +65,7 @@ public final class BlockUtils { * @param block target block */ public static void setUnnaturalBlock(@NotNull Block block) { - mcMMO.getPlaceStore().setTrue(block); + mcMMO.getPlaceStore().setIneligible(block); // Failsafe against lingering metadata if (block.hasMetadata(MetadataConstants.METADATA_KEY_BONUS_DROPS)) @@ -82,7 +82,7 @@ public final class BlockUtils { block.removeMetadata(MetadataConstants.METADATA_KEY_REPLANT, mcMMO.p); } - mcMMO.getPlaceStore().setFalse(block); + mcMMO.getPlaceStore().setEligible(block); } /** diff --git a/src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java b/src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java index ba410788d..c463077db 100644 --- a/src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java +++ b/src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java @@ -1,7 +1,6 @@ package com.gmail.nossr50.util; import com.gmail.nossr50.datatypes.skills.subskills.taming.CallOfTheWildType; -import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.skills.taming.TrackedTamingEntity; import com.gmail.nossr50.util.player.NotificationManager; import com.gmail.nossr50.util.skills.ParticleEffectUtils; diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkManager.java b/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkManager.java index 5f38022d5..d572ec518 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkManager.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkManager.java @@ -152,7 +152,7 @@ public class HashChunkManager implements ChunkManager { } } - private synchronized boolean isTrue(int x, int y, int z, @NotNull World world) { + private synchronized boolean isIneligible(int x, int y, int z, @NotNull World world) { CoordinateKey chunkKey = blockCoordinateToChunkKey(world.getUID(), x, y, z); // Get chunk, load from file if necessary @@ -178,32 +178,42 @@ public class HashChunkManager implements ChunkManager { } @Override - public synchronized boolean isTrue(@NotNull Block block) { - return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld()); + public synchronized boolean isIneligible(@NotNull Block block) { + return isIneligible(block.getX(), block.getY(), block.getZ(), block.getWorld()); } @Override - public synchronized boolean isTrue(@NotNull BlockState blockState) { - return isTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld()); + public synchronized boolean isIneligible(@NotNull BlockState blockState) { + return isIneligible(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld()); } @Override - public synchronized void setTrue(@NotNull Block block) { + public synchronized boolean isEligible(@NotNull Block block) { + return !isIneligible(block); + } + + @Override + public synchronized boolean isEligible(@NotNull BlockState blockState) { + return !isIneligible(blockState); + } + + @Override + public synchronized void setIneligible(@NotNull Block block) { set(block.getX(), block.getY(), block.getZ(), block.getWorld(), true); } @Override - public synchronized void setTrue(@NotNull BlockState blockState) { + public synchronized void setIneligible(@NotNull BlockState blockState) { set(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld(), true); } @Override - public synchronized void setFalse(@NotNull Block block) { + public synchronized void setEligible(@NotNull Block block) { set(block.getX(), block.getY(), block.getZ(), block.getWorld(), false); } @Override - public synchronized void setFalse(@NotNull BlockState blockState) { + public synchronized void setEligible(@NotNull BlockState blockState) { set(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld(), false); } diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/NullChunkManager.java b/src/main/java/com/gmail/nossr50/util/blockmeta/NullChunkManager.java index 203376780..bc700b6e4 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/NullChunkManager.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/NullChunkManager.java @@ -17,24 +17,34 @@ public class NullChunkManager implements ChunkManager { public void unloadWorld(@NotNull World world) {} @Override - public boolean isTrue(@NotNull Block block) { + public boolean isIneligible(@NotNull Block block) { return false; } @Override - public boolean isTrue(@NotNull BlockState blockState) { + public boolean isIneligible(@NotNull BlockState blockState) { return false; } @Override - public void setTrue(@NotNull Block block) {} + public boolean isEligible(@NotNull Block block) { + return false; + } @Override - public void setTrue(@NotNull BlockState blockState) {} + public boolean isEligible(@NotNull BlockState blockState) { + return false; + } @Override - public void setFalse(@NotNull Block block) {} + public void setIneligible(@NotNull Block block) {} @Override - public void setFalse(@NotNull BlockState blockState) {} + public void setIneligible(@NotNull BlockState blockState) {} + + @Override + public void setEligible(@NotNull Block block) {} + + @Override + public void setEligible(@NotNull BlockState blockState) {} } diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/UserBlockTracker.java b/src/main/java/com/gmail/nossr50/util/blockmeta/UserBlockTracker.java index e5428b0c1..501b16cfa 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/UserBlockTracker.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/UserBlockTracker.java @@ -11,46 +11,136 @@ import org.jetbrains.annotations.NotNull; */ public interface UserBlockTracker { /** - * Check to see if a given block location is set to true + * Check to see if a given {@link Block} is ineligible for rewards. + * This is a location-based lookup, and the other properties of the {@link Block} do not matter. * - * @param block Block location to check - * @return true if the given block location is set to true, false if otherwise + * @param block Block to check + * @return true if the given block should not give rewards, false if otherwise */ - boolean isTrue(@NotNull Block block); + boolean isIneligible(@NotNull Block block); /** - * Check to see if a given BlockState location is set to true + * Check to see if a given {@link Block} is eligible for rewards. + * This is a location-based lookup, and the other properties of the {@link Block} do not matter. + * + * @param block Block to check + * @return true if the given block should give rewards, false if otherwise + */ + boolean isEligible(@NotNull Block block); + + /** + * Check to see if a given {@link BlockState} is eligible for rewards. + * This is a location-based lookup, and the other properties of the {@link BlockState} do not matter. * * @param blockState BlockState to check * @return true if the given BlockState location is set to true, false if otherwise */ - boolean isTrue(@NotNull BlockState blockState); + boolean isEligible(@NotNull BlockState blockState); /** - * Set a given block location to true + * Check to see if a given {@link BlockState} is ineligible for rewards. + * This is a location-based lookup, and the other properties of the {@link BlockState} do not matter. * - * @param block Block location to set + * @param blockState BlockState to check + * @return true if the given BlockState location is set to true, false if otherwise */ - void setTrue(@NotNull Block block); + boolean isIneligible(@NotNull BlockState blockState); + + /** + * Set a given {@link Block} as ineligible for rewards. + * This is a location-based lookup, and the other properties of the {@link Block} do not matter. + * + * @param block block whose location to set as ineligible + */ + void setIneligible(@NotNull Block block); /** * Set a given BlockState location to true * * @param blockState BlockState location to set */ - void setTrue(@NotNull BlockState blockState); + void setIneligible(@NotNull BlockState blockState); /** - * Set a given block location to false + * Set a given {@link Block} as eligible for rewards. + * This is a location-based lookup, and the other properties of the {@link Block} do not matter. * - * @param block Block location to set + * @param block block whose location to set as eligible */ - void setFalse(@NotNull Block block); + void setEligible(@NotNull Block block); /** * Set a given BlockState location to false * * @param blockState BlockState location to set */ - void setFalse(@NotNull BlockState blockState); + void setEligible(@NotNull BlockState blockState); + + /** + * Check to see if a given block location is set to true + * + * @param block Block location to check + * @return true if the given block location is set to true, false if otherwise + * @deprecated Use {@link #isIneligible(Block)} instead + */ + @Deprecated(since = "2.2.013") + default boolean isTrue(@NotNull Block block) { + return isIneligible(block); + } + + /** + * Check to see if a given BlockState location is set to true + * + * @param blockState BlockState to check + * @return true if the given BlockState location is set to true, false if otherwise + * @deprecated Use {@link #isIneligible(BlockState)} instead + */ + @Deprecated(since = "2.2.013") + default boolean isTrue(@NotNull BlockState blockState) { + return isIneligible(blockState); + } + + /** + * Set a given block location to true + * + * @param block Block location to set + * @deprecated Use {@link #setIneligible(Block)} instead + */ + @Deprecated(since = "2.2.013") + default void setTrue(@NotNull Block block) { + setIneligible(block); + } + + /** + * Set a given BlockState location to true + * + * @param blockState BlockState location to set + * @deprecated Use {@link #setIneligible(BlockState)} instead + */ + @Deprecated(since = "2.2.013") + default void setTrue(@NotNull BlockState blockState) { + setIneligible(blockState); + } + + /** + * Set a given block location to false + * + * @param block Block location to set + * @deprecated Use {@link #setEligible(Block)} instead + */ + @Deprecated(since = "2.2.013") + default void setFalse(@NotNull Block block) { + setEligible(block); + } + + /** + * Set a given BlockState location to false + * + * @param blockState BlockState location to set + * @deprecated Use {@link #setEligible(BlockState)} instead + */ + @Deprecated(since = "2.2.013") + default void setFalse(@NotNull BlockState blockState) { + setEligible(blockState); + } } diff --git a/src/test/java/com/gmail/nossr50/util/PotionEffectUtilTest.java b/src/test/java/com/gmail/nossr50/util/PotionEffectUtilTest.java index 1618988e9..c8125b763 100644 --- a/src/test/java/com/gmail/nossr50/util/PotionEffectUtilTest.java +++ b/src/test/java/com/gmail/nossr50/util/PotionEffectUtilTest.java @@ -13,9 +13,8 @@ import org.mockito.MockedStatic; import static com.gmail.nossr50.util.PotionEffectUtil.getNauseaPotionEffectType; import static java.util.logging.Logger.getLogger; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.*; -import static org.mockito.Mockito.when; class PotionEffectUtilTest { private MockedStatic mockedStaticMcMMO; diff --git a/src/test/java/com/gmail/nossr50/util/blockmeta/BitSetChunkStoreTest.java b/src/test/java/com/gmail/nossr50/util/blockmeta/BitSetChunkStoreTest.java new file mode 100644 index 000000000..9e266d8d6 --- /dev/null +++ b/src/test/java/com/gmail/nossr50/util/blockmeta/BitSetChunkStoreTest.java @@ -0,0 +1,121 @@ +package com.gmail.nossr50.util.blockmeta; + +import com.gmail.nossr50.mcMMO; +import com.google.common.io.Files; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.junit.jupiter.api.*; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.File; +import java.io.IOException; +import java.util.UUID; + +import static com.gmail.nossr50.util.blockmeta.BlockStoreTestUtils.*; +import static com.gmail.nossr50.util.blockmeta.UserBlockTrackerTest.recursiveDelete; +import static org.bukkit.Bukkit.getWorld; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +class BitSetChunkStoreTest { + private static File tempDir; + + @BeforeAll + public static void setUpClass() { + tempDir = Files.createTempDir(); + } + + @AfterAll + public static void tearDownClass() { + recursiveDelete(tempDir); + } + + private World mockWorld; + + private MockedStatic bukkitMock; + private MockedStatic mcMMOMock; + + @BeforeEach + void setUpMock() { + UUID worldUUID = UUID.randomUUID(); + mockWorld = Mockito.mock(World.class); + when(mockWorld.getUID()).thenReturn(worldUUID); + when(mockWorld.getMaxHeight()).thenReturn(256); + when(mockWorld.getWorldFolder()).thenReturn(tempDir); + + bukkitMock = mockStatic(Bukkit.class); + bukkitMock.when(() -> getWorld(worldUUID)).thenReturn(mockWorld); + + mcMMOMock = mockStatic(mcMMO.class); + + when(mockWorld.getMinHeight()).thenReturn(LEGACY_WORLD_HEIGHT_MIN); + when(mockWorld.getMaxHeight()).thenReturn(LEGACY_WORLD_HEIGHT_MAX); + } + + @AfterEach + void teardownMock() { + bukkitMock.close(); + mcMMOMock.close(); + } + + @Test + void testSetValue() { + final BitSetChunkStore original = new BitSetChunkStore(mockWorld, 0, 0); + original.setTrue(0, 0, 0); + assertTrue(original.isTrue(0, 0, 0)); + original.setFalse(0, 0, 0); + assertFalse(original.isTrue(0, 0, 0)); + } + + @Test + void testIsEmpty() { + final BitSetChunkStore original = new BitSetChunkStore(mockWorld, 0, 0); + assertTrue(original.isEmpty()); + original.setTrue(0, 0, 0); + original.setFalse(0, 0, 0); + assertTrue(original.isEmpty()); + } + + @Test + void testRoundTrip() throws IOException { + final BitSetChunkStore original = new BitSetChunkStore(mockWorld, 1, 2); + original.setTrue(14, 89, 12); + original.setTrue(14, 90, 12); + original.setTrue(13, 89, 12); + byte[] serializedBytes = serializeChunkStore(original); + final ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(new DataInputStream(new ByteArrayInputStream(serializedBytes))); + assertChunkStoreEquals(original, deserialized); + } + + @Test + void testNegativeWorldMin() throws IOException { + when(mockWorld.getMinHeight()).thenReturn(-64); + + final BitSetChunkStore original = new BitSetChunkStore(mockWorld, 1, 2); + original.setTrue(14, -32, 12); + original.setTrue(14, -64, 12); + original.setTrue(13, -63, 12); + byte[] serializedBytes = serializeChunkStore(original); + final ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(new DataInputStream(new ByteArrayInputStream(serializedBytes))); + assertChunkStoreEquals(original, deserialized); + } + + @Test + void testNegativeWorldMinUpgrade() throws IOException { + final BitSetChunkStore original = new BitSetChunkStore(mockWorld, 1, 2); + original.setTrue(14, 1, 12); + original.setTrue(14, 2, 12); + original.setTrue(13, 3, 12); + byte[] serializedBytes = serializeChunkStore(original); + + when(mockWorld.getMinHeight()).thenReturn(-64); + final ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(new DataInputStream(new ByteArrayInputStream(serializedBytes))); + assert deserialized != null; + assertEqualIgnoreMinMax(original, deserialized); + } +} \ No newline at end of file diff --git a/src/test/java/com/gmail/nossr50/util/blockmeta/BlockStoreTestUtils.java b/src/test/java/com/gmail/nossr50/util/blockmeta/BlockStoreTestUtils.java new file mode 100644 index 000000000..709a7b57f --- /dev/null +++ b/src/test/java/com/gmail/nossr50/util/blockmeta/BlockStoreTestUtils.java @@ -0,0 +1,49 @@ +package com.gmail.nossr50.util.blockmeta; + +import org.jetbrains.annotations.NotNull; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BlockStoreTestUtils { + public static final int LEGACY_WORLD_HEIGHT_MAX = 256; + public static final int LEGACY_WORLD_HEIGHT_MIN = 0; + + /** + * Asserts that the two ChunkStores are equal. + * @param expected The expected ChunkStore + * @param actual The actual ChunkStore + */ + static void assertChunkStoreEquals(ChunkStore expected, ChunkStore actual) { + assertEquals(expected.getChunkMin(), actual.getChunkMin()); + assertEquals(expected.getChunkMax(), actual.getChunkMax()); + assertEqualIgnoreMinMax(expected, actual); + } + + static byte[] serializeChunkStore(@NotNull ChunkStore chunkStore) throws IOException { + final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + if (chunkStore instanceof BitSetChunkStore) + BitSetChunkStore.Serialization.writeChunkStore(new DataOutputStream(byteArrayOutputStream), chunkStore); + else + new UnitTestObjectOutputStream(byteArrayOutputStream).writeObject(chunkStore); // Serializes the class as if + // it were the old + // PrimitiveChunkStore + return byteArrayOutputStream.toByteArray(); + } + + static void assertEqualIgnoreMinMax(ChunkStore expected, ChunkStore actual) { + assertEquals(expected.getChunkX(), actual.getChunkX()); + assertEquals(expected.getChunkZ(), actual.getChunkZ()); + assertEquals(expected.getWorldId(), actual.getWorldId()); + for (int y = Math.min(actual.getChunkMin(), expected.getChunkMin()); y < Math.max(actual.getChunkMax(), expected.getChunkMax()); y++) { + if (expected.getChunkMin() > y || actual.getChunkMin() > y || expected.getChunkMax() <= y || actual.getChunkMax() <= y) + continue; // Ignore + for (int x = 0; x < 16; x++) + for (int z = 0; z < 16; z++) + assertEquals(expected.isTrue(x, y, z), actual.isTrue(x, y, z)); + } + } +} diff --git a/src/test/java/com/gmail/nossr50/util/blockmeta/ChunkStoreTest.java b/src/test/java/com/gmail/nossr50/util/blockmeta/ChunkStoreTest.java index 969de6ec4..f7f369076 100644 --- a/src/test/java/com/gmail/nossr50/util/blockmeta/ChunkStoreTest.java +++ b/src/test/java/com/gmail/nossr50/util/blockmeta/ChunkStoreTest.java @@ -1,13 +1,9 @@ package com.gmail.nossr50.util.blockmeta; - import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.util.BlockUtils; import com.google.common.io.Files; import org.bukkit.Bukkit; import org.bukkit.World; -import org.bukkit.block.Block; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.*; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -15,14 +11,13 @@ import org.mockito.Mockito; import java.io.*; import java.util.UUID; -/** - * Could be a lot better. But some tests are better than none! Tests the major things, still kinda unit-testy. Verifies - * that the serialization isn't completely broken. - */ -class ChunkStoreTest { +import static com.gmail.nossr50.util.blockmeta.BlockStoreTestUtils.*; +import static com.gmail.nossr50.util.blockmeta.UserBlockTrackerTest.recursiveDelete; +import static org.bukkit.Bukkit.getWorld; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; - public static final int LEGACY_WORLD_HEIGHT_MAX = 256; - public static final int LEGACY_WORLD_HEIGHT_MIN = 0; +class ChunkStoreTest { private static File tempDir; @BeforeAll @@ -44,151 +39,39 @@ class ChunkStoreTest { void setUpMock() { UUID worldUUID = UUID.randomUUID(); mockWorld = Mockito.mock(World.class); - Mockito.when(mockWorld.getUID()).thenReturn(worldUUID); - Mockito.when(mockWorld.getMaxHeight()).thenReturn(256); - Mockito.when(mockWorld.getWorldFolder()).thenReturn(tempDir); + when(mockWorld.getUID()).thenReturn(worldUUID); + when(mockWorld.getMaxHeight()).thenReturn(256); + when(mockWorld.getWorldFolder()).thenReturn(tempDir); - bukkitMock = Mockito.mockStatic(Bukkit.class); - bukkitMock.when(() -> Bukkit.getWorld(worldUUID)).thenReturn(mockWorld); + bukkitMock = mockStatic(Bukkit.class); + bukkitMock.when(() -> getWorld(worldUUID)).thenReturn(mockWorld); - mcMMOMock = Mockito.mockStatic(mcMMO.class); + mcMMOMock = mockStatic(mcMMO.class); - Mockito.when(mockWorld.getMinHeight()).thenReturn(LEGACY_WORLD_HEIGHT_MIN); - Mockito.when(mockWorld.getMaxHeight()).thenReturn(LEGACY_WORLD_HEIGHT_MAX); + when(mockWorld.getMinHeight()).thenReturn(LEGACY_WORLD_HEIGHT_MIN); + when(mockWorld.getMaxHeight()).thenReturn(LEGACY_WORLD_HEIGHT_MAX); } - + @AfterEach void teardownMock() { bukkitMock.close(); mcMMOMock.close(); } - @Test - void testIndexOutOfBounds() { - Mockito.when(mockWorld.getMinHeight()).thenReturn(-64); - HashChunkManager hashChunkManager = new HashChunkManager(); - - // Top Block - Block illegalHeightBlock = initMockBlock(1337, 256, -1337); - Assertions.assertFalse(hashChunkManager.isTrue(illegalHeightBlock)); - Assertions.assertThrows(IndexOutOfBoundsException.class, () -> hashChunkManager.setTrue(illegalHeightBlock)); - } - - @Test - void testSetTrue() { - Mockito.when(mockWorld.getMinHeight()).thenReturn(-64); - HashChunkManager hashChunkManager = new HashChunkManager(); - int radius = 2; // Could be anything but drastically changes test time - - for (int x = -radius; x <= radius; x++) { - for (int y = mockWorld.getMinHeight(); y < mockWorld.getMaxHeight(); y++) { - for (int z = -radius; z <= radius; z++) { - Block testBlock = initMockBlock(x, y, z); - - hashChunkManager.setTrue(testBlock); - Assertions.assertTrue(hashChunkManager.isTrue(testBlock)); - hashChunkManager.setFalse(testBlock); - Assertions.assertFalse(hashChunkManager.isTrue(testBlock)); - } - } - } - - // Bot Block - Block bottomBlock = initMockBlock(1337, 0, -1337); - Assertions.assertFalse(hashChunkManager.isTrue(bottomBlock)); - - Assertions.assertTrue(BlockUtils.isWithinWorldBounds(bottomBlock)); - hashChunkManager.setTrue(bottomBlock); - Assertions.assertTrue(hashChunkManager.isTrue(bottomBlock)); - - // Top Block - Block topBlock = initMockBlock(1337, 255, -1337); - Assertions.assertFalse(hashChunkManager.isTrue(topBlock)); - - Assertions.assertTrue(BlockUtils.isWithinWorldBounds(topBlock)); - hashChunkManager.setTrue(topBlock); - Assertions.assertTrue(hashChunkManager.isTrue(topBlock)); - } - - @Test - void testSetValue() { - BitSetChunkStore original = new BitSetChunkStore(mockWorld, 0, 0); - original.setTrue(0, 0, 0); - Assertions.assertTrue(original.isTrue(0, 0, 0)); - original.setFalse(0, 0, 0); - Assertions.assertFalse(original.isTrue(0, 0, 0)); - } - - @Test - void testIsEmpty() { - BitSetChunkStore original = new BitSetChunkStore(mockWorld, 0, 0); - Assertions.assertTrue(original.isEmpty()); - original.setTrue(0, 0, 0); - original.setFalse(0, 0, 0); - Assertions.assertTrue(original.isEmpty()); - } - - @Test - void testRoundTrip() throws IOException { - BitSetChunkStore original = new BitSetChunkStore(mockWorld, 1, 2); - original.setTrue(14, 89, 12); - original.setTrue(14, 90, 12); - original.setTrue(13, 89, 12); - byte[] serializedBytes = serializeChunkstore(original); - ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(new DataInputStream(new ByteArrayInputStream(serializedBytes))); - assertEqual(original, deserialized); - } - - @Test - void testNegativeWorldMin() throws IOException { - Mockito.when(mockWorld.getMinHeight()).thenReturn(-64); - - BitSetChunkStore original = new BitSetChunkStore(mockWorld, 1, 2); - original.setTrue(14, -32, 12); - original.setTrue(14, -64, 12); - original.setTrue(13, -63, 12); - byte[] serializedBytes = serializeChunkstore(original); - ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(new DataInputStream(new ByteArrayInputStream(serializedBytes))); - assertEqual(original, deserialized); - } - - @Test - void testNegativeWorldMinUpgrade() throws IOException { - BitSetChunkStore original = new BitSetChunkStore(mockWorld, 1, 2); - original.setTrue(14, 1, 12); - original.setTrue(14, 2, 12); - original.setTrue(13, 3, 12); - byte[] serializedBytes = serializeChunkstore(original); - - Mockito.when(mockWorld.getMinHeight()).thenReturn(-64); - ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(new DataInputStream(new ByteArrayInputStream(serializedBytes))); - assert deserialized != null; - assertEqualIgnoreMinMax(original, deserialized); - } - - @Test - void testChunkCoords() throws IOException { - for (int x = -96; x < 0; x++) { - int cx = x >> 4; - int ix = Math.abs(x) % 16; - //System.out.print(cx + ":" + ix + " "); - } - } - @Test void testUpgrade() throws IOException { LegacyChunkStore original = new LegacyChunkStore(mockWorld, 12, 32); original.setTrue(14, 89, 12); original.setTrue(14, 90, 12); original.setTrue(13, 89, 12); - byte[] serializedBytes = serializeChunkstore(original); + byte[] serializedBytes = serializeChunkStore(original); ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(new DataInputStream(new ByteArrayInputStream(serializedBytes))); assert deserialized != null; - assertEqual(original, deserialized); + assertChunkStoreEquals(original, deserialized); } @Test - void testSimpleRegionRoundtrip() throws IOException { + void testSimpleRegionRoundTrip() throws IOException { LegacyChunkStore original = new LegacyChunkStore(mockWorld, 12, 12); original.setTrue(14, 89, 12); original.setTrue(14, 90, 12); @@ -196,7 +79,7 @@ class ChunkStoreTest { File file = new File(tempDir, "SimpleRegionRoundTrip.region"); McMMOSimpleRegionFile region = new McMMOSimpleRegionFile(file, 0, 0); try (DataOutputStream outputStream = region.getOutputStream(12, 12)) { - outputStream.write(serializeChunkstore(original)); + outputStream.write(serializeChunkStore(original)); } region.close(); region = new McMMOSimpleRegionFile(file, 0, 0); @@ -204,229 +87,10 @@ class ChunkStoreTest { Assertions.assertNotNull(is); ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(is); assert deserialized != null; - assertEqual(original, deserialized); + assertChunkStoreEquals(original, deserialized); } region.close(); file.delete(); } - @Test - void testSimpleRegionRejectsOutOfBounds() { - File file = new File(tempDir, "SimpleRegionRoundTrip.region"); - McMMOSimpleRegionFile region = new McMMOSimpleRegionFile(file, 0, 0); - Assertions.assertThrows(IndexOutOfBoundsException.class, () -> region.getOutputStream(-1, 0)); - Assertions.assertThrows(IndexOutOfBoundsException.class, () -> region.getOutputStream(0, -1)); - Assertions.assertThrows(IndexOutOfBoundsException.class, () -> region.getOutputStream(32, 0)); - Assertions.assertThrows(IndexOutOfBoundsException.class, () -> region.getOutputStream(0, 32)); - region.close(); - } - - @Test - void testChunkStoreRejectsOutOfBounds() { - ChunkStore chunkStore = new BitSetChunkStore(mockWorld, 0, 0); - Assertions.assertThrows(IndexOutOfBoundsException.class, () -> chunkStore.setTrue(-1, 0, 0)); - Assertions.assertThrows(IndexOutOfBoundsException.class, () -> chunkStore.setTrue(0, -1, 0)); - Assertions.assertThrows(IndexOutOfBoundsException.class, () -> chunkStore.setTrue(0, 0, -1)); - Assertions.assertThrows(IndexOutOfBoundsException.class, () -> chunkStore.setTrue(16, 0, 0)); - Assertions.assertThrows(IndexOutOfBoundsException.class, () -> chunkStore.setTrue(0, mockWorld.getMaxHeight(), 0)); - Assertions.assertThrows(IndexOutOfBoundsException.class, () -> chunkStore.setTrue(0, 0, 16)); - } - - @Test - void testRegressionChunkMirrorBug() { - ChunkManager chunkManager = new HashChunkManager(); - Block mockBlockA = Mockito.mock(Block.class); - Mockito.when(mockBlockA.getX()).thenReturn(15); - Mockito.when(mockBlockA.getZ()).thenReturn(15); - Mockito.when(mockBlockA.getY()).thenReturn(0); - Mockito.when(mockBlockA.getWorld()).thenReturn(mockWorld); - Block mockBlockB = Mockito.mock(Block.class); - Mockito.when(mockBlockB.getX()).thenReturn(-15); - Mockito.when(mockBlockB.getZ()).thenReturn(-15); - Mockito.when(mockBlockB.getY()).thenReturn(0); - Mockito.when(mockBlockB.getWorld()).thenReturn(mockWorld); - - chunkManager.setTrue(mockBlockA); - chunkManager.setFalse(mockBlockB); - Assertions.assertTrue(chunkManager.isTrue(mockBlockA)); - } - - private void assertEqual(ChunkStore expected, ChunkStore actual) { - Assertions.assertEquals(expected.getChunkMin(), actual.getChunkMin()); - Assertions.assertEquals(expected.getChunkMax(), actual.getChunkMax()); - assertEqualIgnoreMinMax(expected, actual); - } - - private void assertEqualIgnoreMinMax(ChunkStore expected, ChunkStore actual) { - Assertions.assertEquals(expected.getChunkX(), actual.getChunkX()); - Assertions.assertEquals(expected.getChunkZ(), actual.getChunkZ()); - Assertions.assertEquals(expected.getWorldId(), actual.getWorldId()); - for (int y = Math.min(actual.getChunkMin(), expected.getChunkMin()); y < Math.max(actual.getChunkMax(), expected.getChunkMax()); y++) { - if (expected.getChunkMin() > y || actual.getChunkMin() > y || expected.getChunkMax() <= y || actual.getChunkMax() <= y) - continue; // Ignore - for (int x = 0; x < 16; x++) - for (int z = 0; z < 16; z++) - Assertions.assertEquals(expected.isTrue(x, y, z), actual.isTrue(x, y, z)); - } - } - - private static byte[] serializeChunkstore(@NotNull ChunkStore chunkStore) throws IOException { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - if (chunkStore instanceof BitSetChunkStore) - BitSetChunkStore.Serialization.writeChunkStore(new DataOutputStream(byteArrayOutputStream), chunkStore); - else - new UnitTestObjectOutputStream(byteArrayOutputStream).writeObject(chunkStore); // Serializes the class as if - // it were the old - // PrimitiveChunkStore - return byteArrayOutputStream.toByteArray(); - } - - public static class LegacyChunkStore implements ChunkStore, Serializable { - private static final long serialVersionUID = -1L; - transient private boolean dirty = false; - public boolean[][][] store; - private static final int CURRENT_VERSION = 7; - private static final int MAGIC_NUMBER = 0xEA5EDEBB; - private final int cx; - private final int cz; - private final @NotNull UUID worldUid; - - public LegacyChunkStore(@NotNull World world, int cx, int cz) { - this.cx = cx; - this.cz = cz; - this.worldUid = world.getUID(); - this.store = new boolean[16][16][world.getMaxHeight()]; - } - - @Override - public boolean isDirty() { - return dirty; - } - - @Override - public void setDirty(boolean dirty) { - this.dirty = dirty; - } - - @Override - public int getChunkX() { - return cx; - } - - @Override - public int getChunkZ() { - return cz; - } - - @Override - public int getChunkMin() { - return 0; - } - - @Override - public int getChunkMax() { - return store[0][0].length; - } - - @Override - public @NotNull UUID getWorldId() { - return worldUid; - } - - @Override - public boolean isTrue(int x, int y, int z) { - return store[x][z][y]; - } - - @Override - public void setTrue(int x, int y, int z) { - if (y >= store[0][0].length || y < 0) - return; - store[x][z][y] = true; - dirty = true; - } - - @Override - public void setFalse(int x, int y, int z) { - if (y >= store[0][0].length || y < 0) - return; - store[x][z][y] = false; - dirty = true; - } - - @Override - public void set(int x, int y, int z, boolean value) { - if (y >= store[0][0].length || y < 0) - return; - store[x][z][y] = value; - dirty = true; - } - - @Override - public boolean isEmpty() { - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - for (int y = 0; y < store[0][0].length; y++) { - if (store[x][z][y]) { - return false; - } - } - } - } - return true; - } - - private void writeObject(@NotNull ObjectOutputStream out) throws IOException { - out.writeInt(MAGIC_NUMBER); - out.writeInt(CURRENT_VERSION); - - out.writeLong(worldUid.getLeastSignificantBits()); - out.writeLong(worldUid.getMostSignificantBits()); - out.writeInt(cx); - out.writeInt(cz); - out.writeObject(store); - - dirty = false; - } - - private void readObject(@NotNull ObjectInputStream in) throws IOException, ClassNotFoundException { - throw new UnsupportedOperationException(); - } - - } - - private static class UnitTestObjectOutputStream extends ObjectOutputStream { - - public UnitTestObjectOutputStream(@NotNull OutputStream outputStream) throws IOException { - super(outputStream); - } - - @Override - public void writeUTF(@NotNull String str) throws IOException { - // Pretend to be the old class - if (str.equals(LegacyChunkStore.class.getName())) - str = "com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore"; - super.writeUTF(str); - } - - } - - @NotNull - private Block initMockBlock(int x, int y, int z) { - Block mockBlock = Mockito.mock(Block.class); - Mockito.when(mockBlock.getX()).thenReturn(x); - Mockito.when(mockBlock.getY()).thenReturn(y); - Mockito.when(mockBlock.getZ()).thenReturn(z); - Mockito.when(mockBlock.getWorld()).thenReturn(mockWorld); - return mockBlock; - } - - public static void recursiveDelete(@NotNull File directoryToBeDeleted) { - if (directoryToBeDeleted.isDirectory()) { - for (File file : directoryToBeDeleted.listFiles()) { - recursiveDelete(file); - } - } - directoryToBeDeleted.delete(); - } -} +} \ No newline at end of file diff --git a/src/test/java/com/gmail/nossr50/util/blockmeta/LegacyChunkStore.java b/src/test/java/com/gmail/nossr50/util/blockmeta/LegacyChunkStore.java new file mode 100644 index 000000000..b21cf2594 --- /dev/null +++ b/src/test/java/com/gmail/nossr50/util/blockmeta/LegacyChunkStore.java @@ -0,0 +1,127 @@ +package com.gmail.nossr50.util.blockmeta; + +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.UUID; + +/** + * Used for unit testing upgrades from the old ChunkStore class. + */ +class LegacyChunkStore implements ChunkStore, Serializable { + private static final long serialVersionUID = -1L; + transient private boolean dirty = false; + public boolean[][][] store; + private static final int CURRENT_VERSION = 7; + private static final int MAGIC_NUMBER = 0xEA5EDEBB; + private final int cx; + private final int cz; + private final @NotNull UUID worldUid; + + public LegacyChunkStore(@NotNull World world, int cx, int cz) { + this.cx = cx; + this.cz = cz; + this.worldUid = world.getUID(); + this.store = new boolean[16][16][world.getMaxHeight()]; + } + + @Override + public boolean isDirty() { + return dirty; + } + + @Override + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + + @Override + public int getChunkX() { + return cx; + } + + @Override + public int getChunkZ() { + return cz; + } + + @Override + public int getChunkMin() { + return 0; + } + + @Override + public int getChunkMax() { + return store[0][0].length; + } + + @Override + public @NotNull UUID getWorldId() { + return worldUid; + } + + @Override + public boolean isTrue(int x, int y, int z) { + return store[x][z][y]; + } + + @Override + public void setTrue(int x, int y, int z) { + if (y >= store[0][0].length || y < 0) + return; + store[x][z][y] = true; + dirty = true; + } + + @Override + public void setFalse(int x, int y, int z) { + if (y >= store[0][0].length || y < 0) + return; + store[x][z][y] = false; + dirty = true; + } + + @Override + public void set(int x, int y, int z, boolean value) { + if (y >= store[0][0].length || y < 0) + return; + store[x][z][y] = value; + dirty = true; + } + + @Override + public boolean isEmpty() { + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + for (int y = 0; y < store[0][0].length; y++) { + if (store[x][z][y]) { + return false; + } + } + } + } + return true; + } + + private void writeObject(@NotNull ObjectOutputStream out) throws IOException { + out.writeInt(MAGIC_NUMBER); + out.writeInt(CURRENT_VERSION); + + out.writeLong(worldUid.getLeastSignificantBits()); + out.writeLong(worldUid.getMostSignificantBits()); + out.writeInt(cx); + out.writeInt(cz); + out.writeObject(store); + + dirty = false; + } + + private void readObject(@NotNull ObjectInputStream in) throws IOException, ClassNotFoundException { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/test/java/com/gmail/nossr50/util/blockmeta/UnitTestObjectOutputStream.java b/src/test/java/com/gmail/nossr50/util/blockmeta/UnitTestObjectOutputStream.java new file mode 100644 index 000000000..d7eb1c140 --- /dev/null +++ b/src/test/java/com/gmail/nossr50/util/blockmeta/UnitTestObjectOutputStream.java @@ -0,0 +1,23 @@ +package com.gmail.nossr50.util.blockmeta; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; + +class UnitTestObjectOutputStream extends ObjectOutputStream { + + public UnitTestObjectOutputStream(@NotNull OutputStream outputStream) throws IOException { + super(outputStream); + } + + @Override + public void writeUTF(@NotNull String str) throws IOException { + // Pretend to be the old class + if (str.equals(LegacyChunkStore.class.getName())) + str = "com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore"; + super.writeUTF(str); + } + +} diff --git a/src/test/java/com/gmail/nossr50/util/blockmeta/UserBlockTrackerTest.java b/src/test/java/com/gmail/nossr50/util/blockmeta/UserBlockTrackerTest.java new file mode 100644 index 000000000..89a2f63ff --- /dev/null +++ b/src/test/java/com/gmail/nossr50/util/blockmeta/UserBlockTrackerTest.java @@ -0,0 +1,192 @@ +package com.gmail.nossr50.util.blockmeta; + +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.util.BlockUtils; +import com.google.common.io.Files; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.*; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; + +import static com.gmail.nossr50.util.blockmeta.BlockStoreTestUtils.LEGACY_WORLD_HEIGHT_MAX; +import static com.gmail.nossr50.util.blockmeta.BlockStoreTestUtils.LEGACY_WORLD_HEIGHT_MIN; +import static org.bukkit.Bukkit.getWorld; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +/** + * Could be a lot better. But some tests are better than none! Tests the major things, still kinda unit-testy. Verifies + * that the serialization isn't completely broken. + */ +class UserBlockTrackerTest { + private static File tempDir; + + @BeforeAll + public static void setUpClass() { + tempDir = Files.createTempDir(); + } + + @AfterAll + public static void tearDownClass() { + recursiveDelete(tempDir); + } + + private World mockWorld; + + private MockedStatic bukkitMock; + private MockedStatic mcMMOMock; + + @BeforeEach + void setUpMock() { + UUID worldUUID = UUID.randomUUID(); + mockWorld = Mockito.mock(World.class); + when(mockWorld.getUID()).thenReturn(worldUUID); + when(mockWorld.getMaxHeight()).thenReturn(256); + when(mockWorld.getWorldFolder()).thenReturn(tempDir); + + bukkitMock = mockStatic(Bukkit.class); + bukkitMock.when(() -> getWorld(worldUUID)).thenReturn(mockWorld); + + mcMMOMock = mockStatic(mcMMO.class); + + when(mockWorld.getMinHeight()).thenReturn(LEGACY_WORLD_HEIGHT_MIN); + when(mockWorld.getMaxHeight()).thenReturn(LEGACY_WORLD_HEIGHT_MAX); + } + + @AfterEach + void teardownMock() { + bukkitMock.close(); + mcMMOMock.close(); + } + + @Test + void setIneligibleShouldThrowIndexOutOfBoundsException() { + when(mockWorld.getMinHeight()).thenReturn(-64); + final HashChunkManager hashChunkManager = new HashChunkManager(); + + // Top Block + final Block illegalHeightBlock = initMockBlock(1337, 256, -1337); + assertFalse(hashChunkManager.isIneligible(illegalHeightBlock)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> hashChunkManager.setIneligible(illegalHeightBlock)); + } + + @Test + void testSetEligibility() { + when(mockWorld.getMinHeight()).thenReturn(-64); + final HashChunkManager hashChunkManager = new HashChunkManager(); + int radius = 2; // Could be anything but drastically changes test time + + for (int x = -radius; x <= radius; x++) { + for (int y = mockWorld.getMinHeight(); y < mockWorld.getMaxHeight(); y++) { + for (int z = -radius; z <= radius; z++) { + final Block testBlock = initMockBlock(x, y, z); + // mark ineligible + hashChunkManager.setIneligible(testBlock); + assertTrue(hashChunkManager.isIneligible(testBlock)); + + // mark eligible + hashChunkManager.setEligible(testBlock); + // Might as well test both isIneligible and isEligible while we are here + assertFalse(hashChunkManager.isIneligible(testBlock)); + assertTrue(hashChunkManager.isEligible(testBlock)); + } + } + } + + // TODO: Whatever is going on down here should be in its own test + // Bot Block + final Block bottomBlock = initMockBlock(1337, 0, -1337); + assertFalse(hashChunkManager.isIneligible(bottomBlock)); + + assertTrue(BlockUtils.isWithinWorldBounds(bottomBlock)); + hashChunkManager.setIneligible(bottomBlock); + assertTrue(hashChunkManager.isIneligible(bottomBlock)); + + // Top Block + final Block topBlock = initMockBlock(1337, 255, -1337); + assertFalse(hashChunkManager.isIneligible(topBlock)); + + assertTrue(BlockUtils.isWithinWorldBounds(topBlock)); + hashChunkManager.setIneligible(topBlock); + assertTrue(hashChunkManager.isIneligible(topBlock)); + } + + @Test + void testChunkCoords() throws IOException { + // TODO: Unfinished test? + for (int x = -96; x < 0; x++) { + int cx = x >> 4; + int ix = Math.abs(x) % 16; + //System.out.print(cx + ":" + ix + " "); + } + } + + @Test + void testSimpleRegionRejectsOutOfBounds() { + File file = new File(tempDir, "SimpleRegionRoundTrip.region"); + McMMOSimpleRegionFile region = new McMMOSimpleRegionFile(file, 0, 0); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> region.getOutputStream(-1, 0)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> region.getOutputStream(0, -1)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> region.getOutputStream(32, 0)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> region.getOutputStream(0, 32)); + region.close(); + } + + @Test + void testChunkStoreRejectsOutOfBounds() { + ChunkStore chunkStore = new BitSetChunkStore(mockWorld, 0, 0); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> chunkStore.setTrue(-1, 0, 0)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> chunkStore.setTrue(0, -1, 0)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> chunkStore.setTrue(0, 0, -1)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> chunkStore.setTrue(16, 0, 0)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> chunkStore.setTrue(0, mockWorld.getMaxHeight(), 0)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> chunkStore.setTrue(0, 0, 16)); + } + + @Test + void testRegressionChunkMirrorBug() { + final UserBlockTracker chunkManager = new HashChunkManager(); + Block mockBlockA = Mockito.mock(Block.class); + when(mockBlockA.getX()).thenReturn(15); + when(mockBlockA.getZ()).thenReturn(15); + when(mockBlockA.getY()).thenReturn(0); + when(mockBlockA.getWorld()).thenReturn(mockWorld); + Block mockBlockB = Mockito.mock(Block.class); + when(mockBlockB.getX()).thenReturn(-15); + when(mockBlockB.getZ()).thenReturn(-15); + when(mockBlockB.getY()).thenReturn(0); + when(mockBlockB.getWorld()).thenReturn(mockWorld); + + chunkManager.setIneligible(mockBlockA); + chunkManager.setEligible(mockBlockB); + assertTrue(chunkManager.isIneligible(mockBlockA)); + } + + @NotNull + private Block initMockBlock(int x, int y, int z) { + final Block mockBlock = Mockito.mock(Block.class); + when(mockBlock.getX()).thenReturn(x); + when(mockBlock.getY()).thenReturn(y); + when(mockBlock.getZ()).thenReturn(z); + when(mockBlock.getWorld()).thenReturn(mockWorld); + return mockBlock; + } + + public static void recursiveDelete(@NotNull File directoryToBeDeleted) { + if (directoryToBeDeleted.isDirectory()) { + for (File file : directoryToBeDeleted.listFiles()) { + recursiveDelete(file); + } + } + directoryToBeDeleted.delete(); + } +}