@Mod(Constants.MODID)
public class Geolosys {
    private static Geolosys instance;
    public static CommonProxy proxy = DistExecutor.safeRunForDist(() -> ClientProxy::new, () -> CommonProxy::new);
    public Logger LOGGER = LogManager.getLogger();

    public Geolosys() {
        instance = this;

        // Register the setup method for modloading
        FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup);
        FMLJavaModLoadingContext.get().getModEventBus().addListener(this::clientSetup);

        MinecraftForge.EVENT_BUS.register(this);
        MinecraftForge.EVENT_BUS.register(new ManualGifting());
        MinecraftForge.EVENT_BUS.register(new ModItemsParser());

        this.configSetup();
    }

    public static Geolosys getInstance() {
        return instance;
    }

    private void configSetup() {
        ModLoadingContext.get().registerConfig(Type.CLIENT, ClientConfig.CLIENT_CONFIG);
        ModLoadingContext.get().registerConfig(Type.COMMON, CommonConfig.COMMON_CONFIG);
        CommonConfig.loadConfig(CommonConfig.COMMON_CONFIG, FMLPaths.CONFIGDIR.get().resolve("geolosys-common.toml"));
    }

    private void clientSetup(final FMLClientSetupEvent event) {
        Cutouts.init();
    }

    public void setup(final FMLCommonSetupEvent event) {
        CapabilityManager.INSTANCE.register(IDepositCapability.class, new DepositCapStorage(), DepositCapability::new);

        GeolosysAPI.init();
        proxy.init();
    }

    @SubscribeEvent
    public void onSlashReload(AddReloadListenerEvent evt) {
        evt.addListener(new WorldGenDataLoader());
    }

    @SubscribeEvent
    public void attachCap(AttachCapabilitiesEvent<World> event) {
        event.addCapability(new ResourceLocation(Constants.MODID, "pluton"), new DepositCapProvider());
        String dimName = event.getObject().getDimensionKey().getLocation().toString();
        LOGGER.info("Geolosys capability attached for {}", dimName);
    }

    @SubscribeEvent
    @OnlyIn(Dist.CLIENT)
    public void onHover(ItemTooltipEvent event) {
        if (!ClientConfig.ENABLE_TAG_DEBUG.get()) {
            return;
        }

        Minecraft mc = Minecraft.getInstance();

        if (mc.gameSettings.advancedItemTooltips) {
            Collection<ResourceLocation> tags = ItemTags.getCollection().getOwningTags(event.getItemStack().getItem());
            if (tags.size() > 0) {
                for (ResourceLocation tag : tags) {
                    event.getToolTip().add(new StringTextComponent("\u00A78#" + tag.toString() + "\u00A7r"));
                }
            }
        }
    }

    @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
    public static class RegistryEvents {
        @SubscribeEvent
        public static void onBlocksRegistry(final RegistryEvent.Register<Block> blockRegistryEvent) {
            ModBlocks.getInstance().register(blockRegistryEvent);
        }

        @SubscribeEvent
        public static void onItemsRegistry(final RegistryEvent.Register<Item> itemRegistryEvent) {
            ModBlocks.getInstance().registerItemBlocks(itemRegistryEvent);
            ModItems.getInstance().register(itemRegistryEvent);
        }

        @SubscribeEvent
        public static void onModifierSerializersRegistry(
                final RegistryEvent.Register<GlobalLootModifierSerializer<?>> evt) {
            evt.getRegistry().register(new OsmiumDropModifier.Serializer()
                    .setRegistryName(new ResourceLocation(Constants.MODID, "osmium")));
            evt.getRegistry().register(new QuartzDropModifier.Serializer()
                    .setRegistryName(new ResourceLocation(Constants.MODID, "quartzes")));
            evt.getRegistry().register(new YelloriumDropModifier.Serializer()
                    .setRegistryName(new ResourceLocation(Constants.MODID, "yellorium")));
            evt.getRegistry().register(new SulfurDropModifier.Serializer()
                    .setRegistryName(new ResourceLocation(Constants.MODID, "sulfur")));
        }
    }
} public class DepositUtils {
    private static Random random = new Random();
    private static HashSet<BlockState> defaultMatchersCached = null;

    /**
     * picks a choice out of a mapping between blockstate to weight passing -1.0F as
     * totl will result in a total being calculated.
     * 
     * @param map
     * @param totl
     * @return null if no block should be used or placed, T instanceof BlockState if
     *         actual block should be placed.
     */
    @Nullable
    public static BlockState pick(HashMap<BlockState, Float> map, float totl) {
        if (totl == 1.0F) {
            totl = 0;
            for (Entry<BlockState, Float> e : map.entrySet()) {
                totl += e.getValue();
            }
        }

        float rng = random.nextFloat();
        for (Entry<BlockState, Float> e : map.entrySet()) {
            float wt = e.getValue();
            if (rng < wt) {
                return e.getKey();
            }
            rng -= wt;
        }

        Geolosys.getInstance().LOGGER.error("Could not reach decision on block to place at Utils#pick");
        return null;
    }

    public static boolean canPlaceInBiome(Biome targetBiome, @Nullable List<Biome> biomes,
            @Nullable List<BiomeDictionary.Type> biomeTypes, boolean isBiomeFilterBl) {
        boolean matchForBiome = false;
        boolean matchForBiomeType = false;

        if (biomes != null) {
            matchForBiome = biomes.stream().anyMatch((b) -> {
                return b.getRegistryName().equals(targetBiome.getRegistryName());
            });
        }

        if (biomeTypes != null) {
            for (BiomeDictionary.Type type : biomeTypes) {
                RegistryKey<Biome> regKey = RegistryKey.getOrCreateKey(Registry.BIOME_KEY,
                        targetBiome.getRegistryName());
                Set<BiomeDictionary.Type> dictTypes = BiomeDictionary.getTypes(regKey);
                matchForBiomeType = dictTypes.stream().anyMatch((dictType) -> {
                    return type.getName().equalsIgnoreCase(dictType.getName());
                });
                if (matchForBiomeType) {
                    break;
                }
            }
        }

        return ((matchForBiome || matchForBiomeType) && !isBiomeFilterBl)
                || (!matchForBiome && !matchForBiomeType && isBiomeFilterBl);
    }

    @SuppressWarnings("unchecked")
    public static HashSet<BlockState> getDefaultMatchers() {
        // If the cached data isn't there yet, load it.
        if (defaultMatchersCached == null) {
            defaultMatchersCached = new HashSet<BlockState>();
            // GeolosysAPI.plutonRegistry.getStones().forEach(x ->
            // defaultMatchersCached.add(x.getOre()));

            CommonConfig.DEFAULT_REPLACEMENT_MATS.get().forEach(s -> {
                Block block = ForgeRegistries.BLOCKS.getValue(new ResourceLocation(s));
                if (block == null || !addDefaultMatcher(block)) {
                    Geolosys.getInstance().LOGGER.warn("{} is not a valid block. Please verify.", s);
                }
            });
        }

        return (HashSet<BlockState>) defaultMatchersCached.clone();
    }

    @SuppressWarnings("deprecation")
    public static boolean addDefaultMatcher(Block block) {
        BlockState defaultState = block.getDefaultState();
        if (!defaultState.isAir()) {
            defaultMatchersCached.add(defaultState);
            return true;
        }
        return false;
    }
} public interface IDeposit {
    public int generate(ISeedReader reader, BlockPos pos, IDepositCapability cap);

    public void afterGen(ISeedReader reader, BlockPos pos, IDepositCapability cap);

    public HashSet<BlockState> getAllOres();

    public int getGenWt();

    public boolean canPlaceInBiome(Biome biome);

    public boolean hasBiomeRestrictions();

    public String[] getDimensionFilter();

    public boolean isDimensionFilterBl();

    public HashSet<BlockState> getBlockStateMatchers();
} @Mod.EventBusSubscriber
public class CommonConfig {
    public static final ForgeConfigSpec COMMON_CONFIG;
    private static final Builder COMMON_BUILDER = new Builder();
    public static ForgeConfigSpec.BooleanValue DEBUG_WORLD_GEN;
    public static ForgeConfigSpec.BooleanValue ADVANCED_DEBUG_WORLD_GEN;
    public static ForgeConfigSpec.BooleanValue REMOVE_VANILLA_ORES;
    public static ForgeConfigSpec.IntValue CHUNK_SKIP_CHANCE;
    public static ForgeConfigSpec.IntValue MAX_SAMPLES_PER_CHUNK;
    public static ForgeConfigSpec.ConfigValue<List<? extends String>> SAMPLE_PLACEMENT_BLACKLIST;
    public static ForgeConfigSpec.BooleanValue SAMPLE_TICK_ENABLED;
    public static ForgeConfigSpec.BooleanValue ENABLE_PRO_PICK;
    public static ForgeConfigSpec.BooleanValue ENABLE_PRO_PICK_DMG;
    public static ForgeConfigSpec.IntValue PRO_PICK_DURABILITY;
    public static ForgeConfigSpec.IntValue PRO_PICK_RANGE;
    public static ForgeConfigSpec.IntValue PRO_PICK_DIAMETER;
    public static ForgeConfigSpec.BooleanValue GIVE_MANUAL_TO_NEW;
    public static ForgeConfigSpec.ConfigValue<List<? extends String>> DEFAULT_REPLACEMENT_MATS;
    private static String CATEGORY_FEATURE_CONTROL = "feature control";
    private static String CATEGORY_PROSPECTING = "prospecting";

