/*
GriefPrevention Server Plugin for Minecraft
Copyright (C) 2012 Ryan Hamshire
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package me.ryanhamshire.GriefPrevention;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Tag;
import org.bukkit.World.Environment;
import org.bukkit.block.Biome;
import org.bukkit.block.data.Levelled;
import org.bukkit.block.data.type.Leaves;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Set;
//non-main-thread task which processes world data to repair the unnatural
//after processing is complete, creates a main thread task to make the necessary changes to the world
class RestoreNatureProcessingTask implements Runnable
{
// Definitions of biomes with particularly dense log distribution. These biomes will not have logs reduced.
private static final Set DENSE_LOG_BIOMES = Set.of(
NamespacedKey.minecraft("jungle"),
NamespacedKey.minecraft("bamboo_jungle"),
// Variants for versions < 1.18
NamespacedKey.minecraft("modified_jungle"),
NamespacedKey.minecraft("jungle_hills"),
NamespacedKey.minecraft("bamboo_jungle_hills")
);
// Definitions of biomes where sand covers surfaces instead of grass.
private static final Set SAND_SOIL_BIOMES = Set.of(
NamespacedKey.minecraft("snowy_beach"),
NamespacedKey.minecraft("beach"),
NamespacedKey.minecraft("desert"),
// Variants for versions < 1.18
NamespacedKey.minecraft("desert_hills"),
NamespacedKey.minecraft("desert_lakes")
);
//world information captured from the main thread
//will be updated and sent back to main thread to be applied to the world
private final BlockSnapshot[][][] snapshots;
//other information collected from the main thread.
//not to be updated, only to be passed back to main thread to provide some context about the operation
private int miny;
private final Environment environment;
private final Location lesserBoundaryCorner;
private final Location greaterBoundaryCorner;
private final Player player; //absolutely must not be accessed. not thread safe.
private final Biome biome;
private final boolean creativeMode;
private final int seaLevel;
private final boolean aggressiveMode;
//two lists of materials
private final Set notAllowedToHang; //natural blocks which don't naturally hang in their air
private final Set playerBlocks; //a "complete" list of player-placed blocks. MUST BE MAINTAINED as patches introduce more
public RestoreNatureProcessingTask(BlockSnapshot[][][] snapshots, int miny, Environment environment, Biome biome, Location lesserBoundaryCorner, Location greaterBoundaryCorner, int seaLevel, boolean aggressiveMode, boolean creativeMode, Player player)
{
this.snapshots = snapshots;
this.miny = miny;
if (this.miny < 0) this.miny = 0;
this.environment = environment;
this.lesserBoundaryCorner = lesserBoundaryCorner;
this.greaterBoundaryCorner = greaterBoundaryCorner;
this.biome = biome;
this.seaLevel = seaLevel;
this.aggressiveMode = aggressiveMode;
this.player = player;
this.creativeMode = creativeMode;
this.notAllowedToHang = EnumSet.noneOf(Material.class);
this.notAllowedToHang.add(Material.DIRT);
this.notAllowedToHang.add(Material.GRASS);
this.notAllowedToHang.add(Material.SNOW);
this.notAllowedToHang.add(Material.OAK_LOG);
this.notAllowedToHang.add(Material.SPRUCE_LOG);
this.notAllowedToHang.add(Material.BIRCH_LOG);
this.notAllowedToHang.add(Material.JUNGLE_LOG);
this.notAllowedToHang.add(Material.ACACIA_LOG);
this.notAllowedToHang.add(Material.DARK_OAK_LOG);
if (this.aggressiveMode)
{
this.notAllowedToHang.add(Material.GRASS);
this.notAllowedToHang.add(Material.STONE);
}
this.playerBlocks = EnumSet.noneOf(Material.class);
this.playerBlocks.addAll(RestoreNatureProcessingTask.getPlayerBlocks(this.environment, this.biome));
//in aggressive or creative world mode, also treat these blocks as user placed, to be removed
//this is helpful in the few cases where griefers intentionally use natural blocks to grief,
//like a single-block tower of iron ore or a giant penis constructed with melons
if (this.aggressiveMode || this.creativeMode)
{
this.playerBlocks.add(Material.IRON_ORE);
this.playerBlocks.add(Material.GOLD_ORE);
this.playerBlocks.add(Material.DIAMOND_ORE);
this.playerBlocks.add(Material.MELON);
this.playerBlocks.add(Material.MELON_STEM);
this.playerBlocks.add(Material.BEDROCK);
this.playerBlocks.add(Material.COAL_ORE);
this.playerBlocks.add(Material.PUMPKIN);
this.playerBlocks.add(Material.PUMPKIN_STEM);
}
if (this.aggressiveMode)
{
this.playerBlocks.add(Material.OAK_LEAVES);
this.playerBlocks.add(Material.SPRUCE_LEAVES);
this.playerBlocks.add(Material.BIRCH_LEAVES);
this.playerBlocks.add(Material.JUNGLE_LEAVES);
this.playerBlocks.add(Material.ACACIA_LEAVES);
this.playerBlocks.add(Material.DARK_OAK_LEAVES);
this.playerBlocks.add(Material.OAK_LOG);
this.playerBlocks.add(Material.SPRUCE_LOG);
this.playerBlocks.add(Material.BIRCH_LOG);
this.playerBlocks.add(Material.JUNGLE_LOG);
this.playerBlocks.add(Material.ACACIA_LOG);
this.playerBlocks.add(Material.DARK_OAK_LOG);
this.playerBlocks.add(Material.VINE);
}
}
@Override
public void run()
{
//order is important!
//remove sandstone which appears to be unnatural
this.removeSandstone();
//remove any blocks which are definitely player placed
this.removePlayerBlocks();
//reduce large outcroppings of stone, sandstone
this.reduceStone();
//reduce logs, except in jungle biomes
this.reduceLogs();
//remove natural blocks which are unnaturally hanging in the air
this.removeHanging();
//remove natural blocks which are unnaturally stacked high
this.removeWallsAndTowers();
//fill unnatural thin trenches and single-block potholes
this.fillHolesAndTrenches();
//fill water depressions and fix unnatural surface ripples
//this.fixWater();
//remove water/lava above sea level
this.removeDumpedFluids();
//cover surface stone and gravel with sand or grass, as the biome requires
this.coverSurfaceStone();
//remove any player-placed leaves
///this.removePlayerLeaves();
//schedule main thread task to apply the result to the world
RestoreNatureExecutionTask task = new RestoreNatureExecutionTask(this.snapshots, this.miny, this.lesserBoundaryCorner, this.greaterBoundaryCorner, this.player);
GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task);
}
private void removePlayerLeaves()
{
if (this.seaLevel < 1) return;
for (int x = 1; x < snapshots.length - 1; x++)
{
for (int z = 1; z < snapshots[0][0].length - 1; z++)
{
for (int y = this.seaLevel - 1; y < snapshots[0].length; y++)
{
BlockSnapshot block = snapshots[x][y][z];
if (Tag.LEAVES.isTagged(block.typeId) && ((Leaves) block.data).isPersistent())
{
block.typeId = Material.AIR;
}
}
}
}
}
//converts sandstone adjacent to sand to sand, and any other sandstone to air
private void removeSandstone()
{
for (int x = 1; x < snapshots.length - 1; x++)
{
for (int z = 1; z < snapshots[0][0].length - 1; z++)
{
for (int y = snapshots[0].length - 2; y > miny; y--)
{
if (snapshots[x][y][z].typeId != Material.SANDSTONE) continue;
BlockSnapshot leftBlock = this.snapshots[x + 1][y][z];
BlockSnapshot rightBlock = this.snapshots[x - 1][y][z];
BlockSnapshot upBlock = this.snapshots[x][y][z + 1];
BlockSnapshot downBlock = this.snapshots[x][y][z - 1];
BlockSnapshot underBlock = this.snapshots[x][y - 1][z];
BlockSnapshot aboveBlock = this.snapshots[x][y + 1][z];
//skip blocks which may cause a cave-in
if (aboveBlock.typeId == Material.SAND && underBlock.typeId == Material.AIR) continue;
//count adjacent non-air/non-leaf blocks
if (leftBlock.typeId == Material.SAND ||
rightBlock.typeId == Material.SAND ||
upBlock.typeId == Material.SAND ||
downBlock.typeId == Material.SAND ||
aboveBlock.typeId == Material.SAND ||
underBlock.typeId == Material.SAND)
{
snapshots[x][y][z].typeId = Material.SAND;
}
else
{
snapshots[x][y][z].typeId = Material.AIR;
}
}
}
}
}
private void reduceStone()
{
if (this.seaLevel < 1) return;
for (int x = 1; x < snapshots.length - 1; x++)
{
for (int z = 1; z < snapshots[0][0].length - 1; z++)
{
int thisy = this.highestY(x, z, true);
while (thisy > this.seaLevel - 1 && (this.snapshots[x][thisy][z].typeId == Material.STONE || this.snapshots[x][thisy][z].typeId == Material.SANDSTONE))
{
BlockSnapshot leftBlock = this.snapshots[x + 1][thisy][z];
BlockSnapshot rightBlock = this.snapshots[x - 1][thisy][z];
BlockSnapshot upBlock = this.snapshots[x][thisy][z + 1];
BlockSnapshot downBlock = this.snapshots[x][thisy][z - 1];
//count adjacent non-air/non-leaf blocks
byte adjacentBlockCount = 0;
if (leftBlock.typeId != Material.AIR && !Tag.LEAVES.isTagged(leftBlock.typeId) && leftBlock.typeId != Material.VINE)
{
adjacentBlockCount++;
}
if (rightBlock.typeId != Material.AIR && !Tag.LEAVES.isTagged(rightBlock.typeId) && rightBlock.typeId != Material.VINE)
{
adjacentBlockCount++;
}
if (downBlock.typeId != Material.AIR && !Tag.LEAVES.isTagged(downBlock.typeId) && downBlock.typeId != Material.VINE)
{
adjacentBlockCount++;
}
if (upBlock.typeId != Material.AIR && !Tag.LEAVES.isTagged(upBlock.typeId) && upBlock.typeId != Material.VINE)
{
adjacentBlockCount++;
}
if (adjacentBlockCount < 3)
{
this.snapshots[x][thisy][z].typeId = Material.AIR;
}
thisy--;
}
}
}
}
private void reduceLogs()
{
if (this.seaLevel < 1) return;
boolean jungleBiome = DENSE_LOG_BIOMES.contains(this.biome.getKey());
//scan all blocks above sea level
for (int x = 1; x < snapshots.length - 1; x++)
{
for (int z = 1; z < snapshots[0][0].length - 1; z++)
{
for (int y = this.seaLevel - 1; y < snapshots[0].length; y++)
{
BlockSnapshot block = snapshots[x][y][z];
//skip non-logs
if (!Tag.LOGS.isTagged(block.typeId)) continue;
//if in jungle biome, skip jungle logs
if (jungleBiome && block.typeId == Material.JUNGLE_LOG) continue;
//examine adjacent blocks for logs
BlockSnapshot leftBlock = this.snapshots[x + 1][y][z];
BlockSnapshot rightBlock = this.snapshots[x - 1][y][z];
BlockSnapshot upBlock = this.snapshots[x][y][z + 1];
BlockSnapshot downBlock = this.snapshots[x][y][z - 1];
//if any, remove the log
if (Tag.LOGS.isTagged(leftBlock.typeId) || Tag.LOGS.isTagged(rightBlock.typeId) || Tag.LOGS.isTagged(upBlock.typeId) || Tag.LOGS.isTagged(downBlock.typeId))
{
this.snapshots[x][y][z].typeId = Material.AIR;
}
}
}
}
}
private void removePlayerBlocks()
{
int miny = this.miny;
if (miny < 1) miny = 1;
//remove all player blocks
for (int x = 1; x < snapshots.length - 1; x++)
{
for (int z = 1; z < snapshots[0][0].length - 1; z++)
{
for (int y = miny; y < snapshots[0].length - 1; y++)
{
BlockSnapshot block = snapshots[x][y][z];
if (this.playerBlocks.contains(block.typeId))
{
block.typeId = Material.AIR;
}
}
}
}
}
private void removeHanging()
{
int miny = this.miny;
if (miny < 1) miny = 1;
for (int x = 1; x < snapshots.length - 1; x++)
{
for (int z = 1; z < snapshots[0][0].length - 1; z++)
{
for (int y = miny; y < snapshots[0].length - 1; y++)
{
BlockSnapshot block = snapshots[x][y][z];
BlockSnapshot underBlock = snapshots[x][y - 1][z];
if (underBlock.typeId == Material.AIR || underBlock.typeId == Material.WATER || Tag.LEAVES.isTagged(underBlock.typeId))
{
if (this.notAllowedToHang.contains(block.typeId))
{
block.typeId = Material.AIR;
}
}
}
}
}
}
private void removeWallsAndTowers()
{
Material[] excludedBlocksArray = new Material[]
{
Material.CACTUS,
Material.GRASS,
Material.RED_MUSHROOM,
Material.BROWN_MUSHROOM,
Material.DEAD_BUSH,
Material.DANDELION,
Material.POPPY,
Material.ALLIUM,
Material.BLUE_ORCHID,
Material.AZURE_BLUET,
Material.RED_TULIP,
Material.ORANGE_TULIP,
Material.WHITE_TULIP,
Material.PINK_TULIP,
Material.OXEYE_DAISY,
Material.SUGAR_CANE,
Material.VINE,
Material.PUMPKIN,
Material.LILY_PAD
};
ArrayList excludedBlocks = new ArrayList<>(Arrays.asList(excludedBlocksArray));
excludedBlocks.addAll(Tag.SAPLINGS.getValues());
excludedBlocks.addAll(Tag.LEAVES.getValues());
boolean changed;
do
{
changed = false;
for (int x = 1; x < snapshots.length - 1; x++)
{
for (int z = 1; z < snapshots[0][0].length - 1; z++)
{
int thisy = this.highestY(x, z, false);
if (excludedBlocks.contains(this.snapshots[x][thisy][z].typeId)) continue;
int righty = this.highestY(x + 1, z, false);
int lefty = this.highestY(x - 1, z, false);
while (lefty < thisy && righty < thisy)
{
this.snapshots[x][thisy--][z].typeId = Material.AIR;
changed = true;
}
int upy = this.highestY(x, z + 1, false);
int downy = this.highestY(x, z - 1, false);
while (upy < thisy && downy < thisy)
{
this.snapshots[x][thisy--][z].typeId = Material.AIR;
changed = true;
}
}
}
} while (changed);
}
private void coverSurfaceStone()
{
for (int x = 1; x < snapshots.length - 1; x++)
{
for (int z = 1; z < snapshots[0][0].length - 1; z++)
{
int y = this.highestY(x, z, true);
BlockSnapshot block = snapshots[x][y][z];
if (block.typeId == Material.STONE || block.typeId == Material.GRAVEL || block.typeId == Material.FARMLAND || block.typeId == Material.DIRT || block.typeId == Material.SANDSTONE)
{
if (SAND_SOIL_BIOMES.contains(this.biome.getKey()))
{
this.snapshots[x][y][z].typeId = Material.SAND;
}
else
{
this.snapshots[x][y][z].typeId = Material.GRASS_BLOCK;
}
}
}
}
}
private void fillHolesAndTrenches()
{
ArrayList fillableBlocks = new ArrayList<>();
fillableBlocks.add(Material.AIR);
fillableBlocks.add(Material.WATER);
fillableBlocks.add(Material.LAVA);
fillableBlocks.add(Material.GRASS);
ArrayList notSuitableForFillBlocks = new ArrayList<>();
notSuitableForFillBlocks.add(Material.GRASS);
notSuitableForFillBlocks.add(Material.CACTUS);
notSuitableForFillBlocks.add(Material.WATER);
notSuitableForFillBlocks.add(Material.LAVA);
notSuitableForFillBlocks.addAll(Tag.LOGS.getValues());
boolean changed;
do
{
changed = false;
for (int x = 1; x < snapshots.length - 1; x++)
{
for (int z = 1; z < snapshots[0][0].length - 1; z++)
{
for (int y = 0; y < snapshots[0].length - 1; y++)
{
BlockSnapshot block = this.snapshots[x][y][z];
if (!fillableBlocks.contains(block.typeId)) continue;
BlockSnapshot leftBlock = this.snapshots[x + 1][y][z];
BlockSnapshot rightBlock = this.snapshots[x - 1][y][z];
if (!fillableBlocks.contains(leftBlock.typeId) && !fillableBlocks.contains(rightBlock.typeId))
{
if (!notSuitableForFillBlocks.contains(rightBlock.typeId))
{
block.typeId = rightBlock.typeId;
changed = true;
}
}
BlockSnapshot upBlock = this.snapshots[x][y][z + 1];
BlockSnapshot downBlock = this.snapshots[x][y][z - 1];
if (!fillableBlocks.contains(upBlock.typeId) && !fillableBlocks.contains(downBlock.typeId))
{
if (!notSuitableForFillBlocks.contains(downBlock.typeId))
{
block.typeId = downBlock.typeId;
changed = true;
}
}
}
}
}
} while (changed);
}
private void fixWater()
{
int miny = this.miny;
if (miny < 1) miny = 1;
boolean changed;
//remove hanging water or lava
for (int x = 1; x < snapshots.length - 1; x++)
{
for (int z = 1; z < snapshots[0][0].length - 1; z++)
{
for (int y = miny; y < snapshots[0].length - 1; y++)
{
BlockSnapshot block = this.snapshots[x][y][z];
BlockSnapshot underBlock = this.snapshots[x][y--][z];
if (block.typeId == Material.WATER || block.typeId == Material.LAVA)
{
// check if block below is air or is a non-source fluid block (level 1-7 = flowing, 8 = falling)
if (underBlock.typeId == Material.AIR || (underBlock.typeId == Material.WATER && (((Levelled) underBlock.data).getLevel() != 0)))
{
block.typeId = Material.AIR;
}
}
}
}
}
//fill water depressions
do
{
changed = false;
for (int y = Math.max(this.seaLevel - 10, 0); y <= this.seaLevel; y++)
{
for (int x = 1; x < snapshots.length - 1; x++)
{
for (int z = 1; z < snapshots[0][0].length - 1; z++)
{
BlockSnapshot block = snapshots[x][y][z];
//only consider air blocks and flowing water blocks for upgrade to water source blocks
if (block.typeId == Material.AIR || (block.typeId == Material.WATER && ((Levelled) block.data).getLevel() != 0))
{
BlockSnapshot leftBlock = this.snapshots[x + 1][y][z];
BlockSnapshot rightBlock = this.snapshots[x - 1][y][z];
BlockSnapshot upBlock = this.snapshots[x][y][z + 1];
BlockSnapshot downBlock = this.snapshots[x][y][z - 1];
BlockSnapshot underBlock = this.snapshots[x][y - 1][z];
//block underneath MUST be source water
if (!(underBlock.typeId == Material.WATER && ((Levelled) underBlock.data).getLevel() == 0))
continue;
//count adjacent source water blocks
byte adjacentSourceWaterCount = 0;
if (leftBlock.typeId == Material.WATER && ((Levelled) leftBlock.data).getLevel() == 0)
{
adjacentSourceWaterCount++;
}
if (rightBlock.typeId == Material.WATER && ((Levelled) rightBlock.data).getLevel() == 0)
{
adjacentSourceWaterCount++;
}
if (upBlock.typeId == Material.WATER && ((Levelled) upBlock.data).getLevel() == 0)
{
adjacentSourceWaterCount++;
}
if (downBlock.typeId == Material.WATER && ((Levelled) downBlock.data).getLevel() == 0)
{
adjacentSourceWaterCount++;
}
//at least two adjacent blocks must be source water
if (adjacentSourceWaterCount >= 2)
{
block.typeId = Material.WATER;
((Levelled) downBlock.data).setLevel(0);
changed = true;
}
}
}
}
}
} while (changed);
}
private void removeDumpedFluids()
{
if (this.seaLevel < 1) return;
//remove any surface water or lava above sea level, presumed to be placed by players
//sometimes, this is naturally generated. but replacing it is very easy with a bucket, so overall this is a good plan
if (this.environment == Environment.NETHER) return;
for (int x = 1; x < snapshots.length - 1; x++)
{
for (int z = 1; z < snapshots[0][0].length - 1; z++)
{
for (int y = this.seaLevel; y < snapshots[0].length - 1; y++)
{
BlockSnapshot block = snapshots[x][y][z];
if (block.typeId == Material.WATER || block.typeId == Material.LAVA)
{
block.typeId = Material.AIR;
}
}
}
}
}
private int highestY(int x, int z, boolean ignoreLeaves)
{
int y;
for (y = snapshots[0].length - 1; y > 0; y--)
{
BlockSnapshot block = this.snapshots[x][y][z];
if (block.typeId != Material.AIR &&
!(ignoreLeaves && block.typeId == Material.SNOW) &&
!(ignoreLeaves && Tag.LEAVES.isTagged(block.typeId)) &&
!(block.typeId == Material.WATER) &&
!(block.typeId == Material.LAVA))
{
return y;
}
}
return y;
}
static Set getPlayerBlocks(Environment environment, Biome biome)
{
//NOTE on this list. why not make a list of natural blocks?
//answer: better to leave a few player blocks than to remove too many natural blocks. remember we're "restoring nature"
//a few extra player blocks can be manually removed, but it will be impossible to guess exactly which natural materials to use in manual repair of an overzealous block removal
Set playerBlocks = EnumSet.noneOf(Material.class);
playerBlocks.addAll(Tag.ANVIL.getValues());
playerBlocks.addAll(Tag.BANNERS.getValues());
playerBlocks.addAll(Tag.BEACON_BASE_BLOCKS.getValues());
playerBlocks.addAll(Tag.BEDS.getValues());
playerBlocks.addAll(Tag.BUTTONS.getValues());
playerBlocks.addAll(Tag.CAMPFIRES.getValues());
playerBlocks.addAll(Tag.CANDLE_CAKES.getValues());
playerBlocks.addAll(Tag.CANDLES.getValues());
playerBlocks.addAll(Tag.CARPETS.getValues());
playerBlocks.addAll(Tag.CAULDRONS.getValues());
playerBlocks.addAll(Tag.DOORS.getValues());
playerBlocks.addAll(Tag.FENCE_GATES.getValues());
playerBlocks.addAll(Tag.FENCES.getValues());
playerBlocks.addAll(Tag.FIRE.getValues());
playerBlocks.addAll(Tag.FLOWER_POTS.getValues());
playerBlocks.addAll(Tag.IMPERMEABLE.getValues()); // Glass block variants
playerBlocks.addAll(Tag.LOGS.getValues());
playerBlocks.addAll(Tag.PLANKS.getValues());
playerBlocks.addAll(Tag.PRESSURE_PLATES.getValues());
playerBlocks.addAll(Tag.RAILS.getValues());
playerBlocks.addAll(Tag.SHULKER_BOXES.getValues());
playerBlocks.addAll(Tag.SIGNS.getValues());
playerBlocks.addAll(Tag.SLABS.getValues());
playerBlocks.addAll(Tag.STAIRS.getValues());
playerBlocks.addAll(Tag.STONE_BRICKS.getValues());
playerBlocks.addAll(Tag.TRAPDOORS.getValues());
playerBlocks.addAll(Tag.WALLS.getValues());
playerBlocks.addAll(Tag.WOOL.getValues());
playerBlocks.add(Material.BOOKSHELF);
playerBlocks.add(Material.BREWING_STAND);
playerBlocks.add(Material.BRICK);
playerBlocks.add(Material.COBBLESTONE);
playerBlocks.add(Material.LAPIS_BLOCK);
playerBlocks.add(Material.DISPENSER);
playerBlocks.add(Material.NOTE_BLOCK);
playerBlocks.add(Material.STICKY_PISTON);
playerBlocks.add(Material.PISTON);
playerBlocks.add(Material.PISTON_HEAD);
playerBlocks.add(Material.MOVING_PISTON);
playerBlocks.add(Material.WHEAT);
playerBlocks.add(Material.TNT);
playerBlocks.add(Material.MOSSY_COBBLESTONE);
playerBlocks.add(Material.TORCH);
playerBlocks.add(Material.CHEST);
playerBlocks.add(Material.REDSTONE_WIRE);
playerBlocks.add(Material.CRAFTING_TABLE);
playerBlocks.add(Material.FURNACE);
playerBlocks.add(Material.LADDER);
playerBlocks.add(Material.SCAFFOLDING);
playerBlocks.add(Material.LEVER);
playerBlocks.add(Material.REDSTONE_TORCH);
playerBlocks.add(Material.SNOW_BLOCK);
playerBlocks.add(Material.JUKEBOX);
playerBlocks.add(Material.NETHER_PORTAL);
playerBlocks.add(Material.JACK_O_LANTERN);
playerBlocks.add(Material.CAKE);
playerBlocks.add(Material.REPEATER);
playerBlocks.add(Material.MUSHROOM_STEM);
playerBlocks.add(Material.RED_MUSHROOM_BLOCK);
playerBlocks.add(Material.BROWN_MUSHROOM_BLOCK);
playerBlocks.add(Material.IRON_BARS);
playerBlocks.add(Material.GLASS_PANE);
playerBlocks.add(Material.MELON_STEM);
playerBlocks.add(Material.ENCHANTING_TABLE);
playerBlocks.add(Material.COBWEB);
playerBlocks.add(Material.GRAVEL);
playerBlocks.add(Material.SANDSTONE);
playerBlocks.add(Material.ENDER_CHEST);
playerBlocks.add(Material.COMMAND_BLOCK);
playerBlocks.add(Material.REPEATING_COMMAND_BLOCK);
playerBlocks.add(Material.CHAIN_COMMAND_BLOCK);
playerBlocks.add(Material.BEACON);
playerBlocks.add(Material.CARROT);
playerBlocks.add(Material.POTATO);
playerBlocks.add(Material.SKELETON_SKULL);
playerBlocks.add(Material.WITHER_SKELETON_SKULL);
playerBlocks.add(Material.CREEPER_HEAD);
playerBlocks.add(Material.ZOMBIE_HEAD);
playerBlocks.add(Material.PLAYER_HEAD);
playerBlocks.add(Material.DRAGON_HEAD);
playerBlocks.add(Material.SPONGE);
playerBlocks.add(Material.WHITE_STAINED_GLASS_PANE);
playerBlocks.add(Material.ORANGE_STAINED_GLASS_PANE);
playerBlocks.add(Material.MAGENTA_STAINED_GLASS_PANE);
playerBlocks.add(Material.LIGHT_BLUE_STAINED_GLASS_PANE);
playerBlocks.add(Material.YELLOW_STAINED_GLASS_PANE);
playerBlocks.add(Material.LIME_STAINED_GLASS_PANE);
playerBlocks.add(Material.PINK_STAINED_GLASS_PANE);
playerBlocks.add(Material.GRAY_STAINED_GLASS_PANE);
playerBlocks.add(Material.LIGHT_GRAY_STAINED_GLASS_PANE);
playerBlocks.add(Material.CYAN_STAINED_GLASS_PANE);
playerBlocks.add(Material.PURPLE_STAINED_GLASS_PANE);
playerBlocks.add(Material.BLUE_STAINED_GLASS_PANE);
playerBlocks.add(Material.BROWN_STAINED_GLASS_PANE);
playerBlocks.add(Material.GREEN_STAINED_GLASS_PANE);
playerBlocks.add(Material.RED_STAINED_GLASS_PANE);
playerBlocks.add(Material.BLACK_STAINED_GLASS_PANE);
playerBlocks.add(Material.TRAPPED_CHEST);
playerBlocks.add(Material.COMPARATOR);
playerBlocks.add(Material.DAYLIGHT_DETECTOR);
playerBlocks.add(Material.REDSTONE_BLOCK);
playerBlocks.add(Material.HOPPER);
playerBlocks.add(Material.QUARTZ_BLOCK);
playerBlocks.add(Material.DROPPER);
playerBlocks.add(Material.SLIME_BLOCK);
playerBlocks.add(Material.PRISMARINE);
playerBlocks.add(Material.HAY_BLOCK);
playerBlocks.add(Material.SEA_LANTERN);
playerBlocks.add(Material.COAL_BLOCK);
playerBlocks.add(Material.REDSTONE_LAMP);
playerBlocks.add(Material.RED_NETHER_BRICKS);
playerBlocks.add(Material.POLISHED_ANDESITE);
playerBlocks.add(Material.POLISHED_DIORITE);
playerBlocks.add(Material.POLISHED_GRANITE);
playerBlocks.add(Material.POLISHED_BASALT);
playerBlocks.add(Material.POLISHED_DEEPSLATE);
playerBlocks.add(Material.DEEPSLATE_BRICKS);
playerBlocks.add(Material.CRACKED_DEEPSLATE_BRICKS);
playerBlocks.add(Material.DEEPSLATE_TILES);
playerBlocks.add(Material.CRACKED_DEEPSLATE_TILES);
playerBlocks.add(Material.CHISELED_DEEPSLATE);
playerBlocks.add(Material.RAW_COPPER_BLOCK);
playerBlocks.add(Material.RAW_IRON_BLOCK);
playerBlocks.add(Material.RAW_GOLD_BLOCK);
playerBlocks.add(Material.LIGHTNING_ROD);
//these are unnatural in the nether and end
if (environment != Environment.NORMAL && environment != Environment.CUSTOM)
{
playerBlocks.addAll(Tag.BASE_STONE_OVERWORLD.getValues());
playerBlocks.addAll(Tag.DIRT.getValues());
playerBlocks.addAll(Tag.SAND.getValues());
}
//these are unnatural in the standard world, but not in the nether
if (environment != Environment.NETHER)
{
playerBlocks.addAll(Tag.NYLIUM.getValues());
playerBlocks.addAll(Tag.WART_BLOCKS.getValues());
playerBlocks.addAll(Tag.BASE_STONE_NETHER.getValues());
playerBlocks.add(Material.POLISHED_BLACKSTONE);
playerBlocks.add(Material.CHISELED_POLISHED_BLACKSTONE);
playerBlocks.add(Material.CRACKED_POLISHED_BLACKSTONE_BRICKS);
playerBlocks.add(Material.GILDED_BLACKSTONE);
playerBlocks.add(Material.BONE_BLOCK);
playerBlocks.add(Material.SOUL_SAND);
playerBlocks.add(Material.SOUL_SOIL);
playerBlocks.add(Material.GLOWSTONE);
playerBlocks.add(Material.NETHER_BRICK);
playerBlocks.add(Material.MAGMA_BLOCK);
playerBlocks.add(Material.ANCIENT_DEBRIS);
playerBlocks.add(Material.CHAIN);
playerBlocks.add(Material.SHROOMLIGHT);
playerBlocks.add(Material.NETHER_GOLD_ORE);
playerBlocks.add(Material.NETHER_SPROUTS);
playerBlocks.add(Material.CRIMSON_FUNGUS);
playerBlocks.add(Material.CRIMSON_ROOTS);
playerBlocks.add(Material.NETHER_WART_BLOCK);
playerBlocks.add(Material.WEEPING_VINES);
playerBlocks.add(Material.WEEPING_VINES_PLANT);
playerBlocks.add(Material.WARPED_FUNGUS);
playerBlocks.add(Material.WARPED_ROOTS);
playerBlocks.add(Material.WARPED_WART_BLOCK);
playerBlocks.add(Material.TWISTING_VINES);
playerBlocks.add(Material.TWISTING_VINES_PLANT);
}
//blocks from tags that are natural in the nether
else
{
playerBlocks.remove(Material.CRIMSON_STEM);
playerBlocks.remove(Material.CRIMSON_HYPHAE);
playerBlocks.remove(Material.NETHER_BRICK_FENCE);
playerBlocks.remove(Material.NETHER_BRICK_STAIRS);
playerBlocks.remove(Material.SOUL_FIRE);
playerBlocks.remove(Material.WARPED_STEM);
playerBlocks.remove(Material.WARPED_HYPHAE);
}
//these are unnatural in the standard and nether worlds, but not in the end
if (environment != Environment.THE_END)
{
playerBlocks.add(Material.CHORUS_PLANT);
playerBlocks.add(Material.CHORUS_FLOWER);
playerBlocks.add(Material.END_ROD);
playerBlocks.add(Material.END_STONE);
playerBlocks.add(Material.END_STONE_BRICKS);
playerBlocks.add(Material.OBSIDIAN);
playerBlocks.add(Material.PURPUR_BLOCK);
playerBlocks.add(Material.PURPUR_PILLAR);
}
//blocks from tags that are natural in the end
else
{
playerBlocks.remove(Material.PURPUR_SLAB);
playerBlocks.remove(Material.PURPUR_STAIRS);
}
//these are unnatural in sandy biomes, but not elsewhere
if (SAND_SOIL_BIOMES.contains(biome.getKey()) || environment != Environment.NORMAL)
{
playerBlocks.addAll(Tag.LEAVES.getValues());
}
//blocks from tags that are natural in non-sandy normal biomes
else
{
playerBlocks.remove(Material.OAK_LOG);
playerBlocks.remove(Material.SPRUCE_LOG);
playerBlocks.remove(Material.BIRCH_LOG);
playerBlocks.remove(Material.JUNGLE_LOG);
playerBlocks.remove(Material.ACACIA_LOG);
playerBlocks.remove(Material.DARK_OAK_LOG);
}
return playerBlocks;
}
}