mirror of
				https://github.com/mcMMO-Dev/mcMMO.git
				synced 2025-11-04 11:03:43 +01:00 
			
		
		
		
	update UserBlockTracker API
This commit is contained in:
		@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								pom.xml
									
									
									
									
									
								
							@@ -2,7 +2,7 @@
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
    <groupId>com.gmail.nossr50.mcMMO</groupId>
 | 
			
		||||
    <artifactId>mcMMO</artifactId>
 | 
			
		||||
    <version>2.2.012</version>
 | 
			
		||||
    <version>2.2.013-SNAPSHOT</version>
 | 
			
		||||
    <name>mcMMO</name>
 | 
			
		||||
    <url>https://github.com/mcMMO-Dev/mcMMO</url>
 | 
			
		||||
    <scm>
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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<Block> blocks;
 | 
			
		||||
    private final BlockFace direction;
 | 
			
		||||
    private final Block futureEmptyBlock;
 | 
			
		||||
 | 
			
		||||
    public PistonTrackerTask(List<Block> 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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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<BlockState> futureCenterBlocks, @NotNull Set<BlockState> 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());
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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<mcMMO> mockedStaticMcMMO;
 | 
			
		||||
 
 | 
			
		||||
@@ -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<Bukkit> bukkitMock;
 | 
			
		||||
    private MockedStatic<mcMMO> 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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,17 +39,17 @@ 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
 | 
			
		||||
@@ -63,132 +58,20 @@ class ChunkStoreTest {
 | 
			
		||||
        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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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<Bukkit> bukkitMock;
 | 
			
		||||
    private MockedStatic<mcMMO> 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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user