    static {
        init();
        CompatConfig.init(COMMON_BUILDER);
        COMMON_CONFIG = COMMON_BUILDER.build();
    }

    public static void loadConfig(ForgeConfigSpec spec, Path path) {
        final CommentedFileConfig configData = CommentedFileConfig.builder(path).sync().autosave()
                .writingMode(WritingMode.REPLACE).build();

        configData.load();
        spec.setConfig(configData);
    }

    private static void init() {
        COMMON_BUILDER.comment("Feature Control").push(CATEGORY_FEATURE_CONTROL);
        DEBUG_WORLD_GEN = COMMON_BUILDER.comment("Output info into the logs when generating Geolosys deposits")
                .define("debugWorldgen", false);
        ADVANCED_DEBUG_WORLD_GEN = COMMON_BUILDER.comment("Outputs really advanced info when generating deposits.")
                .define("advancedDebugWorldGen", false);
        REMOVE_VANILLA_ORES = COMMON_BUILDER.comment("Disable generation of Vanilla ores")
                .define("disableVanillaOreGen", true);
        DEFAULT_REPLACEMENT_MATS = COMMON_BUILDER.comment(
                "The fallback materials which a Deposit can replace if they're not specified by the deposit itself\n"
                        + "Format: Comma-delimited set of <modid:block> (see default for example)")
                .defineList("defaultReplacementMaterials",
                        Lists.newArrayList("minecraft:stone", "minecraft:andesite", "minecraft:diorite",
                                "minecraft:granite", "minecraft:netherrack", "minecraft:sandstone"),
                        rawName -> rawName instanceof String);
        COMMON_BUILDER.pop();

        COMMON_BUILDER.comment("Prospecting Options").push(CATEGORY_PROSPECTING);
        MAX_SAMPLES_PER_CHUNK = COMMON_BUILDER
                .comment("Maximum samples that can generate with each pluton within a chunk")
                .defineInRange("maxSamplesPerChunk", 10, 1, 256);
        CHUNK_SKIP_CHANCE = COMMON_BUILDER.comment(
                "The upper limit of RNG for generating any pluton in a given chunk.\nLarger values indicate further distance between plutons.")
                .defineInRange("chunkSkipChance", 100, 1, Integer.MAX_VALUE);
        SAMPLE_PLACEMENT_BLACKLIST = COMMON_BUILDER.comment("A list of <modid:block> that samples may not be placed on")
                .defineList("samplePlacementBlacklist",
                        Lists.newArrayList("minecraft:ice", "minecraft:packed_ice", "minecraft:bedrock"),
                        rawName -> rawName instanceof String);
        SAMPLE_TICK_ENABLED = COMMON_BUILDER.comment(
                "Whether or not Samples randomly tick (like crops). This can be bad for performance but fixes waterlogging issues on worldgen")
                .define("shouldSamplesTick", true);
        ENABLE_PRO_PICK = COMMON_BUILDER.comment("Enable the prospector's pickaxe").define("enableProPick", true);
        ENABLE_PRO_PICK_DMG = COMMON_BUILDER.comment("Allow the prospector's pick to get damaged")
                .define("enableProPickDmg", false);
        PRO_PICK_DURABILITY = COMMON_BUILDER.comment("Max durability of a prospector's pick if damage is enabled")
                .defineInRange("proPickDurability", 1024, 1, Integer.MAX_VALUE);
        PRO_PICK_RANGE = COMMON_BUILDER.comment("The range (depth) of the prospector's pick prospecting cycle")
                .defineInRange("proPickRange", 5, 1, Integer.MAX_VALUE);
        PRO_PICK_DIAMETER = COMMON_BUILDER.comment("The diameter of the prospector's pick prospecting cycle")
                .defineInRange("proPickDiameter", 5, 1, Integer.MAX_VALUE);
        GIVE_MANUAL_TO_NEW = COMMON_BUILDER.comment("Give players a Field Manual if they haven't gotten one")
                .define("giveManual", true);
        COMMON_BUILDER.pop();
    }
} public class SerializerUtils {
    public static String[] getDimFilter(JsonObject obj) {
        if (obj.has("dimensions") && obj.get("dimensions").isJsonObject()) {
            return toStringArray(obj.get("dimensions").getAsJsonObject().get("filter").getAsJsonArray());
        }
        return toStringArray(obj.get("dimBlacklist").getAsJsonArray());
    }

    public static boolean getIsDimFilterBl(JsonObject obj) {
        if (obj.has("dimensions") && obj.get("dimensions").isJsonObject()) {
            return obj.get("dimensions").getAsJsonObject().get("isBlacklist").getAsBoolean();
        }
        return true;
    }

    @Nullable
    public static String[] getBiomeFilter(JsonObject obj) {
        if (obj.has("biomes") && obj.get("biomes").isJsonObject()) {
            return toStringArray(obj.get("biomes").getAsJsonObject().get("filter").getAsJsonArray());
        }
        return null;
    }

    public static boolean getIsBiomeFilterBl(JsonObject obj) {
        if (obj.has("biomes") && obj.get("biomes").isJsonObject()) {
            return obj.get("biomes").getAsJsonObject().get("isBlacklist").getAsBoolean();
        }
        // Return true so that we have an empty blacklist
        return true;
    }

    public static JsonArray deconstructBiomes(List<Biome> biomes, List<BiomeDictionary.Type> types) {
        JsonArray ret = new JsonArray();

        for (Biome b : biomes) {
            ret.add(b.getRegistryName().toString());
        }

        for (BiomeDictionary.Type t : types) {
            ret.add(t.getName());
        }

        return ret;
    }

    @Nullable
    public static BlockState fromString(@Nullable String string) {
        ResourceLocation r = new ResourceLocation(string);
        return ForgeRegistries.BLOCKS.getValue(r).getDefaultState();
    }

    public static String[] toStringArray(JsonArray arr) {
        String[] ret = new String[arr.size()];

        for (int i = 0; i < arr.size(); i++) {
            ret[i] = arr.get(i).getAsString();
        }

        return ret;
    }

    public static HashSet<BlockState> toBlockStateList(JsonArray arr) {
        HashSet<BlockState> ret = new HashSet<BlockState>();

        for (String s : toStringArray(arr)) {
            ret.add(fromString(s));
        }

        return ret;
    }

    public static HashMap<BlockState, Float> buildMultiBlockMap(JsonArray arr) {
        HashMap<BlockState, Float> ret = new HashMap<BlockState, Float>();

        for (JsonElement j : arr) {
            JsonObject pair = j.getAsJsonObject();
            if (pair.get("block").isJsonNull()) {
                ret.put(null, pair.get("chance").getAsFloat());
            } else {
                ret.put(fromString(pair.get("block").getAsString()), pair.get("chance").getAsFloat());
            }
        }

        return ret;
    }

    public static JsonArray deconstructMultiBlockMap(HashMap<BlockState, Float> in) {
        JsonArray ret = new JsonArray();

        for (Entry<BlockState, Float> e : in.entrySet()) {
            JsonObject obj = new JsonObject();
            obj.addProperty("block", e.getKey().getBlock().getRegistryName().toString());
            obj.addProperty("chance", e.getValue());
            ret.add(obj);
        }

        return ret;
    }

    public static List<BiomeDictionary.Type> extractBiomeTypes(String[] arr) {
        List<BiomeDictionary.Type> ret = new ArrayList<BiomeDictionary.Type>();

        for (BiomeDictionary.Type biomeType : BiomeDictionary.Type.getAll()) {
            for (String type : arr) {
                if (biomeType.getName().equalsIgnoreCase(type)) {
                    if (!ret.contains(biomeType)) {
                        ret.add(biomeType);
                    }
                    break;
                }
            }
        }

        return ret;
    }

    public static List<Biome> extractBiomes(String[] arr) {
        List<Biome> ret = new ArrayList<Biome>();

        for (String s : arr) {
            ResourceLocation r = new ResourceLocation(s.toLowerCase());
            if (ForgeRegistries.BIOMES.containsKey(r)) {
                ret.add(ForgeRegistries.BIOMES.getValue(r));
            }
        }

        return ret;
    }
} public class Utils {
    public static ItemStack blockStateToStack(BlockState state) {
        return new ItemStack(state.getBlock().asItem(), 1);
    }

    public static boolean doStatesMatch(BlockState state1, BlockState state2) {
        return (state1.getBlock().getRegistryName() == state2.getBlock().getRegistryName());
    }

    public static BlockPos getTopSolidBlock(IWorld world, BlockPos start) {
        BlockPos retPos = new BlockPos(start.getX(), world.getHeight() - 1, start.getZ());
        while (retPos.getY() > 0) {
            if (world.getBlockState(retPos).getMaterial().isSolid()) {
                break;
            }
            retPos = retPos.down();
        }
        return retPos;
    }

    public static boolean canMine(BlockState state, ItemStack stack) {
        int harvestLvl = stack.getHarvestLevel(ToolType.PICKAXE, null, null);
        return stack.getToolTypes().contains(ToolType.PICKAXE) && state.getHarvestLevel() <= harvestLvl;
    }
} public class SampleUtils {
    private static ArrayList<BlockState> samplePlacementBlacklist = new ArrayList<>();
    private static Random random = new Random();

    @Nullable
    public static BlockPos getSamplePosition(ISeedReader reader, ChunkPos chunkPos) {
        return getSamplePosition(reader, chunkPos, -1);
    }

    @Nullable
    public static BlockPos getSamplePosition(ISeedReader reader, ChunkPos chunkPos, int spread) {

        if (!(reader instanceof WorldGenRegion)) {
            return null;
        }

        WorldGenRegion world = (WorldGenRegion) reader;
        int usedSpread = Math.max(8, spread);
        int xCenter = (chunkPos.getXStart() + chunkPos.getXEnd()) / 2;
        int zCenter = (chunkPos.getZStart() + chunkPos.getZEnd()) / 2;

        // Only put things in the negative X|Z if the spread is provided.
        int blockPosX = xCenter + (random.nextInt(usedSpread) * ((reader.getRandom().nextBoolean()) ? 1 : -1));
        int blockPosZ = zCenter + (random.nextInt(usedSpread) * ((reader.getRandom().nextBoolean()) ? 1 : -1));

        if (!world.chunkExists(chunkPos.x, chunkPos.z)) {
            return null;
        }

        BlockPos searchPosUp = new BlockPos(blockPosX, world.getSeaLevel(), blockPosZ);
        BlockPos searchPosDown = new BlockPos(blockPosX, world.getSeaLevel(), blockPosZ);

        // Try to get something _above_ sea level first
        while (searchPosUp.getY() < world.getHeight()) {
            if (Block.hasEnoughSolidSide(world, searchPosUp.down(), Direction.UP)) {
                if (canReplace(world, searchPosUp) && canReplace(world, searchPosUp.up())
                        && canPlaceOn(world, searchPosUp)) {
                    return searchPosUp;
                }
            }
            searchPosUp = searchPosUp.up();
        }

        // If all else fails try something below sea level..
        while (searchPosDown.getY() > 0) {
            if (Block.hasEnoughSolidSide(world, searchPosDown.down(), Direction.UP)) {
                if (canReplace(world, searchPosDown) && canReplace(world, searchPosDown.up())
                        && canPlaceOn(world, searchPosDown)) {
                    return searchPosDown;
                }
            }
            searchPosDown = searchPosDown.down();
        }

        // Or just forget about it I guess :P
        return null;
    }

    /**
     * Determines if the sample can be placed on this block
     * 
     * @param world: an ISeedReader instance
     * @param pos:   The current searching position that will be used to confirm
     * @return true if the block below is solid on top AND isn't in the blacklist
     */
    public static boolean canPlaceOn(ISeedReader reader, BlockPos pos) {
        return !samplePlacementBlacklist.contains(reader.getBlockState(pos.down()))
                && Block.hasEnoughSolidSide(reader, pos.down(), Direction.UP);
    }

    /**
     * @param reader an ISeedReader instance
     * @param pos    A BlockPos to check in and around
     * @return true if the block at pos is replaceable
     */
    public static boolean canReplace(ISeedReader reader, BlockPos pos) {
        BlockState state = reader.getBlockState(pos);
        Material mat = state.getMaterial();
        return BlockTags.LEAVES.contains(state.getBlock()) || mat.isReplaceable();
    }

    /**
     * @param reader an ISeedReader instance
     * @param pos    A BlockPos to check in and around
     * @return true if the block is water (since we can waterlog)
     */
    public static boolean isInWater(ISeedReader reader, BlockPos pos) {
        return reader.getBlockState(pos).getBlock() == Blocks.WATER;
    }

    /**
     * @param reader an ISeedReader instance
     * @param pos    A BlockPos to check in and around
     * @return true if the block is in a non-water fluid
     */
    public static boolean inNonWaterFluid(ISeedReader reader, BlockPos pos) {
        return reader.getBlockState(pos).getMaterial().isLiquid() && !isInWater(reader, pos);
    }

    /**
     * @param posA
     * @param posB
     * @param range An integer representing how far is acceptable to be considered
     *              in range
     * @return true if within range
     */
    public static boolean isWithinRange(int posA, int posB, int range) {
        return (Math.abs(posA - posB) <= range);
    }

    public static void addSamplePlacementBlacklist(Block block) {
        samplePlacementBlacklist.add(block.getDefaultState());
    }

} public interface IDepositCapability extends INBTSerializable<CompoundNBT> {
    boolean hasPlutonGenerated(ChunkPos pos);

    void setPlutonGenerated(ChunkPos pos);

    void putPendingBlock(BlockPos pos, BlockState state);

    BlockState getPendingBlock(BlockPos pos);

    ConcurrentLinkedQueue<ChunkPos> getGenMap();

    Map<BlockPos, BlockState> getPendingBlocks();

    boolean hasPlayerReceivedManual(UUID uuid);

    void setPlayerReceivedManual(UUID uuid);

    Map<UUID, Boolean> getGivenMap();
} public class DepositFeature extends Feature<NoFeatureConfig> {
    public DepositFeature(Codec<NoFeatureConfig> p_i231976_1_) {
        super(p_i231976_1_);
    }

    @Override
    @ParametersAreNonnullByDefault
    public boolean generate(ISeedReader reader, ChunkGenerator generator, Random rand, BlockPos pos,
            NoFeatureConfig config) {
        if (generator instanceof FlatChunkGenerator) {
            return false;
        }

        IDepositCapability cap = reader.getWorld().getCapability(GeolosysAPI.GEOLOSYS_WORLD_CAPABILITY).orElse(null);
        if (cap == null) {
            Geolosys.getInstance().LOGGER.error("NULL PLUTON CAPABILITY!!!");
            return false;
        }

        boolean placedPending = placePendingBlocks(reader, cap, pos);
        ChunkPos chunkPos = new ChunkPos(pos);
        if (cap.hasPlutonGenerated(chunkPos)) {
            return false;
        }

        IDeposit pluton = GeolosysAPI.plutonRegistry.pick(reader, pos, rand);
        if (pluton == null) { // Could be no pluton for the dimension
            return false;
        }

        if (rand.nextInt(CommonConfig.CHUNK_SKIP_CHANCE.get()) > pluton.getGenWt()) {
            return false;
        }

        boolean anyGenerated = pluton.generate(reader, pos, cap) > 0;

        if (anyGenerated) {
            pluton.afterGen(reader, pos, cap);
            cap.setPlutonGenerated(chunkPos);
            return true;
        }

        return placedPending;
    }

    private boolean placePendingBlocks(ISeedReader reader, IDepositCapability cap, BlockPos pos) {
        boolean placedAny = false;
        for (Entry<BlockPos, BlockState> e : cap.getPendingBlocks().entrySet()) {
            if (FeatureUtils.isInChunk(new ChunkPos(pos), e.getKey())) {
                if (reader.setBlockState(e.getKey(), e.getValue(), 2 | 16)) {
                    placedAny = true;
                    cap.getPendingBlocks().remove(e.getKey());
                    if (CommonConfig.ADVANCED_DEBUG_WORLD_GEN.get()) {
                        Geolosys.getInstance().LOGGER.debug("Generated pending block "
                                + e.getValue().getBlock().getRegistryName().toString() + " at " + e.getKey());
                    }
                } else {
                    if (CommonConfig.DEBUG_WORLD_GEN.get()) {
                        Geolosys.getInstance().LOGGER.error("FAILED to generate pending block "
                                + e.getValue().getBlock().getRegistryName().toString() + " at " + e.getKey());
                    }
                }
            }
        }

        return placedAny;
    }
} public class FeatureUtils {

    public static boolean isInChunk(ChunkPos chunkPos, BlockPos pos) {
        return new ChunkPos(pos).equals(chunkPos);
    }

    public static ConfiguredFeature<?, ?> getFeature(ConfiguredFeature<?, ?> feature) {
        ConfiguredFeature<?, ?> currentFeature = feature;
        if (currentFeature.feature instanceof DecoratedFeature) {
            do {
                currentFeature = ((DecoratedFeatureConfig) currentFeature.getConfig()).feature.get();
            } while (currentFeature.feature instanceof DecoratedFeature);
        }
        return currentFeature;
    }

    public static void destroyFeature(List<Supplier<ConfiguredFeature<?, ?>>> features,
            List<Supplier<ConfiguredFeature<?, ?>>> destroy) {
        for (Supplier<ConfiguredFeature<?, ?>> feature : destroy) {
            features.remove(feature);
        }
    }

    public static BlockState tryGetBlockState(ISeedReader reader, ChunkPos chunk, BlockPos pos) {
        if (reader.chunkExists(chunk.x, chunk.z)) {
            return reader.getBlockState(pos);
        }

        return Blocks.BARRIER.getDefaultState();
    }

    public static boolean tryPlaceBlock(ISeedReader reader, ChunkPos chunk, BlockPos pos, BlockState state,
            IDepositCapability cap) {
        if (isInChunk(chunk, pos) && reader.chunkExists(chunk.x, chunk.z)) {
            boolean ret = reader.setBlockState(pos, state, 2 | 16);
            return ret;
        }

        cap.putPendingBlock(new BlockPos(pos), state);
        return false;
    }

    public static void fixSnowyBlock(ISeedReader reader, BlockPos posPlaced) {
        BlockState below = reader.getBlockState(posPlaced.down());
        if (below.hasProperty(BlockStateProperties.SNOWY)) {
            reader.setBlockState(posPlaced.down(), below.with(BlockStateProperties.SNOWY, Boolean.valueOf(false)),
                    2 | 16);
        }
    }
}
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import javax.annotation.Nullable;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSerializationContext;
import com.oitsjustjose.geolosys.Geolosys;
import com.oitsjustjose.geolosys.api.world.DepositUtils;
import com.oitsjustjose.geolosys.api.world.IDeposit;
import com.oitsjustjose.geolosys.common.config.CommonConfig;
import com.oitsjustjose.geolosys.common.data.serializer.SerializerUtils;
import com.oitsjustjose.geolosys.common.utils.Utils;
import com.oitsjustjose.geolosys.common.world.SampleUtils;
import com.oitsjustjose.geolosys.common.world.capability.IDepositCapability;
import com.oitsjustjose.geolosys.common.world.feature.DepositFeature;
import com.oitsjustjose.geolosys.common.world.feature.FeatureUtils;
import net.minecraft.block.BlockState;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.ISeedReader;
import net.minecraft.world.biome.Biome;
import net.minecraftforge.common.BiomeDictionary;
package com.oitsjustjose.geolosys.api.world.deposit;





