All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yungnickyoung.minecraft.yungsapi.api.world.randomize.BlockStateRandomizer Maven / Gradle / Ivy

The newest version!
package com.yungnickyoung.minecraft.yungsapi.api.world.randomize;

import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.yungnickyoung.minecraft.yungsapi.YungsApiCommon;
import com.yungnickyoung.minecraft.yungsapi.world.structure.context.StructureContext;
import com.yungnickyoung.minecraft.yungsapi.world.structure.condition.StructureCondition;
import com.yungnickyoung.minecraft.yungsapi.world.structure.condition.StructureConditionType;
import java.util.*;
import net.minecraft.class_156;
import net.minecraft.class_2246;
import net.minecraft.class_2248;
import net.minecraft.class_2487;
import net.minecraft.class_2499;
import net.minecraft.class_2680;
import net.minecraft.class_5819;

/**
 * Describes a set of BlockStates and the probability of each BlockState in the set being chosen.
 * Useful for easily adding random variation to worldgen, especially structures and features.
 */
public class BlockStateRandomizer {
    public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance
            .group(
                    Entry.CODEC.listOf().fieldOf("entries").forGetter((randomizer) -> randomizer.entries),
                    class_2680.field_24734.fieldOf("defaultBlockState").forGetter((randomizer) -> randomizer.defaultBlockState))
            .apply(instance, BlockStateRandomizer::new));

    /**
     * Map of BlockState to its corresponding probability.
     * The total sum of all the probabilities should not exceed 1.
     */
    private List entries = new ArrayList<>();

    /**
     * The default BlockState is used for any leftover probability ranges.
     * For example, if the total sum of all the probabilities of the entries is 0.6, then
     * there is a 0.4 chance of the defaultBlockState being selected.
     */
    private class_2680 defaultBlockState = class_2246.field_10124.method_9564();

    /**
     * Saves this BlockStateRandomizer to a new CompoundTag.
     * @return The CompoundTag
     */
    public class_2487 saveTag() {
        class_2487 compoundTag = new class_2487();

        // Save default blockstate
        compoundTag.method_10569("defaultBlockStateId", class_2248.field_10651.method_10206(this.defaultBlockState));

        // Save entries
        class_2499 entriesTag = class_156.method_654(new class_2499(), (tag) -> {
            this.entries.forEach((entry) -> {
                class_2487 entryTag = new class_2487();
                entryTag.method_10569("entryBlockStateId", class_2248.field_10651.method_10206(entry.blockState));
                entryTag.method_10548("entryChance", entry.probability);
                tag.add(entryTag);
            });
        });
        compoundTag.method_10566("entries", entriesTag);

        return compoundTag;
    }

    /**
     * Constructs a new BlockStateRandomizer from a CompoundTag.
     * @param compoundTag The CompoundTag
     */
    public BlockStateRandomizer(class_2487 compoundTag) {
        this.defaultBlockState = class_2248.field_10651.method_10200(compoundTag.method_10550("defaultBlockStateId"));
        this.entries = new ArrayList<>();

        class_2499 entriesTag = compoundTag.method_10554("entries", 10);
        entriesTag.forEach(entryTag -> {
            class_2487 entryCompoundTag = ((class_2487) entryTag);
            class_2680 blockState = class_2248.field_10651.method_10200(entryCompoundTag.method_10550("entryBlockStateId"));
            float chance = entryCompoundTag.method_10583("entryChance");
            this.addBlock(blockState, chance);
        });
    }

    /**
     * Constructs a new BlockStateRandomizer from a Map of BlockStates to their corresponding probabilities.
     * @param entries Map of BlockStates to their corresponding probabilities
     * @param defaultBlockState The default BlockState
     */
    public BlockStateRandomizer(Map entries, class_2680 defaultBlockState) {
        this.entries = new ArrayList<>();
        entries.forEach(this::addBlock);
        this.defaultBlockState = defaultBlockState;
    }

    /**
     * Constructs a new BlockStateRandomizer from a List of BlockStateRandomizer Entries.
     * @param entries List of BlockStateRandomizer Entries
     * @param defaultBlockState The default BlockState
     */
    public BlockStateRandomizer(List entries, class_2680 defaultBlockState) {
        this.entries = entries;
        this.defaultBlockState = defaultBlockState;
    }

    /**
     * Constructs a new BlockStateRandomizer with only a default BlockState.
     * @param defaultBlockState The default BlockState
     */
    public BlockStateRandomizer(class_2680 defaultBlockState) {
        this.defaultBlockState = defaultBlockState;
    }

    /**
     * Constructs a new BlockStateRandomizer with no default BlockState nor entries.
     */
    public BlockStateRandomizer() {
    }

    /**
     * Convenience factory function that constructs a BlockStateRandomizer from a list of BlockStates.
     * Each BlockState will have equal probability of being chosen.
     */
    public static BlockStateRandomizer from(class_2680... blockStates) {
        BlockStateRandomizer randomizer = new BlockStateRandomizer();
        float chance = 1f / blockStates.length;

        for (class_2680 state : blockStates) {
            randomizer.addBlock(state, chance);
        }

        return randomizer;
    }

    /**
     * Adds a BlockState with given chance of being selected.
     * @return The modified BlockStateRandomizer
     */
    public BlockStateRandomizer addBlock(class_2680 blockState, float chance) {
        // Abort if BlockState already a part of this randomizer
        if (entries.stream().anyMatch(entry -> entry.blockState.equals(blockState))) {
            YungsApiCommon.LOGGER.warn("WARNING: duplicate block {} added to BlockStateRandomizer!", blockState.toString());
            return this;
        }

        // Attempt to add BlockState to entries
        float currTotal = entries.stream().map(entry -> entry.probability).reduce(Float::sum).orElse(0f);
        float newTotal = currTotal + chance;
        if (newTotal > 1.0F) { // Total probability cannot exceed 1
            YungsApiCommon.LOGGER.warn("WARNING: block {} added to BlockStateRandomizer exceeds max probabiltiy of 1!", blockState.toString());
            return this;
        }
        entries.add(new Entry(blockState, chance));
        return this;
    }

    /**
     * Randomly selects a BlockState from this BlockStateRandomizer.
     * The Random provided should be one used in generation of your structure or feature,
     * to ensure reproducibility for the same world seed.
     */
    public class_2680 get(Random random) {
        float target = random.nextFloat();
        float currBottom = 0;

        for (Entry entry : entries) {
            if (currBottom <= target && target < currBottom + entry.probability) {
                return entry.blockState;
            }

            currBottom += entry.probability;
        }

        // No match found
        return this.defaultBlockState;
    }

    /**
     * Randomly selects a BlockState from this BlockStateRandomizer.
     * The RandomSource provided should be one used in generation of your structure or feature,
     * to ensure reproducibility for the same world seed.
     */
    public class_2680 get(class_5819 randomSource) {
        float target = randomSource.method_43057();
        float currBottom = 0;

        for (Entry entry : entries) {
            if (currBottom <= target && target < currBottom + entry.probability) {
                return entry.blockState;
            }

            currBottom += entry.probability;
        }

        // No match found
        return this.defaultBlockState;
    }

    /**
     * Randomly selects a BlockState from this BlockStateRandomizer.
     * The RandomSource provided should be one used in generation of your structure or feature,
     * to ensure reproducibility for the same world seed.
     * Enforces conditions via the StructureContext passed in.
     */
    public class_2680 get(class_5819 randomSource, StructureContext ctx) {
        float target = randomSource.method_43057();
        float currBottom = 0;

        for (Entry entry : entries) {
            if (currBottom <= target && target < currBottom + entry.probability && entry.passesCondition(ctx)) {
                return entry.blockState;
            }

            currBottom += entry.probability;
        }

        // No match found
        return this.defaultBlockState;
    }

    /**
     * Sets the default BlockState.
     * This default BlockState is used for any leftover probability ranges.
     */
    public void setDefaultBlockState(class_2680 blockState) {
        this.defaultBlockState = blockState;
    }

    /**
     * Returns a Map of BlockStates to their corresponding probabilities.
     * Does not include the defaultBlockState.
     * @return The Map
     */
    public Map getEntriesAsMap() {
        Map map = new HashMap<>();
        this.entries.forEach(entry -> map.put(entry.blockState, entry.probability));
        return map;
    }

    /**
     * Returns a List of BlockStateRandomizer Entries.
     * @return The List
     */
    public List getEntries() {
        return entries;
    }

    /**
     * Returns the default BlockState.
     * @return The default BlockState
     */
    public class_2680 getDefaultBlockState() {
        return defaultBlockState;
    }

    /**
     * Represents a BlockState and its corresponding probability of being chosen.
     */
    public static class Entry {
        public static Codec CODEC = RecordCodecBuilder.create(instance -> instance
                .group(
                        class_2680.field_24734.fieldOf("blockState").forGetter(entry -> entry.blockState),
                        Codec.floatRange(0.0F, 1.0F).fieldOf("probability").forGetter(entry -> entry.probability),
                        StructureConditionType.CONDITION_CODEC.optionalFieldOf("condition").forGetter(entry -> entry.condition))
                .apply(instance, Entry::new));

        public class_2680 blockState;
        public float probability;
        public Optional condition; // Conditions are ONLY supported via JSON

        public Entry(class_2680 blockState, float probability) {
            this.blockState = blockState;
            this.probability = probability;
        }

        public Entry(class_2680 blockState, float probability, Optional condition) {
            this.blockState = blockState;
            this.probability = probability;
            this.condition = condition;
        }

        public boolean passesCondition(StructureContext ctx) {
            return this.condition.isEmpty() || this.condition.get().passes(ctx);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Entry) {
                return this.blockState.equals(((Entry) obj).blockState);
            } else if (obj instanceof class_2680) {
                return this.blockState.equals(obj);
            }
            return false;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy