diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java b/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java index da1ea1c..bbd0270 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java @@ -13,6 +13,7 @@ import net.minecraft.potion.Effects; import net.minecraft.util.ResourceLocation; import net.minecraftforge.registries.ForgeRegistries; +import javax.annotation.Nullable; import java.util.*; /** @@ -280,13 +281,13 @@ public class BestiaryInfo { public Builder vanillaTextureBaseOnly( String tex ) { return vanillaBaseTexture( tex ).noEyesTexture().noOverlayTexture(); } /** Sets the species default base texture. */ - private Builder vanillaBaseTexture( String tex ) { return baseTexture( tex == null ? null : new ResourceLocation( tex ) ); } + private Builder vanillaBaseTexture( String tex ) { return baseTexture( new ResourceLocation( tex ) ); } /** Sets the species default glowing eyes texture. */ - private Builder vanillaEyesTexture( String eyeTex ) { return eyesTexture( eyeTex == null ? null : new ResourceLocation( eyeTex ) ); } + private Builder vanillaEyesTexture( String eyeTex ) { return eyesTexture( new ResourceLocation( eyeTex ) ); } /** Sets the species default overlay texture. */ - private Builder vanillaOverlayTexture( String ovrTex ) { return overlayTexture( ovrTex == null ? null : new ResourceLocation( ovrTex ) ); } + private Builder vanillaOverlayTexture( String ovrTex ) { return overlayTexture( new ResourceLocation( ovrTex ) ); } //--------------- Textures (Auto-selected) ---------------- @@ -351,19 +352,19 @@ public class BestiaryInfo { public Builder noAnimationTexture() { return noOverlayTexture(); } /** Sets the species default base texture. */ - private Builder baseTexture( ResourceLocation tex ) { + private Builder baseTexture( @Nullable ResourceLocation tex ) { texture = tex; return this; } /** Sets the species default glowing eyes texture. */ - private Builder eyesTexture( ResourceLocation eyeTex ) { + private Builder eyesTexture( @Nullable ResourceLocation eyeTex ) { eyesTexture = eyeTex; return this; } /** Sets the species default overlay texture. */ - private Builder overlayTexture( ResourceLocation ovrTex ) { + private Builder overlayTexture( @Nullable ResourceLocation ovrTex ) { overlayTexture = ovrTex; return this; } diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java index c19b792..affbf32 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java @@ -6,7 +6,6 @@ import fathertoast.specialmobs.common.core.register.SMEntities; import fathertoast.specialmobs.common.core.register.SMItems; import fathertoast.specialmobs.common.util.AnnotationHelper; import fathertoast.specialmobs.common.util.References; -import mcp.MethodsReturnNonnullByDefault; import net.minecraft.block.Block; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; @@ -18,7 +17,6 @@ import net.minecraftforge.common.ForgeSpawnEggItem; import net.minecraftforge.fml.RegistryObject; import javax.annotation.Nullable; -import javax.annotation.ParametersAreNonnullByDefault; import java.util.*; import java.util.function.Function; @@ -28,8 +26,6 @@ import java.util.function.Function; * * @see MobFamily.Species */ -@ParametersAreNonnullByDefault -@MethodsReturnNonnullByDefault public class MobFamily { /** List of all families, generated to make iteration possible. */ private static final ArrayList> FAMILY_LIST = new ArrayList<>(); @@ -238,8 +234,6 @@ public class MobFamily { * * @see MobFamily */ - @ParametersAreNonnullByDefault - @MethodsReturnNonnullByDefault public static class Species { /** The special mob family this species belongs to. */ public final MobFamily family; diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/package-info.java b/src/main/java/fathertoast/specialmobs/common/bestiary/package-info.java new file mode 100644 index 0000000..52dc0a1 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package fathertoast.specialmobs.common.bestiary; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/family/package-info.java b/src/main/java/fathertoast/specialmobs/common/config/family/package-info.java new file mode 100644 index 0000000..5ff563b --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/family/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package fathertoast.specialmobs.common.config.family; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/AbstractConfigField.java b/src/main/java/fathertoast/specialmobs/common/config/field/AbstractConfigField.java index a072b2d..7120589 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/AbstractConfigField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/AbstractConfigField.java @@ -25,7 +25,7 @@ public abstract class AbstractConfigField { * Creates a new field with the supplied key and description. * If the description is null, it will cancel the entire comment, including the automatic field info text. */ - protected AbstractConfigField( String key, String... description ) { + protected AbstractConfigField( String key, @Nullable String... description ) { this( loadingCategory + key, description == null ? null : TomlHelper.newComment( description ) ); } @@ -33,7 +33,7 @@ public abstract class AbstractConfigField { * Creates a new field with the supplied key and comment. This method is only used for very special circumstances. * If the comment is null, it will cancel the entire comment, including the automatic field info text. */ - AbstractConfigField( String key, List comment ) { + AbstractConfigField( String key, @Nullable List comment ) { KEY = key; COMMENT = comment; } diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/AttributeListField.java b/src/main/java/fathertoast/specialmobs/common/config/field/AttributeListField.java index 49a0df4..4a7efe4 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/AttributeListField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/AttributeListField.java @@ -42,16 +42,10 @@ public class AttributeListField extends GenericField { public ConfigDrivenAttributeModifierMap linkedAttributeMap; /** Creates a new field. */ - public AttributeListField( String key, AttributeList defaultValue, String... description ) { + public AttributeListField( String key, AttributeList defaultValue, @Nullable String... description ) { super( key, defaultValue, description ); } - /** Applies all attribute changes in this list to the entity attribute builder. */ - public void apply( AttributeModifierMap.MutableAttribute builder ) { get().apply( builder ); } - - /** Applies all attribute changes in this list to the entity. */ - public void apply( LivingEntity entity ) { get().apply( entity ); } - /** Adds info about the field type, format, and bounds to the end of a field's description. */ public void appendFieldInfo( List comment ) { comment.add( TomlHelper.fieldInfoFormat( "Attribute List", valueDefault, @@ -81,7 +75,7 @@ public class AttributeListField extends GenericField { if( linkedAttributeMap != null ) linkedAttributeMap.invalidate(); } - /** Parses a single entry line and returns a valid result if possible, or null if the entry is completely invalid. */ + /** Parses a single entry line and returns a valid result. */ private AttributeEntry parseEntry( final String line ) { // Parse the attribute-operation-value array final String[] args = line.split( " ", 4 ); @@ -148,4 +142,13 @@ public class AttributeListField extends GenericField { } return value; } + + + // Convenience methods + + /** Applies all attribute changes in this list to the entity attribute builder. */ + public void apply( AttributeModifierMap.MutableAttribute builder ) { get().apply( builder ); } + + /** Applies all attribute changes in this list to the entity. */ + public void apply( LivingEntity entity ) { get().apply( entity ); } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/BlockListField.java b/src/main/java/fathertoast/specialmobs/common/config/field/BlockListField.java index fc0ae85..04f0712 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/BlockListField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/BlockListField.java @@ -28,7 +28,7 @@ public class BlockListField extends GenericField { } /** Creates a new field. */ - public BlockListField( String key, BlockList defaultValue, String... description ) { + public BlockListField( String key, BlockList defaultValue, @Nullable String... description ) { super( key, defaultValue, description ); } @@ -53,6 +53,15 @@ public class BlockListField extends GenericField { value = new BlockList( this, TomlHelper.parseStringList( raw ) ); } + + // Convenience methods + + /** @return Returns true if there are no entries in this block list. */ + public boolean isEmpty() { return get().isEmpty(); } + + /** @return Returns true if the block is contained in this list. */ + public boolean matches( BlockState blockState ) { return get().matches( blockState ); } + /** * Represents two block list fields, a blacklist and a whitelist, combined into one. */ @@ -73,7 +82,7 @@ public class BlockListField extends GenericField { /** @return Returns true if the block is contained in this list. */ public boolean matches( BlockState blockState ) { - return blockState != null && !BLACKLIST.get().matches( blockState ) && WHITELIST.get().matches( blockState ); + return !BLACKLIST.get().matches( blockState ) && WHITELIST.get().matches( blockState ); } } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/BooleanField.java b/src/main/java/fathertoast/specialmobs/common/config/field/BooleanField.java index 6f95a0e..8266444 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/BooleanField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/BooleanField.java @@ -18,7 +18,7 @@ public class BooleanField extends AbstractConfigField { private boolean value; /** Creates a new field. */ - public BooleanField( String key, boolean defaultValue, String... description ) { + public BooleanField( String key, boolean defaultValue, @Nullable String... description ) { super( key, description ); valueDefault = defaultValue; } diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/DoubleField.java b/src/main/java/fathertoast/specialmobs/common/config/field/DoubleField.java index ccfb252..a3ecccd 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/DoubleField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/DoubleField.java @@ -23,12 +23,12 @@ public class DoubleField extends AbstractConfigField { private double value; /** Creates a new field that accepts a common range of values. */ - public DoubleField( String key, double defaultValue, Range range, String... description ) { + public DoubleField( String key, double defaultValue, Range range, @Nullable String... description ) { this( key, defaultValue, range.MIN, range.MAX, description ); } /** Creates a new field that accepts a specialized range of values. */ - public DoubleField( String key, double defaultValue, double min, double max, String... description ) { + public DoubleField( String key, double defaultValue, double min, double max, @Nullable String... description ) { super( key, description ); valueDefault = defaultValue; valueMin = min; diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/EntityListField.java b/src/main/java/fathertoast/specialmobs/common/config/field/EntityListField.java index c50b4cb..ffc7a9c 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/EntityListField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/EntityListField.java @@ -33,7 +33,7 @@ public class EntityListField extends GenericField { } /** Creates a new field. */ - public EntityListField( String key, EntityList defaultValue, String... description ) { + public EntityListField( String key, EntityList defaultValue, @Nullable String... description ) { super( key, defaultValue, description ); } @@ -191,6 +191,36 @@ public class EntityListField extends GenericField { return value; } + + // Convenience methods + + /** @return True if the entity is contained in this list. */ + public boolean contains( @Nullable Entity entity ) { return get().contains( entity ); } + + /** + * @param entity The entity to retrieve values for. + * @return The array of values of the best-match entry. Returns null if the entity is not contained in this entity list. + */ + @Nullable + public double[] getValues( @Nullable Entity entity ) { return get().getValues( entity ); } + + /** + * @param entity The entity to retrieve a value for. + * @return The first value in the best-match entry's value array. Returns 0 if the entity is not contained in this + * entity list or has no values specified. This should only be used for 'single value' lists. + * @see EntityList#setSingleValue() + * @see EntityList#setSinglePercent() + */ + public double getValue( @Nullable Entity entity ) { return get().getValue( entity ); } + + /** + * @param entity The entity to roll a value for. + * @return Randomly rolls the first percentage value in the best-match entry's value array. Returns false if the entity + * is not contained in this entity list or has no values specified. This should only be used for 'single percent' lists. + * @see EntityList#setSinglePercent() + */ + public boolean rollChance( @Nullable LivingEntity entity ) { return get().rollChance( entity ); } + /** * Represents two entity list fields, a blacklist and a whitelist, combined into one. * The blacklist cannot contain values, but the whitelist can have any settings. @@ -210,17 +240,21 @@ public class EntityListField extends GenericField { } } + + // Convenience methods + /** @return True if the entity is contained in this list. */ - public boolean contains( Entity entity ) { - return entity != null && !BLACKLIST.get().contains( entity ) && WHITELIST.get().contains( entity ); + public boolean contains( @Nullable Entity entity ) { + return entity != null && !BLACKLIST.contains( entity ) && WHITELIST.contains( entity ); } /** * @param entity The entity to retrieve values for. * @return The array of values of the best-match entry. Returns null if the entity is not contained in this entity list. */ - public double[] getValues( Entity entity ) { - return entity != null && !BLACKLIST.get().contains( entity ) ? WHITELIST.get().getValues( entity ) : null; + @Nullable + public double[] getValues( @Nullable Entity entity ) { + return entity != null && !BLACKLIST.contains( entity ) ? WHITELIST.getValues( entity ) : null; } /** @@ -230,8 +264,8 @@ public class EntityListField extends GenericField { * @see EntityList#setSingleValue() * @see EntityList#setSinglePercent() */ - public double getValue( Entity entity ) { - return entity != null && !BLACKLIST.get().contains( entity ) ? WHITELIST.get().getValue( entity ) : 0.0; + public double getValue( @Nullable Entity entity ) { + return entity != null && !BLACKLIST.contains( entity ) ? WHITELIST.getValue( entity ) : 0.0; } /** @@ -240,8 +274,8 @@ public class EntityListField extends GenericField { * is not contained in this entity list or has no values specified. This should only be used for 'single percent' lists. * @see EntityList#setSinglePercent() */ - public boolean rollChance( LivingEntity entity ) { - return entity != null && !BLACKLIST.get().contains( entity ) && WHITELIST.get().rollChance( entity ); + public boolean rollChance( @Nullable LivingEntity entity ) { + return entity != null && !BLACKLIST.contains( entity ) && WHITELIST.rollChance( entity ); } } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/EnumField.java b/src/main/java/fathertoast/specialmobs/common/config/field/EnumField.java index ebfa4e2..da4b41c 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/EnumField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/EnumField.java @@ -14,13 +14,13 @@ public class EnumField> extends GenericField { private final T[] valuesValid; /** Creates a new field that accepts any enum value. */ - public EnumField( String key, T defaultValue, String... description ) { + public EnumField( String key, T defaultValue, @Nullable String... description ) { //noinspection unchecked this( key, defaultValue, (T[]) defaultValue.getClass().getEnumConstants(), description ); } /** Creates a new field that accepts the specified set of enum values. */ - public EnumField( String key, T defaultValue, T[] validValues, String... description ) { + public EnumField( String key, T defaultValue, T[] validValues, @Nullable String... description ) { super( key, defaultValue, description ); valuesValid = validValues; } @@ -55,6 +55,7 @@ public class EnumField> extends GenericField { } /** @return Attempts to parse the string literal as one of the valid values for this field and returns it, or null if invalid. */ + @Nullable private T parseValue( String name ) { for( T val : valuesValid ) { if( val.name().equalsIgnoreCase( name ) ) return val; diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/EnvironmentListField.java b/src/main/java/fathertoast/specialmobs/common/config/field/EnvironmentListField.java new file mode 100644 index 0000000..4d4bde8 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/field/EnvironmentListField.java @@ -0,0 +1,291 @@ +package fathertoast.specialmobs.common.config.field; + +import fathertoast.specialmobs.common.config.file.TomlHelper; +import fathertoast.specialmobs.common.config.util.EnvironmentEntry; +import fathertoast.specialmobs.common.config.util.EnvironmentList; +import fathertoast.specialmobs.common.config.util.environment.*; +import fathertoast.specialmobs.common.config.util.environment.biome.*; +import fathertoast.specialmobs.common.config.util.environment.dimension.DimensionPropertyEnvironment; +import fathertoast.specialmobs.common.config.util.environment.dimension.DimensionTypeEnvironment; +import fathertoast.specialmobs.common.config.util.environment.dimension.DimensionTypeGroupEnvironment; +import fathertoast.specialmobs.common.config.util.environment.position.StructureEnvironment; +import fathertoast.specialmobs.common.config.util.environment.position.StructureGroupEnvironment; +import fathertoast.specialmobs.common.config.util.environment.position.YEnvironment; +import fathertoast.specialmobs.common.config.util.environment.position.YFromSeaEnvironment; +import fathertoast.specialmobs.common.config.util.environment.time.*; +import fathertoast.specialmobs.common.core.SpecialMobs; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a config field with an environment list value. + */ +public class EnvironmentListField extends GenericField { + + /* When adding new environment conditions, you must: + * - Add a new constant for its name here + * - Create the environment class and ensure #name() returns the new name constant + * - Link the name to construction in #parseCondition(String,String) below + * - Add any applicable builder methods in EnvironmentEntry.Builder + * - Describe the new environment condition's usage/format in #environmentDescriptions() below + */ + // Dimension-based + public static final String ENV_DIMENSION_PROPERTY = "dimension_property"; + public static final String ENV_DIMENSION_TYPE = "dimension_type"; + // Biome-based + public static final String ENV_RAINFALL = "rainfall"; + public static final String ENV_BIOME_TEMPERATURE = "biome_temp"; + public static final String ENV_TEMPERATURE = "temp"; + public static final String ENV_BIOME_CATEGORY = "biome_category"; + public static final String ENV_BIOME = "biome"; + // Position-based TODO can_see_sky + public static final String ENV_STRUCTURE = "structure"; + public static final String ENV_Y = "y"; + public static final String ENV_Y_FROM_SEA = "y_from_sea"; + // Time-based + public static final String ENV_DIFFICULTY = "difficulty"; + public static final String ENV_SPECIAL_DIFFICULTY = "special_difficulty"; + public static final String ENV_WEATHER = "weather"; + public static final String ENV_MOON_BRIGHTNESS = "moon_brightness"; + public static final String ENV_MOON_PHASE = "moon_phase"; + public static final String ENV_DAY_TIME = "day_time"; + public static final String ENV_TIME_FROM_MIDNIGHT = "time_from_midnight"; + public static final String ENV_WORLD_TIME = "world_time"; + + /** + * Provides a description of how to use environment lists. Recommended to put at the top of any file using environment lists. + * Always use put the environment condition descriptions at the bottom of the file if this is used! + */ + public static List verboseDescription() { + List comment = new ArrayList<>(); + comment.add( "Environment List fields: General format = [ \"value environment1 condition1 & environment2 condition2 & ...\", ... ]" ); + comment.add( " Environment lists are arrays of environment entries. Each entry is a value followed by the environment conditions that must be" ); + comment.add( " satisfied for the value to be chosen. The environments are tested in the order listed, and the first matching entry is chosen." ); + comment.add( " See the bottom of this file for an explanation on each environment condition available." ); + return comment; + } + + /** Provides a detailed description of how to use each environment condition. Recommended to put at the bottom of any file using environment lists. */ + public static List environmentDescriptions() { + List comment = new ArrayList<>(); + comment.add( "Environment conditions (for Environment List entries):" ); + comment.add( " Many environment conditions can be inverted by using \"!\"; these are shown with (!) in the appropriate location." ); + comment.add( " Other environment conditions are numerical comparisons; these use the operators (shown as op) <, >, =, <=, >=, or != to compare value." ); + comment.add( "Valid environment conditions are:" ); + // Dimension-based + comment.add( " \"" + ENV_DIMENSION_PROPERTY + " (!)property\":" ); + comment.add( " Valid property values: " + TomlHelper.literalList( (Object[]) DimensionPropertyEnvironment.Value.values() ) ); + comment.add( " Dimension properties are the true/false values available to dimension types in data packs." ); + comment.add( " See the wiki for more info: [https://minecraft.fandom.com/wiki/Custom_dimension#Syntax]." ); + comment.add( " \"" + ENV_DIMENSION_TYPE + " (!)namespace:dimension_type_name\":" ); + comment.add( " The world's dimension type. In vanilla, these are only \"minecraft:overworld\", \"minecraft:the_nether\", or \"minecraft:the_end\"." ); + // Biome-based + comment.add( " \"" + ENV_RAINFALL + " op value\":" ); + comment.add( " Biome's rainfall parameter. If this is \"= 0\", it checks that rain is disabled. For reference, rainfall > 0.85 suppresses fire." ); + comment.add( " \"" + ENV_BIOME_TEMPERATURE + " op value\" or \"" + ENV_BIOME_TEMPERATURE + " (!)" + TemperatureEnvironment.FREEZING + "\":" ); + comment.add( " Biome's temperature parameter. For reference, freezing is < 0.15 and hot is generally considered > 0.95." ); + comment.add( " \"" + ENV_TEMPERATURE + " op value\" or \"" + ENV_TEMPERATURE + " (!)" + TemperatureEnvironment.FREEZING + "\":" ); + comment.add( " Height-adjusted temperature. For reference, freezing is < 0.15 and hot is generally considered > 0.95." ); + comment.add( " \"" + ENV_BIOME_CATEGORY + " (!)category\":" ); + comment.add( " Valid category values: " + TomlHelper.literalList( (Object[]) BiomeCategoryEnvironment.Value.values() ) ); + comment.add( " \"" + ENV_BIOME + " (!)namespace:biome_name\":" ); + comment.add( " The biome. See the wiki for vanilla biome names (resource locations) [https://minecraft.fandom.com/wiki/Biome#Biome_IDs]." ); + // Position-based + comment.add( " \"" + ENV_STRUCTURE + " (!)namespace:structure_name\":" ); + comment.add( " The structure. See the wiki for vanilla structure names [https://minecraft.fandom.com/wiki/Generated_structures#Locating]." ); + comment.add( " \"" + ENV_Y + " op value\":" ); + comment.add( " The y-value. For reference, sea level is normally 63 and lava level is normally 10." );//TODO change lava level to -54 for MC 1.18 + comment.add( " \"" + ENV_Y_FROM_SEA + " op value\":" ); + comment.add( " The y-value from sea level. Expect the only air <= 0 to be in caves (which may still have direct view of the sky)." ); + // Time-based + comment.add( " \"" + ENV_DIFFICULTY + " op value\":" ); + comment.add( " The regional difficulty (0 to 6.75). This is based on many factors such as difficulty setting, moon brightness, chunk inhabited time, and world time." ); + comment.add( " For reference, this scales up to the max after 63 days in the world and 150 days in a particular chunk, and peaks during full moons." ); + comment.add( " On Peaceful this is always 0, on Easy this is 0.75 to 1.5, on Normal this is 1.5 to 4.0, and on Hard this is 2.25 to 6.75." ); + comment.add( " \"" + ENV_SPECIAL_DIFFICULTY + " op value\":" ); + comment.add( " The 'special multiplier' for regional difficulty (0 to 1). For reference, this is 0 when difficulty <= 2 and 1 when difficulty >= 4." ); + comment.add( " This is always 0 in Easy and below. In Normal, it maxes at absolute peak regional difficulty. In Hard, it starts at 0.125 and maxes out in ~50 days." ); + comment.add( " \"" + ENV_WEATHER + " (!)type\":" ); + comment.add( " Valid type values: " + TomlHelper.literalList( (Object[]) WeatherEnvironment.Value.values() ) ); + comment.add( " \"" + ENV_MOON_BRIGHTNESS + " op value\":" ); + comment.add( " The moon brightness (0 to 1). New moon has 0 brightness, full moon has 1 brightness. Intermediate phases are 0.25, 0.5, or 0.75." ); + comment.add( " \"" + ENV_MOON_PHASE + " (!)phase\":" ); + comment.add( " Valid phase values: " + TomlHelper.literalList( (Object[]) MoonPhaseEnvironment.Value.values() ) ); + comment.add( " \"" + ENV_DAY_TIME + " (!)time\":" ); + comment.add( " Valid time values: " + TomlHelper.literalList( (Object[]) DayTimeEnvironment.Value.values() ) ); + comment.add( " Note that the transition periods, sunset & sunrise, are considered as part of day & night, respectively." ); + comment.add( " \"" + ENV_TIME_FROM_MIDNIGHT + " op value\":" ); + comment.add( " The absolute time in ticks away from midnight. Value must be 0 to 12000." ); + comment.add( " \"" + ENV_MOON_PHASE + " (!)phase\":" ); + comment.add( " Valid phase values: " + TomlHelper.literalList( (Object[]) MoonPhaseEnvironment.Value.values() ) ); + comment.add( " \"" + ENV_WORLD_TIME + " op value\":" ); + comment.add( " The total time the world has existed, in ticks. For reference, each day cycle is 24000 ticks and each lunar cycle is 192000 ticks." ); + return comment; + } + + /** Creates a new field. */ + public EnvironmentListField( String key, EnvironmentList defaultValue, String... description ) { + super( key, defaultValue, description ); + } + + /** Adds info about the field type, format, and bounds to the end of a field's description. */ + public void appendFieldInfo( List comment ) { + comment.add( TomlHelper.fieldInfoFormat( "Environment List", valueDefault, "[ \"value condition1 state1 & condition2 state2 & ...\", ... ]" ) ); + comment.add( " Range for Values: " + TomlHelper.fieldRange( valueDefault.getMinValue(), valueDefault.getMaxValue() ) ); + } + + /** + * Loads this field's value from the given raw toml value. If anything goes wrong, correct it at the lowest level possible. + *

