Merge branch '1.16.5' of https://github.com/FatherToast/SpecialMobs into 1.16.5

This commit is contained in:
Sarinsa 2022-07-25 00:36:46 +02:00
commit 0c8deef601
220 changed files with 4893 additions and 755 deletions

View file

@ -2,7 +2,6 @@ package fathertoast.specialmobs.client;
import fathertoast.specialmobs.common.config.Config;
import fathertoast.specialmobs.common.core.SpecialMobs;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.util.Util;
@ -14,10 +13,6 @@ import net.minecraftforge.fml.ExtensionPoint;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@Mod.EventBusSubscriber( value = Dist.CLIENT, modid = SpecialMobs.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE )
public class ClientEventHandler {

View file

@ -8,7 +8,7 @@ import fathertoast.specialmobs.common.entity.ghast.CorporealShiftGhastEntity;
import fathertoast.specialmobs.common.entity.skeleton.NinjaSkeletonEntity;
import fathertoast.specialmobs.common.entity.witherskeleton.NinjaWitherSkeletonEntity;
import fathertoast.specialmobs.common.entity.zombie.MadScientistZombieEntity;
import mcp.MethodsReturnNonnullByDefault;
import fathertoast.specialmobs.common.entity.zombifiedpiglin.VampireZombifiedPiglinEntity;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ItemRenderer;
import net.minecraft.client.renderer.entity.SpriteRenderer;
@ -23,11 +23,8 @@ import net.minecraftforge.fml.client.registry.RenderingRegistry;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.function.Supplier;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@Mod.EventBusSubscriber( value = Dist.CLIENT, modid = SpecialMobs.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD )
public class ClientRegister {
@ -56,8 +53,11 @@ public class ClientRegister {
// Species overrides
registerSpeciesRenderer( MadScientistZombieEntity.SPECIES, SpecialZombieVillagerRenderer::new );
registerSpeciesRenderer( VampireZombifiedPiglinEntity.SPECIES, SpecialPiglinRenderer::newBothEars );
registerSpeciesRenderer( NinjaSkeletonEntity.SPECIES, NinjaSkeletonRenderer::new );
registerSpeciesRenderer( NinjaWitherSkeletonEntity.SPECIES, NinjaSkeletonRenderer::new );
registerSpeciesRenderer( CorporealShiftGhastEntity.SPECIES, CorporealShiftGhastRenderer::new );
// Other
@ -74,6 +74,7 @@ public class ClientRegister {
RenderingRegistry.registerEntityRenderingHandler( species.entityType.get(), renderFactory );
}
@SuppressWarnings( "SameParameterValue" )
private static <T extends Entity & IRendersAsItem> void registerSpriteRenderer( EntityType<T> entityType, Supplier<Minecraft> minecraftSupplier, float scale, boolean fullBright ) {
ItemRenderer itemRenderer = minecraftSupplier.get().getItemRenderer();
RenderingRegistry.registerEntityRenderingHandler( entityType, ( renderManager ) -> new SpriteRenderer<>( renderManager, itemRenderer, scale, fullBright ) );

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.client.misc;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.client;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -7,49 +7,54 @@ import net.minecraft.client.renderer.model.ModelRenderer;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import java.util.Random;
import java.util.function.Function;
/** Copy of {@link net.minecraft.client.renderer.entity.model.GhastModel} */
@OnlyIn( Dist.CLIENT )
public class CorporealShiftGhastModel<T extends Entity> extends SegmentedModel<T> {
private final ModelRenderer[] tentacles = new ModelRenderer[9];
private final ImmutableList<ModelRenderer> parts;
public CorporealShiftGhastModel() {
ImmutableList.Builder<ModelRenderer> builder = ImmutableList.builder();
ModelRenderer modelrenderer = new ModelRenderer(this, 0, 0);
modelrenderer.addBox(-8.0F, -8.0F, -8.0F, 16.0F, 16.0F, 16.0F);
ModelRenderer modelrenderer = new ModelRenderer( this, 0, 0 );
modelrenderer.addBox( -8.0F, -8.0F, -8.0F, 16.0F, 16.0F, 16.0F );
modelrenderer.y = 17.6F;
builder.add(modelrenderer);
Random random = new Random(1660L);
for (int i = 0; i < this.tentacles.length; ++i) {
this.tentacles[i] = new ModelRenderer(this, 0, 0);
builder.add( modelrenderer );
Random random = new Random( 1660L );
for( int i = 0; i < this.tentacles.length; ++i ) {
this.tentacles[i] = new ModelRenderer( this, 0, 0 );
float f = (((float) (i % 3) - (float) (i / 3 % 2) * 0.5F + 0.25F) / 2.0F * 2.0F - 1.0F) * 5.0F;
float f1 = ((float) (i / 3) / 2.0F * 2.0F - 1.0F) * 5.0F;
int j = random.nextInt(7) + 8;
this.tentacles[i].addBox(-1.0F, 0.0F, -1.0F, 2.0F, (float) j, 2.0F);
int j = random.nextInt( 7 ) + 8;
this.tentacles[i].addBox( -1.0F, 0.0F, -1.0F, 2.0F, (float) j, 2.0F );
this.tentacles[i].x = f;
this.tentacles[i].z = f1;
this.tentacles[i].y = 24.6F;
builder.add(this.tentacles[i]);
builder.add( this.tentacles[i] );
}
this.parts = builder.build();
}
public void setRenderType(Function<ResourceLocation, RenderType> renderTypeFunc) {
public void setRenderType( Function<ResourceLocation, RenderType> renderTypeFunc ) {
this.renderType = renderTypeFunc;
}
public void setupAnim(T ghast, float p_225597_2_, float p_225597_3_, float p_225597_4_, float p_225597_5_, float p_225597_6_) {
for (int i = 0; i < this.tentacles.length; ++i) {
this.tentacles[i].xRot = 0.2F * MathHelper.sin(p_225597_4_ * 0.3F + (float) i) + 0.4F;
@Override
public void setupAnim( T ghast, float p_225597_2_, float p_225597_3_, float p_225597_4_, float p_225597_5_, float p_225597_6_ ) {
for( int i = 0; i < this.tentacles.length; ++i ) {
this.tentacles[i].xRot = 0.2F * MathHelper.sin( p_225597_4_ * 0.3F + (float) i ) + 0.4F;
}
}
@Override
public Iterable<ModelRenderer> parts() {
return this.parts;
}
}
}

View file

@ -7,7 +7,6 @@ import fathertoast.specialmobs.common.core.SpecialMobs;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import fathertoast.specialmobs.common.entity.SpecialMobData;
import fathertoast.specialmobs.common.entity.ghast.CorporealShiftGhastEntity;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererManager;
@ -16,11 +15,8 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.function.Function;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class CorporealShiftGhastRenderer extends MobRenderer<CorporealShiftGhastEntity, CorporealShiftGhastModel<CorporealShiftGhastEntity>> {
@ -47,7 +43,7 @@ public class CorporealShiftGhastRenderer extends MobRenderer<CorporealShiftGhast
@Override
public ResourceLocation getTextureLocation( CorporealShiftGhastEntity entity ) {
final SpecialMobData<?> data = ((ISpecialMob<?>) entity).getSpecialData();
return entity.isCharging() && data.hasOverlayTexture() ? data.getTextureOverlay() : data.getTexture();
return entity.isCharging() && data.getTextureOverlay() != null ? data.getTextureOverlay() : data.getTexture();
}
@Override

View file

@ -5,7 +5,6 @@ import com.mojang.blaze3d.vertex.IVertexBuilder;
import com.mojang.blaze3d.vertex.MatrixApplyingVertexBuilder;
import com.mojang.blaze3d.vertex.VertexBuilderUtils;
import fathertoast.specialmobs.common.entity.ai.INinja;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.*;
@ -30,11 +29,9 @@ import javax.annotation.ParametersAreNonnullByDefault;
import java.util.Random;
import java.util.SortedSet;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class NinjaSkeletonRenderer extends SpecialSkeletonRenderer {
private final BlockRendererDispatcher blockRenderer;
public NinjaSkeletonRenderer( EntityRendererManager rendererManager ) {
@ -45,10 +42,10 @@ public class NinjaSkeletonRenderer extends SpecialSkeletonRenderer {
@Override
public void render( AbstractSkeletonEntity entity, float rotation, float partialTicks,
MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight ) {
INinja ninja = (INinja) entity;
final BlockState disguiseBlock = ninja.getHiddenDragon();
if( disguiseBlock == null ) {
super.render( entity, rotation, partialTicks, matrixStack, buffer, packedLight );
}
@ -61,7 +58,7 @@ public class NinjaSkeletonRenderer extends SpecialSkeletonRenderer {
private void renderBlockDisguise( BlockState block, BlockPos pos, IBlockDisplayReader displayReader, MatrixStack matrixStack, IRenderTypeBuffer buffer, Random random ) {
matrixStack.pushPose();
matrixStack.translate( -0.5, 0.0, -0.5 );
blockRenderer.renderModel(block, pos, displayReader, matrixStack, buffer.getBuffer(RenderType.cutout()), false, random, EmptyModelData.INSTANCE);
blockRenderer.renderModel( block, pos, displayReader, matrixStack, buffer.getBuffer( RenderType.cutout() ), false, random, EmptyModelData.INSTANCE );
matrixStack.popPose();
}
}

View file

@ -2,7 +2,6 @@ package fathertoast.specialmobs.client.renderer.entity;
import com.mojang.blaze3d.matrix.MatrixStack;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.entity.BlazeRenderer;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.entity.monster.BlazeEntity;
@ -10,10 +9,6 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class SpecialBlazeRenderer extends BlazeRenderer {

View file

@ -5,7 +5,6 @@ import fathertoast.specialmobs.client.renderer.entity.layers.SpecialCreeperCharg
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobEyesLayer;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobOverlayLayer;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.entity.CreeperRenderer;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.layers.CreeperChargeLayer;
@ -15,10 +14,6 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class SpecialCreeperRenderer extends CreeperRenderer {

View file

@ -4,7 +4,6 @@ import com.mojang.blaze3d.matrix.MatrixStack;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobEyesLayer;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobOverlayLayer;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.entity.EndermanRenderer;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.layers.EndermanEyesLayer;
@ -14,10 +13,6 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class SpecialEndermanRenderer extends EndermanRenderer {

View file

@ -3,7 +3,6 @@ package fathertoast.specialmobs.client.renderer.entity;
import com.mojang.blaze3d.matrix.MatrixStack;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import fathertoast.specialmobs.common.entity.SpecialMobData;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.GhastRenderer;
import net.minecraft.entity.monster.GhastEntity;
@ -11,10 +10,6 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class SpecialGhastRenderer extends GhastRenderer {
@ -32,7 +27,7 @@ public class SpecialGhastRenderer extends GhastRenderer {
@Override
public ResourceLocation getTextureLocation( GhastEntity entity ) {
final SpecialMobData<?> data = ((ISpecialMob<?>) entity).getSpecialData();
return entity.isCharging() && data.hasOverlayTexture() ? data.getTextureOverlay() : data.getTexture();
return entity.isCharging() && data.getTextureOverlay() != null ? data.getTextureOverlay() : data.getTexture();
}
@Override

View file

@ -2,7 +2,6 @@ package fathertoast.specialmobs.client.renderer.entity;
import com.mojang.blaze3d.matrix.MatrixStack;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.MagmaCubeRenderer;
import net.minecraft.entity.monster.MagmaCubeEntity;
@ -10,10 +9,6 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class SpecialMagmaCubeRenderer extends MagmaCubeRenderer {

View file

@ -4,7 +4,6 @@ import com.mojang.blaze3d.matrix.MatrixStack;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobEyesLayer;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobOverlayLayer;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.PiglinRenderer;
import net.minecraft.client.renderer.entity.model.PiglinModel;
@ -13,10 +12,6 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class SpecialPiglinRenderer extends PiglinRenderer {

View file

@ -3,7 +3,6 @@ package fathertoast.specialmobs.client.renderer.entity;
import com.mojang.blaze3d.matrix.MatrixStack;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobEyesLayer;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.SilverfishRenderer;
import net.minecraft.entity.monster.SilverfishEntity;
@ -11,10 +10,6 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class SpecialSilverfishRenderer extends SilverfishRenderer {

View file

@ -4,7 +4,6 @@ import com.mojang.blaze3d.matrix.MatrixStack;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobEyesLayer;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobOverlayLayer;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.SkeletonRenderer;
import net.minecraft.client.renderer.entity.model.SkeletonModel;
@ -13,10 +12,6 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class SpecialSkeletonRenderer extends SkeletonRenderer {

View file

@ -3,7 +3,6 @@ package fathertoast.specialmobs.client.renderer.entity;
import com.mojang.blaze3d.matrix.MatrixStack;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobEyesLayer;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.SlimeRenderer;
import net.minecraft.entity.monster.SlimeEntity;
@ -11,10 +10,6 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class SpecialSlimeRenderer extends SlimeRenderer {

View file

@ -3,7 +3,6 @@ package fathertoast.specialmobs.client.renderer.entity;
import com.mojang.blaze3d.matrix.MatrixStack;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobEyesLayer;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.SpiderRenderer;
import net.minecraft.client.renderer.entity.layers.SpiderEyesLayer;
@ -12,10 +11,6 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class SpecialSpiderRenderer extends SpiderRenderer<SpiderEntity> {

View file

@ -4,7 +4,6 @@ import com.mojang.blaze3d.matrix.MatrixStack;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobEyesLayer;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobOverlayLayer;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.WitchRenderer;
import net.minecraft.client.renderer.entity.model.WitchModel;
@ -13,10 +12,6 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class SpecialWitchRenderer extends WitchRenderer {

View file

@ -4,7 +4,6 @@ import com.mojang.blaze3d.matrix.MatrixStack;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobEyesLayer;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobOverlayLayer;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.ZombieRenderer;
import net.minecraft.client.renderer.entity.model.ZombieModel;
@ -13,33 +12,29 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class SpecialZombieRenderer extends ZombieRenderer {
private final float baseShadowRadius;
public SpecialZombieRenderer(EntityRendererManager rendererManager) {
super(rendererManager);
public SpecialZombieRenderer( EntityRendererManager rendererManager ) {
super( rendererManager );
baseShadowRadius = shadowRadius;
addLayer( new SpecialMobEyesLayer<>( this ) );
addLayer( new SpecialMobOverlayLayer<>( this, new ZombieModel<>( 0.25F, true ) ) );
}
@Override
public ResourceLocation getTextureLocation(ZombieEntity entity ) {
public ResourceLocation getTextureLocation( ZombieEntity entity ) {
return ((ISpecialMob<?>) entity).getSpecialData().getTexture();
}
@Override
protected void scale(ZombieEntity entity, MatrixStack matrixStack, float partialTick ) {
protected void scale( ZombieEntity entity, MatrixStack matrixStack, float partialTick ) {
super.scale( entity, matrixStack, partialTick );
final float scale = ((ISpecialMob<?>) entity).getSpecialData().getRenderScale();
shadowRadius = baseShadowRadius * scale;
matrixStack.scale( scale, scale, scale );
}
}
}

View file

@ -4,7 +4,6 @@ import com.mojang.blaze3d.matrix.MatrixStack;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobEyesLayer;
import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobOverlayLayer;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.entity.BipedRenderer;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.layers.BipedArmorLayer;
@ -14,10 +13,6 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class SpecialZombieVillagerRenderer extends BipedRenderer<ZombieEntity, ZombieVillagerModel<ZombieEntity>> {

View file

@ -12,38 +12,41 @@ import net.minecraft.client.renderer.entity.model.EntityModel;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.entity.monster.CreeperEntity;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn( Dist.CLIENT )
public class SpecialCreeperChargeLayer<T extends CreeperEntity, M extends EntityModel<T>> extends LayerRenderer<T, M> {
private static final ResourceLocation[] CHARGED = new ResourceLocation[] {
new ResourceLocation("textures/entity/creeper/creeper_armor.png"),
SpecialMobs.resourceLoc("textures/entity/creeper/super_charged.png")
new ResourceLocation( "textures/entity/creeper/creeper_armor.png" ),
SpecialMobs.resourceLoc( "textures/entity/creeper/super_charged.png" )
};
private final M model;
public SpecialCreeperChargeLayer( IEntityRenderer<T, M> renderer, M model ) {
super( renderer );
this.model = model;
}
@Override
public void render(MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight, T creeper, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch) {
if (creeper.isPowered() || ((_SpecialCreeperEntity)creeper).isSupercharged()) {
float tickAndPartial = (float)creeper.tickCount + partialTicks;
public void render( MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight, T creeper, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch ) {
if( creeper.isPowered() || ((_SpecialCreeperEntity) creeper).isSupercharged() ) {
float tickAndPartial = (float) creeper.tickCount + partialTicks;
float textureOffset = tickAndPartial * 0.01F;
model.prepareMobModel(creeper, limbSwing, limbSwingAmount, partialTicks);
this.getParentModel().copyPropertiesTo(model);
IVertexBuilder ivertexbuilder = buffer.getBuffer(RenderType.energySwirl(this.getTextureLocation(creeper), textureOffset, textureOffset));
model.setupAnim(creeper, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch);
model.renderToBuffer(matrixStack, ivertexbuilder, packedLight, OverlayTexture.NO_OVERLAY, 0.5F, 0.5F, 0.5F, 1.0F);
model.prepareMobModel( creeper, limbSwing, limbSwingAmount, partialTicks );
this.getParentModel().copyPropertiesTo( model );
IVertexBuilder ivertexbuilder = buffer.getBuffer( RenderType.energySwirl( this.getTextureLocation( creeper ), textureOffset, textureOffset ) );
model.setupAnim( creeper, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch );
model.renderToBuffer( matrixStack, ivertexbuilder, packedLight, OverlayTexture.NO_OVERLAY, 0.5F, 0.5F, 0.5F, 1.0F );
}
}
@Override
protected ResourceLocation getTextureLocation(CreeperEntity creeper) {
return ((_SpecialCreeperEntity)creeper).isSupercharged() ? CHARGED[1] : CHARGED[0];
protected ResourceLocation getTextureLocation( CreeperEntity creeper ) {
return ((_SpecialCreeperEntity) creeper).isSupercharged() ? CHARGED[1] : CHARGED[0];
}
}
}

View file

@ -12,31 +12,34 @@ import net.minecraft.client.renderer.entity.model.EntityModel;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.entity.monster.GhastEntity;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn( Dist.CLIENT )
public class SpecialGhastEyesLayer<T extends GhastEntity, M extends EntityModel<T>> extends AbstractEyesLayer<T, M> {
private final RenderType FALLBACK = RenderType.eyes( new ResourceLocation( "textures/entity/spider_eyes.png" ) );
private final ResourceLocation eyes;
private final ResourceLocation shootEyes;
public SpecialGhastEyesLayer( IEntityRenderer<T, M> renderer, ResourceLocation eyes, ResourceLocation shootEyes ) {
super( renderer );
this.eyes = eyes;
this.shootEyes = shootEyes;
}
@Override
public void render(MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight, T ghast, float limbSwing,
float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch ) {
IVertexBuilder vertexBuilder = buffer.getBuffer( RenderType.entityCutout(ghast.isCharging() ? shootEyes : eyes) );
this.getParentModel().renderToBuffer( matrixStack, vertexBuilder, LightTexture.pack(15, 15), OverlayTexture.NO_OVERLAY,
public void render( MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight, T ghast, float limbSwing,
float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch ) {
final IVertexBuilder vertexBuilder = buffer.getBuffer( RenderType.entityCutout( ghast.isCharging() ? shootEyes : eyes ) );
getParentModel().renderToBuffer( matrixStack, vertexBuilder, LightTexture.pack( 15, 15 ), OverlayTexture.NO_OVERLAY,
1.0F, 1.0F, 1.0F, 1.0F );
}
@Override
public RenderType renderType() {
SpecialMobs.LOG.warn( "Something is attempting to get eye layer 'render type' for some reason! :(" );
return FALLBACK;
}
}
}

View file

@ -4,7 +4,6 @@ import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import fathertoast.specialmobs.common.core.SpecialMobs;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderType;
@ -17,27 +16,20 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class SpecialMobEyesLayer<T extends Entity, M extends EntityModel<T>> extends AbstractEyesLayer<T, M> {
private final RenderType FALLBACK = RenderType.eyes( new ResourceLocation( "textures/entity/spider_eyes.png" ) );
private static final RenderType FALLBACK = RenderType.eyes( new ResourceLocation( "textures/entity/spider_eyes.png" ) );
public SpecialMobEyesLayer( IEntityRenderer<T, M> renderer ) { super( renderer ); }
public SpecialMobEyesLayer( IEntityRenderer<T, M> renderer ) {
super( renderer );
}
@Override
public void render( MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight, T entity, float limbSwing,
float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch ) {
final ResourceLocation eyesTexture = ((ISpecialMob<?>) entity).getSpecialData().getTextureEyes();
if( eyesTexture == null ) return;
//TODO does not work; for some reason, all the transparency renders as white
IVertexBuilder vertexBuilder = buffer.getBuffer( RenderType.eyes(eyesTexture) );
this.getParentModel().renderToBuffer( matrixStack, vertexBuilder, LightTexture.pack(15, 15), OverlayTexture.NO_OVERLAY,
final IVertexBuilder vertexBuilder = buffer.getBuffer( RenderType.eyes( eyesTexture ) );
getParentModel().renderToBuffer( matrixStack, vertexBuilder, LightTexture.pack( 15, 15 ), OverlayTexture.NO_OVERLAY,
1.0F, 1.0F, 1.0F, 1.0F );
}

View file

@ -3,7 +3,6 @@ package fathertoast.specialmobs.client.renderer.entity.layers;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.IEntityRenderer;
@ -15,10 +14,6 @@ import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class SpecialMobOverlayLayer<T extends Entity, M extends EntityModel<T>> extends LayerRenderer<T, M> {

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.client.renderer.entity.layers;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.client.renderer.entity;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -1,6 +1,8 @@
package fathertoast.specialmobs.common.bestiary;
import fathertoast.specialmobs.common.config.field.DoubleField;
import fathertoast.specialmobs.common.config.util.*;
import fathertoast.specialmobs.common.config.util.environment.biome.BiomeCategory;
import fathertoast.specialmobs.common.core.SpecialMobs;
import fathertoast.specialmobs.common.util.References;
import net.minecraft.block.Block;
@ -13,7 +15,10 @@ import net.minecraft.potion.Effects;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.registries.ForgeRegistries;
import java.util.*;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* This class serves solely to store data for mob species in an organized way, providing builder methods as applicable.
@ -22,24 +27,76 @@ import java.util.*;
public class BestiaryInfo {
public enum DefaultWeight {
DEFAULT( 600 ),
DISABLED( 0 ),
LOWEST( DEFAULT.value / 8 ),
LOW( DEFAULT.value / 4 ),
HIGH( DEFAULT.value * 4 ),
HIGHEST( DEFAULT.value * 8 );
DEFAULT( 60.0 ),
DISABLED( 0.0 ),
LOWEST( DEFAULT.value / 8.0 ),
LOW( DEFAULT.value / 4.0 ),
HIGH( DEFAULT.value * 2.5 ),
HIGHEST( DEFAULT.value * 5.0 );
public final int value;
public final double value;
DefaultWeight( int v ) { value = v; }
DefaultWeight( double v ) { value = v; }
}
public enum Theme {
NONE,
FIRE, ICE,
DESERT, WATER,
FOREST, MOUNTAIN,
FISHING
NONE( new EnvironmentList() ),
FIRE( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inUltraWarmDimension().build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).isRaining().canSeeSky().notInDryBiome().build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).isHot().build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).isWarm().build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).isFreezing().build()
) ),
ICE( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inUltraWarmDimension().build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).isFreezing().build(),
EnvironmentEntry.builder( DefaultWeight.LOW.value ).isWarm().build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).isHot().build()
) ),
DESERT( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inUltraWarmDimension().build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inDryBiome().build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inWaterBiome().build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inHumidBiome().build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).isRaining().canSeeSky().build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).belowHalfMoonLight().build()
) ),
WATER( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inUltraWarmDimension().build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inDryBiome().build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inWaterBiome().build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inHumidBiome().build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).isRaining().canSeeSky().build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).aboveHalfMoonLight().build()
) ),
FOREST( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inBiomeCategory( BiomeCategory.TAIGA ).build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inBiomeCategory( BiomeCategory.JUNGLE ).build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inBiomeCategory( BiomeCategory.FOREST ).build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inBiomeCategory( BiomeCategory.SWAMP ).build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).atMaxMoonLight().build()
) ),
MOUNTAIN( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inMountainBiome().build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).aboveMountainLevel().build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).atNoMoonLight().build(),
EnvironmentEntry.builder( DefaultWeight.LOW.value ).belowSeaLevel().build()
) ),
STORM( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).isThundering().build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).isRaining().build(),
EnvironmentEntry.builder( DefaultWeight.LOW.value ).cannotSeeSky().build()
) ),
FISHING( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inWaterBiome().build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).atMaxMoonLight().build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).isRaining().notInDryBiome().build()
) );
public final EnvironmentList value;
Theme( EnvironmentList v ) { value = v.setRange( DoubleField.Range.NON_NEGATIVE ); }
}
/** The spot color for spawn eggs of this species. The base color is determined by the family. */
@ -280,13 +337,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 +408,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;
}
@ -435,6 +492,12 @@ public class BestiaryInfo {
return this;
}
/** Sets the species as NOT damaged by water. */
public Builder waterInsensitive() {
isDamagedByWater = false;
return this;
}
/** Sets the species as leashable (can have a lead attached). */
public Builder leashable() {
allowLeashing = true;
@ -476,6 +539,11 @@ public class BestiaryInfo {
return rangedDamage( damage ).rangedSpread( spread ).rangedWalkSpeed( walkSpeed ).rangedCooldown( cooldown ).rangedMaxRange( range );
}
/** Sets the species ranged attack stats (for a throwing item user). */
public Builder throwAttack( double spread, double walkSpeed, int cooldown, double range ) {
return rangedSpread( spread ).rangedWalkSpeed( walkSpeed ).rangedCooldown( cooldown ).rangedMaxRange( range );
}
/** Sets the species ranged attack stats (for a fireball shooter). */
public Builder fireballAttack( double spread, int charge, int cooldown, double range ) {
return rangedSpread( spread ).rangedCooldown( charge ).rangedMaxCooldown( charge + cooldown ).rangedMaxRange( range );

View file

@ -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<T extends LivingEntity, V extends FamilyConfig> {
/** List of all families, generated to make iteration possible. */
private static final ArrayList<MobFamily<?, ?>> FAMILY_LIST = new ArrayList<>();
@ -44,18 +40,22 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
public static final MobFamily<CreeperEntity, CreeperFamilyConfig> CREEPER = new MobFamily<>( CreeperFamilyConfig::new,
"Creeper", "creepers", 0x0DA70B, new EntityType[] { EntityType.CREEPER },
"Dark", "Death", "Dirt", "Doom", "Drowning", "Ender", "Fire", "Gravel", "Jumping", "Lightning",
"Mini", /*"Scope",*/ "Skeleton", "Splitting"
);
"Mini", "Sand", /*"Scope",*/ "Snow", "Skeleton", "Splitting"
);//TODO scope
public static final MobFamily<ZombieEntity, FamilyConfig> ZOMBIE = new MobFamily<>( FamilyConfig::newLessSpecial,
"Zombie", "zombies", 0x00AFAF, new EntityType[] { EntityType.ZOMBIE, EntityType.HUSK },
"Brute", "Fire", /*"Fishing",*/ "Giant", "Hungry", "Husk", "MadScientist", "Plague"
);
);//TODO fishing
// TODO Drowned family and zombie transform mechanic
// public static final MobFamily<ZombieEntity, FamilyConfig> DROWNED = new MobFamily<>( FamilyConfig::new,
// "Zombie", "zombies", 0x8FF1D7, new EntityType[] { EntityType.DROWNED }, //VR egg: 0x799C65
// "Brute", /*"Fishing",*/ "Giant", "Hungry", "Knight", "Plague"
// );
public static final MobFamily<ZombifiedPiglinEntity, FamilyConfig> ZOMBIFIED_PIGLIN = new MobFamily<>( FamilyConfig::new,
"ZombifiedPiglin", "zombified piglins", 0xEA9393, new EntityType[] { EntityType.ZOMBIFIED_PIGLIN },
"Brute", /*"Fishing",*/ "Giant", "Hungry", "Knight", "Plague", "Vampire"//TODO figure out crossbows
);
);//TODO fishing
public static final MobFamily<AbstractSkeletonEntity, SkeletonFamilyConfig> SKELETON = new MobFamily<>( SkeletonFamilyConfig::new,
"Skeleton", "skeletons", 0xC1C1C1, new EntityType[] { EntityType.SKELETON, EntityType.STRAY },
@ -77,21 +77,21 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
public static final MobFamily<SpiderEntity, FamilyConfig> SPIDER = new MobFamily<>( FamilyConfig::newMoreSpecial,
"Spider", "spiders", 0x342D27, new EntityType[] { EntityType.SPIDER },
"Baby", "Desert", "Flying", "Giant", "Hungry", "Mother", "Pale", "Poison", /*"Water",*/ "Web", "Witch"
"Baby", "Desert", "Fire", "Flying", "Giant", "Hungry", "Mother", "Pale", "Poison", "Water", "Web", "Witch"
);
public static final MobFamily<CaveSpiderEntity, FamilyConfig> CAVE_SPIDER = new MobFamily<>( FamilyConfig::newMoreSpecial,
"CaveSpider", "cave spiders", 0x0C424E, new EntityType[] { EntityType.CAVE_SPIDER },
"Baby", "Flying", "Mother", /*"Water",*/ "Web", "Witch"
"Baby", "Desert", "Fire", "Flying", "Mother", "Pale", "Water", "Web", "Witch"
);
public static final MobFamily<SilverfishEntity, SilverfishFamilyConfig> SILVERFISH = new MobFamily<>( SilverfishFamilyConfig::new,
"Silverfish", "silverfish", 0x6E6E6E, new EntityType[] { EntityType.SILVERFISH },
"Blinding", /*"Fishing",*/ "Flying", "Poison", /*"Puffer",*/ "Tough"
);
"Blinding", "Desiccated", /*"Fishing",*/ "Flying", "Poison", /*"Puffer",*/ "Tough"
);//TODO fishing, puffer
public static final MobFamily<EndermanEntity, FamilyConfig> ENDERMAN = new MobFamily<>( FamilyConfig::new,
"Enderman", "endermen", 0x161616, new EntityType[] { EntityType.ENDERMAN },
"Blinding", "Icy", "Lightning", "Mini", "Mirage", "Thief"
"Blinding", "Flame", "Icy", "Lightning", "Mini", "Mirage", "Runic", "Thief"
);
public static final MobFamily<WitchEntity, WitchFamilyConfig> WITCH = new MobFamily<>( WitchFamilyConfig::new,
@ -101,7 +101,7 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
public static final MobFamily<GhastEntity, GhastFamilyConfig> GHAST = new MobFamily<>( GhastFamilyConfig::new,
"Ghast", "ghasts", 0xF9F9F9, new EntityType[] { EntityType.GHAST },
"Baby", "Fighter", "King", "Queen", "Unholy", "CorporealShift"
"Baby", "CorporealShift", "Fighter", "King", "Queen", "Unholy"
);
public static final MobFamily<BlazeEntity, FamilyConfig> BLAZE = new MobFamily<>( FamilyConfig::new,
@ -196,31 +196,10 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
}
/** Pick a new species from this family, based on the location. */
public Species<? extends T> nextVariant( World world, BlockPos pos ) {
// Build weights for the current location
int totalWeight = 0;
final int[] variantWeights = new int[variants.length];
for( int i = 0; i < variants.length; i++ ) {
final int weight = config.GENERAL.specialVariantWeights[i].get(); //TODO environment exceptions
if( weight > 0 ) {
totalWeight += weight;
variantWeights[i] = weight;
}
}
// Pick one item at random
if( totalWeight > 0 ) {
int weight = world.random.nextInt( totalWeight );
for( int i = 0; i < variants.length; i++ ) {
if( variantWeights[i] > 0 ) {
weight -= variantWeights[i];
if( weight < 0 ) {
return variants[i];
}
}
}
}
return vanillaReplacement;
public Species<? extends T> nextVariant( World world, @Nullable BlockPos pos ) {
final Species<?> species = config.GENERAL.specialVariantList.next( world.random, world, pos );
//noinspection unchecked
return species == null ? vanillaReplacement : (Species<? extends T>) species;
}
@ -238,8 +217,6 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
*
* @see MobFamily
*/
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
public static class Species<T extends LivingEntity> {
/** The special mob family this species belongs to. */
public final MobFamily<? super T, ?> family;

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.common.bestiary;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.common.compat.top;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -24,6 +24,7 @@ public class Config {
/** Performs initial loading of all configs in this mod. */
public static void initialize() {
ToastConfigSpec.freezeFileWatcher = true;
ReadMeConfig.makeReadMe( CONFIG_DIR );
MAIN.SPEC.initialize();
MobFamily.initBestiary();
ToastConfigSpec.freezeFileWatcher = false;

View file

@ -15,20 +15,6 @@ public class MainConfig extends Config.AbstractConfig {
super( dir, fileName,
"This config contains options that apply to the mod as a whole, including some master settings",
"toggles for convenience." );
SPEC.newLine();
SPEC.comment(
"Terminology used in Special Mobs configs:",
" * Mob - An entity that is 'alive', short for \"Mobile\" or MobEntity.",
" * Family - The group of mobs based on (but not including) a particular vanilla mob; e.g., Creepers.",
" * Species - A specific type of mob within a family; e.g., Fire Creepers or vanilla-replacement Creepers.",
" * Vanilla Replacement - The one species within a family that is intended to be a replica of the base vanilla mob.",
" * Special Variant - Any species that is not the family's vanilla replacement. Includes species that are",
" replicas of 'vanilla special variants'; i.e. Husks and Strays.",
" * Mob Replacer - The tool that watches vanilla mob spawns and cancels them to spawn this mod's entities." );
SPEC.newLine();
SPEC.describeAttributeList();
SPEC.newLine();
SPEC.describeRegistryEntryList();
GENERAL = new General( SPEC );
}

View file

@ -0,0 +1,36 @@
package fathertoast.specialmobs.common.config;
import fathertoast.specialmobs.common.config.field.BooleanField;
import java.io.File;
public class ReadMeConfig extends Config.AbstractConfig {
@SuppressWarnings( "SameParameterValue" )
static void makeReadMe( File dir ) { new ReadMeConfig( dir ).SPEC.initialize(); }
/** Builds the config spec that should be used for this config. */
private ReadMeConfig( File dir ) {
super( dir, "__README__", "This file contains helpful information about how to use the config files in this mod." );
SPEC.newLine( 2 );
SPEC.comment(
"Terminology used in Special Mobs configs:",
" * Mob - An entity that is 'alive', short for \"Mobile\" or MobEntity.",
" * Family - The group of mobs based on (but not including) a particular vanilla mob; e.g., Creepers.",
" * Species - A specific type of mob within a family; e.g., Fire Creepers or vanilla-replacement Creepers.",
" * Vanilla Replacement - The one species within a family that is intended to be a replica of the base vanilla mob.",
" * Special Variant - Any species that is not the family's vanilla replacement. Includes species that are",
" replicas of 'vanilla special variants'; i.e. Husks and Strays.",
" * Mob Replacer - The tool that watches vanilla mob spawns and cancels them to spawn this mod's entities." );
SPEC.newLine( 2 );
SPEC.describeAttributeList();
SPEC.newLine( 2 );
SPEC.describeRegistryEntryList();
SPEC.newLine( 2 );
SPEC.describeEnvironmentListPart1of2();
SPEC.newLine();
SPEC.describeEnvironmentListPart2of2();
SPEC.newLine( 4 );
SPEC.define( new BooleanField( "secret_mode", false, (String[]) null ) );
}
}

View file

@ -4,10 +4,12 @@ import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.config.Config;
import fathertoast.specialmobs.common.config.field.BooleanField;
import fathertoast.specialmobs.common.config.field.DoubleField;
import fathertoast.specialmobs.common.config.field.IntField;
import fathertoast.specialmobs.common.config.field.EnvironmentListField;
import fathertoast.specialmobs.common.config.file.ToastConfigSpec;
import fathertoast.specialmobs.common.config.file.TomlHelper;
import fathertoast.specialmobs.common.config.util.ConfigUtil;
import fathertoast.specialmobs.common.config.util.EnvironmentEntry;
import fathertoast.specialmobs.common.config.util.EnvironmentList;
import java.io.File;
import java.util.List;
@ -51,9 +53,9 @@ public class FamilyConfig extends Config.AbstractConfig {
public final DoubleField familyRandomScaling;
public final DoubleField specialVariantChance;
public final DoubleField.EnvironmentSensitive specialVariantChance;
public final IntField[] specialVariantWeights;
public final DoubleField.EnvironmentSensitiveWeightedList<MobFamily.Species<?>> specialVariantList;
General( ToastConfigSpec parent, MobFamily<?, ?> family, double variantChance ) {
super( parent, "general",
@ -72,27 +74,49 @@ public class FamilyConfig extends Config.AbstractConfig {
SPEC.newLine();
specialVariantChance = SPEC.define( new DoubleField( "special_variant_chance", variantChance, DoubleField.Range.PERCENT,
"The chance for " + family.configName + " to spawn as special variants." ) );
// TODO special variant chance exceptions
specialVariantChance = new DoubleField.EnvironmentSensitive(
SPEC.define( new DoubleField( "special_variant_chance.base", variantChance, DoubleField.Range.PERCENT,
"The chance for " + family.configName + " to spawn as special variants." ) ),
SPEC.define( new EnvironmentListField( "special_variant_chance.exceptions", new EnvironmentList(
EnvironmentEntry.builder( (float) variantChance * 0.5F ).beforeDays( 5 ).build(), // Also skips first night's full moon
EnvironmentEntry.builder( (float) variantChance * 2.0F ).atMaxMoonLight().aboveDifficulty( 0.5F ).build(),
EnvironmentEntry.builder( (float) variantChance * 1.5F ).atMaxMoonLight().build(),
EnvironmentEntry.builder( (float) variantChance * 1.5F ).aboveDifficulty( 0.5F ).build() )
.setRange( DoubleField.Range.PERCENT ),
"The chance for " + family.configName + " to spawn as special variants when specific environmental conditions are met." ) )
);
SPEC.newLine();
List<String> comment;
final DoubleField[] baseWeights = new DoubleField[family.variants.length];
final EnvironmentListField[] weightExceptions = new EnvironmentListField[family.variants.length];
// TODO consider wrapping this all up into an 'environment sensitive weighted list' config object? seems handy
comment = TomlHelper.newComment(
"The weight of each " + ConfigUtil.camelCaseToLowerSpace( family.name ) + " species to be chosen as the replacement when",
family.configName + " spawn as special variants. Higher weight is more common." );
comment.add( TomlHelper.multiFieldInfo( IntField.Range.NON_NEGATIVE ) );
comment.add( TomlHelper.multiFieldInfo( DoubleField.Range.NON_NEGATIVE ) );
SPEC.comment( comment );
specialVariantWeights = new IntField[family.variants.length];
for( int i = 0; i < specialVariantWeights.length; i++ ) {
specialVariantWeights[i] = SPEC.define( new IntField(
"weight." + ConfigUtil.camelCaseToLowerUnderscore( family.variants[i].specialVariantName ),
family.variants[i].bestiaryInfo.defaultWeight.value, IntField.Range.NON_NEGATIVE, (String[]) null ) );
for( int i = 0; i < family.variants.length; i++ ) {
baseWeights[i] = SPEC.define( new DoubleField(
"weight." + ConfigUtil.camelCaseToLowerUnderscore( family.variants[i].specialVariantName ) + ".base",
family.variants[i].bestiaryInfo.defaultWeight.value, DoubleField.Range.NON_NEGATIVE, (String[]) null ) );
}
// TODO special variant weights exceptions
SPEC.newLine();
comment = TomlHelper.newComment(
"The weight of each " + ConfigUtil.camelCaseToLowerSpace( family.name ) + " species to be chosen as the replacement when",
family.configName + " spawn as special variants when specific environmental conditions are met. Higher weight is more common." );
comment.add( TomlHelper.multiFieldInfo( DoubleField.Range.NON_NEGATIVE ) );
SPEC.comment( comment );
for( int i = 0; i < family.variants.length; i++ ) {
weightExceptions[i] = SPEC.define( new EnvironmentListField(
"weight." + ConfigUtil.camelCaseToLowerUnderscore( family.variants[i].specialVariantName ) + ".exceptions",
family.variants[i].bestiaryInfo.theme.value, (String[]) null ) );
}
specialVariantList = new DoubleField.EnvironmentSensitiveWeightedList<>( family.variants, baseWeights, weightExceptions );
}
}
}

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.common.config.family;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -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<String> comment ) {
AbstractConfigField( String key, @Nullable List<String> comment ) {
KEY = key;
COMMENT = comment;
}

View file

@ -42,16 +42,10 @@ public class AttributeListField extends GenericField<AttributeList> {
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<String> comment ) {
comment.add( TomlHelper.fieldInfoFormat( "Attribute List", valueDefault,
@ -81,7 +75,7 @@ public class AttributeListField extends GenericField<AttributeList> {
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<AttributeList> {
}
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 ); }
}

View file

@ -28,7 +28,7 @@ public class BlockListField extends GenericField<BlockList> {
}
/** 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<BlockList> {
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<BlockList> {
/** @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 );
}
}
}

View file

@ -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;
}

View file

@ -2,8 +2,12 @@ package fathertoast.specialmobs.common.config.field;
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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
@ -23,12 +27,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;
@ -156,4 +160,89 @@ public class DoubleField extends AbstractConfigField {
return getMin();
}
}
/**
* Represents a double field and an environment exception list, combined into one.
* This has convenience methods for returning the value that should be used based on the environment.
*/
public static class EnvironmentSensitive {
/** The base value. */
private final DoubleField BASE;
/** The environment exceptions list. */
private final EnvironmentListField EXCEPTIONS;
/** Links two fields together as base and exceptions. */
public EnvironmentSensitive( DoubleField base, EnvironmentListField exceptions ) {
BASE = base;
EXCEPTIONS = exceptions;
}
/** @return Returns the config field's value. */
public double get( World world, @Nullable BlockPos pos ) { return EXCEPTIONS.getOrElse( world, pos, BASE ); }
/** @return Treats the config field's value as a percent chance (from 0 to 1) and returns the result of a single roll. */
public boolean rollChance( Random random, World world, @Nullable BlockPos pos ) { return random.nextDouble() < get( world, pos ); }
}
/**
* Represents an environment sensitive list of weighted values. Unlike the normal weighted list, this is just a simple
* wrapper class, and its weights are doubles.
* It sacrifices automation for flexibility, largely to help with the craziness of environment list fields.
*/
public static class EnvironmentSensitiveWeightedList<T> {
private final List<Entry<T>> UNDERLYING_LIST;
/** Links an array of values to two arrays of fields as base weights and exceptions. */
public EnvironmentSensitiveWeightedList( T[] values, DoubleField[] baseWeights, EnvironmentListField[] weightExceptions ) {
if( values.length != baseWeights.length || values.length != weightExceptions.length )
throw new IllegalArgumentException( "All arrays must be equal length!" );
final ArrayList<Entry<T>> list = new ArrayList<>();
for( int i = 0; i < values.length; i++ ) {
list.add( new Entry<>( values[i], new EnvironmentSensitive( baseWeights[i], weightExceptions[i] ) ) );
// Do a bit of error checking; allows us to ignore the possibility of negative weights
if( baseWeights[i].valueMin < 0.0 || weightExceptions[i].valueDefault.getMinValue() < 0.0 ) {
throw new IllegalArgumentException( "Weight is not allowed to be negative! See " +
baseWeights[i].getKey() + " and/or " + weightExceptions[i].getKey() );
}
}
list.trimToSize();
UNDERLYING_LIST = Collections.unmodifiableList( list );
}
/** @return Returns a random item from this weighted list. Null if none of the items have a positive weight. */
@Nullable
public T next( Random random, World world, @Nullable BlockPos pos ) {
// Due to the 'nebulous' nature of environment-based weights, we must recalculate weights for EVERY call
final double[] weights = new double[UNDERLYING_LIST.size()];
double targetWeight = 0.0;
for( int i = 0; i < weights.length; i++ ) {
targetWeight += weights[i] = UNDERLYING_LIST.get( i ).WEIGHT.get( world, pos );
}
if( targetWeight <= 0.0 ) return null;
// Now we unravel the target weight to a random point
targetWeight *= random.nextDouble();
for( int i = 0; i < weights.length; i++ ) {
targetWeight -= weights[i];
if( targetWeight < 0.0 ) return UNDERLYING_LIST.get( i ).VALUE;
}
SpecialMobs.LOG.error( "Environment-sensitive weight list was unable to return a value when it should have! " +
"This is probably due to error in floating point calculations, perhaps try changing the scale of weights." );
return null;
}
private static class Entry<T> {
final T VALUE;
final DoubleField.EnvironmentSensitive WEIGHT;
Entry( T value, DoubleField.EnvironmentSensitive weight ) {
VALUE = value;
WEIGHT = weight;
}
}
}
}

View file

@ -5,10 +5,8 @@ import fathertoast.specialmobs.common.config.util.EntityEntry;
import fathertoast.specialmobs.common.config.util.EntityList;
import fathertoast.specialmobs.common.core.SpecialMobs;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.registries.ForgeRegistries;
import javax.annotation.Nullable;
import java.util.ArrayList;
@ -35,7 +33,7 @@ public class EntityListField extends GenericField<EntityList> {
}
/** 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 );
}
@ -85,16 +83,12 @@ public class EntityListField extends GenericField<EntityList> {
List<String> list = TomlHelper.parseStringList( raw );
List<EntityEntry> entryList = new ArrayList<>();
for( String line : list ) {
EntityEntry entry = parseEntry( line );
if( entry != null ) {
entryList.add( entry );
}
entryList.add( parseEntry( line ) );
}
value = new EntityList( entryList );
}
/** Parses a single entry line and returns a valid result if possible, or null if the entry is completely invalid. */
@Nullable
/** Parses a single entry line and returns the result. */
private EntityEntry parseEntry( final String line ) {
String modifiedLine = line;
@ -110,20 +104,14 @@ public class EntityListField extends GenericField<EntityList> {
// Parse the entity-value array
final String[] args = modifiedLine.split( " " );
final EntityType<? extends Entity> entityType;
final ResourceLocation regKey;
if( REG_KEY_DEFAULT.equalsIgnoreCase( args[0].trim() ) ) {
// Handle the special case of a default entry
entityType = null;
regKey = null;
}
else {
// Normal entry
final ResourceLocation regKey = new ResourceLocation( args[0].trim() );
if( !ForgeRegistries.ENTITIES.containsKey( regKey ) ) {
SpecialMobs.LOG.warn( "Invalid entry for {} \"{}\"! Deleting entry. Invalid entry: {}",
getClass(), getKey(), line );
return null;
}
entityType = ForgeRegistries.ENTITIES.getValue( regKey );
regKey = new ResourceLocation( args[0].trim() );
}
final List<Double> valuesList = new ArrayList<>();
final int reqValues = valueDefault.getRequiredValues();
@ -173,7 +161,7 @@ public class EntityListField extends GenericField<EntityList> {
for( int i = 0; i < values.length; i++ ) {
values[i] = valuesList.get( i );
}
return new EntityEntry( entityType, extendable, values );
return new EntityEntry( this, regKey, extendable, values );
}
/** Parses a single value argument and returns a valid result. */
@ -203,6 +191,36 @@ public class EntityListField extends GenericField<EntityList> {
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.
@ -222,17 +240,21 @@ public class EntityListField extends GenericField<EntityList> {
}
}
// 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;
}
/**
@ -242,8 +264,8 @@ public class EntityListField extends GenericField<EntityList> {
* @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;
}
/**
@ -252,8 +274,8 @@ public class EntityListField extends GenericField<EntityList> {
* 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 );
}
}
}

View file

@ -14,13 +14,13 @@ public class EnumField<T extends Enum<T>> extends GenericField<T> {
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<T extends Enum<T>> extends GenericField<T> {
}
/** @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;

View file

@ -0,0 +1,312 @@
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.*;
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;
import java.util.Locale;
/**
* Represents a config field with an environment list value.
*/
public class EnvironmentListField extends GenericField<EnvironmentList> {
/* 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 and valid name array 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_TERRAIN_DEPTH = "terrain_depth";
public static final String ENV_TERRAIN_SCALE = "terrain_scale";
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
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";
public static final String ENV_POSITION = "position";
// 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";
public static final String ENV_CHUNK_TIME = "chunk_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<String> verboseDescription() {
List<String> 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<String> environmentDescriptions() {
List<String> 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_TERRAIN_DEPTH + " op value\":" );//TODO see if this changes in MC 1.18
comment.add( " Biome's depth parameter. A measure of how high the terrain generates; depth < 0 makes a watery biome. For reference, generally vanilla" );
comment.add( " plateaus are 1.5, mountains are 1, plains are 0.125, swamps are -0.2, rivers are -0.5, oceans are -1, and deep oceans are -1.8." );
comment.add( " \"" + ENV_TERRAIN_SCALE + " op value\":" );
comment.add( " Biome's scale parameter. A measure of how 'wavy' the terrain generates. For reference, generally vanilla mountains are 0.5 and plains are 0.05." );
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[]) BiomeCategory.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/ravines (which may still have direct view of the sky)." );
comment.add( " \"" + ENV_POSITION + " (!)state\":" );
comment.add( " Valid state values: " + TomlHelper.literalList( (Object[]) PositionEnvironment.Value.values() ) );
comment.add( " Miscellaneous conditions that generally do what you expect. For reference, 'near' a village is ~3 chunks, and redstone checks weak power." );
// 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( " For reference, the first day in a new world is always a full moon." );
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." );
comment.add( " \"" + ENV_CHUNK_TIME + " op value\":" );
comment.add( " The total time the chunk has been loaded, 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, @Nullable 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<String> 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.
* <p>
* 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<String> list = TomlHelper.parseStringList( raw );
List<EnvironmentEntry> 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<AbstractEnvironment> conditions = new ArrayList<>();
if( args.length > 1 ) {
final String[] condArgs = args[1].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].toLowerCase( Locale.ROOT ) ) {
// 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_TERRAIN_DEPTH:
return new TerrainDepthEnvironment( this, value );
case ENV_TERRAIN_SCALE:
return new TerrainScaleEnvironment( this, value );
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 );
case ENV_POSITION:
return new PositionEnvironment( 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 );
case ENV_CHUNK_TIME:
return new ChunkTimeEnvironment( 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_TERRAIN_DEPTH, ENV_TERRAIN_SCALE, ENV_RAINFALL, ENV_BIOME_TEMPERATURE, ENV_TEMPERATURE, ENV_BIOME_CATEGORY, ENV_BIOME,
// Position-based
ENV_STRUCTURE, ENV_Y, ENV_Y_FROM_SEA, ENV_POSITION,
// 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, ENV_CHUNK_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 ); }
}

View file

@ -1,5 +1,7 @@
package fathertoast.specialmobs.common.config.field;
import javax.annotation.Nullable;
/**
* Represents a config field with an object value.
* <p>
@ -16,7 +18,7 @@ public abstract class GenericField<T> 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;
}

View file

@ -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;

View file

@ -17,7 +17,7 @@ import javax.annotation.Nullable;
public class LazyRegistryEntryListField<T extends IForgeRegistryEntry<T>> extends RegistryEntryListField<T> {
/** Creates a new field. */
public LazyRegistryEntryListField( String key, RegistryEntryList<T> defaultValue, String... description ) {
public LazyRegistryEntryListField( String key, RegistryEntryList<T> defaultValue, @Nullable String... description ) {
super( key, defaultValue, description );
}

View file

@ -31,7 +31,7 @@ public class RegistryEntryListField<T extends IForgeRegistryEntry<T>> extends Ge
}
/** Creates a new field. */
public RegistryEntryListField( String key, RegistryEntryList<T> defaultValue, String... description ) {
public RegistryEntryListField( String key, RegistryEntryList<T> defaultValue, @Nullable String... description ) {
super( key, defaultValue, description );
}
@ -57,15 +57,18 @@ public class RegistryEntryListField<T extends IForgeRegistryEntry<T>> extends Ge
value = new RegistryEntryList<>( this, valueDefault.getRegistry(), TomlHelper.parseStringList( raw ) );
}
// Convenience methods
/** @return The registry this list draws from. */
public IForgeRegistry<T> getRegistry() { return value.getRegistry(); }
public IForgeRegistry<T> getRegistry() { return get().getRegistry(); }
/** @return The entries in this list. */
public Set<T> getEntries() { return value.getEntries(); }
public Set<T> 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 ); }
}

View file

@ -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;
}

View file

@ -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 );
}

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.common.config.field;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -227,6 +227,30 @@ public class ToastConfigSpec {
}
}
/** Represents an appendix header comment. */
private static class AppendixHeader extends Format {
/** The appendix comment. */
private final List<String> COMMENT;
/** Create a new header action that will insert the opening file comment. */
private AppendixHeader( List<String> comment ) {
COMMENT = comment;
}
/** Called when the config is saved. */
@Override
public void write( ToastTomlWriter writer, CharacterOutput output ) {
writer.decreaseIndentLevel();
writer.writeNewLine( output );
writer.writeNewLine( output );
writer.writeComment( "Appendix:", output );
writer.writeComment( COMMENT, output );
writer.increaseIndentLevel();
}
}
/** Represents a category comment. */
private static class Category extends Format {
/** The category comment. */
@ -355,6 +379,9 @@ public class ToastConfigSpec {
/** @param comment The file comment to insert. */
public void header( List<String> comment ) { ACTIONS.add( new Header( this, comment ) ); }
/** @param comment The appendix comment to insert. */
public void appendixHeader( String... comment ) { ACTIONS.add( new AppendixHeader( TomlHelper.newComment( comment ) ) ); }
/** Inserts a detailed description of how to use the registry entry list field. */
public void describeRegistryEntryList() { ACTIONS.add( new Comment( RegistryEntryListField.verboseDescription() ) ); }
@ -367,6 +394,12 @@ public class ToastConfigSpec {
/** Inserts a detailed description of how to use the block list field. */
public void describeBlockList() { ACTIONS.add( new Comment( BlockListField.verboseDescription() ) ); }
/** Inserts the first part of a detailed description of how to use the environment list field. Should go with the other field descriptions. */
public void describeEnvironmentListPart1of2() { ACTIONS.add( new Comment( EnvironmentListField.verboseDescription() ) ); }
/** Inserts the second and last part of a detailed description of how to use the environment list field. Should go at the bottom of the file. */
public void describeEnvironmentListPart2of2() { ACTIONS.add( new Comment( EnvironmentListField.environmentDescriptions() ) ); }
/**
* @param name The category name.
* @param comment The category comment to insert.

View file

@ -5,6 +5,7 @@ import com.electronwill.nightconfig.core.io.*;
import fathertoast.specialmobs.common.config.field.AbstractConfigField;
import fathertoast.specialmobs.common.core.SpecialMobs;
import javax.annotation.Nullable;
import java.io.Writer;
import java.util.Iterator;
import java.util.List;
@ -85,7 +86,7 @@ public class ToastTomlWriter implements ConfigWriter {
}
/** Writes a literal array of single-line strings. */
public void writeStringArray( List<String> list, CharacterOutput output ) {
public void writeStringArray( @Nullable List<String> list, CharacterOutput output ) {
if( list == null || list.isEmpty() ) {
writeLine( "[]", output );
return;

View file

@ -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<Object> list ) {
public static String literalList( @Nullable List<Object> 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<String> splitKey( String key ) { return StringUtils.split( key, '.' ); }
/** Combines a toml path into a key. */
public static String mergePath( List<String> path ) {
public static String mergePath( @Nullable List<String> 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<String> newComment( String... lines ) {
return new ArrayList<>( Arrays.asList( lines ) );
}
public static ArrayList<String> 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<Object> list ) {
public static String combineList( @Nullable List<Object> list ) {
if( list == null || list.isEmpty() ) return "";
StringBuilder key = new StringBuilder();
for( Object obj : list ) {

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.common.config.file;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.common.config;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,35 @@
package fathertoast.specialmobs.common.config.species;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.config.Config;
import fathertoast.specialmobs.common.config.field.IntField;
import fathertoast.specialmobs.common.config.file.ToastConfigSpec;
import fathertoast.specialmobs.common.config.util.ConfigUtil;
public class DesiccatedSilverfishSpeciesConfig extends SilverfishSpeciesConfig {
public final Desiccated DESICCATED;
/** Builds the config spec that should be used for this config. */
public DesiccatedSilverfishSpeciesConfig( MobFamily.Species<?> species, double spitChance, int minAbsorb, int maxAbsorb ) {
super( species, spitChance );
DESICCATED = new Desiccated( SPEC, species, speciesName, minAbsorb, maxAbsorb );
}
public static class Desiccated extends Config.AbstractCategory {
public final IntField.RandomRange absorbCount;
Desiccated( ToastConfigSpec parent, MobFamily.Species<?> species, String speciesName, int minAbsorb, int maxAbsorb ) {
super( parent, ConfigUtil.camelCaseToLowerUnderscore( species.specialVariantName ),
"Options specific to " + speciesName + "." );
absorbCount = new IntField.RandomRange(
SPEC.define( new IntField( "water_absorbed.min", minAbsorb, IntField.Range.NON_NEGATIVE,
"The minimum and maximum (inclusive) number of water blocks " + speciesName + " can absorb." ) ),
SPEC.define( new IntField( "water_absorbed.max", maxAbsorb, IntField.Range.NON_NEGATIVE ) )
);
}
}
}

View file

@ -38,8 +38,6 @@ public class SpeciesConfig extends Config.AbstractConfig {
super( FamilyConfig.dir( species.family ), fileName( species ),
String.format( "This config contains options that apply only to the %s %s species.",
variantName( species ), ConfigUtil.camelCaseToLowerSpace( species.family.name ) ) );
SPEC.newLine();
SPEC.comment( "See the main mod config for common terms and how to use special field types like Attribute List or Registry List." );
speciesName = variantName( species ) + " " + species.family.configName;

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.common.config.species;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -2,6 +2,7 @@ package fathertoast.specialmobs.common.config.util;
import fathertoast.specialmobs.common.config.field.AbstractConfigField;
import fathertoast.specialmobs.common.core.SpecialMobs;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.attributes.Attribute;
import net.minecraft.entity.ai.attributes.AttributeModifierMap;
@ -10,12 +11,17 @@ import net.minecraft.entity.ai.attributes.ModifiableAttributeInstance;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.registries.ForgeRegistries;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* One attribute-operation-value entry in an attribute list. Uses a 'lazy' implementation so the attribute registry is
* not polled until this entry is actually used.
* <p>
* See also {@link ConfigDrivenAttributeModifierMap}
*/
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@SuppressWarnings( "unused" )
public class AttributeEntry {
/** The field containing this entry. We save a reference to help improve error/warning reports. */
@ -48,7 +54,7 @@ public class AttributeEntry {
}
/** Creates an entry with the specified values. */
public AttributeEntry( AbstractConfigField field, ResourceLocation regKey, boolean multiply, double value ) {
public AttributeEntry( @Nullable AbstractConfigField field, @Nullable ResourceLocation regKey, boolean multiply, double value ) {
FIELD = field;
ATTRIBUTE_KEY = regKey;
MULTIPLY = multiply;
@ -95,7 +101,7 @@ public class AttributeEntry {
}
/** Applies this attribute change to the attribute instance. Assumes that the instance is for this entry's target attribute. */
private void apply( ModifiableAttributeInstance attributeInstance ) {
private void apply( @Nullable ModifiableAttributeInstance attributeInstance ) {
if( attributeInstance == null )
throw new IllegalStateException( "Attempted to modify non-registered attribute " + ATTRIBUTE_KEY );

View file

@ -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() );

View file

@ -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<? extends Comparable<?>> 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<Block, BlockState> 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() );
}

View file

@ -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 ) );
}
}
}

View file

@ -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++; }
}

View file

@ -1,5 +1,6 @@
package fathertoast.specialmobs.common.config.util;
import fathertoast.specialmobs.common.config.field.AbstractConfigField;
import fathertoast.specialmobs.common.config.field.EntityListField;
import fathertoast.specialmobs.common.core.SpecialMobs;
import net.minecraft.entity.Entity;
@ -8,58 +9,84 @@ import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.registries.ForgeRegistries;
import javax.annotation.Nullable;
/**
* One entity-value entry in an entity list.
* One entity-value entry in an entity list. Uses a 'lazy' implementation so the entity type registry is
* not polled until this entry is actually used.
*/
@SuppressWarnings( "unused" )
public class EntityEntry {
/** The entity type this entry is defined for. If this is null, then this entry will match any entity. */
public final EntityType<? extends Entity> TYPE;
/** The field containing this entry. We save a reference to help improve error/warning reports. */
private final AbstractConfigField FIELD;
/** The registry key for this entry's entity type. */
public final ResourceLocation ENTITY_KEY;
/** True if this should check for instanceof the entity class (as opposed to equals). */
public final boolean EXTEND;
/** The values given to this entry. Null for comparison objects. */
public final double[] VALUES;
/** The entity type this entry is defined for. If this is null, then this entry will match any entity. */
private EntityType<? extends Entity> entityType;
/** The class this entry is defined for. This is not assigned until a world has been loaded. */
Class<? extends Entity> entityClass;
/** Creates an entry used to compare entity classes internally with the entries in an entity list. */
EntityEntry( Entity entity ) {
TYPE = entity.getType();
FIELD = null;
ENTITY_KEY = entity.getType().getRegistryName();
EXTEND = false;
VALUES = null;
entityType = entity.getType();
entityClass = entity.getClass();
}
/** Creates an entry with the specified values that acts as a default matching all entity types. Used for creating default configs. */
public EntityEntry( double... values ) {
this( null, true, values );
}
public EntityEntry( double... values ) { this( null, true, values ); }
/** Creates an extendable entry with the specified values. Used for creating default configs. */
public EntityEntry( EntityType<? extends Entity> entityType, double... values ) {
this( entityType, true, values );
}
public EntityEntry( @Nullable EntityType<? extends Entity> type, double... values ) { this( type, true, values ); }
/** Creates an entry with the specified values. Used for creating default configs. */
public EntityEntry( EntityType<? extends Entity> entityType, boolean extend, double... values ) {
TYPE = entityType;
public EntityEntry( @Nullable EntityType<? extends Entity> type, boolean extend, double... values ) {
this( null, type == null ? null : type.getRegistryName(), extend, values );
entityType = type;
}
/** Creates an entry with the specified values. */
public EntityEntry( @Nullable AbstractConfigField field, @Nullable ResourceLocation regKey, boolean extend, double... values ) {
FIELD = field;
ENTITY_KEY = regKey;
EXTEND = extend;
VALUES = values;
}
/** @return Loads the entity type from registry. Returns true if successful. */
private boolean validate() {
if( entityType != null || ENTITY_KEY == null ) return true; // Null entity key means this is a default entry
if( !ForgeRegistries.ENTITIES.containsKey( ENTITY_KEY ) ) {
SpecialMobs.LOG.warn( "Invalid entry for {} \"{}\"! Invalid entry: {}",
FIELD.getClass(), FIELD.getKey(), ENTITY_KEY.toString() );
return false;
}
entityType = ForgeRegistries.ENTITIES.getValue( ENTITY_KEY );
return true;
}
/** Called on this entry before using it to check if the entity class has been determined, and loads the class if it has not been. */
void checkClass( World world ) {
if( TYPE != null && entityClass == null ) {
if( validate() && entityType != null && entityClass == null ) {
try {
final Entity entity = TYPE.create( world );
final Entity entity = entityType.create( world );
if( entity != null ) {
entityClass = entity.getClass();
entity.remove();
}
}
catch( Exception ex ) {
SpecialMobs.LOG.warn( "Failed to load class of entity type {}!", TYPE );
SpecialMobs.LOG.warn( "Failed to load class of entity type {}!", entityType );
ex.printStackTrace();
}
}
@ -72,9 +99,11 @@ public class EntityEntry {
* entries for the same class in a list.
*/
public boolean contains( EntityEntry entry ) {
if( !validate() ) return false;
// Handle default entries
if( TYPE == null ) return true;
if( entry.TYPE == null ) return false;
if( entityType == null ) return true;
if( entry.entityType == null ) return false;
// Same entity, but non-extendable is more specific
if( entityClass == entry.entityClass ) return !entry.EXTEND;
// Extendable entry, check if the other is for a subclass
@ -91,8 +120,7 @@ public class EntityEntry {
@Override
public String toString() {
// Start with the entity type registry key
ResourceLocation resource = TYPE == null ? null : ForgeRegistries.ENTITIES.getKey( TYPE );
StringBuilder str = new StringBuilder( resource == null ? EntityListField.REG_KEY_DEFAULT : resource.toString() );
StringBuilder str = new StringBuilder( ENTITY_KEY == null ? EntityListField.REG_KEY_DEFAULT : ENTITY_KEY.toString() );
// Insert "specific" prefix if not extendable
if( !EXTEND ) {
str.insert( 0, '~' );

View file

@ -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 );
}

View file

@ -0,0 +1,319 @@
package fathertoast.specialmobs.common.config.util;
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.PositionEnvironment;
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 fathertoast.specialmobs.common.config.util.environment.time.*;
import net.minecraft.util.RegistryKey;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.DimensionType;
import net.minecraft.world.World;
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<AbstractEnvironment> 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.
* <p>
* 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();
}
// Builder Implementation
/** Creates a new entry builder. The value is rounded to 2 decimal place precision. */
public static Builder builder( float value ) { return new Builder( Math.round( value * 100.0 ) / 100.0 ); }
/** Creates a new entry builder. */
public static Builder builder( double value ) { return new Builder( value ); }
/**
* Builder class used to simplify creation of environment entries for default configs,
* with shortcuts for the most commonly used environments.
* <p>
* Keep in mind that ALL conditions in an entry must be satisfied for it to be chosen.
*/
public static class Builder {
private final double VALUE;
private final ArrayList<AbstractEnvironment> 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
/** Check if the dimension type causes water to instantly vaporize and has faster lava flow. */
public Builder inUltraWarmDimension() { return inDimensionWithProperty( DimensionPropertyEnvironment.Value.ULTRAWARM, false ); }
/** Check if the dimension type causes water to instantly vaporize and has faster lava flow. */
public Builder notInUltraWarmDimension() { return inDimensionWithProperty( DimensionPropertyEnvironment.Value.ULTRAWARM, true ); }
/** Check if the dimension type allows clocks, compasses, and beds to work. */
public Builder inNaturalDimension() { return inDimensionWithProperty( DimensionPropertyEnvironment.Value.NATURAL, false ); }
/** Check if the dimension type allows clocks, compasses, and beds to work. */
public Builder notInNaturalDimension() { return inDimensionWithProperty( DimensionPropertyEnvironment.Value.NATURAL, true ); }
private Builder inDimensionWithProperty( DimensionPropertyEnvironment.Value property, boolean invert ) {
return in( new DimensionPropertyEnvironment( property, invert ) );
}
public Builder inOverworld() { return inDimensionType( DimensionType.OVERWORLD_LOCATION, false ); }
public Builder notInOverworld() { return inDimensionType( DimensionType.OVERWORLD_LOCATION, true ); }
public Builder inNether() { return inDimensionType( DimensionType.NETHER_LOCATION, false ); }
public Builder notInNether() { return inDimensionType( DimensionType.NETHER_LOCATION, true ); }
public Builder inTheEnd() { return inDimensionType( DimensionType.END_LOCATION, false ); }
public Builder notInTheEnd() { return inDimensionType( DimensionType.END_LOCATION, true ); }
private Builder inDimensionType( RegistryKey<DimensionType> dimType, boolean invert ) { return in( new DimensionTypeEnvironment( dimType, invert ) ); }
/** Check if the dimension type is vanilla (registered with the "minecraft" namespace). */
public Builder inVanillaDimension() { return in( new DimensionTypeGroupEnvironment( new ResourceLocation( "" ), false ) ); }
/** Check if the dimension type is vanilla (registered with the "minecraft" namespace). */
public Builder notInVanillaDimension() { return in( new DimensionTypeGroupEnvironment( new ResourceLocation( "" ), true ) ); }
// Biome-based
/** Check if the biome is a water or beach biome (depth at or below sea level). */
public Builder inWaterBiome() { return isDepth( ComparisonOperator.LESS_OR_EQUAL, 0.0F ); }
/** Check if the biome is a water or beach biome (depth at or below sea level). */
public Builder notInWaterBiome() { return isDepth( ComparisonOperator.LESS_OR_EQUAL.invert(), 0.0F ); }
/** Check if the biome has relatively high elevation. */
public Builder inMountainBiome() { return isDepth( ComparisonOperator.GREATER_OR_EQUAL, 0.4F ); }
/** Check if the biome has relatively high elevation. */
public Builder notInMountainBiome() { return isDepth( ComparisonOperator.GREATER_OR_EQUAL.invert(), 0.4F ); }
private Builder isDepth( ComparisonOperator op, float value ) { return in( new TerrainDepthEnvironment( op, value ) ); }
/** Check if the biome is relatively flat. */
public Builder inFlatBiome() { return isScale( ComparisonOperator.LESS_OR_EQUAL, 0.1F ); }
/** Check if the biome is relatively flat. */
public Builder notInFlatBiome() { return isScale( ComparisonOperator.LESS_OR_EQUAL.invert(), 0.1F ); }
/** Check if the biome is relatively hilly. */
public Builder inHillyBiome() { return isScale( ComparisonOperator.GREATER_OR_EQUAL, 0.3F ); }
/** Check if the biome is relatively hilly. */
public Builder notInHillyBiome() { return isScale( ComparisonOperator.GREATER_OR_EQUAL.invert(), 0.3F ); }
private Builder isScale( ComparisonOperator op, float value ) { return in( new TerrainScaleEnvironment( op, value ) ); }
/** Check if the biome has rain disabled. */
public Builder inDryBiome() { return inAvgRainfall( ComparisonOperator.EQUAL_TO, 0.0F ); }
/** Check if the biome has rain disabled. */
public Builder notInDryBiome() { return inAvgRainfall( ComparisonOperator.EQUAL_TO.invert(), 0.0F ); }
/** Check if the biome's humidity hinders fire spread. */
public Builder inHumidBiome() { return inAvgRainfall( ComparisonOperator.GREATER_THAN, 0.85F ); }
/** Check if the biome's humidity hinders fire spread. */
public Builder notInHumidBiome() { return inAvgRainfall( ComparisonOperator.GREATER_THAN.invert(), 0.85F ); }
private Builder inAvgRainfall( ComparisonOperator op, float value ) { return in( new RainfallEnvironment( op, value ) ); }
/** Check if the temperature is freezing. */
public Builder isFreezing() { return in( new TemperatureEnvironment( true ) ); }
/** Check if the temperature is freezing. */
public Builder isNotFreezing() { return in( new TemperatureEnvironment( false ) ); }
/** Check if the temperature is warm (disables snow golem trails). */
public Builder isWarm() { return isTemperature( ComparisonOperator.GREATER_OR_EQUAL, 0.8F ); }
/** Check if the temperature is warm (disables snow golem trails). */
public Builder isNotWarm() { return isTemperature( ComparisonOperator.GREATER_OR_EQUAL.invert(), 0.8F ); }
/** Check if the temperature is hot (causes snow golems to die). */
public Builder isHot() { return isTemperature( ComparisonOperator.GREATER_THAN, 1.0F ); }
/** Check if the temperature is hot (causes snow golems to die). */
public Builder isNotHot() { return isTemperature( ComparisonOperator.GREATER_THAN.invert(), 1.0F ); }
private Builder isTemperature( ComparisonOperator op, float value ) { return in( new TemperatureEnvironment( op, value ) ); }
/** Check if the biome belongs to a specific category. */
public Builder inBiomeCategory( BiomeCategory category ) { return in( new BiomeCategoryEnvironment( category, false ) ); }
/** Check if the biome belongs to a specific category. */
public Builder notInBiomeCategory( BiomeCategory category ) { return in( new BiomeCategoryEnvironment( category, true ) ); }
// Position-based
/** Check if the position is inside a particular structure. See {@link Structure}. */
public Builder inStructure( Structure<?> structure ) { return in( new StructureEnvironment( structure, false ) ); }
/** Check if the position is inside a particular structure. See {@link Structure}. */
public Builder notInStructure( Structure<?> structure ) { return in( new StructureEnvironment( structure, true ) ); }
/** Check if diamond/redstone ore can generate at the position. */
public Builder belowDiamondLevel() { return belowY( 15 ); } // TODO update ore-based logic in 1.18
/** Check if diamond/redstone ore can generate at the position. */
public Builder aboveDiamondLevel() { return aboveY( 15 ); }
/** Check if gold/lapis ore can generate at the position. */
public Builder belowGoldLevel() { return belowY( 33 ); }
/** Check if gold/lapis ore can generate at the position. */
public Builder aboveGoldLevel() { return aboveY( 33 ); }
private Builder belowY( int y ) { return in( new YEnvironment( ComparisonOperator.LESS_OR_EQUAL, y ) ); }
private Builder aboveY( int y ) { return in( new YEnvironment( ComparisonOperator.LESS_OR_EQUAL.invert(), y ) ); }
/** Check if the position is above/below sea level. */
public Builder belowSeaLevel() { return in( new YFromSeaEnvironment( ComparisonOperator.LESS_OR_EQUAL, 0 ) ); }
/** Check if the position is above/below sea level. */
public Builder aboveSeaLevel() { return in( new YFromSeaEnvironment( ComparisonOperator.LESS_OR_EQUAL.invert(), 0 ) ); }
/** Check if the position is above/below the average sea floor. */
public Builder belowSeaFloor() { return in( new YFromSeaEnvironment( ComparisonOperator.LESS_OR_EQUAL, -18 ) ); }
/** Check if the position is above/below the average sea floor. */
public Builder aboveSeaFloor() { return in( new YFromSeaEnvironment( ComparisonOperator.LESS_OR_EQUAL.invert(), -18 ) ); }
/** Check if the position is above/below 'mountain level' - that is, high enough to die from falling to sea level. */
public Builder aboveMountainLevel() { return in( new YFromSeaEnvironment( ComparisonOperator.GREATER_OR_EQUAL, 24 ) ); }
/** Check if the position is above/below 'mountain level' - that is, high enough to die from falling to sea level. */
public Builder belowMountainLevel() { return in( new YFromSeaEnvironment( ComparisonOperator.GREATER_OR_EQUAL.invert(), 24 ) ); }
public Builder canSeeSky() { return inPositionWithState( PositionEnvironment.Value.CAN_SEE_SKY, false ); }
public Builder cannotSeeSky() { return inPositionWithState( PositionEnvironment.Value.CAN_SEE_SKY, true ); }
public Builder isNearVillage() { return inPositionWithState( PositionEnvironment.Value.IS_NEAR_VILLAGE, false ); }
public Builder isNotNearVillage() { return inPositionWithState( PositionEnvironment.Value.IS_NEAR_VILLAGE, true ); }
public Builder isNearRaid() { return inPositionWithState( PositionEnvironment.Value.IS_NEAR_RAID, false ); }
public Builder isNotNearRaid() { return inPositionWithState( PositionEnvironment.Value.IS_NEAR_RAID, true ); }
private Builder inPositionWithState( PositionEnvironment.Value state, boolean invert ) { return in( new PositionEnvironment( state, invert ) ); }
// Time-based
/** Check if the special difficulty multiplier is above a threshold (0 - 1). */
public Builder aboveDifficulty( float percent ) { return in( new SpecialDifficultyEnvironment( ComparisonOperator.GREATER_OR_EQUAL, percent ) ); }
/** Check if the special difficulty multiplier is above a threshold (0 - 1). */
public Builder belowDifficulty( float percent ) { return in( new SpecialDifficultyEnvironment( ComparisonOperator.GREATER_OR_EQUAL.invert(), percent ) ); }
public Builder isRaining() { return inWeather( WeatherEnvironment.Value.RAIN, false ); } // same as "is not clear"
public Builder isNotRaining() { return inWeather( WeatherEnvironment.Value.RAIN, true ); } // same as "is clear"
public Builder isThundering() { return inWeather( WeatherEnvironment.Value.THUNDER, false ); }
public Builder isNotThundering() { return inWeather( WeatherEnvironment.Value.THUNDER, true ); }
private Builder inWeather( WeatherEnvironment.Value weather, boolean invert ) { return in( new WeatherEnvironment( weather, invert ) ); }
public Builder atMaxMoonLight() { return in( new MoonPhaseEnvironment( MoonPhaseEnvironment.Value.FULL, false ) ); }
public Builder aboveHalfMoonLight() { return fromHalfMoonLight( ComparisonOperator.GREATER_THAN ); }
public Builder atHalfMoonLight() { return fromHalfMoonLight( ComparisonOperator.EQUAL_TO ); }
public Builder belowHalfMoonLight() { return fromHalfMoonLight( ComparisonOperator.LESS_THAN ); }
public Builder atNoMoonLight() { return in( new MoonPhaseEnvironment( MoonPhaseEnvironment.Value.NEW, false ) ); }
private Builder fromHalfMoonLight( ComparisonOperator op ) { return in( new MoonBrightnessEnvironment( op, 0.5F ) ); }
public Builder isNight() { return in( new DayTimeEnvironment( DayTimeEnvironment.Value.NIGHT, false ) ); }
public Builder isDay() { return in( new DayTimeEnvironment( DayTimeEnvironment.Value.DAY, false ) ); }
/** Check if the time is during a quarter of the night centered on midnight. */
public Builder isNearMidnight() { return in( new TimeFromMidnightEnvironment( ComparisonOperator.LESS_OR_EQUAL, 1_500 ) ); }
/** Check if the time is during a quarter of the night centered on midnight. */
public Builder isNotNearMidnight() { return in( new TimeFromMidnightEnvironment( ComparisonOperator.LESS_OR_EQUAL.invert(), 1_500 ) ); }
/** Check if the world time is after a certain number of days. */
public Builder afterDays( int days ) { return in( new WorldTimeEnvironment( ComparisonOperator.GREATER_OR_EQUAL, 24_000L * days ) ); }
/** Check if the world time is after a certain number of days. */
public Builder beforeDays( int days ) { return in( new WorldTimeEnvironment( ComparisonOperator.GREATER_OR_EQUAL.invert(), 24_000L * days ) ); }
/** Check if the chunk inhabited time is after a certain number of days. */
public Builder afterDaysInChunk( int days ) { return in( new ChunkTimeEnvironment( ComparisonOperator.GREATER_OR_EQUAL, 24_000L * days ) ); }
/** Check if the chunk inhabited time is after a certain number of days. */
public Builder beforeDaysInChunk( int days ) { return in( new ChunkTimeEnvironment( ComparisonOperator.GREATER_OR_EQUAL.invert(), 24_000L * days ) ); }
}
}

View file

@ -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.
* <p>
* 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<EnvironmentEntry> entries ) { this( entries.toArray( new EnvironmentEntry[0] ) ); }
/**
* Create a new environment list from an array of entries. Used for creating default configs.
* <p>
* 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<String> toStringList() {
// Create a list of the entries in string format
final List<String> 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; }
}

View file

@ -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<T extends IForgeRegistryEntry<T>> implements IStr
/** The list used to write back to file. */
protected final List<String> PRINT_LIST = new ArrayList<>();
protected RegistryEntryList( IForgeRegistry<T> registry ) {
REGISTRY = registry;
}
protected RegistryEntryList( IForgeRegistry<T> 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<T extends IForgeRegistryEntry<T>> 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<T extends IForgeRegistryEntry<T>> 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 ) {

View file

@ -13,10 +13,8 @@ import java.util.*;
* <p>
* Creates a config field for each item so weights can be defined by the user.
*/
@SuppressWarnings( "unused" )
public class WeightedList<T extends WeightedList.Value> {
/** The spec used by this config that defines the file's format. */
protected final ToastConfigSpec SPEC;
/** The weighted entries in this list. */
private final List<Entry<T>> ENTRIES;
/** The total weight of all entries in this list. */
@ -25,20 +23,20 @@ public class WeightedList<T extends WeightedList.Value> {
/**
* Creates a new weighted list config option and registers it and any needed definitions with the spec.
*/
public WeightedList( ToastConfigSpec parent, String key, T[] values, String... description ) {
this( parent, key, Arrays.asList( values ), description );
public WeightedList( ToastConfigSpec SPEC, String key, T[] values, @Nullable String... description ) {
this( SPEC, key, Arrays.asList( values ), description );
}
/**
* Creates a new weighted list config option and registers it and any needed definitions with the spec.
*/
public WeightedList( ToastConfigSpec parent, String key, Iterable<T> values, String... description ) {
SPEC = parent;
public WeightedList( ToastConfigSpec SPEC, String key, Iterable<T> values, @Nullable String... description ) {
final IntField.Range fieldRange = IntField.Range.NON_NEGATIVE;
List<String> comment = TomlHelper.newComment( description );
comment.add( TomlHelper.multiFieldInfo( fieldRange ) );
SPEC.comment( comment );
if( description != null ) {
List<String> comment = TomlHelper.newComment( description );
comment.add( TomlHelper.multiFieldInfo( fieldRange ) );
SPEC.comment( comment );
}
// Define each value's weight field and connect the value to its weight in an entry
List<Entry<T>> list = new ArrayList<>();
@ -110,6 +108,7 @@ public class WeightedList<T extends WeightedList.Value> {
default int getDefaultWeight() { return 1; }
/** @return Returns the comment for this object. */
@Nullable
default String[] getComment() { return null; }
}
}

View file

@ -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 );
}

View file

@ -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 );
}

View file

@ -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 );
}

View file

@ -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 );
}

View file

@ -0,0 +1,76 @@
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; }
/** @return A convenience method that returns the opposite comparison operator only if the passed value is true. */
public ComparisonOperator invert( boolean invert ) { return invert ? invert() : this; }
/** @return The opposite comparison operator. */
public ComparisonOperator invert() {
switch( this ) {
case LESS_THAN: return GREATER_OR_EQUAL;
case LESS_OR_EQUAL: return GREATER_THAN;
case GREATER_THAN: return LESS_OR_EQUAL;
case GREATER_OR_EQUAL: return LESS_THAN;
case EQUAL_TO: return NOT_EQUAL_TO;
case NOT_EQUAL_TO: return EQUAL_TO;
}
throw new IllegalStateException( "Inversion implementation is invalid! :(" );
}
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;
}
}

View file

@ -0,0 +1,76 @@
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<T> 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, 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<Registry<T>> 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<T> 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;
}
}

View file

@ -0,0 +1,85 @@
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<T> 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<T> registryEntries;
/** The value of ConfigUtil#DYNAMIC_REGISTRY_VERSION at the time of last poll. */
private byte version = -1;
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<Registry<T>> 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<T> getRegistryEntries( ServerWorld world ) {
if( version != ConfigUtil.DYNAMIC_REGISTRY_VERSION ) {
version = ConfigUtil.DYNAMIC_REGISTRY_VERSION;
registryEntries = new ArrayList<>();
final Registry<T> 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;
}
}

View file

@ -0,0 +1,39 @@
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<T extends Enum<T>> 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, 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 ); }
}

View file

@ -0,0 +1,57 @@
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<T extends IForgeRegistryEntry<T>> 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, 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<T> 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;
}
}

View file

@ -0,0 +1,69 @@
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<T extends IForgeRegistryEntry<T>> 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<T> registryEntries;
public RegistryGroupEnvironment( T regEntry, boolean invert ) {
//noinspection ConstantConditions
this( regEntry.getRegistryName(), invert );
}
public RegistryGroupEnvironment( ResourceLocation regKey, boolean invert ) {
FIELD = null;
INVERT = invert;
NAMESPACE = regKey.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<T> getRegistry();
/** @return The registry entries. */
protected final List<T> 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;
}
}

View file

@ -0,0 +1,31 @@
package fathertoast.specialmobs.common.config.util.environment.biome;
import net.minecraft.world.biome.Biome;
/**
* Used to wrap the vanilla enum Biome.Category so that it can be safely used in configs.
* The declared names should match the string passed into vanilla enums' constructors so that both enums serialize identically.
*/
public enum BiomeCategory {
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;
BiomeCategory( Biome.Category base ) { BASE = base; }
}

View file

@ -0,0 +1,26 @@
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 javax.annotation.Nullable;
public class BiomeCategoryEnvironment extends EnumEnvironment<BiomeCategory> {
public BiomeCategoryEnvironment( BiomeCategory value, boolean invert ) { super( value, invert ); }
public BiomeCategoryEnvironment( AbstractConfigField field, String line ) { super( field, line, BiomeCategory.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;
}
}

View file

@ -0,0 +1,34 @@
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<Biome> {
public BiomeEnvironment( RegistryKey<Biome> 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<Registry<Biome>> 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;
}
}

View file

@ -0,0 +1,44 @@
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.ResourceLocation;
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<Biome> {
public BiomeGroupEnvironment( RegistryKey<Biome> biome, boolean invert ) { this( biome.getRegistryName(), invert ); }
public BiomeGroupEnvironment( ResourceLocation regKey, boolean invert ) { super( regKey, 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<Registry<Biome>> 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<Biome> entries = getRegistryEntries( world );
for( Biome entry : entries ) {
if( entry.equals( target ) ) return !INVERT;
}
}
return INVERT;
}
}

View file

@ -0,0 +1,28 @@
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.ComparisonOperator;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import javax.annotation.Nullable;
public class BiomeTemperatureEnvironment extends TemperatureEnvironment {
public BiomeTemperatureEnvironment( boolean freezing ) { super( freezing ); }
public BiomeTemperatureEnvironment( ComparisonOperator op, float value ) { super( op, value ); }
public BiomeTemperatureEnvironment( 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_TEMPERATURE; }
/** @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();
}
}

View file

@ -0,0 +1,39 @@
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;
if( COMPARATOR == ComparisonOperator.NOT_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();
}
}

View file

@ -0,0 +1,50 @@
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 final float FREEZING_POINT = 0.15F;
public static String handleTempInput( String line ) {
if( line.equalsIgnoreCase( FREEZING ) )
return ComparisonOperator.LESS_THAN + " " + FREEZING_POINT;
if( line.equalsIgnoreCase( "!" + FREEZING ) )
return ComparisonOperator.LESS_THAN.invert() + " " + FREEZING_POINT;
return line;
}
public TemperatureEnvironment( boolean freezing ) {
this( ComparisonOperator.LESS_THAN.invert( !freezing ), FREEZING_POINT );
}
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 == FREEZING_POINT ) return FREEZING;
if( COMPARATOR == ComparisonOperator.LESS_THAN.invert() && VALUE == FREEZING_POINT ) 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 );
}
}

View file

@ -0,0 +1,27 @@
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 TerrainDepthEnvironment extends CompareFloatEnvironment {
public TerrainDepthEnvironment( ComparisonOperator op, float value ) { super( op, value ); }
public TerrainDepthEnvironment( 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_TERRAIN_DEPTH; }
/** @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 ).getDepth();
}
}

View file

@ -0,0 +1,27 @@
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 TerrainScaleEnvironment extends CompareFloatEnvironment {
public TerrainScaleEnvironment( ComparisonOperator op, float value ) { super( op, value ); }
public TerrainScaleEnvironment( 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_TERRAIN_SCALE; }
/** @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 ).getScale();
}
}

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.common.config.util.environment.biome;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,49 @@
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<DimensionPropertyEnvironment.Value> {
/**
* Represents all boolean values defined by dimension type, named to match data pack format.
*
* @see <a href="https://minecraft.fandom.com/wiki/Custom_dimension#Syntax">Data pack format (Minecraft Wiki)</a>
*/
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<DimensionType, Boolean> SUPPLIER;
Value( Function<DimensionType, Boolean> supplier ) { SUPPLIER = supplier; }
public boolean of( DimensionType dimType ) { return SUPPLIER.apply( dimType ); }
}
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; }
}

View file

@ -0,0 +1,34 @@
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<DimensionType> {
public DimensionTypeEnvironment( RegistryKey<DimensionType> 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<Registry<DimensionType>> 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;
}
}

View file

@ -0,0 +1,42 @@
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.ResourceLocation;
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<DimensionType> {
public DimensionTypeGroupEnvironment( RegistryKey<DimensionType> dimType, boolean invert ) { this( dimType.getRegistryName(), invert ); }
public DimensionTypeGroupEnvironment( ResourceLocation regKey, boolean invert ) { super( regKey, 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<Registry<DimensionType>> 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<DimensionType> entries = getRegistryEntries( world );
for( DimensionType entry : entries ) {
if( entry.equals( target ) ) return !INVERT;
}
return INVERT;
}
}

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.common.config.util.environment.dimension;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.common.config.util.environment;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,45 @@
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.EnumEnvironment;
import net.minecraft.tags.FluidTags;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.server.ServerWorld;
import javax.annotation.Nullable;
import java.util.function.BiFunction;
public class PositionEnvironment extends EnumEnvironment<PositionEnvironment.Value> {
public enum Value {
CAN_SEE_SKY( ( world, pos ) -> pos != null && world.canSeeSky( pos ) ),
IS_IN_VILLAGE( ( world, pos ) -> pos != null && world instanceof ServerWorld && ((ServerWorld) world).isVillage( pos ) ),
IS_NEAR_VILLAGE( ( world, pos ) -> pos != null && world instanceof ServerWorld &&
((ServerWorld) world).isCloseToVillage( pos, 3 ) ),
IS_NEAR_RAID( ( world, pos ) -> pos != null && world instanceof ServerWorld && ((ServerWorld) world).isRaided( pos ) ),
IS_IN_WATER( ( world, pos ) -> pos != null && world.getFluidState( pos ).is( FluidTags.WATER ) ),
IS_IN_LAVA( ( world, pos ) -> pos != null && world.getFluidState( pos ).is( FluidTags.LAVA ) ),
IS_IN_FLUID( ( world, pos ) -> pos != null && !world.getFluidState( pos ).isEmpty() ),
HAS_REDSTONE_POWER( ( world, pos ) -> pos != null && world.getDirectSignalTo( pos ) > 0 );
private final BiFunction<World, BlockPos, Boolean> SUPPLIER;
Value( BiFunction<World, BlockPos, Boolean> supplier ) { SUPPLIER = supplier; }
public boolean of( World world, @Nullable BlockPos pos ) { return SUPPLIER.apply( world, pos ); }
}
public PositionEnvironment( Value value, boolean invert ) { super( value, invert ); }
public PositionEnvironment( 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_POSITION; }
/** @return Returns true if this environment matches the provided environment. */
@Override
public boolean matches( World world, @Nullable BlockPos pos ) { return VALUE.of( world, pos ) != INVERT; }
}

View file

@ -0,0 +1,36 @@
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<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<Structure<?>> 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;
}
}

View file

@ -0,0 +1,47 @@
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.ResourceLocation;
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<Structure<?>> {
public StructureGroupEnvironment( Structure<?> structure, boolean invert ) { super( structure, invert ); }
public StructureGroupEnvironment( ResourceLocation regKey, boolean invert ) { super( regKey, 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<Structure<?>> 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<Structure<?>> entries = getRegistryEntries();
for( Structure<?> entry : entries ) {
if( structureManager.getStructureAt( pos, false, entry ).isValid() ) return !INVERT;
}
}
return INVERT;
}
}

View file

@ -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(); }
}

View file

@ -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(); }
}

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.common.config.util.environment.position;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

Some files were not shown because too many files have changed in this diff Show more