public class DenseDeposit implements IDeposit {
    public static final String JSON_TYPE = "geolosys:deposit_dense";

    private HashMap<BlockState, Float> oreToWtMap = new HashMap<>();
    private HashMap<BlockState, Float> sampleToWtMap = new HashMap<>();
    private int yMin;
    private int yMax;
    private int size;
    private int genWt;
    private HashSet<BlockState> blockStateMatchers;
    private String[] dimFilter;
    private boolean isDimFilterBl;

    // Optional biome stuff!
    @Nullable
    private List<BiomeDictionary.Type> biomeTypeFilter;
    @Nullable
    private List<Biome> biomeFilter;
    @Nullable
    private boolean isBiomeFilterBl;

    private float sumWtOres = 0.0F;
    private float sumWtSamples = 0.0F;

    public DenseDeposit(HashMap<BlockState, Float> oreBlocks, HashMap<BlockState, Float> sampleBlocks, int yMin,
            int yMax, int size, int genWt, String[] dimFilter, boolean isDimFilterBl,
            @Nullable List<BiomeDictionary.Type> biomeTypes, @Nullable List<Biome> biomeFilter,
            @Nullable boolean isBiomeFilterBl, HashSet<BlockState> blockStateMatchers) {
        this.oreToWtMap = oreBlocks;
        this.sampleToWtMap = sampleBlocks;
        this.yMin = yMin;
        this.yMax = yMax;
        this.size = size;
        this.genWt = genWt;
        this.dimFilter = dimFilter;
        this.isDimFilterBl = isDimFilterBl;
        this.biomeTypeFilter = biomeTypes;
        this.isBiomeFilterBl = isBiomeFilterBl;
        this.blockStateMatchers = blockStateMatchers;
        this.biomeFilter = biomeFilter;

        for (Entry<BlockState, Float> e : this.oreToWtMap.entrySet()) {
            this.sumWtOres += e.getValue();
        }
        assert sumWtOres == 1.0F : "Sum of weights for pluton blocks should equal 1.0";

        for (Entry<BlockState, Float> e : this.sampleToWtMap.entrySet()) {
            this.sumWtSamples += e.getValue();
        }
        assert sumWtSamples == 1.0F : "Sum of weights for pluton samples should equal 1.0";
    }

    /**
     * Uses {@link DepositUtils#pick(HashMap, float)} to find a random ore block to
     * return.
     * 
     * @return the random ore block chosen (based on weight) Can be null to
     *         represent "density" of the ore -- null results should be used to
     *         determine if the block in the world should be replaced. If null,
     *         don't replace 😉
     */
    @Nullable
    public BlockState getOre() {
        return DepositUtils.pick(this.oreToWtMap, this.sumWtOres);