+ * For example, a missing value should be set to the default, while an out-of-range value should be adjusted to the + * nearest in-range value + */ + @Override + public void load( @Nullable Object raw ) { + if( raw == null ) { + value = valueDefault; + return; + } + List list = TomlHelper.parseStringList( raw ); + List entryList = new ArrayList<>(); + for( String line : list ) { + entryList.add( parseEntry( line ) ); + } + value = new EnvironmentList( entryList ); + } + + /** Parses a single entry line and returns the result. */ + private EnvironmentEntry parseEntry( final String line ) { + // Parse the value out of the conditions + final String[] args = line.split( " ", 2 ); + final double value = parseValue( args[0], line ); + + final List conditions = new ArrayList<>(); + if( args.length > 1 ) { + final String[] condArgs = args[1].trim().split( "&" ); + for( String condArg : condArgs ) { + conditions.add( parseCondition( condArg.trim(), line ) ); + } + } + if( conditions.isEmpty() ) { + SpecialMobs.LOG.warn( "No environments defined in entry for {} \"{}\"! Invalid entry: {}", + getClass(), getKey(), line ); + } + + return new EnvironmentEntry( value, conditions ); + } + + /** Parses a single value argument and returns a valid result. */ + private double parseValue( final String arg, final String line ) { + // Try to parse the value + double value; + try { + value = Double.parseDouble( arg ); + } + catch( NumberFormatException ex ) { + // This is thrown if the string is not a parsable number + SpecialMobs.LOG.warn( "Invalid value for {} \"{}\"! Falling back to 0. Invalid entry: {}", + getClass(), getKey(), line ); + value = 0.0; + } + // Verify value is within range + if( value < valueDefault.getMinValue() ) { + SpecialMobs.LOG.warn( "Value for {} \"{}\" is below the minimum ({})! Clamping value. Invalid value: {}", + getClass(), getKey(), valueDefault.getMinValue(), value ); + value = valueDefault.getMinValue(); + } + else if( value > valueDefault.getMaxValue() ) { + SpecialMobs.LOG.warn( "Value for {} \"{}\" is above the maximum ({})! Clamping value. Invalid value: {}", + getClass(), getKey(), valueDefault.getMaxValue(), value ); + value = valueDefault.getMaxValue(); + } + return value; + } + + /** Parses a single environment condition argument and returns a valid result. */ + private AbstractEnvironment parseCondition( final String arg, final String line ) { + // First parse the environment name, since it defines the format for the rest + final String[] args = arg.split( " ", 2 ); + + final String value; + if( args.length < 2 ) value = ""; + else value = args[1].trim(); + + switch( args[0] ) { + // Dimension-based + case ENV_DIMENSION_PROPERTY: + return new DimensionPropertyEnvironment( this, value ); + case ENV_DIMENSION_TYPE: + return value.endsWith( "*" ) ? new DimensionTypeGroupEnvironment( this, value ) : new DimensionTypeEnvironment( this, value ); + // Biome-based + case ENV_RAINFALL: + return new RainfallEnvironment( this, value ); + case ENV_BIOME_TEMPERATURE: + return new BiomeTemperatureEnvironment( this, value ); + case ENV_TEMPERATURE: + return new TemperatureEnvironment( this, value ); + case ENV_BIOME_CATEGORY: + return new BiomeCategoryEnvironment( this, value ); + case ENV_BIOME: + return value.endsWith( "*" ) ? new BiomeGroupEnvironment( this, value ) : new BiomeEnvironment( this, value ); + // Position-based + case ENV_STRUCTURE: + return value.endsWith( "*" ) ? new StructureGroupEnvironment( this, value ) : new StructureEnvironment( this, value ); + case ENV_Y: + return new YEnvironment( this, value ); + case ENV_Y_FROM_SEA: + return new YFromSeaEnvironment( this, value ); + // Time-based + case ENV_DIFFICULTY: + return new DifficultyEnvironment( this, value ); + case ENV_SPECIAL_DIFFICULTY: + return new SpecialDifficultyEnvironment( this, value ); + case ENV_WEATHER: + return new WeatherEnvironment( this, value ); + case ENV_MOON_BRIGHTNESS: + return new MoonBrightnessEnvironment( this, value ); + case ENV_MOON_PHASE: + return new MoonPhaseEnvironment( this, value ); + case ENV_DAY_TIME: + return new DayTimeEnvironment( this, value ); + case ENV_TIME_FROM_MIDNIGHT: + return new TimeFromMidnightEnvironment( this, value ); + case ENV_WORLD_TIME: + return new WorldTimeEnvironment( this, value ); + } + + // The environment name was not recognized; try to provide some good feedback because this field is complicated + final String[] environmentNames = { + // Dimension-based + ENV_DIMENSION_PROPERTY, ENV_DIMENSION_TYPE, + // Biome-based + ENV_RAINFALL, ENV_BIOME_TEMPERATURE, ENV_TEMPERATURE, ENV_BIOME_CATEGORY, ENV_BIOME, + // Position-based + ENV_STRUCTURE, ENV_Y, ENV_Y_FROM_SEA, + // Time-based + ENV_DIFFICULTY, ENV_SPECIAL_DIFFICULTY, ENV_WEATHER, ENV_MOON_BRIGHTNESS, ENV_MOON_PHASE, ENV_DAY_TIME, + ENV_TIME_FROM_MIDNIGHT, ENV_WORLD_TIME + }; + final AbstractEnvironment fallback = new YEnvironment( ComparisonOperator.LESS_THAN, 0 ); + SpecialMobs.LOG.warn( "Invalid environment '{}' for {} \"{}\"! Falling back to \"{}\". Environment name must be in the set [ {} ]. Invalid environment: {}", + args[0], getClass(), getKey(), fallback, TomlHelper.literalList( (Object[]) environmentNames ), line ); + return fallback; + } + + + // Convenience methods + + /** @return The value matching the given environment, or the default value if no matching environment is defined. */ + public double getOrElse( World world, @Nullable BlockPos pos, DoubleField defaultValue ) { return get().getOrElse( world, pos, defaultValue ); } + + /** @return The value matching the given environment, or the default value if no matching environment is defined. */ + public double getOrElse( World world, @Nullable BlockPos pos, double defaultValue ) { return get().getOrElse( world, pos, defaultValue ); } + + /** @return The value matching the given environment, or null if no matching environment is defined. */ + @Nullable + public Double get( World world, @Nullable BlockPos pos ) { return get().get( world, pos ); } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/GenericField.java b/src/main/java/fathertoast/specialmobs/common/config/field/GenericField.java index bc4305e..d9e4094 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/GenericField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/GenericField.java @@ -1,5 +1,7 @@ package fathertoast.specialmobs.common.config.field; +import javax.annotation.Nullable; + /** * Represents a config field with an object value. *

@@ -16,7 +18,7 @@ public abstract class GenericField extends AbstractConfigField { protected T value; /** Creates a new field. */ - public GenericField( String key, T defaultValue, String... description ) { + public GenericField( String key, T defaultValue, @Nullable String... description ) { super( key, description ); valueDefault = defaultValue; } diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/IntField.java b/src/main/java/fathertoast/specialmobs/common/config/field/IntField.java index 8a1bb78..260f0e3 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/IntField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/IntField.java @@ -23,12 +23,12 @@ public class IntField extends AbstractConfigField { private int value; /** Creates a new field that accepts a common range of values. */ - public IntField( String key, int defaultValue, Range range, String... description ) { + public IntField( String key, int defaultValue, Range range, @Nullable String... description ) { this( key, defaultValue, range.MIN, range.MAX, description ); } /** Creates a new field that accepts a specialized range of values. */ - public IntField( String key, int defaultValue, int min, int max, String... description ) { + public IntField( String key, int defaultValue, int min, int max, @Nullable String... description ) { super( key, description ); valueDefault = defaultValue; valueMin = min; diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/LazyRegistryEntryListField.java b/src/main/java/fathertoast/specialmobs/common/config/field/LazyRegistryEntryListField.java index 0a1c882..24f5b58 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/LazyRegistryEntryListField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/LazyRegistryEntryListField.java @@ -17,7 +17,7 @@ import javax.annotation.Nullable; public class LazyRegistryEntryListField> extends RegistryEntryListField { /** Creates a new field. */ - public LazyRegistryEntryListField( String key, RegistryEntryList defaultValue, String... description ) { + public LazyRegistryEntryListField( String key, RegistryEntryList defaultValue, @Nullable String... description ) { super( key, defaultValue, description ); } diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/RegistryEntryListField.java b/src/main/java/fathertoast/specialmobs/common/config/field/RegistryEntryListField.java index 5d61176..903658d 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/RegistryEntryListField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/RegistryEntryListField.java @@ -31,7 +31,7 @@ public class RegistryEntryListField> extends Ge } /** Creates a new field. */ - public RegistryEntryListField( String key, RegistryEntryList defaultValue, String... description ) { + public RegistryEntryListField( String key, RegistryEntryList defaultValue, @Nullable String... description ) { super( key, defaultValue, description ); } @@ -57,15 +57,18 @@ public class RegistryEntryListField> extends Ge value = new RegistryEntryList<>( this, valueDefault.getRegistry(), TomlHelper.parseStringList( raw ) ); } + + // Convenience methods + /** @return The registry this list draws from. */ - public IForgeRegistry getRegistry() { return value.getRegistry(); } + public IForgeRegistry getRegistry() { return get().getRegistry(); } /** @return The entries in this list. */ - public Set getEntries() { return value.getEntries(); } + public Set getEntries() { return get().getEntries(); } /** @return Returns true if there are no entries in this list. */ - public boolean isEmpty() { return value.isEmpty(); } + public boolean isEmpty() { return get().isEmpty(); } /** @return Returns true if the entry is contained in this list. */ - public boolean contains( T entry ) { return value.contains( entry ); } + public boolean contains( @Nullable T entry ) { return get().contains( entry ); } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/ScaledDoubleField.java b/src/main/java/fathertoast/specialmobs/common/config/field/ScaledDoubleField.java index a2c98a1..8a2085c 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/ScaledDoubleField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/ScaledDoubleField.java @@ -14,13 +14,13 @@ public class ScaledDoubleField extends DoubleField { private double valueScaled; /** Creates a new field that accepts a common range of values. */ - public ScaledDoubleField( String key, double defaultValue, double scale, Range range, String... description ) { + public ScaledDoubleField( String key, double defaultValue, double scale, Range range, @Nullable String... description ) { super( key, defaultValue, range, description ); SCALE = scale; } /** Creates a new field that accepts a specialized range of values. */ - public ScaledDoubleField( String key, double defaultValue, double scale, double min, double max, String... description ) { + public ScaledDoubleField( String key, double defaultValue, double scale, double min, double max, @Nullable String... description ) { super( key, defaultValue, min, max, description ); SCALE = scale; } diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/SqrDoubleField.java b/src/main/java/fathertoast/specialmobs/common/config/field/SqrDoubleField.java index 627a54d..7798f41 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/SqrDoubleField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/SqrDoubleField.java @@ -11,12 +11,12 @@ public class SqrDoubleField extends DoubleField { private double valueSqr; /** Creates a new field that accepts a common range of values. */ - public SqrDoubleField( String key, double defaultValue, Range range, String... description ) { + public SqrDoubleField( String key, double defaultValue, Range range, @Nullable String... description ) { super( key, defaultValue, range, description ); } /** Creates a new field that accepts a specialized range of values. */ - public SqrDoubleField( String key, double defaultValue, double min, double max, String... description ) { + public SqrDoubleField( String key, double defaultValue, double min, double max, @Nullable String... description ) { super( key, defaultValue, min, max, description ); } diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/package-info.java b/src/main/java/fathertoast/specialmobs/common/config/field/package-info.java new file mode 100644 index 0000000..29826ca --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/field/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package fathertoast.specialmobs.common.config.field; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/file/TomlHelper.java b/src/main/java/fathertoast/specialmobs/common/config/file/TomlHelper.java index c4017e8..53da30d 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/file/TomlHelper.java +++ b/src/main/java/fathertoast/specialmobs/common/config/file/TomlHelper.java @@ -6,10 +6,12 @@ import fathertoast.specialmobs.common.config.field.DoubleField; import fathertoast.specialmobs.common.config.field.IntField; import fathertoast.specialmobs.common.config.util.ConfigUtil; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +@SuppressWarnings( "unused" ) public final class TomlHelper { private TomlHelper() { } // This is a static access only class that cannot be instantiated @@ -32,7 +34,7 @@ public final class TomlHelper { } /** Attempts to convert a string value to a raw non-string toml literal. May or may not be accurate. */ - public static Object parseRaw( String value ) { + public static Object parseRaw( @Nullable String value ) { // Note: It is very important here that the returned value is NOT a string if( value != null && !"".equals( value ) ) { @@ -56,9 +58,9 @@ public final class TomlHelper { } /** Attempts to convert an object to a toml literal. May or may not be accurate. */ - public static String toLiteral( Object value ) { + public static String toLiteral( @Nullable Object value ) { if( value == null ) { - return "null"; + return ""; } else if( value instanceof Enum ) { return "\"" + ((Enum) value).name().toLowerCase() + "\""; @@ -66,21 +68,22 @@ public final class TomlHelper { else if( value instanceof String ) { return "\"" + value + "\""; } - else if( value instanceof Double && ((Double) value).isInfinite() ) { - // Toml infinite literals do not match java; these may also be unsupported in the current version - return (Double) value > 0.0 ? "Inf" : "-Inf"; - } + // Infinite values not supported + //else if( value instanceof Double && ((Double) value).isInfinite() ) { + // // Toml infinite literals do not match java + // return (Double) value > 0.0 ? "Inf" : "-Inf"; + //} else { return value.toString(); } } /** Attempts to convert an object array to a toml literal. May or may not be accurate. */ - public static String toLiteral( Object... values ) { + public static String toLiteral( @Nullable Object... values ) { if( values == null ) { - return "null"; + return ""; } - else if( values.length < 1 ) { + if( values.length < 1 ) { return "[]"; } else { @@ -92,7 +95,7 @@ public final class TomlHelper { public static String literalList( Object... list ) { return literalList( Arrays.asList( list ) ); } /** Attempts to convert an object list to a list of toml literals. May or may not be accurate. */ - public static String literalList( List list ) { + public static String literalList( @Nullable List list ) { if( list == null || list.isEmpty() ) return ""; StringBuilder literals = new StringBuilder(); for( Object obj : list ) { @@ -196,7 +199,7 @@ public final class TomlHelper { public static List splitKey( String key ) { return StringUtils.split( key, '.' ); } /** Combines a toml path into a key. */ - public static String mergePath( List path ) { + public static String mergePath( @Nullable List path ) { if( path == null || path.isEmpty() ) return ""; StringBuilder key = new StringBuilder(); for( String subKey : path ) { @@ -207,15 +210,13 @@ public final class TomlHelper { } /** Convenience method for creating a list of single-line comments (no \n or \r). */ - public static ArrayList newComment( String... lines ) { - return new ArrayList<>( Arrays.asList( lines ) ); - } + public static ArrayList newComment( String... lines ) { return new ArrayList<>( Arrays.asList( lines ) ); } /** Combines an array of objects as a comma-separated string. */ public static String combineList( Object... list ) { return combineList( Arrays.asList( list ) ); } /** Combines a list of objects as a comma-separated string. */ - public static String combineList( List list ) { + public static String combineList( @Nullable List list ) { if( list == null || list.isEmpty() ) return ""; StringBuilder key = new StringBuilder(); for( Object obj : list ) { diff --git a/src/main/java/fathertoast/specialmobs/common/config/file/package-info.java b/src/main/java/fathertoast/specialmobs/common/config/file/package-info.java new file mode 100644 index 0000000..249454b --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/file/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package fathertoast.specialmobs.common.config.file; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/package-info.java b/src/main/java/fathertoast/specialmobs/common/config/species/package-info.java new file mode 100644 index 0000000..fe4acb4 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/species/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package fathertoast.specialmobs.common.config.species; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/AttributeList.java b/src/main/java/fathertoast/specialmobs/common/config/util/AttributeList.java index a8dc479..1d8df71 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/AttributeList.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/AttributeList.java @@ -5,6 +5,7 @@ import fathertoast.specialmobs.common.config.file.TomlHelper; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; @@ -33,7 +34,7 @@ public class AttributeList implements IStringArray { /** @return Returns true if this object has the same value as another object. */ @Override - public boolean equals( Object other ) { + public boolean equals( @Nullable Object other ) { if( !(other instanceof AttributeList) ) return false; // Compare by the string list view of the object return toStringList().equals( ((AttributeList) other).toStringList() ); diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/BlockEntry.java b/src/main/java/fathertoast/specialmobs/common/config/util/BlockEntry.java index bb2ecfa..e9a502c 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/BlockEntry.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/BlockEntry.java @@ -32,7 +32,7 @@ public class BlockEntry implements Cloneable { this( block.getBlock() ); // Add all the properties present in the block state - StateBuilder state = new StateBuilder(); + StateBuilder state = new StateBuilder( BLOCK ); for( Property> property : block.getProperties() ) { state.add( property, block.getValue( property ) ); } @@ -45,13 +45,8 @@ public class BlockEntry implements Cloneable { public BlockEntry( AbstractConfigField field, String line ) { // Parse the base block final String[] pair = line.split( "\\[", 2 ); - final ResourceLocation regKey = new ResourceLocation( pair[0] ); - if( !ForgeRegistries.BLOCKS.containsKey( regKey ) ) { - BLOCK = Blocks.AIR; - } - else { - BLOCK = ForgeRegistries.BLOCKS.getValue( regKey ); - } + final Block block = ForgeRegistries.BLOCKS.getValue( new ResourceLocation( pair[0] ) ); + BLOCK = block == null ? Blocks.AIR : block; // We are done constructing if the entry is invalid or does not specify block states if( BLOCK == Blocks.AIR || pair.length < 2 ) { @@ -143,7 +138,7 @@ public class BlockEntry implements Cloneable { final StateContainer stateContainer = block.getStateDefinition(); // Parse the state and build the matcher - final StateBuilder builder = new StateBuilder(); + final StateBuilder builder = new StateBuilder( block ); final String[] properties = stateString.split( "," ); for( String combinedEntry : properties ) { // Parse an individual property key-value pair @@ -271,9 +266,6 @@ public class BlockEntry implements Cloneable { /** Can be used for building default configs to make block entries with target states. */ public StateBuilder( Block block ) { BLOCK = block; } - /** Used by block entries to build target states. Can not be built into a block entry when using this constructor. */ - private StateBuilder() { BLOCK = null; } - /** @return Returns true if this state builder has no properties set. */ @SuppressWarnings( "BooleanMethodIsAlwaysInverted" ) public boolean isEmpty() { return propertiesToMatch.isEmpty(); } @@ -289,7 +281,7 @@ public class BlockEntry implements Cloneable { /** @return Returns a block entry reflecting the current state of this builder. */ public BlockEntry toBlockEntry() { - BlockEntry target = new BlockEntry( BLOCK ); + final BlockEntry target = new BlockEntry( BLOCK ); if( !isEmpty() ) { target.MATCHERS.add( toTargetState() ); } diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/BlockList.java b/src/main/java/fathertoast/specialmobs/common/config/util/BlockList.java index b8fec09..2180d44 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/BlockList.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/BlockList.java @@ -10,6 +10,7 @@ import net.minecraft.block.Blocks; import net.minecraft.util.ResourceLocation; import net.minecraftforge.registries.ForgeRegistries; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -61,13 +62,11 @@ public class BlockList implements IStringArray { /** @return A string representation of this object. */ @Override - public String toString() { - return TomlHelper.toLiteral( PRINT_LIST.toArray() ); - } + public String toString() { return TomlHelper.toLiteral( PRINT_LIST.toArray() ); } /** @return Returns true if this object has the same value as another object. */ @Override - public boolean equals( Object other ) { + public boolean equals( @Nullable Object other ) { if( !(other instanceof BlockList) ) return false; // Compare by the string list view of the object return toStringList().equals( ((BlockList) other).toStringList() ); @@ -109,10 +108,9 @@ public class BlockList implements IStringArray { private void mergeFromNamespace( String namespace ) { for( ResourceLocation regKey : ForgeRegistries.BLOCKS.getKeys() ) { if( regKey.toString().startsWith( namespace ) ) { - BlockEntry entry = new BlockEntry( ForgeRegistries.BLOCKS.getValue( regKey ) ); - if( entry.BLOCK != null && entry.BLOCK != Blocks.AIR ) { - mergeFrom( entry ); - } + final Block block = ForgeRegistries.BLOCKS.getValue( regKey ); + if( block != null && block != Blocks.AIR ) + mergeFrom( new BlockEntry( block ) ); } } } diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/ConfigUtil.java b/src/main/java/fathertoast/specialmobs/common/config/util/ConfigUtil.java index 6a00d46..6bb7109 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/ConfigUtil.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/ConfigUtil.java @@ -1,11 +1,19 @@ package fathertoast.specialmobs.common.config.util; import com.electronwill.nightconfig.core.file.FileConfig; +import fathertoast.specialmobs.common.core.SpecialMobs; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.server.FMLServerAboutToStartEvent; import net.minecraftforge.fml.loading.FMLPaths; import java.io.File; -public abstract class ConfigUtil { +@Mod.EventBusSubscriber( modid = SpecialMobs.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE ) +public final class ConfigUtil { + /** The current "version" of the dynamic registries. This is incremented each time dynamic registries are loaded. */ + public static byte DYNAMIC_REGISTRY_VERSION; /** The plus or minus symbol (+/-). */ public static final String PLUS_OR_MINUS = "\u00b1"; @@ -44,4 +52,12 @@ public abstract class ConfigUtil { /** @return A string representation of the file from the game directory. */ public static String toRelativePath( File gameFile ) { return FMLPaths.GAMEDIR.get().relativize( gameFile.toPath() ).toString(); } + + /** + * Called when a server (integrated or dedicated) is about to start. + * + * @param event The event data. + */ + @SubscribeEvent( priority = EventPriority.NORMAL ) + public static void onServerAboutToStart( FMLServerAboutToStartEvent event ) { DYNAMIC_REGISTRY_VERSION++; } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/EntityList.java b/src/main/java/fathertoast/specialmobs/common/config/util/EntityList.java index 45e4c17..b790b12 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/EntityList.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/EntityList.java @@ -5,6 +5,7 @@ import fathertoast.specialmobs.common.config.file.TomlHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; @@ -45,7 +46,7 @@ public class EntityList implements IStringArray { /** @return Returns true if this object has the same value as another object. */ @Override - public boolean equals( Object other ) { + public boolean equals( @Nullable Object other ) { if( !(other instanceof EntityList) ) return false; // Compare by the string list view of the object return toStringList().equals( ((EntityList) other).toStringList() ); @@ -63,7 +64,8 @@ public class EntityList implements IStringArray { } /** @return True if the entity is contained in this list. */ - public boolean contains( Entity entity ) { + public boolean contains( @Nullable Entity entity ) { + if( entity == null ) return false; final EntityEntry targetEntry = new EntityEntry( entity ); for( EntityEntry currentEntry : ENTRIES ) { currentEntry.checkClass( entity.level ); @@ -77,7 +79,9 @@ public class EntityList implements IStringArray { * @param entity The entity to retrieve values for. * @return The array of values of the best-match entry. Returns null if the entity is not contained in this entity list. */ - public double[] getValues( Entity entity ) { + @Nullable + public double[] getValues( @Nullable Entity entity ) { + if( entity == null ) return null; final EntityEntry targetEntry = new EntityEntry( entity ); EntityEntry bestMatch = null; for( EntityEntry currentEntry : ENTRIES ) { @@ -101,7 +105,7 @@ public class EntityList implements IStringArray { * @see #setSingleValue() * @see #setSinglePercent() */ - public double getValue( Entity entity ) { + public double getValue( @Nullable Entity entity ) { final double[] values = getValues( entity ); return values == null || values.length < 1 ? 0.0 : values[0]; } @@ -112,7 +116,7 @@ public class EntityList implements IStringArray { * is not contained in this entity list or has no values specified. This should only be used for 'single percent' lists. * @see #setSinglePercent() */ - public boolean rollChance( LivingEntity entity ) { + public boolean rollChance( @Nullable LivingEntity entity ) { return ENTRIES.length > 0 && entity != null && entity.getRandom().nextDouble() < getValue( entity ); } diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/EnvironmentEntry.java b/src/main/java/fathertoast/specialmobs/common/config/util/EnvironmentEntry.java new file mode 100644 index 0000000..6ea47e4 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/EnvironmentEntry.java @@ -0,0 +1,112 @@ +package fathertoast.specialmobs.common.config.util; + +import fathertoast.specialmobs.common.config.util.environment.*; +import fathertoast.specialmobs.common.config.util.environment.biome.BiomeEnvironment; +import fathertoast.specialmobs.common.config.util.environment.dimension.DimensionTypeEnvironment; +import fathertoast.specialmobs.common.config.util.environment.position.StructureEnvironment; +import fathertoast.specialmobs.common.config.util.environment.position.YEnvironment; +import fathertoast.specialmobs.common.config.util.environment.position.YFromSeaEnvironment; +import net.minecraft.util.RegistryKey; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.DimensionType; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.gen.feature.structure.Structure; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +/** + * One condition-value entry in an environment list. Uses a 'lazy' implementation so any needed registries are + * not polled until this entry is actually used. + */ +@SuppressWarnings( "unused" ) +public class EnvironmentEntry { + /** The value given to this entry. */ + public final double VALUE; + /** The conditions that define this entry's environment. */ + private final AbstractEnvironment[] CONDITIONS; + + /** Creates an entry with the specified values. */ + public EnvironmentEntry( double value, List conditions ) { this( value, conditions.toArray( new AbstractEnvironment[0] ) ); } + + /** Creates an entry with the specified values. */ + public EnvironmentEntry( double value, AbstractEnvironment... conditions ) { + VALUE = value; + CONDITIONS = conditions; + } + + /** @return Returns true if all this entry's conditions match the provided environment. */ + public boolean matches( World world, @Nullable BlockPos pos ) { + for( AbstractEnvironment condition : CONDITIONS ) { + if( !condition.matches( world, pos ) ) return false; + } + return true; + } + + /** + * @return The string representation of this environment entry, as it would appear in a config file. + *

+ * Format is "value condition1 state1 & condition2 state2 & ...". + */ + @Override + public String toString() { + // Start with the value + final StringBuilder str = new StringBuilder().append( VALUE ).append( ' ' ); + // List all conditions + boolean first = true; + for( AbstractEnvironment condition : CONDITIONS ) { + if( first ) first = false; + else str.append( " & " ); + str.append( condition ); + } + return str.toString(); + } + + public static Builder builder( double value ) { return new Builder( value ); } + + /** + * Builder class used to simplify creation of environment entries, with shortcuts for the most commonly used environments. + */ + public static class Builder { + private final double VALUE; + private final ArrayList CONDITIONS = new ArrayList<>(); + + private Builder( double value ) { VALUE = value; } + + public EnvironmentEntry build() { return new EnvironmentEntry( VALUE, CONDITIONS ); } + + public Builder in( AbstractEnvironment condition ) { + CONDITIONS.add( condition ); + return this; + } + + + // Dimension-based + + public Builder inDimensionType( RegistryKey dimType ) { return in( new DimensionTypeEnvironment( dimType ) ); } + + + // Biome-based + + public Builder inBiome( RegistryKey biome ) { return in( new BiomeEnvironment( biome ) ); } + + + // Position-based + + public Builder inStructure( Structure structure ) { return in( new StructureEnvironment( structure ) ); } + + public Builder belowY( int y ) { return in( new YEnvironment( ComparisonOperator.LESS_THAN, y ) ); } + + public Builder atLeastY( int y ) { return in( new YEnvironment( ComparisonOperator.GREATER_OR_EQUAL, y ) ); } + + public Builder belowSeaLevel() { return in( new YFromSeaEnvironment( ComparisonOperator.LESS_THAN, 0 ) ); } + + public Builder atLeastSeaLevel() { return in( new YFromSeaEnvironment( ComparisonOperator.GREATER_OR_EQUAL, 0 ) ); } + + + // Time-based + + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/EnvironmentList.java b/src/main/java/fathertoast/specialmobs/common/config/util/EnvironmentList.java new file mode 100644 index 0000000..1fc2de7 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/EnvironmentList.java @@ -0,0 +1,100 @@ +package fathertoast.specialmobs.common.config.util; + +import fathertoast.specialmobs.common.config.field.DoubleField; +import fathertoast.specialmobs.common.config.field.IStringArray; +import fathertoast.specialmobs.common.config.file.TomlHelper; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +/** + * A list of condition-value entries used to link one number to specific environments. + */ +@SuppressWarnings( { "unused", "SameParameterValue" } ) +public class EnvironmentList implements IStringArray { + /** The condition-value entries in this list. */ + private final EnvironmentEntry[] ENTRIES; + + /** The minimum value accepted for entry values in this list. */ + private double minValue = Double.NEGATIVE_INFINITY; + /** The maximum value accepted for entry values in this list. */ + private double maxValue = Double.POSITIVE_INFINITY; + + /** + * Create a new environment list from a list of entries. + *

+ * By default, environment list value(s) can be any numerical double. + * This can be changed with helper methods that alter values' bounds and return 'this'. + */ + public EnvironmentList( List entries ) { this( entries.toArray( new EnvironmentEntry[0] ) ); } + + /** + * Create a new environment list from an array of entries. Used for creating default configs. + *

+ * By default, environment list value(s) can be any numerical double. + * This can be changed with helper methods that alter values' bounds and return 'this'. + */ + public EnvironmentList( EnvironmentEntry... entries ) { ENTRIES = entries; } + + /** @return A string representation of this object. */ + @Override + public String toString() { return TomlHelper.toLiteral( toStringList().toArray() ); } + + /** @return Returns true if this object has the same value as another object. */ + @Override + public boolean equals( @Nullable Object other ) { + if( !(other instanceof EnvironmentList) ) return false; + // Compare by the string list view of the object + return toStringList().equals( ((EnvironmentList) other).toStringList() ); + } + + /** @return A list of strings that will represent this object when written to a toml file. */ + @Override + public List toStringList() { + // Create a list of the entries in string format + final List list = new ArrayList<>( ENTRIES.length ); + for( EnvironmentEntry entry : ENTRIES ) { + list.add( entry.toString() ); + } + return list; + } + + /** @return The value matching the given environment, or the default value if no matching environment is defined. */ + public double getOrElse( World world, @Nullable BlockPos pos, DoubleField defaultValue ) { + return getOrElse( world, pos, defaultValue.get() ); + } + + /** @return The value matching the given environment, or the default value if no matching environment is defined. */ + public double getOrElse( World world, @Nullable BlockPos pos, double defaultValue ) { + final Double value = get( world, pos ); + return value == null ? defaultValue : value; + } + + /** @return The value matching the given environment, or null if no matching environment is defined. */ + @Nullable + public Double get( World world, @Nullable BlockPos pos ) { + for( EnvironmentEntry entry : ENTRIES ) { + if( entry.matches( world, pos ) ) return entry.VALUE; + } + return null; + } + + /** Bounds entry values in this list to the specified range. */ + public EnvironmentList setRange( DoubleField.Range range ) { return setRange( range.MIN, range.MAX ); } + + /** Bounds entry values in this list to the specified limits, inclusive. */ + public EnvironmentList setRange( double min, double max ) { + minValue = min; + maxValue = max; + return this; + } + + /** @return The minimum value that can be given to entry values. */ + public double getMinValue() { return minValue; } + + /** @return The maximum value that can be given to entry values. */ + public double getMaxValue() { return maxValue; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/RegistryEntryList.java b/src/main/java/fathertoast/specialmobs/common/config/util/RegistryEntryList.java index c3ea6dd..f003e80 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/RegistryEntryList.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/RegistryEntryList.java @@ -8,6 +8,7 @@ import net.minecraft.util.ResourceLocation; import net.minecraftforge.registries.IForgeRegistry; import net.minecraftforge.registries.IForgeRegistryEntry; +import javax.annotation.Nullable; import java.util.*; /** @@ -25,9 +26,7 @@ public class RegistryEntryList> implements IStr /** The list used to write back to file. */ protected final List PRINT_LIST = new ArrayList<>(); - protected RegistryEntryList( IForgeRegistry registry ) { - REGISTRY = registry; - } + protected RegistryEntryList( IForgeRegistry registry ) { REGISTRY = registry; } /** * Create a new registry entry list from an array of entries. Used for creating default configs. @@ -79,13 +78,11 @@ public class RegistryEntryList> implements IStr /** @return A string representation of this object. */ @Override - public String toString() { - return TomlHelper.toLiteral( PRINT_LIST.toArray() ); - } + public String toString() { return TomlHelper.toLiteral( PRINT_LIST.toArray() ); } /** @return Returns true if this object has the same value as another object. */ @Override - public boolean equals( Object other ) { + public boolean equals( @Nullable Object other ) { if( !(other instanceof RegistryEntryList) ) return false; // Compare by the registries used and string list view of the object return getRegistry() == ((RegistryEntryList) other).getRegistry() && @@ -100,7 +97,7 @@ public class RegistryEntryList> implements IStr public boolean isEmpty() { return UNDERLYING_SET.isEmpty(); } /** @return Returns true if the entry is contained in this list. */ - public boolean contains( T entry ) { return UNDERLYING_SET.contains( entry ); } + public boolean contains( @Nullable T entry ) { return UNDERLYING_SET.contains( entry ); } /** @return Adds the registry entry if it exists and isn't already present, returns true if successful. */ protected boolean mergeFrom( ResourceLocation regKey ) { diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/WeightedList.java b/src/main/java/fathertoast/specialmobs/common/config/util/WeightedList.java index e32321c..ed91404 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/WeightedList.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/WeightedList.java @@ -13,6 +13,7 @@ import java.util.*; *

* Creates a config field for each item so weights can be defined by the user. */ +@SuppressWarnings( "unused" ) public class WeightedList { /** The spec used by this config that defines the file's format. */ protected final ToastConfigSpec SPEC; @@ -110,6 +111,7 @@ public class WeightedList { default int getDefaultWeight() { return 1; } /** @return Returns the comment for this object. */ + @Nullable default String[] getComment() { return null; } } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/AbstractEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/AbstractEnvironment.java new file mode 100644 index 0000000..b30a895 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/AbstractEnvironment.java @@ -0,0 +1,21 @@ +package fathertoast.specialmobs.common.config.util.environment; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public abstract class AbstractEnvironment { + /** @return The string representation of this environment, as it would appear in a config file. */ + @Override + public final String toString() { return name() + " " + value(); } + + /** @return The string name of this environment, as it would appear in a config file. */ + public abstract String name(); + + /** @return The string value of this environment, as it would appear in a config file. */ + public abstract String value(); + + /** @return Returns true if this environment matches the provided environment. */ + public abstract boolean matches( World world, @Nullable BlockPos pos ); +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/CompareFloatEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/CompareFloatEnvironment.java new file mode 100644 index 0000000..4e3ef84 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/CompareFloatEnvironment.java @@ -0,0 +1,87 @@ +package fathertoast.specialmobs.common.config.util.environment; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.file.TomlHelper; +import fathertoast.specialmobs.common.core.SpecialMobs; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public abstract class CompareFloatEnvironment extends AbstractEnvironment { + /** How the actual value is compared to this environment's value. */ + public final ComparisonOperator COMPARATOR; + /** The value for this environment. */ + public final float VALUE; + + public CompareFloatEnvironment( ComparisonOperator op, float value ) { + COMPARATOR = op; + VALUE = value; + } + + public CompareFloatEnvironment( AbstractConfigField field, String line ) { + if( line.isEmpty() ) { + COMPARATOR = ComparisonOperator.LESS_THAN; + VALUE = 0.0F; + SpecialMobs.LOG.warn( "Invalid entry for {} \"{}\"! Not defined. Defaulting to \"{}\". Invalid entry: {}", + field.getClass(), field.getKey(), value(), line ); + } + else { + final ComparisonOperator op = ComparisonOperator.parse( line ); + if( op == null ) { + COMPARATOR = ComparisonOperator.LESS_THAN; + SpecialMobs.LOG.warn( "Invalid entry for {} \"{}\"! Comparison not defined (must be in the set [ {} ]). Defaulting to \"{}\". Invalid entry: {}", + field.getClass(), field.getKey(), TomlHelper.literalList( (Object[]) ComparisonOperator.values() ), COMPARATOR, line ); + } + else COMPARATOR = op; + VALUE = parseValue( field, line, line.substring( COMPARATOR.toString().length() ).trim() ); + } + } + + /** @return Parses the value and returns a valid result. */ + private float parseValue( AbstractConfigField field, String line, String arg ) { + // Try to parse the value + float value; + try { + value = Float.parseFloat( arg ); + } + catch( NumberFormatException ex ) { + SpecialMobs.LOG.warn( "Invalid entry for {} \"{}\"! Value not defined (must be a float). Defaulting to '0'. Invalid entry: {}", + field.getClass(), field.getKey(), line ); + value = 0.0F; + } + // Verify value is within range + if( value < getMinValue() ) { + SpecialMobs.LOG.warn( "Value for {} \"{}\" is below the minimum ({})! Clamping value. Invalid value: {}", + field.getClass(), field.getKey(), getMinValue(), value ); + value = getMinValue(); + } + else if( value > getMaxValue() ) { + SpecialMobs.LOG.warn( "Value for {} \"{}\" is above the maximum ({})! Clamping value. Invalid value: {}", + field.getClass(), field.getKey(), getMaxValue(), value ); + value = getMaxValue(); + } + return value; + } + + /** @return The minimum value that can be given to the value. */ + protected float getMinValue() { return Float.NEGATIVE_INFINITY; } + + /** @return The maximum value that can be given to the value. */ + protected float getMaxValue() { return Float.POSITIVE_INFINITY; } + + /** @return The string value of this environment, as it would appear in a config file. */ + @Override + public String value() { return COMPARATOR + " " + VALUE; } + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public boolean matches( World world, @Nullable BlockPos pos ) { + final float actual = getActual( world, pos ); + return !Float.isNaN( actual ) && COMPARATOR.apply( actual, VALUE ); + } + + /** @return Returns the actual value to compare, or Float.NaN if there isn't enough information. */ + public abstract float getActual( World world, @Nullable BlockPos pos ); + +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/CompareIntEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/CompareIntEnvironment.java new file mode 100644 index 0000000..74fd323 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/CompareIntEnvironment.java @@ -0,0 +1,87 @@ +package fathertoast.specialmobs.common.config.util.environment; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.file.TomlHelper; +import fathertoast.specialmobs.common.core.SpecialMobs; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public abstract class CompareIntEnvironment extends AbstractEnvironment { + /** How the actual value is compared to this environment's value. */ + public final ComparisonOperator COMPARATOR; + /** The value for this environment. */ + public final int VALUE; + + public CompareIntEnvironment( ComparisonOperator op, int value ) { + COMPARATOR = op; + VALUE = value; + } + + public CompareIntEnvironment( AbstractConfigField field, String line ) { + if( line.isEmpty() ) { + COMPARATOR = ComparisonOperator.LESS_THAN; + VALUE = 0; + SpecialMobs.LOG.warn( "Invalid entry for {} \"{}\"! Not defined. Defaulting to \"{}\". Invalid entry: {}", + field.getClass(), field.getKey(), value(), line ); + } + else { + final ComparisonOperator op = ComparisonOperator.parse( line ); + if( op == null ) { + COMPARATOR = ComparisonOperator.LESS_THAN; + SpecialMobs.LOG.warn( "Invalid entry for {} \"{}\"! Comparison not defined (must be in the set [ {} ]). Defaulting to \"{}\". Invalid entry: {}", + field.getClass(), field.getKey(), TomlHelper.literalList( (Object[]) ComparisonOperator.values() ), COMPARATOR, line ); + } + else COMPARATOR = op; + VALUE = parseValue( field, line, line.substring( COMPARATOR.toString().length() ).trim() ); + } + } + + /** @return Parses the value and returns a valid result. */ + private int parseValue( AbstractConfigField field, String line, String arg ) { + // Try to parse the value + int value; + try { + value = Integer.parseInt( arg ); + } + catch( NumberFormatException ex ) { + SpecialMobs.LOG.warn( "Invalid entry for {} \"{}\"! Value not defined (must be an integer). Defaulting to '0'. Invalid entry: {}", + field.getClass(), field.getKey(), line ); + value = 0; + } + // Verify value is within range + if( value < getMinValue() ) { + SpecialMobs.LOG.warn( "Value for {} \"{}\" is below the minimum ({})! Clamping value. Invalid value: {}", + field.getClass(), field.getKey(), getMinValue(), value ); + value = getMinValue(); + } + else if( value > getMaxValue() ) { + SpecialMobs.LOG.warn( "Value for {} \"{}\" is above the maximum ({})! Clamping value. Invalid value: {}", + field.getClass(), field.getKey(), getMaxValue(), value ); + value = getMaxValue(); + } + return value; + } + + /** @return The minimum value that can be given to the value. */ + protected int getMinValue() { return Integer.MIN_VALUE; } + + /** @return The maximum value that can be given to the value. */ + protected int getMaxValue() { return Integer.MAX_VALUE; } + + /** @return The string value of this environment, as it would appear in a config file. */ + @Override + public String value() { return COMPARATOR + " " + VALUE; } + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public boolean matches( World world, @Nullable BlockPos pos ) { + final Integer actual = getActual( world, pos ); + return actual != null && COMPARATOR.apply( actual, VALUE ); + } + + /** @return Returns the actual value to compare, or null if there isn't enough information. */ + @Nullable + public abstract Integer getActual( World world, @Nullable BlockPos pos ); +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/CompareLongEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/CompareLongEnvironment.java new file mode 100644 index 0000000..e8bd88d --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/CompareLongEnvironment.java @@ -0,0 +1,87 @@ +package fathertoast.specialmobs.common.config.util.environment; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.file.TomlHelper; +import fathertoast.specialmobs.common.core.SpecialMobs; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public abstract class CompareLongEnvironment extends AbstractEnvironment { + /** How the actual value is compared to this environment's value. */ + public final ComparisonOperator COMPARATOR; + /** The value for this environment. */ + public final long VALUE; + + public CompareLongEnvironment( ComparisonOperator op, long value ) { + COMPARATOR = op; + VALUE = value; + } + + public CompareLongEnvironment( AbstractConfigField field, String line ) { + if( line.isEmpty() ) { + COMPARATOR = ComparisonOperator.LESS_THAN; + VALUE = 0L; + SpecialMobs.LOG.warn( "Invalid entry for {} \"{}\"! Not defined. Defaulting to \"{}\". Invalid entry: {}", + field.getClass(), field.getKey(), value(), line ); + } + else { + final ComparisonOperator op = ComparisonOperator.parse( line ); + if( op == null ) { + COMPARATOR = ComparisonOperator.LESS_THAN; + SpecialMobs.LOG.warn( "Invalid entry for {} \"{}\"! Comparison not defined (must be in the set [ {} ]). Defaulting to \"{}\". Invalid entry: {}", + field.getClass(), field.getKey(), TomlHelper.literalList( (Object[]) ComparisonOperator.values() ), COMPARATOR, line ); + } + else COMPARATOR = op; + VALUE = parseValue( field, line, line.substring( COMPARATOR.toString().length() ).trim() ); + } + } + + /** @return Parses the value and returns a valid result. */ + private long parseValue( AbstractConfigField field, String line, String arg ) { + // Try to parse the value + long value; + try { + value = Long.parseLong( arg ); + } + catch( NumberFormatException ex ) { + SpecialMobs.LOG.warn( "Invalid entry for {} \"{}\"! Value not defined (must be a long). Defaulting to '0'. Invalid entry: {}", + field.getClass(), field.getKey(), line ); + value = 0; + } + // Verify value is within range + if( value < getMinValue() ) { + SpecialMobs.LOG.warn( "Value for {} \"{}\" is below the minimum ({})! Clamping value. Invalid value: {}", + field.getClass(), field.getKey(), getMinValue(), value ); + value = getMinValue(); + } + else if( value > getMaxValue() ) { + SpecialMobs.LOG.warn( "Value for {} \"{}\" is above the maximum ({})! Clamping value. Invalid value: {}", + field.getClass(), field.getKey(), getMaxValue(), value ); + value = getMaxValue(); + } + return value; + } + + /** @return The minimum value that can be given to the value. */ + protected long getMinValue() { return Long.MIN_VALUE; } + + /** @return The maximum value that can be given to the value. */ + protected long getMaxValue() { return Long.MAX_VALUE; } + + /** @return The string value of this environment, as it would appear in a config file. */ + @Override + public String value() { return COMPARATOR + " " + VALUE; } + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public boolean matches( World world, @Nullable BlockPos pos ) { + final Long actual = getActual( world, pos ); + return actual != null && COMPARATOR.apply( actual, VALUE ); + } + + /** @return Returns the actual value to compare, or null if there isn't enough information. */ + @Nullable + public abstract Long getActual( World world, @Nullable BlockPos pos ); +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/ComparisonOperator.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/ComparisonOperator.java new file mode 100644 index 0000000..9744dc0 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/ComparisonOperator.java @@ -0,0 +1,78 @@ +package fathertoast.specialmobs.common.config.util.environment; + +import javax.annotation.Nullable; + +public enum ComparisonOperator { + NOT_EQUAL_TO( "!=" ), LESS_OR_EQUAL( "<=" ), GREATER_OR_EQUAL( ">=" ), + EQUAL_TO( "=" ), LESS_THAN( "<" ), GREATER_THAN( ">" ); + + private final String LITERAL; + + ComparisonOperator( String str ) { LITERAL = str; } + + @Override + public String toString() { return LITERAL; } + + public boolean apply( float first, float second ) { + switch( this ) { + case LESS_THAN: + return first < second; + case LESS_OR_EQUAL: + return first <= second; + case GREATER_THAN: + return first > second; + case GREATER_OR_EQUAL: + return first >= second; + case EQUAL_TO: + return first == second; + case NOT_EQUAL_TO: + return first != second; + } + throw new IllegalStateException( "Float comparison implementation is invalid! :(" ); + } + + public boolean apply( int first, int second ) { + switch( this ) { + case LESS_THAN: + return first < second; + case LESS_OR_EQUAL: + return first <= second; + case GREATER_THAN: + return first > second; + case GREATER_OR_EQUAL: + return first >= second; + case EQUAL_TO: + return first == second; + case NOT_EQUAL_TO: + return first != second; + } + throw new IllegalStateException( "Integer comparison implementation is invalid! :(" ); + } + + public boolean apply( long first, long second ) { + switch( this ) { + case LESS_THAN: + return first < second; + case LESS_OR_EQUAL: + return first <= second; + case GREATER_THAN: + return first > second; + case GREATER_OR_EQUAL: + return first >= second; + case EQUAL_TO: + return first == second; + case NOT_EQUAL_TO: + return first != second; + } + throw new IllegalStateException( "Long comparison implementation is invalid! :(" ); + } + + /** @return The operator described by a given string, or null if invalid. */ + @Nullable + public static ComparisonOperator parse( String op ) { + for( ComparisonOperator operator : values() ) { + if( op.startsWith( operator.LITERAL ) ) return operator; + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/DynamicRegistryEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/DynamicRegistryEnvironment.java new file mode 100644 index 0000000..2b19374 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/DynamicRegistryEnvironment.java @@ -0,0 +1,78 @@ +package fathertoast.specialmobs.common.config.util.environment; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.util.ConfigUtil; +import fathertoast.specialmobs.common.core.SpecialMobs; +import net.minecraft.util.RegistryKey; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.World; +import net.minecraft.world.server.ServerWorld; + +import javax.annotation.Nullable; + +/** + * Dynamic registries are contained in {@link net.minecraft.util.registry.DynamicRegistries} + */ +public abstract class DynamicRegistryEnvironment extends AbstractEnvironment { + /** The field containing this entry. We save a reference to help improve error/warning reports. */ + private final AbstractConfigField FIELD; + + /** If true, the condition is inverted. */ + protected final boolean INVERT; + /** The registry key for this environment. */ + private final ResourceLocation REGISTRY_KEY; + + private T registryEntry; + /** The value of ConfigUtil#DYNAMIC_REGISTRY_VERSION at the time of last poll. */ + private byte version = -1; + + public DynamicRegistryEnvironment( ResourceLocation regKey ) { this( regKey, false ); } + + public DynamicRegistryEnvironment( ResourceLocation regKey, boolean invert ) { + FIELD = null; + INVERT = invert; + REGISTRY_KEY = regKey; + } + + public DynamicRegistryEnvironment( AbstractConfigField field, String line ) { + FIELD = field; + INVERT = line.startsWith( "!" ); + REGISTRY_KEY = new ResourceLocation( INVERT ? line.substring( 1 ) : line ); + } + + /** @return The string value of this environment, as it would appear in a config file. */ + @Override + public final String value() { return (INVERT ? "!" : "") + REGISTRY_KEY.toString(); } + + /** @return The registry used. */ + public abstract RegistryKey> getRegistry(); + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public final boolean matches( World world, @Nullable BlockPos pos ) { + if( world instanceof ServerWorld ) + return matches( (ServerWorld) world, pos ); // These don't work on the client :( + return INVERT; + } + + /** @return Returns true if this environment matches the provided environment. */ + public abstract boolean matches( ServerWorld world, @Nullable BlockPos pos ); + + /** @return The target registry object. */ + @Nullable + public final T getRegistryEntry( ServerWorld world ) { + if( version != ConfigUtil.DYNAMIC_REGISTRY_VERSION ) { + version = ConfigUtil.DYNAMIC_REGISTRY_VERSION; + + final Registry registry = world.getServer().registryAccess().registryOrThrow( getRegistry() ); + registryEntry = registry.get( REGISTRY_KEY ); + if( registryEntry == null ) { + SpecialMobs.LOG.info( "Missing entry for {} \"{}\"! Not present in registry \"{}\". Missing entry: {}", + FIELD.getClass(), FIELD.getKey(), getRegistry().getRegistryName(), REGISTRY_KEY ); + } + } + return registryEntry; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/DynamicRegistryGroupEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/DynamicRegistryGroupEnvironment.java new file mode 100644 index 0000000..c39987d --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/DynamicRegistryGroupEnvironment.java @@ -0,0 +1,87 @@ +package fathertoast.specialmobs.common.config.util.environment; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.util.ConfigUtil; +import fathertoast.specialmobs.common.core.SpecialMobs; +import net.minecraft.util.RegistryKey; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.World; +import net.minecraft.world.server.ServerWorld; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Dynamic registries are contained in {@link net.minecraft.util.registry.DynamicRegistries} + */ +public abstract class DynamicRegistryGroupEnvironment extends AbstractEnvironment { + /** The field containing this entry. We save a reference to help improve error/warning reports. */ + private final AbstractConfigField FIELD; + + /** If true, the condition is inverted. */ + protected final boolean INVERT; + /** The namespace for this environment. */ + private final String NAMESPACE; + + private List registryEntries; + /** The value of ConfigUtil#DYNAMIC_REGISTRY_VERSION at the time of last poll. */ + private byte version = -1; + + public DynamicRegistryGroupEnvironment( ResourceLocation regKey ) { this( regKey, false ); } + + public DynamicRegistryGroupEnvironment( ResourceLocation regKey, boolean invert ) { + FIELD = null; + INVERT = invert; + NAMESPACE = regKey.toString(); + } + + public DynamicRegistryGroupEnvironment( AbstractConfigField field, String line ) { + FIELD = field; + INVERT = line.startsWith( "!" ); + NAMESPACE = line.substring( INVERT ? 1 : 0, line.length() - 1 ); + } + + /** @return The string value of this environment, as it would appear in a config file. */ + @Override + public final String value() { return (INVERT ? "!" : "") + NAMESPACE + "*"; } + + /** @return The registry used. */ + public abstract RegistryKey> getRegistry(); + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public final boolean matches( World world, @Nullable BlockPos pos ) { + if( world instanceof ServerWorld ) + return matches( (ServerWorld) world, pos ); // These don't work on the client :( + return INVERT; + } + + /** @return Returns true if this environment matches the provided environment. */ + public abstract boolean matches( ServerWorld world, @Nullable BlockPos pos ); + + /** @return The target registry object. */ + protected final List getRegistryEntries( ServerWorld world ) { + if( version != ConfigUtil.DYNAMIC_REGISTRY_VERSION ) { + version = ConfigUtil.DYNAMIC_REGISTRY_VERSION; + + registryEntries = new ArrayList<>(); + final Registry registry = world.getServer().registryAccess().registryOrThrow( getRegistry() ); + for( ResourceLocation regKey : registry.keySet() ) { + if( regKey.toString().startsWith( NAMESPACE ) ) { + final T entry = registry.get( regKey ); + if( entry != null ) registryEntries.add( entry ); + } + } + if( registryEntries.isEmpty() ) { + SpecialMobs.LOG.info( "Namespace entry for {} \"{}\" did not match anything in registry \"{}\"! Questionable entry: {}", + FIELD == null ? "DEFAULT" : FIELD.getClass(), FIELD == null ? "DEFAULT" : FIELD.getKey(), getRegistry().getRegistryName(), NAMESPACE ); + } + registryEntries = Collections.unmodifiableList( registryEntries ); + } + return registryEntries; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/EnumEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/EnumEnvironment.java new file mode 100644 index 0000000..df3ab48 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/EnumEnvironment.java @@ -0,0 +1,41 @@ +package fathertoast.specialmobs.common.config.util.environment; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.file.TomlHelper; +import fathertoast.specialmobs.common.core.SpecialMobs; + +import java.util.Locale; + +public abstract class EnumEnvironment> extends AbstractEnvironment { + /** If true, the condition is inverted. */ + protected final boolean INVERT; + /** The enum value for this environment. */ + protected final T VALUE; + + public EnumEnvironment( T value ) { this( value, false ); } + + public EnumEnvironment( T value, boolean invert ) { + INVERT = invert; + VALUE = value; + } + + public EnumEnvironment( AbstractConfigField field, String line, T[] validValues ) { + INVERT = line.startsWith( "!" ); + VALUE = parseValue( field, line, validValues, INVERT ? line.substring( 1 ) : line ); + } + + /** @return Attempts to parse the string literal as one of the valid values and returns it, or null if invalid. */ + private T parseValue( AbstractConfigField field, String line, T[] validValues, String name ) { + for( T value : validValues ) { + if( value.name().equalsIgnoreCase( name ) ) return value; + } + // Value cannot be parsed + SpecialMobs.LOG.warn( "Invalid entry for {} \"{}\"! Value not defined (must be in the set [ {} ]). Defaulting to {}. Invalid entry: {}", + field.getClass(), field.getKey(), TomlHelper.literalList( (Object[]) validValues ), TomlHelper.toLiteral(), line ); + return validValues[0]; + } + + /** @return The string value of this environment, as it would appear in a config file. */ + @Override + public final String value() { return (INVERT ? "!" : "") + VALUE.name().toLowerCase( Locale.ROOT ); } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/RegistryEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/RegistryEnvironment.java new file mode 100644 index 0000000..96db0d1 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/RegistryEnvironment.java @@ -0,0 +1,59 @@ +package fathertoast.specialmobs.common.config.util.environment; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.core.SpecialMobs; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.registries.IForgeRegistry; +import net.minecraftforge.registries.IForgeRegistryEntry; + +import javax.annotation.Nullable; + +/** + * Registries are contained in {@link net.minecraftforge.registries.ForgeRegistries} + */ +public abstract class RegistryEnvironment> extends AbstractEnvironment { + /** The field containing this entry. We save a reference to help improve error/warning reports. */ + private final AbstractConfigField FIELD; + + /** If true, the condition is inverted. */ + protected final boolean INVERT; + /** The registry key for this environment. */ + private final ResourceLocation REGISTRY_KEY; + + private T registryEntry; + + public RegistryEnvironment( T regEntry ) { this( regEntry, false ); } + + public RegistryEnvironment( T regEntry, boolean invert ) { + FIELD = null; + INVERT = invert; + REGISTRY_KEY = regEntry.getRegistryName(); + registryEntry = regEntry; + } + + public RegistryEnvironment( AbstractConfigField field, String line ) { + FIELD = field; + INVERT = line.startsWith( "!" ); + REGISTRY_KEY = new ResourceLocation( INVERT ? line.substring( 1 ) : line ); + } + + /** @return The string value of this environment, as it would appear in a config file. */ + @Override + public final String value() { return (INVERT ? "!" : "") + REGISTRY_KEY.toString(); } + + /** @return The registry used. */ + public abstract IForgeRegistry getRegistry(); + + /** @return The registry entry. */ + @Nullable + protected final T getRegistryEntry() { + if( registryEntry == null ) { + if( !getRegistry().containsKey( REGISTRY_KEY ) ) { + SpecialMobs.LOG.warn( "Invalid entry for {} \"{}\"! Not present in registry \"{}\". Invalid entry: {}", + FIELD.getClass(), FIELD.getKey(), getRegistry().getRegistryName(), REGISTRY_KEY ); + } + registryEntry = getRegistry().getValue( REGISTRY_KEY ); + } + return registryEntry; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/RegistryGroupEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/RegistryGroupEnvironment.java new file mode 100644 index 0000000..6c436b1 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/RegistryGroupEnvironment.java @@ -0,0 +1,67 @@ +package fathertoast.specialmobs.common.config.util.environment; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.core.SpecialMobs; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.registries.IForgeRegistry; +import net.minecraftforge.registries.IForgeRegistryEntry; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Registries are contained in {@link net.minecraftforge.registries.ForgeRegistries} + */ +public abstract class RegistryGroupEnvironment> extends AbstractEnvironment { + /** The field containing this entry. We save a reference to help improve error/warning reports. */ + private final AbstractConfigField FIELD; + + /** If true, the condition is inverted. */ + protected final boolean INVERT; + /** The namespace for this environment. */ + private final String NAMESPACE; + + private List registryEntries; + + public RegistryGroupEnvironment( T regEntry ) { this( regEntry, false ); } + + public RegistryGroupEnvironment( T regEntry, boolean invert ) { + FIELD = null; + INVERT = invert; + //noinspection ConstantConditions + NAMESPACE = regEntry.getRegistryName().toString(); + } + + public RegistryGroupEnvironment( AbstractConfigField field, String line ) { + FIELD = field; + INVERT = line.startsWith( "!" ); + NAMESPACE = line.substring( INVERT ? 1 : 0, line.length() - 1 ); + } + + /** @return The string value of this environment, as it would appear in a config file. */ + @Override + public final String value() { return (INVERT ? "!" : "") + NAMESPACE + "*"; } + + /** @return The registry used. */ + public abstract IForgeRegistry getRegistry(); + + /** @return The registry entries. */ + protected final List getRegistryEntries() { + if( registryEntries == null ) { + registryEntries = new ArrayList<>(); + for( ResourceLocation regKey : getRegistry().getKeys() ) { + if( regKey.toString().startsWith( NAMESPACE ) ) { + final T entry = getRegistry().getValue( regKey ); + if( entry != null ) registryEntries.add( entry ); + } + } + if( registryEntries.isEmpty() ) { + SpecialMobs.LOG.warn( "Namespace entry for {} \"{}\" did not match anything in registry \"{}\"! Questionable entry: {}", + FIELD == null ? "DEFAULT" : FIELD.getClass(), FIELD == null ? "DEFAULT" : FIELD.getKey(), getRegistry().getRegistryName(), NAMESPACE ); + } + registryEntries = Collections.unmodifiableList( registryEntries ); + } + return registryEntries; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeCategoryEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeCategoryEnvironment.java new file mode 100644 index 0000000..21d7fc9 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeCategoryEnvironment.java @@ -0,0 +1,52 @@ +package fathertoast.specialmobs.common.config.util.environment.biome; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.EnumEnvironment; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; + +import javax.annotation.Nullable; + +public class BiomeCategoryEnvironment extends EnumEnvironment { + public enum Value { + NONE( Biome.Category.NONE ), + TAIGA( Biome.Category.TAIGA ), + EXTREME_HILLS( Biome.Category.EXTREME_HILLS ), + JUNGLE( Biome.Category.JUNGLE ), + MESA( Biome.Category.MESA ), + PLAINS( Biome.Category.PLAINS ), + SAVANNA( Biome.Category.SAVANNA ), + ICY( Biome.Category.ICY ), + THE_END( Biome.Category.THEEND ), + BEACH( Biome.Category.BEACH ), + FOREST( Biome.Category.FOREST ), + OCEAN( Biome.Category.OCEAN ), + DESERT( Biome.Category.DESERT ), + RIVER( Biome.Category.RIVER ), + SWAMP( Biome.Category.SWAMP ), + MUSHROOM( Biome.Category.MUSHROOM ), + NETHER( Biome.Category.NETHER ); + + public final Biome.Category BASE; + + Value( Biome.Category vanillaCat ) { BASE = vanillaCat; } + } + + public BiomeCategoryEnvironment( Value value ) { super( value ); } + + public BiomeCategoryEnvironment( Value value, boolean invert ) { super( value, invert ); } + + public BiomeCategoryEnvironment( AbstractConfigField field, String line ) { super( field, line, Value.values() ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_BIOME_CATEGORY; } + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public boolean matches( World world, @Nullable BlockPos pos ) { + return (pos != null && VALUE.BASE.equals( world.getBiome( pos ).getBiomeCategory() )) != INVERT; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeEnvironment.java new file mode 100644 index 0000000..e2ce149 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeEnvironment.java @@ -0,0 +1,36 @@ +package fathertoast.specialmobs.common.config.util.environment.biome; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.DynamicRegistryEnvironment; +import net.minecraft.util.RegistryKey; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.server.ServerWorld; + +import javax.annotation.Nullable; + +public class BiomeEnvironment extends DynamicRegistryEnvironment { + + public BiomeEnvironment( RegistryKey biome ) { super( biome.getRegistryName() ); } + + public BiomeEnvironment( RegistryKey biome, boolean invert ) { super( biome.getRegistryName(), invert ); } + + public BiomeEnvironment( AbstractConfigField field, String line ) { super( field, line ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_BIOME; } + + /** @return The registry used. */ + @Override + public RegistryKey> getRegistry() { return Registry.BIOME_REGISTRY; } + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public boolean matches( ServerWorld world, @Nullable BlockPos pos ) { + final Biome entry = getRegistryEntry( world ); + return (entry != null && pos != null && entry.equals( world.getBiome( pos ) )) != INVERT; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeGroupEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeGroupEnvironment.java new file mode 100644 index 0000000..34c5398 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeGroupEnvironment.java @@ -0,0 +1,43 @@ +package fathertoast.specialmobs.common.config.util.environment.biome; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.DynamicRegistryGroupEnvironment; +import net.minecraft.util.RegistryKey; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.server.ServerWorld; + +import javax.annotation.Nullable; +import java.util.List; + +public class BiomeGroupEnvironment extends DynamicRegistryGroupEnvironment { + + public BiomeGroupEnvironment( RegistryKey biome ) { super( biome.getRegistryName() ); } + + public BiomeGroupEnvironment( RegistryKey biome, boolean invert ) { super( biome.getRegistryName(), invert ); } + + public BiomeGroupEnvironment( AbstractConfigField field, String line ) { super( field, line ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_BIOME; } + + /** @return The registry used. */ + @Override + public RegistryKey> getRegistry() { return Registry.BIOME_REGISTRY; } + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public final boolean matches( ServerWorld world, @Nullable BlockPos pos ) { + final Biome target = pos == null ? null : world.getBiome( pos ); + if( target != null ) { + final List entries = getRegistryEntries( world ); + for( Biome entry : entries ) { + if( entry.equals( target ) ) return !INVERT; + } + } + return INVERT; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeTemperatureEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeTemperatureEnvironment.java new file mode 100644 index 0000000..a49182d --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeTemperatureEnvironment.java @@ -0,0 +1,37 @@ +package fathertoast.specialmobs.common.config.util.environment.biome; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.CompareFloatEnvironment; +import fathertoast.specialmobs.common.config.util.environment.ComparisonOperator; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class BiomeTemperatureEnvironment extends CompareFloatEnvironment { + + public BiomeTemperatureEnvironment( ComparisonOperator op, float value ) { super( op, value ); } + + public BiomeTemperatureEnvironment( AbstractConfigField field, String line ) { super( field, TemperatureEnvironment.handleTempInput( line ) ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_BIOME_TEMPERATURE; } + + /** @return The string value of this environment, as it would appear in a config file. */ + @Override + public String value() { + if( COMPARATOR == ComparisonOperator.LESS_THAN && VALUE == 0.15F ) + return TemperatureEnvironment.FREEZING; + if( COMPARATOR == ComparisonOperator.GREATER_THAN && VALUE == Math.nextDown( 0.15F ) ) + return "!" + TemperatureEnvironment.FREEZING; + return super.value(); + } + + /** @return Returns the actual value to compare, or Float.NaN if there isn't enough information. */ + @Override + public float getActual( World world, @Nullable BlockPos pos ) { + return pos == null ? Float.NaN : world.getBiome( pos ).getBaseTemperature(); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/RainfallEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/RainfallEnvironment.java new file mode 100644 index 0000000..71504d9 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/RainfallEnvironment.java @@ -0,0 +1,37 @@ +package fathertoast.specialmobs.common.config.util.environment.biome; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.CompareFloatEnvironment; +import fathertoast.specialmobs.common.config.util.environment.ComparisonOperator; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; + +import javax.annotation.Nullable; + +public class RainfallEnvironment extends CompareFloatEnvironment { + + public RainfallEnvironment( ComparisonOperator op, float value ) { super( op, value ); } + + public RainfallEnvironment( AbstractConfigField field, String line ) { super( field, line ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_RAINFALL; } + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public boolean matches( World world, @Nullable BlockPos pos ) { + // Handle the special case of no rainfall + if( COMPARATOR == ComparisonOperator.EQUAL_TO && VALUE == 0.0F ) + return pos != null && world.getBiome( pos ).getPrecipitation() == Biome.RainType.NONE; + return super.matches( world, pos ); + } + + /** @return Returns the actual value to compare, or Float.NaN if there isn't enough information. */ + @Override + public float getActual( World world, @Nullable BlockPos pos ) { + return pos == null ? Float.NaN : world.getBiome( pos ).getDownfall(); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/TemperatureEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/TemperatureEnvironment.java new file mode 100644 index 0000000..9b90196 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/TemperatureEnvironment.java @@ -0,0 +1,47 @@ +package fathertoast.specialmobs.common.config.util.environment.biome; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.CompareFloatEnvironment; +import fathertoast.specialmobs.common.config.util.environment.ComparisonOperator; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class TemperatureEnvironment extends CompareFloatEnvironment { + + public static final String FREEZING = "freezing"; + + public static String handleTempInput( String line ) { + if( line.equalsIgnoreCase( FREEZING ) ) + return ComparisonOperator.LESS_THAN + " " + 0.15F; + if( line.equalsIgnoreCase( "!" + FREEZING ) ) + return ComparisonOperator.GREATER_THAN + " " + Math.nextDown( 0.15F ); + return line; + } + + public TemperatureEnvironment( ComparisonOperator op, float value ) { super( op, value ); } + + public TemperatureEnvironment( AbstractConfigField field, String line ) { super( field, handleTempInput( line ) ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_TEMPERATURE; } + + /** @return The string value of this environment, as it would appear in a config file. */ + @Override + public String value() { + if( COMPARATOR == ComparisonOperator.LESS_THAN && VALUE == 0.15F ) + return FREEZING; + if( COMPARATOR == ComparisonOperator.GREATER_THAN && VALUE == Math.nextDown( 0.15F ) ) + return "!" + FREEZING; + return super.value(); + } + + /** @return Returns the actual value to compare, or Float.NaN if there isn't enough information. */ + @Override + public float getActual( World world, @Nullable BlockPos pos ) { + return pos == null ? Float.NaN : world.getBiome( pos ).getTemperature( pos ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/package-info.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/package-info.java new file mode 100644 index 0000000..9c63dd4 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package fathertoast.specialmobs.common.config.util.environment.biome; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionPropertyEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionPropertyEnvironment.java new file mode 100644 index 0000000..ebd9a32 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionPropertyEnvironment.java @@ -0,0 +1,51 @@ +package fathertoast.specialmobs.common.config.util.environment.dimension; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.EnumEnvironment; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.DimensionType; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.function.Function; + +public class DimensionPropertyEnvironment extends EnumEnvironment { + /** + * Represents all boolean values defined by dimension type, named to match data pack format. + * + * @see Data pack format (Minecraft Wiki) + */ + public enum Value { + @SuppressWarnings( "SpellCheckingInspection" ) + ULTRAWARM( DimensionType::ultraWarm ), + NATURAL( DimensionType::natural ), + HAS_SKYLIGHT( DimensionType::hasSkyLight ), + HAS_CEILING( DimensionType::hasCeiling ), + FIXED_TIME( DimensionType::hasFixedTime ), + PIGLIN_SAFE( DimensionType::piglinSafe ), + BED_WORKS( DimensionType::bedWorks ), + RESPAWN_ANCHOR_WORKS( DimensionType::respawnAnchorWorks ), + HAS_RAIDS( DimensionType::hasRaids ); + + private final Function SUPPLIER; + + Value( Function supplier ) { SUPPLIER = supplier; } + + public boolean of( DimensionType dimType ) { return SUPPLIER.apply( dimType ); } + } + + public DimensionPropertyEnvironment( Value value ) { super( value ); } + + public DimensionPropertyEnvironment( Value value, boolean invert ) { super( value, invert ); } + + public DimensionPropertyEnvironment( AbstractConfigField field, String line ) { super( field, line, Value.values() ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_DIMENSION_PROPERTY; } + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public boolean matches( World world, @Nullable BlockPos pos ) { return VALUE.of( world.dimensionType() ) != INVERT; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionTypeEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionTypeEnvironment.java new file mode 100644 index 0000000..77d5c9b --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionTypeEnvironment.java @@ -0,0 +1,36 @@ +package fathertoast.specialmobs.common.config.util.environment.dimension; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.DynamicRegistryEnvironment; +import net.minecraft.util.RegistryKey; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.DimensionType; +import net.minecraft.world.server.ServerWorld; + +import javax.annotation.Nullable; + +public class DimensionTypeEnvironment extends DynamicRegistryEnvironment { + + public DimensionTypeEnvironment( RegistryKey dimType ) { super( dimType.getRegistryName() ); } + + public DimensionTypeEnvironment( RegistryKey dimType, boolean invert ) { super( dimType.getRegistryName(), invert ); } + + public DimensionTypeEnvironment( AbstractConfigField field, String line ) { super( field, line ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_DIMENSION_TYPE; } + + /** @return The registry used. */ + @Override + public RegistryKey> getRegistry() { return Registry.DIMENSION_TYPE_REGISTRY; } + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public boolean matches( ServerWorld world, @Nullable BlockPos pos ) { + final DimensionType entry = getRegistryEntry( world ); + return (entry != null && entry.equals( world.dimensionType() )) != INVERT; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionTypeGroupEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionTypeGroupEnvironment.java new file mode 100644 index 0000000..9af3e68 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionTypeGroupEnvironment.java @@ -0,0 +1,41 @@ +package fathertoast.specialmobs.common.config.util.environment.dimension; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.DynamicRegistryGroupEnvironment; +import net.minecraft.util.RegistryKey; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.DimensionType; +import net.minecraft.world.server.ServerWorld; + +import javax.annotation.Nullable; +import java.util.List; + +public class DimensionTypeGroupEnvironment extends DynamicRegistryGroupEnvironment { + + public DimensionTypeGroupEnvironment( RegistryKey dimType ) { super( dimType.getRegistryName() ); } + + public DimensionTypeGroupEnvironment( RegistryKey dimType, boolean invert ) { super( dimType.getRegistryName(), invert ); } + + public DimensionTypeGroupEnvironment( AbstractConfigField field, String line ) { super( field, line ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_DIMENSION_TYPE; } + + /** @return The registry used. */ + @Override + public RegistryKey> getRegistry() { return Registry.DIMENSION_TYPE_REGISTRY; } + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public final boolean matches( ServerWorld world, @Nullable BlockPos pos ) { + final DimensionType target = world.dimensionType(); + final List entries = getRegistryEntries( world ); + for( DimensionType entry : entries ) { + if( entry.equals( target ) ) return !INVERT; + } + return INVERT; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/package-info.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/package-info.java new file mode 100644 index 0000000..6b06803 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package fathertoast.specialmobs.common.config.util.environment.dimension; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/package-info.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/package-info.java new file mode 100644 index 0000000..144a8be --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package fathertoast.specialmobs.common.config.util.environment; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/position/StructureEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/position/StructureEnvironment.java new file mode 100644 index 0000000..9ac3202 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/position/StructureEnvironment.java @@ -0,0 +1,38 @@ +package fathertoast.specialmobs.common.config.util.environment.position; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.RegistryEnvironment; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.gen.feature.structure.Structure; +import net.minecraft.world.server.ServerWorld; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.IForgeRegistry; + +import javax.annotation.Nullable; + +public class StructureEnvironment extends RegistryEnvironment> { + + public StructureEnvironment( Structure structure ) { super( structure ); } + + public StructureEnvironment( Structure structure, boolean invert ) { super( structure, invert ); } + + public StructureEnvironment( AbstractConfigField field, String line ) { super( field, line ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_STRUCTURE; } + + /** @return The registry used. */ + @Override + public IForgeRegistry> getRegistry() { return ForgeRegistries.STRUCTURE_FEATURES; } + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public boolean matches( World world, @Nullable BlockPos pos ) { + final Structure entry = getRegistryEntry(); + return (entry != null && pos != null && world instanceof ServerWorld && + ((ServerWorld) world).structureFeatureManager().getStructureAt( pos, false, entry ).isValid()) != INVERT; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/position/StructureGroupEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/position/StructureGroupEnvironment.java new file mode 100644 index 0000000..584ee56 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/position/StructureGroupEnvironment.java @@ -0,0 +1,46 @@ +package fathertoast.specialmobs.common.config.util.environment.position; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.RegistryGroupEnvironment; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.gen.feature.structure.Structure; +import net.minecraft.world.gen.feature.structure.StructureManager; +import net.minecraft.world.server.ServerWorld; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.IForgeRegistry; + +import javax.annotation.Nullable; +import java.util.List; + +public class StructureGroupEnvironment extends RegistryGroupEnvironment> { + + public StructureGroupEnvironment( Structure biome ) { super( biome ); } + + public StructureGroupEnvironment( Structure biome, boolean invert ) { super( biome, invert ); } + + public StructureGroupEnvironment( AbstractConfigField field, String line ) { super( field, line ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_STRUCTURE; } + + /** @return The registry used. */ + @Override + public IForgeRegistry> getRegistry() { return ForgeRegistries.STRUCTURE_FEATURES; } + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public final boolean matches( World world, @Nullable BlockPos pos ) { + final StructureManager structureManager = pos != null && world instanceof ServerWorld ? + ((ServerWorld) world).structureFeatureManager() : null; + if( structureManager != null ) { + final List> entries = getRegistryEntries(); + for( Structure entry : entries ) { + if( structureManager.getStructureAt( pos, false, entry ).isValid() ) return !INVERT; + } + } + return INVERT; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/position/YEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/position/YEnvironment.java new file mode 100644 index 0000000..a51606b --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/position/YEnvironment.java @@ -0,0 +1,25 @@ +package fathertoast.specialmobs.common.config.util.environment.position; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.CompareIntEnvironment; +import fathertoast.specialmobs.common.config.util.environment.ComparisonOperator; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class YEnvironment extends CompareIntEnvironment { + + public YEnvironment( ComparisonOperator op, int value ) { super( op, value ); } + + public YEnvironment( AbstractConfigField field, String line ) { super( field, line ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_Y; } + + /** @return Returns the actual value to compare, or null if there isn't enough information. */ + @Override + public Integer getActual( World world, @Nullable BlockPos pos ) { return pos == null ? null : pos.getY(); } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/position/YFromSeaEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/position/YFromSeaEnvironment.java new file mode 100644 index 0000000..3f60e19 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/position/YFromSeaEnvironment.java @@ -0,0 +1,25 @@ +package fathertoast.specialmobs.common.config.util.environment.position; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.CompareIntEnvironment; +import fathertoast.specialmobs.common.config.util.environment.ComparisonOperator; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class YFromSeaEnvironment extends CompareIntEnvironment { + + public YFromSeaEnvironment( ComparisonOperator op, int value ) { super( op, value ); } + + public YFromSeaEnvironment( AbstractConfigField field, String line ) { super( field, line ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_Y_FROM_SEA; } + + /** @return Returns the actual value to compare, or null if there isn't enough information. */ + @Override + public Integer getActual( World world, @Nullable BlockPos pos ) { return pos == null ? null : pos.getY() - world.getSeaLevel(); } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/position/package-info.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/position/package-info.java new file mode 100644 index 0000000..817d9d0 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/position/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package fathertoast.specialmobs.common.config.util.environment.position; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/DayTimeEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/DayTimeEnvironment.java new file mode 100644 index 0000000..98f038d --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/DayTimeEnvironment.java @@ -0,0 +1,45 @@ +package fathertoast.specialmobs.common.config.util.environment.time; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.EnumEnvironment; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class DayTimeEnvironment extends EnumEnvironment { + /** Values match up to the vanilla set time command. */ + public enum Value { + DAY( 1_000, 13_000 ), SUNSET( 12_000, 13_000 ), + NIGHT( 13_000, 1_000 ), SUNRISE( 23_000, 1_000 ); + + private final int START, END; + + Value( int start, int end ) { + START = start; + END = end; + } + + public boolean matches( int dayTime ) { + if( START < END ) return START <= dayTime && dayTime < END; + return START <= dayTime || dayTime < END; // Handle day wrapping + } + } + + public DayTimeEnvironment( Value value ) { super( value ); } + + public DayTimeEnvironment( Value value, boolean invert ) { super( value, invert ); } + + public DayTimeEnvironment( AbstractConfigField field, String line ) { super( field, line, Value.values() ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_DAY_TIME; } + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public boolean matches( World world, @Nullable BlockPos pos ) { + return (VALUE.matches( (int) (world.dayTime() / 24_000L) )) != INVERT; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/DifficultyEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/DifficultyEnvironment.java new file mode 100644 index 0000000..c7b4e70 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/DifficultyEnvironment.java @@ -0,0 +1,40 @@ +package fathertoast.specialmobs.common.config.util.environment.time; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.CompareFloatEnvironment; +import fathertoast.specialmobs.common.config.util.environment.ComparisonOperator; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +/** + * Notes on regional difficulty: + * Maxes out over 63 days in the world and 150 days the in the chunk (effectively, time the chunk has been loaded). + * Peaks during the full moon and dramatically scaled by difficulty setting. + *

+ * Peaceful: 0 + * Easy: 0.75 to 1.5 (0.25 from world time, 0.375 from chunk time, and 0.125 from moon brightness) + * Normal: 1.5 to 4.0 (0.5 from world time, 1.5 from chunk time, and 0.5 from moon brightness) + * Hard: 2.25 to 6.75 (0.75 from world time, 3.0 from chunk time, and 0.75 from moon brightness) + */ +public class DifficultyEnvironment extends CompareFloatEnvironment { + + public DifficultyEnvironment( ComparisonOperator op, float value ) { super( op, value ); } + + public DifficultyEnvironment( AbstractConfigField field, String line ) { super( field, line ); } + + /** @return The minimum value that can be given to the value. */ + protected float getMinValue() { return 0.0F; } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_DIFFICULTY; } + + /** @return Returns the actual value to compare, or Float.NaN if there isn't enough information. */ + @Override + public float getActual( World world, @Nullable BlockPos pos ) { + return pos == null ? Float.NaN : world.getCurrentDifficultyAt( pos ).getEffectiveDifficulty(); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/MoonBrightnessEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/MoonBrightnessEnvironment.java new file mode 100644 index 0000000..1e358a9 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/MoonBrightnessEnvironment.java @@ -0,0 +1,34 @@ +package fathertoast.specialmobs.common.config.util.environment.time; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.CompareFloatEnvironment; +import fathertoast.specialmobs.common.config.util.environment.ComparisonOperator; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.DimensionType; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class MoonBrightnessEnvironment extends CompareFloatEnvironment { + + public MoonBrightnessEnvironment( ComparisonOperator op, float value ) { super( op, value ); } + + public MoonBrightnessEnvironment( AbstractConfigField field, String line ) { super( field, line ); } + + /** @return The minimum value that can be given to the value. */ + protected float getMinValue() { return 0.0F; } + + /** @return The maximum value that can be given to the value. */ + protected float getMaxValue() { return 1.0F; } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_MOON_BRIGHTNESS; } + + /** @return Returns the actual value to compare, or Float.NaN if there isn't enough information. */ + @Override + public float getActual( World world, @Nullable BlockPos pos ) { + return pos == null ? Float.NaN : DimensionType.MOON_BRIGHTNESS_PER_PHASE[world.dimensionType().moonPhase( world.dayTime() )]; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/MoonPhaseEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/MoonPhaseEnvironment.java new file mode 100644 index 0000000..4a92ddc --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/MoonPhaseEnvironment.java @@ -0,0 +1,38 @@ +package fathertoast.specialmobs.common.config.util.environment.time; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.EnumEnvironment; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class MoonPhaseEnvironment extends EnumEnvironment { + /** Values match up to the vanilla weather command. */ + public enum Value { + FULL( 0 ), WANING_GIBBOUS( 1 ), LAST_QUARTER( 2 ), WANING_CRESCENT( 3 ), + NEW( 4 ), WAXING_CRESCENT( 5 ), FIRST_QUARTER( 6 ), WAXING_GIBBOUS( 7 ); + + public final int INDEX; + + Value( int i ) { INDEX = i; } + } + + public MoonPhaseEnvironment( Value value ) { super( value ); } + + public MoonPhaseEnvironment( Value value, boolean invert ) { super( value, invert ); } + + public MoonPhaseEnvironment( AbstractConfigField field, String line ) { super( field, line, Value.values() ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_MOON_PHASE; } + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public boolean matches( World world, @Nullable BlockPos pos ) { + final int phase = world.dimensionType().moonPhase( world.dayTime() ); + return (VALUE.INDEX == phase) != INVERT; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/SpecialDifficultyEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/SpecialDifficultyEnvironment.java new file mode 100644 index 0000000..f33a7e7 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/SpecialDifficultyEnvironment.java @@ -0,0 +1,40 @@ +package fathertoast.specialmobs.common.config.util.environment.time; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.CompareFloatEnvironment; +import fathertoast.specialmobs.common.config.util.environment.ComparisonOperator; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +/** + * Notes on special multiplier difficulty: + * This is 0 while regional difficulty is <= 2 and this is 1 while regional difficulty is >= 4 (linearly scales between). + *

+ * In Peaceful and Easy, this is always 0. In Normal, this only maxes out at the absolute peak regional difficulty. + * In Hard, this starts out as 0.125 and reaches 1 during new moons with only ~50 days in the area. + */ +public class SpecialDifficultyEnvironment extends CompareFloatEnvironment { + + public SpecialDifficultyEnvironment( ComparisonOperator op, float value ) { super( op, value ); } + + public SpecialDifficultyEnvironment( AbstractConfigField field, String line ) { super( field, line ); } + + /** @return The minimum value that can be given to the value. */ + protected float getMinValue() { return 0.0F; } + + /** @return The maximum value that can be given to the value. */ + protected float getMaxValue() { return 1.0F; } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_SPECIAL_DIFFICULTY; } + + /** @return Returns the actual value to compare, or Float.NaN if there isn't enough information. */ + @Override + public float getActual( World world, @Nullable BlockPos pos ) { + return pos == null ? Float.NaN : world.getCurrentDifficultyAt( pos ).getSpecialMultiplier(); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/TimeFromMidnightEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/TimeFromMidnightEnvironment.java new file mode 100644 index 0000000..95053d2 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/TimeFromMidnightEnvironment.java @@ -0,0 +1,37 @@ +package fathertoast.specialmobs.common.config.util.environment.time; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.CompareIntEnvironment; +import fathertoast.specialmobs.common.config.util.environment.ComparisonOperator; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class TimeFromMidnightEnvironment extends CompareIntEnvironment { + + public TimeFromMidnightEnvironment( ComparisonOperator op, int value ) { super( op, value ); } + + public TimeFromMidnightEnvironment( AbstractConfigField field, String line ) { super( field, line ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_TIME_FROM_MIDNIGHT; } + + /** @return The minimum value that can be given to the value. */ + @Override + protected int getMinValue() { return 0; } + + /** @return The maximum value that can be given to the value. */ + @Override + protected int getMaxValue() { return 12_000; } + + /** @return Returns the actual value to compare, or null if there isn't enough information. */ + @Override + public Integer getActual( World world, @Nullable BlockPos pos ) { + int dayTime = (int) (world.dayTime() / 24_000L); + if( dayTime < 18_000 ) dayTime += 24_000; + return dayTime - 18_000; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/WeatherEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/WeatherEnvironment.java new file mode 100644 index 0000000..5d16e1f --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/WeatherEnvironment.java @@ -0,0 +1,32 @@ +package fathertoast.specialmobs.common.config.util.environment.time; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.EnumEnvironment; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class WeatherEnvironment extends EnumEnvironment { + /** Values match up to the vanilla weather command. */ + public enum Value { CLEAR, RAIN, THUNDER } + + public WeatherEnvironment( Value value ) { super( value ); } + + public WeatherEnvironment( Value value, boolean invert ) { super( value, invert ); } + + public WeatherEnvironment( AbstractConfigField field, String line ) { super( field, line, Value.values() ); } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_BIOME_CATEGORY; } + + /** @return Returns true if this environment matches the provided environment. */ + @Override + public boolean matches( World world, @Nullable BlockPos pos ) { + if( world.getLevelData().isThundering() ) return (VALUE == Value.CLEAR) == INVERT; // Thunder implies rain + if( world.getLevelData().isRaining() ) return (VALUE == Value.RAIN) != INVERT; + return (VALUE == Value.CLEAR) != INVERT; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/WorldTimeEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/WorldTimeEnvironment.java new file mode 100644 index 0000000..799faff --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/WorldTimeEnvironment.java @@ -0,0 +1,29 @@ +package fathertoast.specialmobs.common.config.util.environment.time; + +import fathertoast.specialmobs.common.config.field.AbstractConfigField; +import fathertoast.specialmobs.common.config.field.EnvironmentListField; +import fathertoast.specialmobs.common.config.util.environment.CompareLongEnvironment; +import fathertoast.specialmobs.common.config.util.environment.ComparisonOperator; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class WorldTimeEnvironment extends CompareLongEnvironment { + + public WorldTimeEnvironment( ComparisonOperator op, long value ) { super( op, value ); } + + public WorldTimeEnvironment( AbstractConfigField field, String line ) { super( field, line ); } + + /** @return The minimum value that can be given to the value. */ + @Override + protected long getMinValue() { return 0L; } + + /** @return The string name of this environment, as it would appear in a config file. */ + @Override + public String name() { return EnvironmentListField.ENV_MOON_BRIGHTNESS; } + + /** @return Returns the actual value to compare, or null if there isn't enough information. */ + @Override + public Long getActual( World world, @Nullable BlockPos pos ) { return world.dayTime(); } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/package-info.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/package-info.java new file mode 100644 index 0000000..6782ba4 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/time/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package fathertoast.specialmobs.common.config.util.environment.time; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/package-info.java b/src/main/java/fathertoast/specialmobs/common/config/util/package-info.java new file mode 100644 index 0000000..aa9f9da --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package fathertoast.specialmobs.common.config.util; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobReplacer.java b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobReplacer.java index 359c630..b1152e7 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobReplacer.java +++ b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobReplacer.java @@ -4,7 +4,6 @@ import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.config.Config; import fathertoast.specialmobs.common.entity.MobHelper; import fathertoast.specialmobs.common.util.References; -import mcp.MethodsReturnNonnullByDefault; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.nbt.CompoundNBT; @@ -18,12 +17,9 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import javax.annotation.Nullable; -import javax.annotation.ParametersAreNonnullByDefault; import java.util.ArrayDeque; import java.util.Deque; -@ParametersAreNonnullByDefault -@MethodsReturnNonnullByDefault @Mod.EventBusSubscriber( modid = SpecialMobs.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE ) public final class SpecialMobReplacer { /** List of data for mobs needing replacement. */ diff --git a/src/main/java/fathertoast/specialmobs/common/core/package-info.java b/src/main/java/fathertoast/specialmobs/common/core/package-info.java new file mode 100644 index 0000000..db2795a --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/core/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package fathertoast.specialmobs.common.core; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/core/register/package-info.java b/src/main/java/fathertoast/specialmobs/common/core/register/package-info.java new file mode 100644 index 0000000..e96eecd --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/core/register/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package fathertoast.specialmobs.common.core.register; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file