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

 Conflicts:
	src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java
This commit is contained in:
Sarinsa 2022-08-15 23:24:11 +02:00
commit 471f4b4378
137 changed files with 3387 additions and 282 deletions

View file

@ -3,15 +3,14 @@ package fathertoast.specialmobs.client;
import fathertoast.specialmobs.client.renderer.entity.family.*;
import fathertoast.specialmobs.client.renderer.entity.projectile.BugSpitRenderer;
import fathertoast.specialmobs.client.renderer.entity.projectile.SpecialFishingBobberRenderer;
import fathertoast.specialmobs.client.renderer.entity.species.CorporealShiftGhastRenderer;
import fathertoast.specialmobs.client.renderer.entity.species.NinjaSkeletonRenderer;
import fathertoast.specialmobs.client.renderer.entity.species.PotionSlimeRenderer;
import fathertoast.specialmobs.client.renderer.entity.species.SpecialZombieVillagerRenderer;
import fathertoast.specialmobs.client.renderer.entity.species.*;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.config.Config;
import fathertoast.specialmobs.common.core.SpecialMobs;
import fathertoast.specialmobs.common.core.register.SMEntities;
import fathertoast.specialmobs.common.entity.creeper.EnderCreeperEntity;
import fathertoast.specialmobs.common.entity.ghast.CorporealShiftGhastEntity;
import fathertoast.specialmobs.common.entity.silverfish.PufferSilverfishEntity;
import fathertoast.specialmobs.common.entity.skeleton.NinjaSkeletonEntity;
import fathertoast.specialmobs.common.entity.slime.PotionSlimeEntity;
import fathertoast.specialmobs.common.entity.witherskeleton.NinjaWitherSkeletonEntity;
@ -54,6 +53,7 @@ public class ClientRegister {
// Family-based renderers
registerFamilyRenderers( MobFamily.CREEPER, SpecialCreeperRenderer::new );
registerFamilyRenderers( MobFamily.ZOMBIE, SpecialZombieRenderer::new );
registerFamilyRenderers( MobFamily.DROWNED, SpecialDrownedRenderer::new );
registerFamilyRenderers( MobFamily.ZOMBIFIED_PIGLIN, SpecialPiglinRenderer::newMissingRightEar );
registerFamilyRenderers( MobFamily.SKELETON, SpecialSkeletonRenderer::new );
registerFamilyRenderers( MobFamily.WITHER_SKELETON, SpecialSkeletonRenderer::new );
@ -68,6 +68,8 @@ public class ClientRegister {
registerFamilyRenderers( MobFamily.BLAZE, SpecialBlazeRenderer::new );
// Species overrides
registerSpeciesRenderer( EnderCreeperEntity.SPECIES, EnderCreeperRenderer::new );
registerSpeciesRenderer( MadScientistZombieEntity.SPECIES, SpecialZombieVillagerRenderer::new );
registerSpeciesRenderer( VampireZombifiedPiglinEntity.SPECIES, SpecialPiglinRenderer::newBothEars );
@ -76,6 +78,8 @@ public class ClientRegister {
registerSpeciesRenderer( PotionSlimeEntity.SPECIES, PotionSlimeRenderer::new );
registerSpeciesRenderer( PufferSilverfishEntity.SPECIES, ShortSilverfishRenderer::new );
registerSpeciesRenderer( CorporealShiftGhastEntity.SPECIES, CorporealShiftGhastRenderer::new );
// Other

View file

@ -21,10 +21,10 @@ public class SpecialCreeperRenderer extends CreeperRenderer {
public SpecialCreeperRenderer( EntityRendererManager rendererManager ) {
super( rendererManager );
baseShadowRadius = shadowRadius;
// Get rid of this one since we have our own implementation
layers.removeIf( ( layer ) -> layer instanceof CreeperChargeLayer );
baseShadowRadius = shadowRadius;
addLayer( new SpecialMobEyesLayer<>( this ) );
addLayer( new SpecialMobOverlayLayer<>( this, new CreeperModel<>( 0.25F ) ) );
addLayer( new SpecialCreeperChargeLayer<>( this, new CreeperModel<>( 2.0F ) ) );

View file

@ -0,0 +1,44 @@
package fathertoast.specialmobs.client.renderer.entity.family;
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 net.minecraft.client.renderer.entity.DrownedRenderer;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.layers.DrownedOuterLayer;
import net.minecraft.client.renderer.entity.model.DrownedModel;
import net.minecraft.entity.monster.DrownedEntity;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn( Dist.CLIENT )
public class SpecialDrownedRenderer extends DrownedRenderer {
private final float baseShadowRadius;
public SpecialDrownedRenderer( EntityRendererManager rendererManager ) {
super( rendererManager );
baseShadowRadius = shadowRadius;
// Get rid of this one since we have our own implementation
layers.removeIf( ( layer ) -> layer instanceof DrownedOuterLayer );
addLayer( new SpecialMobEyesLayer<>( this ) );
addLayer( new SpecialMobOverlayLayer<>( this, new DrownedModel<>( 0.25F, 0.0F, 64, 64 ) ) );
}
@Override
public ResourceLocation getTextureLocation( DrownedEntity entity ) {
return ((ISpecialMob<?>) entity).getSpecialData().getTexture();
}
@Override
protected void scale( DrownedEntity 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

@ -21,7 +21,7 @@ public class SpecialZombieRenderer extends ZombieRenderer {
super( rendererManager );
baseShadowRadius = shadowRadius;
addLayer( new SpecialMobEyesLayer<>( this ) );
addLayer( new SpecialMobOverlayLayer<>( this, new ZombieModel<>( 0.25F, true ) ) );
addLayer( new SpecialMobOverlayLayer<>( this, new ZombieModel<>( 0.25F, false ) ) );
}
@Override

View file

@ -0,0 +1,30 @@
package fathertoast.specialmobs.client.renderer.entity.species;
import fathertoast.specialmobs.client.renderer.entity.family.SpecialCreeperRenderer;
import fathertoast.specialmobs.common.entity.creeper.EnderCreeperEntity;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.entity.monster.CreeperEntity;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import java.util.Random;
@OnlyIn( Dist.CLIENT )
public class EnderCreeperRenderer extends SpecialCreeperRenderer {
private static final double VIBE_STR = 0.02;
private final Random random = new Random();
public EnderCreeperRenderer( EntityRendererManager rendererManager ) { super( rendererManager ); }
@Override
public Vector3d getRenderOffset( CreeperEntity entity, float partialTicks ) {
if( ((EnderCreeperEntity) entity).isCreepy() ) {
return new Vector3d( random.nextGaussian() * VIBE_STR,
0.0, random.nextGaussian() * VIBE_STR );
}
return super.getRenderOffset( entity, partialTicks );
}
}

View file

@ -0,0 +1,27 @@
package fathertoast.specialmobs.client.renderer.entity.species;
import fathertoast.specialmobs.client.renderer.entity.family.SpecialSilverfishRenderer;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.entity.monster.SilverfishEntity;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn( Dist.CLIENT )
public class ShortSilverfishRenderer extends SpecialSilverfishRenderer {
private static final float FORWARD_OFFSET = 0.4F;
public ShortSilverfishRenderer( EntityRendererManager rendererManager ) {
super( rendererManager );
}
@Override
public Vector3d getRenderOffset( SilverfishEntity entity, float partialTicks ) {
final float angle = MathHelper.lerp( partialTicks, entity.yRotO, entity.yRot ) * (float) Math.PI / 180.0F;
final float forwardX = -MathHelper.sin( angle );
final float forwardZ = MathHelper.cos( angle );
return new Vector3d( forwardX * FORWARD_OFFSET, 0.0, forwardZ * FORWARD_OFFSET );
}
}

View file

@ -12,6 +12,7 @@ import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.potion.Effect;
import net.minecraft.potion.Effects;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.biome.Biomes;
import net.minecraftforge.registries.ForgeRegistries;
import javax.annotation.Nullable;
@ -55,7 +56,7 @@ public class BestiaryInfo {
) ),
DESERT( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inUltraWarmDimension().build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inDryBiome().build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inNaturalDimension().inDryBiome().build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inWaterBiome().build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inHumidBiome().build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).isRaining().canSeeSky().build(),
@ -63,7 +64,7 @@ public class BestiaryInfo {
) ),
WATER( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inUltraWarmDimension().build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inDryBiome().build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inNaturalDimension().inDryBiome().build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inWaterBiome().build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inHumidBiome().build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).isRaining().canSeeSky().build(),
@ -74,6 +75,7 @@ public class BestiaryInfo {
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.HIGHEST.value ).inBiome( Biomes.CRIMSON_FOREST ).build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).atMaxMoonLight().build()
) ),
MOUNTAIN( new EnvironmentList(
@ -150,7 +152,7 @@ public class BestiaryInfo {
private BestiaryInfo( int eggColor, float scale, DefaultWeight weight, Theme spawnTheme, List<AttributeEntry> attributes,
ResourceLocation tex, ResourceLocation eyeTex, ResourceLocation ovrTex,
int xp, int regen, double fallDmg, boolean fireImm, boolean burnImm, boolean drownImm, boolean pushImm,
boolean waterDmg, boolean leash, boolean plateImm, Block[] blockImm, Effect[] effectImm,
boolean waterDmg, boolean leash, boolean plateImm, Object[] blockImm, Object[] effectImm,
double raDmg, double raVar, double raSpd, int raCD, int raMCD, double raRng ) {
eggSpotsColor = eggColor;
baseScale = scale;
@ -173,8 +175,8 @@ public class BestiaryInfo {
isDamagedByWater = waterDmg;
allowLeashing = leash;
ignorePressurePlates = plateImm;
immuneToStickyBlocks = new RegistryEntryList<>( ForgeRegistries.BLOCKS, blockImm );
immuneToPotions = new RegistryEntryList<>( ForgeRegistries.POTIONS, effectImm );
immuneToStickyBlocks = new LazyRegistryEntryList<>( ForgeRegistries.BLOCKS, blockImm );
immuneToPotions = new LazyRegistryEntryList<>( ForgeRegistries.POTIONS, effectImm );
rangedAttackDamage = raDmg;
rangedAttackSpread = raVar;
rangedWalkSpeed = raSpd;
@ -213,8 +215,8 @@ public class BestiaryInfo {
private boolean isDamagedByWater;
private boolean allowLeashing;
private boolean ignorePressurePlates;
private final ArrayList<Block> immuneToStickyBlocks = new ArrayList<>();
private final ArrayList<Effect> immuneToPotions = new ArrayList<>();
private final ArrayList<Object> immuneToStickyBlocks = new ArrayList<>();
private final ArrayList<Object> immuneToPotions = new ArrayList<>();
private double rangedAttackDamage = -1.0;
private double rangedAttackSpread = -1.0;
private double rangedWalkSpeed = -1.0;
@ -270,7 +272,7 @@ public class BestiaryInfo {
return new BestiaryInfo( eggSpotsColor, baseScale, defaultWeight, spawnTheme, attributes, texture, eyesTexture, overlayTexture,
experience, healTime, fallDamageMultiplier, isImmuneToFire, isImmuneToBurning, canBreatheInWater, ignoreWaterPush, isDamagedByWater,
allowLeashing, ignorePressurePlates, immuneToStickyBlocks.toArray( new Block[0] ), immuneToPotions.toArray( new Effect[0] ),
allowLeashing, ignorePressurePlates, immuneToStickyBlocks.toArray(), immuneToPotions.toArray(),
rangedAttackDamage, rangedAttackSpread, rangedWalkSpeed, rangedAttackCooldown, rangedAttackMaxCooldown, rangedAttackMaxRange );
}
@ -431,7 +433,7 @@ public class BestiaryInfo {
//--------------- Creature Type Templates ----------------
/** Sets the standard species stats implied by being undead. */
public Builder undead() { return effectImmune( Effects.REGENERATION, Effects.POISON ); }
public Builder undead() { return drownImmune().effectImmune( Effects.REGENERATION, Effects.POISON ); }
/** Sets the standard species stats implied by being a spider. */
public Builder spider() { return webImmune().effectImmune( Effects.POISON ); }
@ -521,14 +523,20 @@ public class BestiaryInfo {
/** Sets the species as cobweb immune. */
public Builder webImmune() { return stickyBlockImmune( Blocks.COBWEB ); }
/** Sets the species as immune to a specific list of sticky blocks. */
public Builder stickyBlockImmune( Block... blocks ) {
/**
* Sets the species as immune to a specific list of sticky blocks.
* Acceptable argument types are {@code Block}, {@code RegistryObject<Block>}, {@code ResourceLocation}, or {@code String}.
*/
public Builder stickyBlockImmune( Object... blocks ) {
immuneToStickyBlocks.addAll( Arrays.asList( blocks ) );
return this;
}
/** Sets the species as immune to a specific list of effects. */
public Builder effectImmune( Effect... effects ) {
/**
* Sets the species as immune to a specific list of effects.
* Acceptable argument types are {@code Effect}, {@code RegistryObject<Effect>}, {@code ResourceLocation}, or {@code String}.
*/
public Builder effectImmune( Object... effects ) {
immuneToPotions.addAll( Arrays.asList( effects ) );
return this;
}
@ -566,6 +574,9 @@ public class BestiaryInfo {
/** Converts the entity to a fishing rod user by disabling unused ranged attack stats (for a bow user). */
public Builder convertBowToFishing() { return rangedDamage( -1.0 ).rangedWalkSpeed( -1.0 ); }
/** Converts the entity to a fishing rod user by disabling unused ranged attack stats (for a throwing item user). */
public Builder convertThrowToFishing() { return rangedWalkSpeed( -1.0 ); }
/** Converts the entity to a fishing rod user by disabling unused ranged attack stats (for a spit shooter). */
public Builder convertSpitToFishing() { return rangedDamage( -1.0 ).rangedMaxCooldown( -1 ); }

View file

@ -2,6 +2,7 @@ package fathertoast.specialmobs.common.bestiary;
import fathertoast.specialmobs.common.config.family.*;
import fathertoast.specialmobs.common.config.species.SpeciesConfig;
import fathertoast.specialmobs.common.config.util.ConfigUtil;
import fathertoast.specialmobs.common.core.register.SMEntities;
import fathertoast.specialmobs.common.core.register.SMItems;
import fathertoast.specialmobs.common.util.AnnotationHelper;
@ -47,11 +48,10 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
"Zombie", "zombies", 0x00AFAF, new EntityType[] { EntityType.ZOMBIE, EntityType.HUSK },
"Brute", "Fire", "Fishing", "Frozen", "Giant", "Hungry", "Husk", "MadScientist", "Plague"
);
// TODO Drowned family and zombie transform mechanic
// public static final MobFamily<DrownedEntity, FamilyConfig> DROWNED = new MobFamily<>( FamilyConfig::new,
// "Drowned", "drowned", 0x8FF1D7, new EntityType[] { EntityType.DROWNED }, //VR egg: 0x799C65
// "Brute", "Fishing", "Giant", "Hungry", "Knight", "Plague"
// );// ice/fire themes? (cold/warm ocean)
public static final MobFamily<DrownedEntity, FamilyConfig> DROWNED = new MobFamily<>( FamilyConfig::new,
"Drowned", "drowned", 0x8FF1D7, new EntityType[] { EntityType.DROWNED },
"Abyssal", "Brute", "Fishing", /*"Frozen",*/ "Giant", "Hungry", "Knight", "Plague"//, "Tropical"
); //TODO Textures! - brute, hungry, plague, frozen, tropical
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
@ -87,7 +87,7 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
public static final MobFamily<SilverfishEntity, SilverfishFamilyConfig> SILVERFISH = new MobFamily<>( SilverfishFamilyConfig::new,
"Silverfish", "silverfish", 0x6E6E6E, new EntityType[] { EntityType.SILVERFISH },
"Albino", "Blinding", "Desiccated", "Fire", "Fishing", "Flying", "Poison", "Puffer", "Tough"
);//TODO puffer
);
public static final MobFamily<EndermanEntity, FamilyConfig> ENDERMAN = new MobFamily<>( FamilyConfig::new,
"Enderman", "endermen", 0x161616, new EntityType[] { EntityType.ENDERMAN },
@ -197,9 +197,14 @@ 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, @Nullable BlockPos pos ) {
final Species<?> species = config.GENERAL.specialVariantList.next( world.random, world, pos );
return nextVariant( world, pos, null, vanillaReplacement );
}
/** Pick a new species from this family, based on the location. */
public Species<? extends T> nextVariant( World world, @Nullable BlockPos pos, @Nullable Function<Species<?>, Boolean> selector, Species<? extends T> fallback ) {
final Species<?> species = config.GENERAL.specialVariantList.next( world.random, world, pos, selector );
//noinspection unchecked
return species == null ? vanillaReplacement : (Species<? extends T>) species;
return species == null ? fallback : (Species<? extends T>) species;
}
@ -218,6 +223,13 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
* @see MobFamily
*/
public static class Species<T extends LivingEntity> {
/** Maps each species's entity type back to the species. */
private static final Map<EntityType<?>, Species<?>> TYPE_TO_SPECIES_MAP = new HashMap<>();
/** @return The entity type's species, or null if the entity type does not represent any mob species. */
@Nullable
public static Species<?> of( EntityType<?> entityType ) { return TYPE_TO_SPECIES_MAP.get( entityType ); }
/** The special mob family this species belongs to. */
public final MobFamily<? super T, ?> family;
/** The name of this special variant; or null for vanilla replacement species. */
@ -238,6 +250,9 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
/** This species's config. */
public final SpeciesConfig config;
/** The scale of this species's height in relation to the base vanilla entity's height. */
private float heightScale = -1.0F;
/** Constructs a new mob species. For vanilla replacements, the variant name is null. */
private Species( MobFamily<? super T, ?> parentFamily, String packageRoot, @Nullable String variantName ) {
final boolean vanillaReplacement = variantName == null;
@ -297,5 +312,31 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
.clientTrackingRange( original.clientTrackingRange() ).updateInterval( original.updateInterval() )
.setShouldReceiveVelocityUpdates( original.trackDeltas() );
}
/** Registers this species's spawn placement and links the (now loaded) entity type to this species. */
public void registerSpawnPlacement() {
TYPE_TO_SPECIES_MAP.put( entityType.get(), this );
AnnotationHelper.registerSpawnPlacement( this );
}
/** @return The plural name used to refer to this species in unlocalized situations; e.g. config comments. */
public String getConfigName() {
return (specialVariantName == null ? "vanilla replacement " :
ConfigUtil.camelCaseToLowerSpace( specialVariantName ) + " ") + family.configName;
}
/** @return The singular name used to refer to this species in unlocalized situations; e.g. config comments. */
public String getConfigNameSingular() {
return (specialVariantName == null ? "vanilla replacement " :
ConfigUtil.camelCaseToLowerSpace( specialVariantName ) + " ") + ConfigUtil.camelCaseToLowerSpace( family.name );
}
/** @return The height scale. Used to calculate eye height for families that are not auto-scaled. */
public float getHeightScale() {
if( heightScale < 0.0F ) {
heightScale = entityType.get().getHeight() / family.replaceableTypes[0].getHeight();
}
return heightScale;
}
}
}

View file

@ -88,6 +88,22 @@ public @interface SpecialMob {
@Target( ElementType.METHOD )
@interface AttributeSupplier { }
/**
* OVERRIDABLE. This is called during registration to register the species's spawn placement.
* 'Overridable' static methods inherit from their superclass if not defined in a subclass, but must be defined somewhere.
* This is 'overridable' because some species may have a different natural spawn placement from the rest of their family.
* <p>
* The annotated method must have a signature that follows the pattern:
* <p>
* {@code public static void METHOD_NAME( MobFamily.Species<?> species )}
*
* @see fathertoast.specialmobs.common.event.NaturalSpawnManager
* @see net.minecraft.entity.EntitySpawnPlacementRegistry
*/
@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.METHOD )
@interface SpawnPlacementRegistrar { }
/**
* REQUIRED. This is called during data generation to build the mod's default lang files.
* This is not 'overridable' because all species must have unique names.

View file

@ -2,14 +2,21 @@ package fathertoast.specialmobs.common.config;
import fathertoast.specialmobs.common.config.field.BooleanField;
import fathertoast.specialmobs.common.config.field.DoubleField;
import fathertoast.specialmobs.common.config.field.EnvironmentListField;
import fathertoast.specialmobs.common.config.field.IntField;
import fathertoast.specialmobs.common.config.file.ToastConfigSpec;
import fathertoast.specialmobs.common.config.util.ConfigUtil;
import fathertoast.specialmobs.common.config.util.EnvironmentEntry;
import fathertoast.specialmobs.common.config.util.EnvironmentList;
import fathertoast.specialmobs.common.config.util.RestartNote;
import net.minecraft.world.gen.feature.structure.Structure;
import java.io.File;
public class MainConfig extends Config.AbstractConfig {
public final General GENERAL;
public final NaturalSpawning NATURAL_SPAWNING;
/** Builds the config spec that should be used for this config. */
MainConfig( File dir, String fileName ) {
@ -18,11 +25,13 @@ public class MainConfig extends Config.AbstractConfig {
"toggles for convenience." );
GENERAL = new General( SPEC );
NATURAL_SPAWNING = new NaturalSpawning( SPEC );
}
public static class General extends Config.AbstractCategory {
public final BooleanField enableMobReplacement;
public final BooleanField enableNaturalSpawning;
public final BooleanField masterVanillaReplacement;
public final DoubleField masterRandomScaling;
@ -39,6 +48,8 @@ public class MainConfig extends Config.AbstractConfig {
"Whether the Mob Replacer is enabled. This 'hijacks' vanilla mob spawns to use as its own.",
"The Mob Replacer is the traditional spawn method for Special Mobs which allows everything that spawns",
"valid vanilla mobs (e.g. dungeon spawners) to spawn this mod's mobs based on your configs instead." ) );
enableNaturalSpawning = SPEC.define( new BooleanField( "enable_added_natural_spawning", true,
"Whether the natural spawning category (see below) is enabled." ) );
SPEC.newLine();
@ -63,4 +74,115 @@ public class MainConfig extends Config.AbstractConfig {
"You must restart the client for changes to this setting to take effect." ) );
}
}
public static class NaturalSpawning extends Config.AbstractCategory {
public final DoubleField caveSpiderSpawnMultiplier;
public final DoubleField.EnvironmentSensitive caveSpiderSpawnChance;
public final IntField drowningCreeperOceanWeight;
public final IntField drowningCreeperRiverWeight;
public final IntField blueberrySlimeOceanWeight;
public final IntField blueberrySlimeRiverWeight;
public final IntField witherSkeletonNetherWeight;
public final IntField witherSkeletonSoulSandValleyWeight;
public final IntField blazeNetherWeight;
public final IntField blazeBasaltDeltasWeight;
public final IntField fireCreeperNetherWeight;
public final IntField fireZombieNetherWeight;
public final IntField fireSpiderNetherWeight;
public final DoubleField enderCreeperSpawnMultiplier;
NaturalSpawning( ToastConfigSpec parent ) {
super( parent, "natural_spawning",
"Options to customize the additional natural monster spawning from this mod.",
"Most changes to options in this category require the game to be restarted to take effect." );
SPEC.increaseIndent();
SPEC.subcategory( "general_spawns",
"Added natural spawns derived from existing spawns." );
caveSpiderSpawnMultiplier = SPEC.define( new DoubleField( "cave_spider_spawn_multiplier", 0.5, DoubleField.Range.NON_NEGATIVE,
"Option to add vanilla cave spiders as natural spawns. These spawns will be added to all biomes that can",
"spawn regular spiders. Cave spider spawn weight is the same as the spider spawn weight, multiplied by",
"this value. When set to 0, the added cave spider spawn feature is completely disabled.",
"Finer tuning can be done with the spawn chances below." ), RestartNote.GAME_PARTIAL );
caveSpiderSpawnChance = new DoubleField.EnvironmentSensitive(
SPEC.define( new DoubleField( "cave_spider_chance.base", 0.0, DoubleField.Range.PERCENT,
"The chance for added cave spider natural spawn attempts to succeed. Does not affect mob replacement." ) ),
SPEC.define( new EnvironmentListField( "cave_spider_chance.exceptions", new EnvironmentList(
EnvironmentEntry.builder( 1.0F ).belowDiamondLevel().build(),
EnvironmentEntry.builder( 1.0F ).inStructure( Structure.MINESHAFT ).build(),
EnvironmentEntry.builder( 0.33F ).belowSeaFloor().build() )
.setRange( DoubleField.Range.PERCENT ),
"The chance for added cave spider natural spawn attempts to succeed when specific environmental conditions are met." ) )
);
SPEC.newLine();
enderCreeperSpawnMultiplier = SPEC.define( new DoubleField( "ender_creeper_spawn_multiplier", 0.1, DoubleField.Range.NON_NEGATIVE,
"Option to add ender creepers as natural spawns. These spawns will be added to all biomes that can",
"spawn endermen. Ender creeper spawn weight is the same as the enderman weight, multiplied by",
"this value. When set to 0, the added ender creeper spawn feature is completely disabled.",
"Finer tuning can be done with the natural spawn chances in the species config file." ), RestartNote.WORLD );
SPEC.subcategory( "water_spawns" );
drowningCreeperOceanWeight = SPEC.define( new IntField( "drowning_creeper_weight.ocean", 1, IntField.Range.NON_NEGATIVE,
"Option to add drowning creepers as natural spawns to oceans.",
"When set to 0, this added spawn feature is completely disabled.",
"Finer tuning can be done with the natural spawn chances in the species config file." ), RestartNote.WORLD );
drowningCreeperRiverWeight = SPEC.define( new IntField( "drowning_creeper_weight.river", 1, IntField.Range.NON_NEGATIVE,
"Option to add drowning creepers as natural spawns to rivers.",
"When set to 0, this added spawn feature is completely disabled.",
"Finer tuning can be done with the natural spawn chances in the species config file." ), RestartNote.WORLD );
SPEC.newLine();
blueberrySlimeOceanWeight = SPEC.define( new IntField( "blueberry_slime_weight.ocean", 2, IntField.Range.NON_NEGATIVE,
"Option to add blueberry slimes as natural spawns to oceans.",
"When set to 0, this added spawn feature is completely disabled.",
"Finer tuning can be done with the natural spawn chances in the species config file." ), RestartNote.WORLD );
blueberrySlimeRiverWeight = SPEC.define( new IntField( "blueberry_slime_weight.river", 1, IntField.Range.NON_NEGATIVE,
"Option to add blueberry slimes as natural spawns to rivers.",
"When set to 0, this added spawn feature is completely disabled.",
"Finer tuning can be done with the natural spawn chances in the species config file." ), RestartNote.WORLD );
SPEC.subcategory( "nether_spawns" );
witherSkeletonNetherWeight = SPEC.define( new IntField( "wither_skeleton_weight.nether", 2, IntField.Range.NON_NEGATIVE,
"Option to add vanilla wither skeletons as natural spawns to the Nether (except for soul sand valley and",
"warped forest biomes). When set to 0, this added spawn feature is completely disabled." ), RestartNote.WORLD );
witherSkeletonSoulSandValleyWeight = SPEC.define( new IntField( "wither_skeleton_weight.soul_sand_valley", 5, IntField.Range.NON_NEGATIVE,
"Option to add vanilla wither skeletons as natural spawns to the soul sand valley biome.",
"When set to 0, this added spawn feature is completely disabled." ), RestartNote.WORLD );
SPEC.newLine();
blazeNetherWeight = SPEC.define( new IntField( "blaze_weight.nether", 1, IntField.Range.NON_NEGATIVE,
"Option to add vanilla blazes as natural spawns to the Nether (except for soul sand valley, warped forest,",
"and basalt deltas biomes). When set to 0, the added blaze spawn feature is completely disabled." ), RestartNote.WORLD );
blazeBasaltDeltasWeight = SPEC.define( new IntField( "blaze_weight.basalt_deltas", 20, IntField.Range.NON_NEGATIVE,
"Option to add vanilla blazes as natural spawns to the basalt deltas biome.",
"When set to 0, the added blaze spawn feature is completely disabled." ), RestartNote.WORLD );
SPEC.newLine();
fireCreeperNetherWeight = SPEC.define( new IntField( "fire_creeper_weight.nether", 1, IntField.Range.NON_NEGATIVE,
"Option to add fire creepers, zombies, and spiders as natural spawns to the Nether (except for soul sand",
"valley and warped forest biomes). When set to 0, that added spawn feature is completely disabled.",
"Finer tuning can be done with the natural spawn chances in the species config files." ), RestartNote.WORLD );
fireZombieNetherWeight = SPEC.define( new IntField( "fire_zombie_weight.nether", 1, IntField.Range.NON_NEGATIVE,
(String[]) null ) );
fireSpiderNetherWeight = SPEC.define( new IntField( "fire_spider_weight.nether", 1, IntField.Range.NON_NEGATIVE,
(String[]) null ) );
SPEC.decreaseIndent();
}
}
}

View file

@ -3,6 +3,8 @@ package fathertoast.specialmobs.common.config.field;
import com.electronwill.nightconfig.core.io.CharacterOutput;
import fathertoast.specialmobs.common.config.file.ToastTomlWriter;
import fathertoast.specialmobs.common.config.file.TomlHelper;
import fathertoast.specialmobs.common.config.util.IStringArray;
import fathertoast.specialmobs.common.config.util.RestartNote;
import javax.annotation.Nullable;
import java.util.ArrayList;
@ -46,8 +48,9 @@ public abstract class AbstractConfigField {
public final List<String> getComment() { return COMMENT; }
/** Adds procedural information to the comment and then makes it unmodifiable. */
public final void finalizeComment() {
public final void finalizeComment( @Nullable RestartNote restartNote ) {
if( COMMENT == null ) return;
RestartNote.appendComment( COMMENT, restartNote );
appendFieldInfo( COMMENT );
((ArrayList<String>) COMMENT).trimToSize();
COMMENT = Collections.unmodifiableList( COMMENT );

View file

@ -10,6 +10,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
/**
* Represents a config field with a double value.
@ -214,12 +215,19 @@ public class DoubleField extends AbstractConfigField {
/** @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 ) {
public T next( Random random, World world, @Nullable BlockPos pos ) { return next( random, world, pos, null ); }
/** @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, @Nullable Function<T, Boolean> selector ) {
// 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 );
final Entry<T> entry = UNDERLYING_LIST.get( i );
if( selector == null || selector.apply( entry.VALUE ) ) {
targetWeight += weights[i] = entry.WEIGHT.get( world, pos );
}
}
if( targetWeight <= 0.0 ) return null;

View file

@ -8,8 +8,10 @@ import com.electronwill.nightconfig.core.io.ParsingException;
import com.electronwill.nightconfig.core.io.WritingException;
import fathertoast.specialmobs.common.config.field.*;
import fathertoast.specialmobs.common.config.util.ConfigUtil;
import fathertoast.specialmobs.common.config.util.RestartNote;
import fathertoast.specialmobs.common.core.SpecialMobs;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@ -19,8 +21,8 @@ import java.util.Objects;
/**
* A config spec maps read and write functions to the runtime variables used to hold them.
* <p>
* Contains helper functions to build a spec similarly to writing a default file, allowing insertion of
* comments and formatting as desired.
* Contains helper functions at the bottom of this class to build a spec similarly to writing a default file,
* allowing insertion of fields, load actions, comments, and formatting as desired.
*/
@SuppressWarnings( "unused" )
public class ToastConfigSpec {
@ -197,9 +199,7 @@ public class ToastConfigSpec {
/** Called when the config is saved. */
@Override
public void write( ToastTomlWriter writer, CharacterOutput output ) {
writer.changeIndentLevel( AMOUNT );
}
public void write( ToastTomlWriter writer, CharacterOutput output ) { writer.changeIndentLevel( AMOUNT ); }
}
/** Represents a comment. */
@ -243,18 +243,16 @@ public class ToastConfigSpec {
/** 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;
}
/** Create a new appendix header action that will insert a closing 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.writeNewLine( output );
writer.writeNewLine( output );
writer.writeComment( "Appendix:", output );
writer.writeComment( COMMENT, output );
@ -281,9 +279,33 @@ public class ToastConfigSpec {
writer.writeNewLine( output );
writer.writeNewLine( output );
writer.writeComment( COMMENT, output );
writer.writeNewLine( output );
writer.increaseIndentLevel();
writer.writeNewLine( output );
}
}
/** Represents a subcategory comment. */
private static class Subcategory extends Format {
/** The subcategory comment. */
private final List<String> COMMENT;
/** Create a new subcategory action that will insert the subcategory comment. */
private Subcategory( String subcategoryName, List<String> comment ) {
comment.add( 0, "Subcategory: " + subcategoryName );
COMMENT = comment;
}
/** Called when the config is saved. */
@Override
public void write( ToastTomlWriter writer, CharacterOutput output ) {
writer.decreaseIndentLevel();
writer.writeNewLine( output );
writer.writeComment( COMMENT, output );
writer.increaseIndentLevel();
writer.writeNewLine( output );
}
}
@ -315,10 +337,10 @@ public class ToastConfigSpec {
private final AbstractConfigField FIELD;
/** Create a new field action that will load/create and save the field value. */
private Field( ToastConfigSpec parent, AbstractConfigField field ) {
private Field( ToastConfigSpec parent, AbstractConfigField field, @Nullable RestartNote restartNote ) {
PARENT = parent;
FIELD = field;
FIELD.finalizeComment();
FIELD.finalizeComment( restartNote );
}
/** Called when the config is loaded. */
@ -348,30 +370,63 @@ public class ToastConfigSpec {
}
}
// Spec building methods below
/**
* Adds a field. The added field will automatically update its value when the config file is loaded.
* It is good practice to avoid storing the field's value whenever possible.
* <p>
* When not possible (e.g. the field is used to initialize something that you can't modify afterward),
* consider providing a restart note to inform users of the limitation.
*
* @param field The field to define in this config spec.
* @return The same field for convenience in constructing.
*/
public <T extends AbstractConfigField> T define( T field ) {
public <T extends AbstractConfigField> T define( T field ) { return define( field, null ); }
/**
* Adds a field. The added field will automatically update its value when the config file is loaded.
* It is good practice to avoid storing the field's value whenever possible.
* <p>
* When not possible (e.g. the field is used to initialize something that you can't modify afterward),
* consider providing a restart note to inform users of the limitation.
*
* @param field The field to define in this config spec.
* @param restartNote Note to provide for the field's restart requirements.
* @return The same field for convenience in constructing.
*/
public <T extends AbstractConfigField> T define( T field, @Nullable RestartNote restartNote ) {
// Double check just to make sure we don't screw up the spec
for( Action action : ACTIONS ) {
if( action instanceof Field && field.getKey().equalsIgnoreCase( ((Field) action).FIELD.getKey() ) ) {
throw new IllegalStateException( "Attempted to register duplicate field key '" + field.getKey() + "' in config " + NAME );
}
}
ACTIONS.add( new Field( this, field ) );
ACTIONS.add( new Field( this, field, restartNote ) );
return field;
}
/** @param callback The callback to run on read. */
/**
* Registers a runnable (or void no-argument method reference) to be called when the config is loaded.
* It is called at exactly the point defined, so fields defined above will be loaded with new values, while fields
* below will still contain their previous values (null/zero on the first load).
* <p>
* This is effectively an "on config loading" event.
*
* @param callback The callback to run on read.
*/
public void callback( Runnable callback ) { ACTIONS.add( new ReadCallback( callback ) ); }
/** Inserts a single new line. */
public void newLine() { newLine( 1 ); }
/** @param count The number of new lines to insert. */
public void newLine( int count ) { ACTIONS.add( new NewLines( count ) ); }
/** Increases the indent by one level. */
public void increaseIndent() { indent( +1 ); }
@ -381,37 +436,90 @@ public class ToastConfigSpec {
/** @param count The amount to change the indent by. */
public void indent( int count ) { ACTIONS.add( new Indent( count ) ); }
/** @param comment The comment to insert. */
public void comment( String... comment ) { comment( TomlHelper.newComment( comment ) ); }
/** @param comment The comment to insert. */
public void comment( List<String> comment ) { ACTIONS.add( new Comment( comment ) ); }
/** @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() ) ); }
/** Inserts a detailed description of how to use the entity list field. */
public void describeEntityList() { ACTIONS.add( new Comment( EntityListField.verboseDescription() ) ); }
/** Inserts a detailed description of how to use the attribute list field. */
public void describeAttributeList() { ACTIONS.add( new Comment( AttributeListField.verboseDescription() ) ); }
/** 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() ) ); }
/**
* Adds a comment. Each argument is printed on a separate line, in the order given.
*
* @param comment The comment to insert.
*/
public void comment( String... comment ) { comment( TomlHelper.newComment( comment ) ); }
/**
* Adds a comment. Each string in the list is printed on a separate line, in the order returned by iteration.
*
* @param comment The comment to insert.
*/
public void comment( List<String> comment ) { ACTIONS.add( new Comment( comment ) ); }
/**
* Adds a subcategory header, optionally including a comment to describe/summarize the contents of the file.
* <p>
* The header and its comment are printed at the current indent level - 1. Therefore, it is good practice to always
* increase the indent before the first subcategory and then decrease the indent after the final subcategory.
*
* @param name The subcategory name.
* @param comment The subcategory comment to insert.
*/
public void subcategory( String name, String... comment ) { ACTIONS.add( new Subcategory( name, TomlHelper.newComment( comment ) ) ); }
/**
* Adds a header to signal the start of the appendix section, optionally including a comment to describe/summarize the section.
*
* @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.
* Recommended to include either in a README or at the start of each config that contains any registry entry list fields.
*/
public void describeRegistryEntryList() { ACTIONS.add( new Comment( RegistryEntryListField.verboseDescription() ) ); }
/**
* Inserts a detailed description of how to use the entity list field.
* Recommended to include either in a README or at the start of each config that contains any entity list fields.
*/
public void describeEntityList() { ACTIONS.add( new Comment( EntityListField.verboseDescription() ) ); }
/**
* Inserts a detailed description of how to use the attribute list field.
* Recommended to include either in a README or at the start of each config that contains any attribute list fields.
*/
public void describeAttributeList() { ACTIONS.add( new Comment( AttributeListField.verboseDescription() ) ); }
/**
* Inserts a detailed description of how to use the block list field.
* Recommended to include either in a README or at the start of each config that contains any block list fields.
*/
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, preferably after the appendix header (if used).
*/
public void describeEnvironmentListPart2of2() { ACTIONS.add( new Comment( EnvironmentListField.environmentDescriptions() ) ); }
/**
* NOTE: You should never need to call this method. It is called automatically in the config constructor.
* Adds a config header with a comment to describe/summarize the contents of the file.
*
* @param comment The file comment to insert.
*/
public void header( List<String> comment ) { ACTIONS.add( new Header( this, comment ) ); }
/**
* NOTE: You should never need to call this method. It is called automatically in the category constructor.
* Adds a category header with a comment to describe/summarize the contents of the category section.
*
* @param name The category name.
* @param comment The category comment to insert.
*/

View file

@ -5,6 +5,7 @@ import com.electronwill.nightconfig.core.utils.StringUtils;
import fathertoast.specialmobs.common.config.field.DoubleField;
import fathertoast.specialmobs.common.config.field.IntField;
import fathertoast.specialmobs.common.config.util.ConfigUtil;
import fathertoast.specialmobs.common.config.util.IStringArray;
import javax.annotation.Nullable;
import java.util.ArrayList;
@ -57,6 +58,18 @@ public final class TomlHelper {
return NullObject.NULL_OBJECT;
}
/** Attempts to convert an object to a toml literal for the use of a comment. Prevents printing of excessively long lists. */
public static String toLiteralForComment( @Nullable Object value ) {
if( value instanceof IStringArray ) {
final List<String> list = ((IStringArray) value).toStringList();
if( list.size() > 10 ) {
String str = toLiteral( list.subList( 0, 9 ).toArray() );
return str.substring( 0, str.length() - 2 ) + ", ... ]";
}
}
return toLiteral( value );
}
/** Attempts to convert an object to a toml literal. May or may not be accurate. */
public static String toLiteral( @Nullable Object value ) {
if( value == null ) {
@ -107,13 +120,13 @@ public final class TomlHelper {
/** @return The default field info for a field with a value format/structure. */
public static String fieldInfoFormat( String typeName, Object defaultValue, String format ) {
return String.format( "<%s> Format: %s, Default: %s", typeName, format, toLiteral( defaultValue ) );
return String.format( "<%s> Format: %s, Default: %s", typeName, format, toLiteralForComment( defaultValue ) );
}
/** @return The default field info for a field with a limited set of valid values. */
public static String fieldInfoValidValues( String typeName, Object defaultValue, Object... validValues ) {
return String.format( "<%s> Valid Values: { %s }, Default: %s",
typeName, TomlHelper.literalList( validValues ), toLiteral( defaultValue ) );
typeName, TomlHelper.literalList( validValues ), toLiteralForComment( defaultValue ) );
}
/** @return The default field info for a series of int fields (no defaults listed). */

View file

@ -17,7 +17,7 @@ public class BlazeSpeciesConfig extends SpeciesConfig {
public BlazeSpeciesConfig( MobFamily.Species<?> species, int fireballBurstCount, int fireballBurstDelay ) {
super( species );
BLAZES = new Blazes( SPEC, species, speciesName, fireballBurstCount, fireballBurstDelay );
BLAZES = new Blazes( SPEC, species, species.getConfigName(), fireballBurstCount, fireballBurstDelay );
}
public static class Blazes extends Config.AbstractCategory {

View file

@ -19,7 +19,7 @@ public class CreeperSpeciesConfig extends SpeciesConfig {
boolean cannotExplodeWhileWet, boolean explodeWhileBurning, boolean explodeWhenShot ) {
super( species );
CREEPERS = new Creepers( SPEC, species, speciesName, cannotExplodeWhileWet, explodeWhileBurning, explodeWhenShot );
CREEPERS = new Creepers( SPEC, species, species.getConfigName(), cannotExplodeWhileWet, explodeWhileBurning, explodeWhenShot );
}
public static class Creepers extends Config.AbstractCategory {

View file

@ -14,7 +14,7 @@ public class DesiccatedSilverfishSpeciesConfig extends SilverfishSpeciesConfig {
public DesiccatedSilverfishSpeciesConfig( MobFamily.Species<?> species, double spitChance, int minAbsorb, int maxAbsorb ) {
super( species, spitChance );
DESICCATED = new Desiccated( SPEC, species, speciesName, minAbsorb, maxAbsorb );
DESICCATED = new Desiccated( SPEC, species, species.getConfigName(), minAbsorb, maxAbsorb );
}
public static class Desiccated extends Config.AbstractCategory {

View file

@ -0,0 +1,45 @@
package fathertoast.specialmobs.common.config.species;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.config.Config;
import fathertoast.specialmobs.common.config.field.DoubleField;
import fathertoast.specialmobs.common.config.file.ToastConfigSpec;
import fathertoast.specialmobs.common.config.util.ConfigUtil;
/**
* This is the base species config for zombies, drowned, and zombified piglins.
*/
public class DrownedSpeciesConfig extends SpeciesConfig {
public final Drowned DROWNED;
/** Builds the config spec that should be used for this config. */
public DrownedSpeciesConfig( MobFamily.Species<?> species, double tridentChance, double shieldChance ) {
super( species );
DROWNED = new Drowned( SPEC, species, species.getConfigName(), tridentChance, shieldChance );
}
public static class Drowned extends Config.AbstractCategory {
public final DoubleField tridentEquipChance;
public final DoubleField shieldEquipChance;
Drowned( ToastConfigSpec parent, MobFamily.Species<?> species, String speciesName, double tridentChance, double shieldChance ) {
super( parent, ConfigUtil.noSpaces( species.family.configName ),
"Options standard to all " + species.family.configName + "." );
// Automatically set the default trident chance to 0% if the mob has ranged attacks disabled by default
final double effectiveDefault = species.bestiaryInfo.rangedAttackMaxRange > 0.0F ? tridentChance : 0.0;
tridentEquipChance = SPEC.define( new DoubleField( "trident_chance", effectiveDefault, DoubleField.Range.PERCENT,
"Chance for " + speciesName + " to spawn with a trident, which enables their ranged attack (if max range > 0)." ) );
SPEC.newLine();
shieldEquipChance = SPEC.define( new DoubleField( "shield_chance", shieldChance, DoubleField.Range.PERCENT,
"Chance for " + speciesName + " to spawn with a shield.",
"Shield users have a 33% chance to block frontal attacks (100% vs. long range attacks) and can be broken by axes." ) );
}
}
}

View file

@ -0,0 +1,40 @@
package fathertoast.specialmobs.common.config.species;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.config.Config;
import fathertoast.specialmobs.common.config.field.DoubleField;
import fathertoast.specialmobs.common.config.field.EnvironmentListField;
import fathertoast.specialmobs.common.config.file.ToastConfigSpec;
import fathertoast.specialmobs.common.config.util.ConfigUtil;
import fathertoast.specialmobs.common.config.util.EnvironmentEntry;
import fathertoast.specialmobs.common.config.util.EnvironmentList;
public class HuskZombieSpeciesConfig extends ZombieSpeciesConfig {
public final Husk HUSK;
/** Builds the config spec that should be used for this config. */
public HuskZombieSpeciesConfig( MobFamily.Species<?> species, double bowChance, double shieldChance ) {
super( species, bowChance, shieldChance );
HUSK = new Husk( SPEC, species, species.getConfigName() );
}
public static class Husk extends Config.AbstractCategory {
public final DoubleField.EnvironmentSensitive convertVariantChance;
Husk( ToastConfigSpec parent, MobFamily.Species<?> species, String speciesName ) {
super( parent, ConfigUtil.camelCaseToLowerUnderscore( species.specialVariantName ),
"Options specific to " + speciesName + "." );
convertVariantChance = new DoubleField.EnvironmentSensitive(
SPEC.define( new DoubleField( "special_variant_chance.base", 0.33, DoubleField.Range.PERCENT,
"The chance for " + speciesName + " to convert to a special zombie variant when drowned." ) ),
SPEC.define( new EnvironmentListField( "special_variant_chance.exceptions", new EnvironmentList(
EnvironmentEntry.builder( 0.66F ).atMaxMoonLight().build() ).setRange( DoubleField.Range.PERCENT ),
"The chance for " + speciesName + " to convert to a special zombie variant when drowned while specific environmental conditions are met." ) )
);
}
}
}

View file

@ -14,7 +14,7 @@ public class MadScientistZombieSpeciesConfig extends ZombieSpeciesConfig {
public MadScientistZombieSpeciesConfig( MobFamily.Species<?> species, double bowChance, double shieldChance, int minCharges, int maxCharges ) {
super( species, bowChance, shieldChance );
MAD_SCIENTIST = new MadScientist( SPEC, species, speciesName, minCharges, maxCharges );
MAD_SCIENTIST = new MadScientist( SPEC, species, species.getConfigName(), minCharges, maxCharges );
}
public static class MadScientist extends Config.AbstractCategory {

View file

@ -15,7 +15,7 @@ public class MotherSpiderSpeciesConfig extends SpiderSpeciesConfig {
int minBabies, int maxBabies, int minExtraBabies, int maxExtraBabies ) {
super( species, spitChance );
MOTHER = new Mother( SPEC, species, speciesName, minBabies, maxBabies, minExtraBabies, maxExtraBabies );
MOTHER = new Mother( SPEC, species, species.getConfigName(), minBabies, maxBabies, minExtraBabies, maxExtraBabies );
}
public static class Mother extends Config.AbstractCategory {

View file

@ -20,7 +20,7 @@ public class PotionSlimeSpeciesConfig extends SpeciesConfig {
public PotionSlimeSpeciesConfig( MobFamily.Species<?> species ) {
super( species );
POTION = new Potion( SPEC, species, speciesName );
POTION = new Potion( SPEC, species, species.getConfigName() );
}
public static class Potion extends Config.AbstractCategory {
@ -38,7 +38,7 @@ public class PotionSlimeSpeciesConfig extends SpeciesConfig {
Effects.DAMAGE_BOOST, Effects.WEAKNESS,
Effects.HEAL, Effects.HARM, Effects.HUNGER,
Effects.REGENERATION, Effects.POISON, Effects.WITHER,
Effects.JUMP, Effects.SLOW_FALLING, Effects.LEVITATION,
Effects.JUMP, Effects.LEVITATION, Effects.SLOW_FALLING, SMEffects.WEIGHT,
Effects.DAMAGE_RESISTANCE, SMEffects.VULNERABILITY,
Effects.FIRE_RESISTANCE, Effects.WATER_BREATHING,
Effects.BLINDNESS, Effects.NIGHT_VISION, Effects.CONFUSION,

View file

@ -14,7 +14,7 @@ public class QueenGhastSpeciesConfig extends SpeciesConfig {
public QueenGhastSpeciesConfig( MobFamily.Species<?> species, int minBabies, int maxBabies, int minSummons, int maxSummons ) {
super( species );
QUEEN = new Queen( SPEC, species, speciesName, minBabies, maxBabies, minSummons, maxSummons );
QUEEN = new Queen( SPEC, species, species.getConfigName(), minBabies, maxBabies, minSummons, maxSummons );
}
public static class Queen extends Config.AbstractCategory {

View file

@ -17,7 +17,7 @@ public class SilverfishSpeciesConfig extends SpeciesConfig {
public SilverfishSpeciesConfig( MobFamily.Species<?> species, double spitChance ) {
super( species );
SILVERFISH = new Silverfish( SPEC, species, speciesName, spitChance );
SILVERFISH = new Silverfish( SPEC, species, species.getConfigName(), spitChance );
}
public static class Silverfish extends Config.AbstractCategory {

View file

@ -17,7 +17,7 @@ public class SkeletonSpeciesConfig extends SpeciesConfig {
public SkeletonSpeciesConfig( MobFamily.Species<?> species, double bowChance, double shieldChance ) {
super( species );
SKELETONS = new Skeletons( SPEC, species, speciesName, bowChance, shieldChance );
SKELETONS = new Skeletons( SPEC, species, species.getConfigName(), bowChance, shieldChance );
}
public static class Skeletons extends Config.AbstractCategory {

View file

@ -7,6 +7,7 @@ import fathertoast.specialmobs.common.config.family.FamilyConfig;
import fathertoast.specialmobs.common.config.field.*;
import fathertoast.specialmobs.common.config.file.ToastConfigSpec;
import fathertoast.specialmobs.common.config.util.ConfigUtil;
import fathertoast.specialmobs.common.config.util.EnvironmentList;
import net.minecraft.block.Block;
import net.minecraft.potion.Effect;
@ -15,37 +16,31 @@ import net.minecraft.potion.Effect;
* options that are used by all species should be defined in this class.
*/
public class SpeciesConfig extends Config.AbstractConfig {
public static final String SPECIAL_DATA_SUBCAT = "special_data.";
/** Set this field right before creating a new species config; this will then be set as a default value for that config. */
public static EnvironmentList NEXT_NATURAL_SPAWN_CHANCE_EXCEPTIONS;
protected static String fileName( MobFamily.Species<?> species ) {
return (species.specialVariantName == null ? "_normal" : ConfigUtil.camelCaseToLowerUnderscore( species.specialVariantName ))
+ "_" + ConfigUtil.noSpaces( species.family.configName );
}
protected static String variantName( MobFamily.Species<?> species ) {
return species.specialVariantName == null ? "vanilla replacement" : ConfigUtil.camelCaseToLowerSpace( species.specialVariantName );
}
/** The full readable name for the species in lower space case; e.g., "baby cave spiders". */
protected final String speciesName;
/** Category containing all options applicable to mob species as a whole; i.e. not specific to any particular species. */
public final General GENERAL;
/** Builds the config spec that should be used for this config. */
public SpeciesConfig( MobFamily.Species<?> species ) {
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 ) ) );
"This config contains options that apply only to the " + species.getConfigNameSingular() + " species." );
speciesName = variantName( species ) + " " + species.family.configName;
GENERAL = new General( SPEC, species, speciesName );
GENERAL = new General( SPEC, species, species.getConfigName() );
}
public static class General extends Config.AbstractCategory {
public final DoubleField.EnvironmentSensitive naturalSpawnChance;
public final DoubleField randomScaling;
public final AttributeListField attributeChanges;
@ -76,6 +71,16 @@ public class SpeciesConfig extends Config.AbstractConfig {
"Options standard to all mob species (that is, not specific to any particular mob species)." );
final BestiaryInfo info = species.bestiaryInfo;
naturalSpawnChance = new DoubleField.EnvironmentSensitive(
SPEC.define( new DoubleField( "natural_spawn_chance.base", 1.0, DoubleField.Range.PERCENT,
"The chance for " + speciesName + " to succeed at natural spawn attempts. Does not affect mob replacement.",
"Note: Most species do NOT naturally spawn - they must be added by a mod or data pack for this option to do anything." ) ),
SPEC.define( new EnvironmentListField( "natural_spawn_chance.exceptions", getDefaultSpawnExceptions().setRange( DoubleField.Range.PERCENT ),
"The chance for " + speciesName + " to succeed at natural spawn attempts when specific environmental conditions are met." ) )
);
SPEC.newLine();
randomScaling = SPEC.define( new DoubleField( "random_scaling", -1.0, DoubleField.Range.SIGNED_PERCENT,
"When greater than 0, " + speciesName + " will have a random render scale applied. This is a visual effect only.",
"If this is set to a non-negative value, it overrides the value set for both \"master_random_scaling\" and",
@ -137,5 +142,15 @@ public class SpeciesConfig extends Config.AbstractConfig {
"The maximum distance (in blocks) at which " + speciesName + " can use their ranged attacks.",
"0 disables ranged attacks." ) );
}
/** @return The next default natural spawn chance exceptions to use. */
private static EnvironmentList getDefaultSpawnExceptions() {
if( NEXT_NATURAL_SPAWN_CHANCE_EXCEPTIONS == null ) return new EnvironmentList();
// A hacky way to have an extra optional constructor parameter without overloading EVERY SINGLE constructor
final EnvironmentList presetValue = NEXT_NATURAL_SPAWN_CHANCE_EXCEPTIONS;
NEXT_NATURAL_SPAWN_CHANCE_EXCEPTIONS = null;
return presetValue;
}
}
}

View file

@ -17,7 +17,7 @@ public class SpiderSpeciesConfig extends SpeciesConfig {
public SpiderSpeciesConfig( MobFamily.Species<?> species, double spitChance ) {
super( species );
SPIDERS = new Spiders( SPEC, species, speciesName, spitChance );
SPIDERS = new Spiders( SPEC, species, species.getConfigName(), spitChance );
}
public static class Spiders extends Config.AbstractCategory {

View file

@ -16,7 +16,7 @@ public class SplittingCreeperSpeciesConfig extends CreeperSpeciesConfig {
int minExtraBabies, int maxExtraBabies ) {
super( species, cannotExplodeWhileWet, explodeWhileBurning, explodeWhenShot );
SPLITTING = new Splitting( SPEC, species, speciesName, minExtraBabies, maxExtraBabies );
SPLITTING = new Splitting( SPEC, species, species.getConfigName(), minExtraBabies, maxExtraBabies );
}
public static class Splitting extends Config.AbstractCategory {

View file

@ -14,7 +14,7 @@ public class UndeadWitchSpeciesConfig extends SpeciesConfig {
public UndeadWitchSpeciesConfig( MobFamily.Species<?> species, int minSummons, int maxSummons ) {
super( species );
UNDEAD = new Undead( SPEC, species, speciesName, minSummons, maxSummons );
UNDEAD = new Undead( SPEC, species, species.getConfigName(), minSummons, maxSummons );
}
public static class Undead extends Config.AbstractCategory {

View file

@ -14,7 +14,7 @@ public class WebSpiderSpeciesConfig extends SpiderSpeciesConfig {
public WebSpiderSpeciesConfig( MobFamily.Species<?> species, double spitChance, int minWebs, int maxWebs ) {
super( species, spitChance );
WEB = new Web( SPEC, species, speciesName, minWebs, maxWebs );
WEB = new Web( SPEC, species, species.getConfigName(), minWebs, maxWebs );
}
public static class Web extends Config.AbstractCategory {

View file

@ -15,7 +15,7 @@ public class WildfireBlazeSpeciesConfig extends BlazeSpeciesConfig {
int minBabies, int maxBabies, int minSummons, int maxSummons ) {
super( species, fireballBurstCount, fireballBurstDelay );
WILDFIRE = new Wildfire( SPEC, species, speciesName, minBabies, maxBabies, minSummons, maxSummons );
WILDFIRE = new Wildfire( SPEC, species, species.getConfigName(), minBabies, maxBabies, minSummons, maxSummons );
}
public static class Wildfire extends Config.AbstractCategory {

View file

@ -15,7 +15,7 @@ public class WildsWitchSpeciesConfig extends SpeciesConfig {
int minSwarms, int maxSwarms, int minSwarmSize, int maxSwarmSize ) {
super( species );
WILDS = new Wilds( SPEC, species, speciesName, minMounts, maxMounts, minSwarms, maxSwarms, minSwarmSize, maxSwarmSize );
WILDS = new Wilds( SPEC, species, species.getConfigName(), minMounts, maxMounts, minSwarms, maxSwarms, minSwarmSize, maxSwarmSize );
}
public static class Wilds extends Config.AbstractCategory {

View file

@ -17,7 +17,7 @@ public class ZombieSpeciesConfig extends SpeciesConfig {
public ZombieSpeciesConfig( MobFamily.Species<?> species, double bowChance, double shieldChance ) {
super( species );
ZOMBIES = new Zombies( SPEC, species, speciesName, bowChance, shieldChance );
ZOMBIES = new Zombies( SPEC, species, species.getConfigName(), bowChance, shieldChance );
}
public static class Zombies extends Config.AbstractCategory {

View file

@ -1,6 +1,5 @@
package fathertoast.specialmobs.common.config.util;
import fathertoast.specialmobs.common.config.field.IStringArray;
import fathertoast.specialmobs.common.config.file.TomlHelper;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.attributes.AttributeModifierMap;

View file

@ -1,7 +1,6 @@
package fathertoast.specialmobs.common.config.util;
import fathertoast.specialmobs.common.config.field.AbstractConfigField;
import fathertoast.specialmobs.common.config.field.IStringArray;
import fathertoast.specialmobs.common.config.file.TomlHelper;
import fathertoast.specialmobs.common.core.SpecialMobs;
import net.minecraft.block.Block;

View file

@ -1,6 +1,5 @@
package fathertoast.specialmobs.common.config.util;
import fathertoast.specialmobs.common.config.field.IStringArray;
import fathertoast.specialmobs.common.config.file.TomlHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;

View file

@ -15,6 +15,7 @@ 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.biome.Biome;
import net.minecraft.world.gen.feature.structure.Structure;
import javax.annotation.Nullable;
@ -206,6 +207,12 @@ public class EnvironmentEntry {
/** Check if the biome belongs to a specific category. */
public Builder notInBiomeCategory( BiomeCategory category ) { return in( new BiomeCategoryEnvironment( category, true ) ); }
/** Check if the biome is a specific one. */
public Builder inBiome( RegistryKey<Biome> biome ) { return in( new BiomeEnvironment( biome, false ) ); }
/** Check if the biome is a specific one. */
public Builder notInBiome( RegistryKey<Biome> biome ) { return in( new BiomeEnvironment( biome, true ) ); }
// Position-based
@ -232,22 +239,32 @@ public class EnvironmentEntry {
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 ) ); }
public Builder belowSeaLevel() { return belowSeaLevel( 0 ); }
/** Check if the position is above/below sea level. */
public Builder aboveSeaLevel() { return in( new YFromSeaEnvironment( ComparisonOperator.LESS_OR_EQUAL.invert(), 0 ) ); }
public Builder aboveSeaLevel() { return aboveSeaLevel( 0 ); }
/** Check if the position is above/below the average sea floor. */
public Builder belowSeaFloor() { return in( new YFromSeaEnvironment( ComparisonOperator.LESS_OR_EQUAL, -18 ) ); }
public Builder belowSeaDepths() { return belowSeaLevel( -6 ); }
/** Check if the position is above/below the average sea floor. */
public Builder aboveSeaFloor() { return in( new YFromSeaEnvironment( ComparisonOperator.LESS_OR_EQUAL.invert(), -18 ) ); }
public Builder aboveSeaDepths() { return aboveSeaLevel( -6 ); }
/** Check if the position is above/below the average sea floor. */
public Builder belowSeaFloor() { return belowSeaLevel( -18 ); }
/** Check if the position is above/below the average sea floor. */
public Builder aboveSeaFloor() { return aboveSeaLevel( -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 ) ); }
public Builder belowMountainLevel() { return belowSeaLevel( 25 ); }
/** 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 aboveMountainLevel() { return aboveSeaLevel( 25 ); }
private Builder belowSeaLevel( int dY ) { return in( new YFromSeaEnvironment( ComparisonOperator.LESS_OR_EQUAL, dY ) ); }
private Builder aboveSeaLevel( int dY ) { return in( new YFromSeaEnvironment( ComparisonOperator.LESS_OR_EQUAL.invert(), dY ) ); }
public Builder canSeeSky() { return inPositionWithState( PositionEnvironment.Value.CAN_SEE_SKY, false ); }

View file

@ -1,7 +1,6 @@
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;

View file

@ -1,4 +1,4 @@
package fathertoast.specialmobs.common.config.field;
package fathertoast.specialmobs.common.config.util;
import java.util.List;

View file

@ -17,7 +17,6 @@ import java.util.Set;
* <p>
* See also: {@link net.minecraftforge.registries.ForgeRegistries}
*/
@SuppressWarnings( "unused" )
public class LazyRegistryEntryList<T extends IForgeRegistryEntry<T>> extends RegistryEntryList<T> {
/** The field containing this list. We save a reference to help improve error/warning reports. */
private final AbstractConfigField FIELD;

View file

@ -1,7 +1,6 @@
package fathertoast.specialmobs.common.config.util;
import fathertoast.specialmobs.common.config.field.AbstractConfigField;
import fathertoast.specialmobs.common.config.field.IStringArray;
import fathertoast.specialmobs.common.config.file.TomlHelper;
import fathertoast.specialmobs.common.core.SpecialMobs;
import net.minecraft.util.ResourceLocation;

View file

@ -0,0 +1,23 @@
package fathertoast.specialmobs.common.config.util;
import javax.annotation.Nullable;
import java.util.List;
public enum RestartNote {
NONE( " * Does NOT require a world/game restart to take effect *" ),
WORLD( " * Requires a WORLD restart to take effect *" ),
WORLD_PARTIAL( " * Requires a WORLD restart to take full effect *" ),
GAME( " * Requires a GAME restart to take effect *" ),
GAME_PARTIAL( " * Requires a GAME restart to take full effect *" );
/** Adds the note's comment, if any. */
public static void appendComment( List<String> comment, @Nullable RestartNote note ) {
if( note != null ) comment.add( note.COMMENT );
}
private final String COMMENT;
RestartNote( String comment ) { COMMENT = comment; }
}

View file

@ -68,7 +68,7 @@ public abstract class DynamicRegistryEnvironment<T> extends AbstractEnvironment
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 );
FIELD.getClass(), FIELD.getKey(), getRegistry().location(), REGISTRY_KEY );
}
}
return registryEntry;

View file

@ -76,7 +76,7 @@ public abstract class DynamicRegistryGroupEnvironment<T> extends AbstractEnviron
}
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 );
FIELD == null ? "DEFAULT" : FIELD.getClass(), FIELD == null ? "DEFAULT" : FIELD.getKey(), getRegistry().location(), NAMESPACE );
}
registryEntries = Collections.unmodifiableList( registryEntries );
}

View file

@ -13,7 +13,7 @@ import javax.annotation.Nullable;
public class BiomeEnvironment extends DynamicRegistryEnvironment<Biome> {
public BiomeEnvironment( RegistryKey<Biome> biome, boolean invert ) { super( biome.getRegistryName(), invert ); }
public BiomeEnvironment( RegistryKey<Biome> biome, boolean invert ) { super( biome.location(), invert ); }
public BiomeEnvironment( AbstractConfigField field, String line ) { super( field, line ); }

View file

@ -15,7 +15,7 @@ import java.util.List;
public class BiomeGroupEnvironment extends DynamicRegistryGroupEnvironment<Biome> {
public BiomeGroupEnvironment( RegistryKey<Biome> biome, boolean invert ) { this( biome.getRegistryName(), invert ); }
public BiomeGroupEnvironment( RegistryKey<Biome> biome, boolean invert ) { this( biome.location(), invert ); }
public BiomeGroupEnvironment( ResourceLocation regKey, boolean invert ) { super( regKey, invert ); }

View file

@ -13,7 +13,7 @@ import javax.annotation.Nullable;
public class DimensionTypeEnvironment extends DynamicRegistryEnvironment<DimensionType> {
public DimensionTypeEnvironment( RegistryKey<DimensionType> dimType, boolean invert ) { super( dimType.getRegistryName(), invert ); }
public DimensionTypeEnvironment( RegistryKey<DimensionType> dimType, boolean invert ) { super( dimType.location(), invert ); }
public DimensionTypeEnvironment( AbstractConfigField field, String line ) { super( field, line ); }

View file

@ -15,7 +15,7 @@ import java.util.List;
public class DimensionTypeGroupEnvironment extends DynamicRegistryGroupEnvironment<DimensionType> {
public DimensionTypeGroupEnvironment( RegistryKey<DimensionType> dimType, boolean invert ) { this( dimType.getRegistryName(), invert ); }
public DimensionTypeGroupEnvironment( RegistryKey<DimensionType> dimType, boolean invert ) { this( dimType.location(), invert ); }
public DimensionTypeGroupEnvironment( ResourceLocation regKey, boolean invert ) { super( regKey, invert ); }

View file

@ -134,15 +134,19 @@ public final class SpecialMobReplacer {
replacement.load( tag );
MobHelper.finalizeSpawn( replacement, (IServerWorld) world, world.getCurrentDifficultyAt( entityPos ), null, null );
world.addFreshEntity( replacement );
for( Entity rider : entityToReplace.getPassengers() ) {
rider.stopRiding();
rider.startRiding( replacement, true );
}
if( entityToReplace.getVehicle() != null ) {
replacement.startRiding( entityToReplace.getVehicle(), true );
final Entity vehicle = entityToReplace.getVehicle();
entityToReplace.stopRiding();
replacement.startRiding( vehicle, true );
}
entityToReplace.remove();
world.addFreshEntity( replacement );
}
/** All data needed for a single mob we want to replace. */

View file

@ -5,16 +5,17 @@ import fathertoast.specialmobs.common.config.Config;
import fathertoast.specialmobs.common.core.register.SMEffects;
import fathertoast.specialmobs.common.core.register.SMEntities;
import fathertoast.specialmobs.common.core.register.SMItems;
import fathertoast.specialmobs.common.event.BiomeEvents;
import fathertoast.specialmobs.common.event.GameEvents;
import fathertoast.specialmobs.common.event.NaturalSpawnManager;
import fathertoast.specialmobs.common.network.PacketHandler;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.InterModComms;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLConstructModEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.registries.ForgeRegistryEntry;
@ -38,14 +39,22 @@ public class SpecialMobs {
* - general
* - entity replacer
* - environment-sensitive configs
* + natural spawning
* o nether spawns
* o end spawns
* ? ocean/water spawns
* - natural spawning
* - copied spawns
* - spiders -> cave spiders
* - endermen -> ender creepers
* - ocean/river spawns
* - drowning creepers
* - blueberry slimes
* - nether spawns
* - wither skeletons (outside of fortresses)
* - blazes (outside of fortresses)
* - fire creepers/zombies/spiders
* ? warped/crimson mobs
* - potions
* - vulnerability (opposite of resistance)
* ? gravity (opposite of levitation)
* o entities
* - weight (opposite of levitation)
* - entities
* - nbt-driven capabilities (special mob data)
* - fish hook
* - bug spit
@ -54,13 +63,14 @@ public class SpecialMobs {
* - monster families (see doc for specifics)
* - creepers
* - chance to spawn charged during thunderstorms
* + scope
* + scope - perhaps delay this until 1.18 where spyglasses will be in the game
* - zombies
* o villager infection (is this still reasonably applicable?)
* + transformations (husk -> any other non-water-sensitive zombie -> any drowned)
* - transformations (husk -> any other non-water-sensitive zombie -> analogous drowned)
* - ranged attack AI (using bow)
* - use shields
* + drowned
* - drowned
* - AI functions in shallow water
* - use shields
* - zombified piglins
* - ranged attack AI (using bow)
* + ranged attack AI (using crossbow)
@ -80,10 +90,8 @@ public class SpecialMobs {
* - ranged attack AI (spitter)
* - cave spiders
* - ranged attack AI (spitter)
* + natural spawning
* - silverfish
* - ranged attack AI (spitter)
* + puffer
* - endermen
* - witches
* - ability to equip held items (wonky)
@ -118,24 +126,26 @@ public class SpecialMobs {
packetHandler.registerMessages();
MinecraftForge.EVENT_BUS.register( new BiomeEvents() );
final IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
SMEffects.REGISTRY.register( modEventBus );
SMEntities.REGISTRY.register( modEventBus );
SMItems.REGISTRY.register( modEventBus );
modEventBus.addListener( SMEntities::createAttributes );
modEventBus.addListener( this::setup );
modEventBus.addListener( this::sendIMCMessages );
MinecraftForge.EVENT_BUS.addListener( EventPriority.LOW, NaturalSpawnManager::onBiomeLoad );
MinecraftForge.EVENT_BUS.register( new GameEvents() );
IEventBus eventBus = FMLJavaModLoadingContext.get().getModEventBus();
eventBus.addListener( SMEntities::createAttributes );
eventBus.addListener( this::sendIMCMessages );
SMEffects.REGISTRY.register( eventBus );
SMEntities.REGISTRY.register( eventBus );
SMItems.REGISTRY.register( eventBus );
}
// TODO - This could very well help out the config malformation issue
// Only problem here is that this event is never fired apparently.
// Perhaps DeferredWorkQueue.runLater() could work (ignore deprecation, simply marked for removal)
public void onParallelDispatch( FMLConstructModEvent event ) {
event.enqueueWork( Config::initialize );
// public void onParallelDispatch( FMLConstructModEvent event ) {
// event.enqueueWork( Config::initialize );
// }
public void setup( FMLCommonSetupEvent event ) {
NaturalSpawnManager.registerSpawnPlacements();
}
public void sendIMCMessages( InterModEnqueueEvent event ) {

View file

@ -1,6 +1,7 @@
package fathertoast.specialmobs.common.core.register;
import fathertoast.specialmobs.common.core.SpecialMobs;
import fathertoast.specialmobs.common.potion.WeightEffect;
import net.minecraft.potion.Effect;
import net.minecraft.potion.EffectType;
import net.minecraftforge.fml.RegistryObject;
@ -14,6 +15,7 @@ public class SMEffects {
public static final DeferredRegister<Effect> REGISTRY = DeferredRegister.create( ForgeRegistries.POTIONS, SpecialMobs.MOD_ID );
public static final RegistryObject<Effect> VULNERABILITY = register( "vulnerability", EffectType.HARMFUL, 0x96848D );
public static final RegistryObject<Effect> WEIGHT = register( "weight", () -> new WeightEffect( EffectType.HARMFUL, 0x353A6B ) );
/** Registers a simple effect to the deferred register. */
public static RegistryObject<Effect> register( String name, EffectType type, int color ) {

View file

@ -51,9 +51,4 @@ public class SMEntities {
species.config.GENERAL.attributeChanges, AnnotationHelper.createAttributes( species ) ) );
}
}
/** Sets the natural spawn placement rules for entity types. */
public static void registerSpawnPlacements() {
//TODO
}
}

View file

@ -7,7 +7,9 @@ import fathertoast.specialmobs.common.core.SpecialMobs;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.entity.EntitySize;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.Pose;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.nbt.StringNBT;
@ -278,8 +280,17 @@ public class SpecialMobData<T extends LivingEntity & ISpecialMob<T>> {
//** @return The render scale for the entity without its family scale factored in; used to correct scaling for pre-scaled vanilla values. */
//public float getBaseScaleForPreScaledValues() { return getBaseScale() / getFamilyBaseScale(); }
/** @return The height scale. Used to calculate eye height for families that are not auto-scaled. */
public float getHeightScale() { return theEntity.getSpecies().getHeightScale(); }
/**
* @return The height scale, including baby modifier if applicable. Used to calculate eye height for families that are not auto-scaled.
* Note: Baby scale is derived from {@link net.minecraft.entity.monster.ZombieEntity#getStandingEyeHeight(Pose, EntitySize)}.
*/
public float getHeightScaleByAge() { return getHeightScale() * (theEntity.isBaby() ? 0.53448F : 1.0F); }
/** @return The base render scale for the entity, which is a property of the mob species. */
public float getBaseScale() { return theEntity.getSpecies().bestiaryInfo.baseScale; }
private float getBaseScale() { return theEntity.getSpecies().bestiaryInfo.baseScale; }
/** @return A random render scale based on config settings. */
private float nextScale() {

View file

@ -0,0 +1,57 @@
package fathertoast.specialmobs.common.entity.ai;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.MobEntity;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.entity.ai.controller.MovementController;
import net.minecraft.util.math.MathHelper;
/**
* The drowned movement controller repurposed for use on other mobs.
* <p>
* {@link net.minecraft.entity.monster.DrownedEntity.MoveHelperController}
*/
public class AmphibiousMovementController<T extends MobEntity & IAmphibiousMob> extends MovementController {
private final T owner;
public AmphibiousMovementController( T entity ) {
super( entity );
owner = entity;
}
public void tick() {
final LivingEntity target = owner.getTarget();
if( owner.shouldSwim() && owner.isInWater() ) {
if( target != null && target.getY() > owner.getY() || owner.isSwimmingUp() ) {
owner.setDeltaMovement( owner.getDeltaMovement().add( 0.0, 0.002, 0.0 ) );
}
if( operation != MovementController.Action.MOVE_TO || owner.getNavigation().isDone() ) {
owner.setSpeed( 0.0F );
return;
}
final double dX = wantedX - owner.getX();
final double dY = wantedY - owner.getY();
final double dZ = wantedZ - owner.getZ();
final double distance = MathHelper.sqrt( dX * dX + dY * dY + dZ * dZ );
final float targetYRot = (float) MathHelper.atan2( dZ, dX ) * 180.0F / (float) Math.PI - 90.0F;
owner.yRot = rotlerp( owner.yRot, targetYRot, 90.0F );
owner.yBodyRot = owner.yRot;
final float maxSpeed = (float) (speedModifier * owner.getAttributeValue( Attributes.MOVEMENT_SPEED ));
final float speed = MathHelper.lerp( 0.125F, owner.getSpeed(), maxSpeed );
owner.setSpeed( speed );
owner.setDeltaMovement( owner.getDeltaMovement().add(
speed * dX * 0.005, speed * dY / distance * 0.1, speed * dZ * 0.005 ) );
}
else {
if( !owner.isOnGround() ) {
owner.setDeltaMovement( owner.getDeltaMovement().add( 0.0, -0.008, 0.0 ) );
}
super.tick();
}
}
}

View file

@ -0,0 +1,21 @@
package fathertoast.specialmobs.common.entity.ai;
/**
* Monsters must implement this interface to use the amphibious AI.
*/
public interface IAmphibiousMob {
/** @return True if this mob should use its swimming navigator for its current goal. */
default boolean shouldSwim() { return isSwimmingUp(); }
/** Sets whether this mob should swim upward. */
void setSwimmingUp( boolean value );
/** @return True if this mob should swim upward. */
boolean isSwimmingUp();
/** Sets this mob's current navigator to swimming mode. */
void setNavigatorToSwim();
/** Sets this mob's current navigator to ground mode. */
void setNavigatorToGround();
}

View file

@ -0,0 +1,51 @@
package fathertoast.specialmobs.common.entity.ai.goal;
import fathertoast.specialmobs.common.entity.ai.IAmphibiousMob;
import net.minecraft.entity.CreatureEntity;
import net.minecraft.entity.ai.goal.MoveToBlockGoal;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IWorldReader;
/**
* The drowned "go to beach" goal repurposed for use on other mobs.
* <p>
* {@link net.minecraft.entity.monster.DrownedEntity.GoToBeachGoal}
*/
public class AmphibiousGoToShoreGoal<T extends CreatureEntity & IAmphibiousMob> extends MoveToBlockGoal {
private final T amphibiousMob;
private boolean disableAtDay = true;
public AmphibiousGoToShoreGoal( T entity, double speed ) {
super( entity, speed, 8, 2 );
amphibiousMob = entity;
}
/** Builder that allows this goal to run during the day. */
public AmphibiousGoToShoreGoal<T> alwaysEnabled() {
disableAtDay = false;
return this;
}
/** @return Returns true if this AI can be activated. */
@Override
public boolean canUse() {
return super.canUse() && !(disableAtDay && mob.level.isDay()) && mob.isInWater() && mob.getY() >= mob.level.getSeaLevel() - 3;
}
/** @return True if the position is valid to move to. */
@Override
protected boolean isValidTarget( IWorldReader world, BlockPos targetPos ) {
return world.isEmptyBlock( targetPos.above() ) && world.isEmptyBlock( targetPos.above( 2 ) ) &&
world.getBlockState( targetPos ).entityCanStandOn( world, targetPos, mob );
}
/** Called when this AI is activated. */
@Override
public void start() {
amphibiousMob.setSwimmingUp( false );
amphibiousMob.setNavigatorToGround();
super.start();
}
}

View file

@ -0,0 +1,80 @@
package fathertoast.specialmobs.common.entity.ai.goal;
import net.minecraft.block.Blocks;
import net.minecraft.entity.MobEntity;
import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Vector3d;
import javax.annotation.Nullable;
import java.util.EnumSet;
import java.util.Random;
/**
* The drowned "go to water" goal repurposed for use on other mobs.
* <p>
* {@link net.minecraft.entity.monster.DrownedEntity.GoToWaterGoal}
*/
public class AmphibiousGoToWaterGoal extends Goal {
private final MobEntity mob;
private final double speedModifier;
private boolean disableAtNight = true;
private double wantedX;
private double wantedY;
private double wantedZ;
public AmphibiousGoToWaterGoal( MobEntity entity, double speed ) {
mob = entity;
speedModifier = speed;
setFlags( EnumSet.of( Goal.Flag.MOVE ) );
}
/** Builder that allows this goal to run during the night. */
public AmphibiousGoToWaterGoal alwaysEnabled() {
disableAtNight = false;
return this;
}
/** @return Returns true if this AI can be activated. */
@Override
public boolean canUse() {
if( disableAtNight && !mob.level.isDay() || mob.isInWater() ) return false;
final Vector3d targetPos = findWaterPos();
if( targetPos == null ) return false;
wantedX = targetPos.x;
wantedY = targetPos.y;
wantedZ = targetPos.z;
return true;
}
/** @return Called each update while active and returns true if this AI can remain active. */
@Override
public boolean canContinueToUse() { return !mob.getNavigation().isDone(); }
/** Called when this AI is activated. */
@Override
public void start() { mob.getNavigation().moveTo( wantedX, wantedY, wantedZ, speedModifier ); }
/** @return A random nearby position of water, or null if none is found after a few tries. */
@Nullable
private Vector3d findWaterPos() {
final Random random = mob.getRandom();
final BlockPos origin = mob.blockPosition();
for( int i = 0; i < 10; i++ ) {
final BlockPos target = origin.offset(
random.nextInt( 20 ) - 10,
2 - random.nextInt( 8 ),
random.nextInt( 20 ) - 10 );
if( mob.level.getBlockState( target ).is( Blocks.WATER ) ) {
return Vector3d.atBottomCenterOf( target );
}
}
return null;
}
}

View file

@ -0,0 +1,46 @@
package fathertoast.specialmobs.common.entity.ai.goal;
import net.minecraft.entity.CreatureEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.goal.MeleeAttackGoal;
import javax.annotation.Nullable;
/**
* The drowned "attack" goal repurposed for use on other mobs.
* Prevents mobs from attacking targets outside of water during the day.
* <p>
* {@link net.minecraft.entity.monster.DrownedEntity.AttackGoal}
*/
public class AmphibiousMeleeAttackGoal extends MeleeAttackGoal {
/** @return True if the target is valid. */
public static boolean isValidTarget( @Nullable LivingEntity target ) {
return target != null && (!target.level.isDay() || target.isInWater());
}
public AmphibiousMeleeAttackGoal( CreatureEntity entity, double speed, boolean longMemory ) { super( entity, speed, longMemory ); }
/** @return Returns true if this AI can be activated. */
@Override
public boolean canUse() { return super.canUse() && isValidTarget( mob.getTarget() ); }
/** @return Called each update while active and returns true if this AI can remain active. */
@Override
public boolean canContinueToUse() { return super.canContinueToUse() && isValidTarget( mob.getTarget() ); }
// Uncomment if ever needed
// /** A zombie attack version of the normal amphibious melee goal. */
// public static class Zombie extends ZombieAttackGoal {
//
// public Zombie( ZombieEntity entity, double speed, boolean longMemory ) { super( entity, speed, longMemory ); }
//
// /** @return Returns true if this AI can be activated. */
// @Override
// public boolean canUse() { return super.canUse() && isValidTarget( mob.getTarget() ); }
//
// /** @return Called each update while active and returns true if this AI can remain active. */
// @Override
// public boolean canContinueToUse() { return super.canContinueToUse() && isValidTarget( mob.getTarget() ); }
// }
}

View file

@ -0,0 +1,82 @@
package fathertoast.specialmobs.common.entity.ai.goal;
import fathertoast.specialmobs.common.entity.ai.IAmphibiousMob;
import net.minecraft.entity.CreatureEntity;
import net.minecraft.entity.ai.RandomPositionGenerator;
import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.pathfinding.Path;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Vector3d;
/**
* The drowned "swim up" goal repurposed for use on other mobs.
* <p>
* {@link net.minecraft.entity.monster.DrownedEntity.SwimUpGoal}
*/
public class AmphibiousSwimUpGoal<T extends CreatureEntity & IAmphibiousMob> extends Goal {
private final T mob;
private final double speedModifier;
private final int seaLevel;
private boolean disableAtDay = true;
private boolean stuck;
public AmphibiousSwimUpGoal( T entity, double speed ) {
mob = entity;
speedModifier = speed;
seaLevel = entity.level.getSeaLevel() - 1;
}
/** Builder that allows this goal to run during the day. */
public AmphibiousSwimUpGoal<T> alwaysEnabled() {
disableAtDay = false;
return this;
}
/** @return Returns true if this AI can be activated. */
@Override
public boolean canUse() { return !(disableAtDay && mob.level.isDay()) && mob.isInWater() && mob.getY() < seaLevel - 1; }
/** @return Called each update while active and returns true if this AI can remain active. */
@Override
public boolean canContinueToUse() { return canUse() && !stuck; }
/** Called when this AI is activated. */
@Override
public void start() {
mob.setSwimmingUp( true );
stuck = false;
}
/** Called when this AI is deactivated. */
@Override
public void stop() { mob.setSwimmingUp( false ); }
/** Called each tick while this AI is active. */
@Override
public void tick() {
if( mob.getY() < seaLevel && (mob.getNavigation().isDone() || closeToNextPos()) ) {
final Vector3d pos = RandomPositionGenerator.getPosTowards( mob, 4, 8,
new Vector3d( mob.getX(), seaLevel, mob.getZ() ) );
if( pos == null ) {
stuck = true;
return;
}
mob.getNavigation().moveTo( pos.x, pos.y, pos.z, speedModifier );
}
}
/** @return True if the entity is within 2 blocks of its pathing target. */
private boolean closeToNextPos() {
final Path path = mob.getNavigation().getPath();
if( path != null ) {
final BlockPos pos = path.getTarget();
return mob.distanceToSqr( pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5 ) < 4.0;
}
return false;
}
}

View file

@ -0,0 +1,27 @@
package fathertoast.specialmobs.common.entity.ai.goal;
import net.minecraft.entity.ai.goal.ZombieAttackGoal;
import net.minecraft.entity.monster.DrownedEntity;
/**
* Copy of the drowned attack goal made accessible.
* <p>
* {@link DrownedEntity.AttackGoal}
*/
public class SpecialDrownedAttackGoal extends ZombieAttackGoal {
private final DrownedEntity drowned;
public SpecialDrownedAttackGoal( DrownedEntity entity, double moveSpeed, boolean longMemory ) {
super( entity, moveSpeed, longMemory );
drowned = entity;
}
/** @return Returns true if this AI can be activated. */
@Override
public boolean canUse() { return super.canUse() && drowned.okTarget( drowned.getTarget() ); }
/** @return Called each update while active and returns true if this AI can remain active. */
@Override
public boolean canContinueToUse() { return super.canContinueToUse() && drowned.okTarget( drowned.getTarget() ); }
}

View file

@ -41,7 +41,7 @@ public class SpecialGhastFireballAttackGoal extends Goal {
if( target.distanceToSqr( ghast ) < data.getRangedAttackMaxRange() * data.getRangedAttackMaxRange() && ghast.canSee( target ) ) {
chargeTime++;
if( chargeTime == (data.getRangedAttackCooldown() >> 1) && !ghast.isSilent() ) {
ghast.level.levelEvent( null, References.EVENT_GHAST_WARN, ghast.blockPosition(), 0 );
References.LevelEvent.GHAST_WARN.play( ghast );
}
if( chargeTime >= data.getRangedAttackCooldown() ) {
ghast.performRangedAttack( target, 1.0F );

View file

@ -23,21 +23,29 @@ public class SpecialSwellGoal<T extends MobEntity & IExplodingMob> extends Goal
setFlags( EnumSet.of( Flag.MOVE ) );
}
/** @return Returns true if this AI can be activated. */
@Override
public boolean canUse() {
final LivingEntity target = mob.getTarget();
return mob.getSwellDir() > 0 || target != null && mob.distanceToSqr( target ) < 9.0F + mob.getExtraRange();
}
/** Called when this AI is activated. */
@Override
public void start() {
mob.getNavigation().stop();
target = mob.getTarget();
}
/** Called when this AI is deactivated. */
@Override
public void stop() {
mob.setSwellDir( -1 );
target = null;
}
/** Called each tick while this AI is active. */
@Override
public void tick() {
if( target == null || mob.distanceToSqr( target ) > 49.0 || !mob.getSensing().canSee( target ) ) {
mob.setSwellDir( -1 );

View file

@ -0,0 +1,43 @@
package fathertoast.specialmobs.common.entity.ai.goal;
import net.minecraft.entity.IRangedAttackMob;
import net.minecraft.entity.MobEntity;
import net.minecraft.entity.ai.goal.RangedAttackGoal;
import net.minecraft.entity.monster.DrownedEntity;
import net.minecraft.item.Items;
import net.minecraft.util.Hand;
/**
* Copy of the drowned trident attack goal made accessible.
* <p>
* {@link DrownedEntity.TridentAttackGoal}
*/
public class SpecialTridentAttackGoal extends RangedAttackGoal {
private final MobEntity mob;
public SpecialTridentAttackGoal( IRangedAttackMob entity, double p_i48907_2_, int p_i48907_4_, float p_i48907_5_ ) {
super( entity, p_i48907_2_, p_i48907_4_, p_i48907_5_ );
mob = (DrownedEntity) entity;
}
/** @return Returns true if this AI can be activated. */
@Override
public boolean canUse() { return super.canUse() && mob.getMainHandItem().getItem() == Items.TRIDENT; }
/** Called when this AI is activated. */
@Override
public void start() {
super.start();
mob.setAggressive( true );
mob.startUsingItem( Hand.MAIN_HAND );
}
/** Called when this AI is deactivated. */
@Override
public void stop() {
super.stop();
mob.stopUsingItem();
mob.setAggressive( false );
}
}

View file

@ -70,7 +70,7 @@ public class HellfireBlazeEntity extends _SpecialBlazeEntity {
/** Called to attack the target with a ranged attack. */
@Override
public void performRangedAttack( LivingEntity target, float damageMulti ) {
if( !isSilent() ) level.levelEvent( null, References.EVENT_BLAZE_SHOOT, blockPosition(), 0 );
References.LevelEvent.BLAZE_SHOOT.play( this );
final float accelVariance = MathHelper.sqrt( distanceTo( target ) ) * 0.5F * getSpecialData().getRangedAttackSpread();
final double dX = target.getX() - getX() + getRandom().nextGaussian() * accelVariance;

View file

@ -96,7 +96,7 @@ public class WildfireBlazeEntity extends _SpecialBlazeEntity {
final double vH = Math.sqrt( vX * vX + vZ * vZ );
spawnBaby( vX / vH * 0.8 + getDeltaMovement().x * 0.2, vZ / vH * 0.8 + getDeltaMovement().z * 0.2, null );
spawnAnim();
if( !isSilent() ) level.levelEvent( null, References.EVENT_BLAZE_SHOOT, blockPosition(), 0 );
References.LevelEvent.BLAZE_SHOOT.play( this );
}
else {
super.performRangedAttack( target, damageMulti );
@ -114,7 +114,7 @@ public class WildfireBlazeEntity extends _SpecialBlazeEntity {
groupData = spawnBaby( (random.nextDouble() - 0.5) * 0.3, (random.nextDouble() - 0.5) * 0.3, groupData );
}
spawnAnim();
if( !isSilent() ) level.levelEvent( null, References.EVENT_BLAZE_SHOOT, blockPosition(), 0 );
References.LevelEvent.BLAZE_SHOOT.play( this );
}
super.remove( keepData );
}

View file

@ -11,6 +11,7 @@ import fathertoast.specialmobs.common.entity.SpecialMobData;
import fathertoast.specialmobs.common.entity.ai.AIHelper;
import fathertoast.specialmobs.common.entity.ai.goal.SpecialBlazeAttackGoal;
import fathertoast.specialmobs.common.entity.ai.goal.SpecialHurtByTargetGoal;
import fathertoast.specialmobs.common.event.NaturalSpawnManager;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.BlockState;
@ -62,6 +63,11 @@ public class _SpecialBlazeEntity extends BlazeEntity implements IRangedAttackMob
@SpecialMob.AttributeSupplier
public static AttributeModifierMap.MutableAttribute createAttributes() { return BlazeEntity.createAttributes(); }
@SpecialMob.SpawnPlacementRegistrar
public static void registerSpawnPlacement( MobFamily.Species<? extends _SpecialBlazeEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species, NaturalSpawnManager::checkSpawnRulesIgnoreLight );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Blaze",
@ -111,7 +117,7 @@ public class _SpecialBlazeEntity extends BlazeEntity implements IRangedAttackMob
/** Called to attack the target with a ranged attack. */
@Override
public void performRangedAttack( LivingEntity target, float damageMulti ) {
if( !isSilent() ) level.levelEvent( null, References.EVENT_BLAZE_SHOOT, blockPosition(), 0 );
References.LevelEvent.BLAZE_SHOOT.play( this );
final float accelVariance = MathHelper.sqrt( distanceTo( target ) ) * 0.5F * getSpecialData().getRangedAttackSpread();
final double dX = target.getX() - getX() + getRandom().nextGaussian() * accelVariance;
@ -177,6 +183,18 @@ public class _SpecialBlazeEntity extends BlazeEntity implements IRangedAttackMob
@Override
public final void setExperience( int xp ) { xpReward = xp; }
/** Converts this entity to one of another type. */
@Nullable
@Override
public <T extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override
@ -212,10 +230,10 @@ public class _SpecialBlazeEntity extends BlazeEntity implements IRangedAttackMob
getSpecialData().tick();
}
// /** @return The eye height of this entity when standing. */ - Blazes use auto-scaled eye height
// /** @return The eye height of this entity when standing. */
// @Override
// protected float getStandingEyeHeight( Pose pose, EntitySize size ) {
// return super.getStandingEyeHeight( pose, size ) * getSpecialData().getBaseScale() * (isBaby() ? 0.53448F : 1.0F);
// return super.getStandingEyeHeight( pose, size ) * getSpecialData().getHeightScaleByAge();
// }
/** @return Whether this entity is immune to fire damage. */

View file

@ -5,7 +5,9 @@ import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.entity.EntitySize;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.Pose;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.item.Items;
import net.minecraft.world.World;
@ -51,4 +53,10 @@ public class BabyCaveSpiderEntity extends _SpecialCaveSpiderEntity {
//--------------- Variant-Specific Implementations ----------------
public BabyCaveSpiderEntity( EntityType<? extends _SpecialCaveSpiderEntity> entityType, World world ) { super( entityType, world ); }
/** @return The eye height of this entity when standing. */
@Override
protected float getStandingEyeHeight( Pose pose, EntitySize size ) {
return super.getStandingEyeHeight( pose, size ) * 0.8F; // Convert to normal spider height scale; prevents suffocation
}
}

View file

@ -28,7 +28,7 @@ public class DesertCaveSpiderEntity extends _SpecialCaveSpiderEntity {
bestiaryInfo.color( 0xE6DDAC ).theme( BestiaryInfo.Theme.DESERT )
.uniqueTextureWithEyes()
.size( 0.6F, 0.7F, 0.5F )
.addExperience( 2 )
.addExperience( 2 ).effectImmune( Effects.MOVEMENT_SLOWDOWN, SMEffects.VULNERABILITY )
.addToAttribute( Attributes.MAX_HEALTH, 4.0 );
}

View file

@ -25,7 +25,7 @@ public class PaleCaveSpiderEntity extends _SpecialCaveSpiderEntity {
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0xDED4C6 ).theme( BestiaryInfo.Theme.ICE )
.uniqueTextureWithEyes()
.addExperience( 1 )
.addExperience( 1 ).effectImmune( Effects.WEAKNESS )
.addToAttribute( Attributes.ARMOR, 15.0 );
}

View file

@ -10,6 +10,7 @@ import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.SpecialMobData;
import fathertoast.specialmobs.common.entity.ai.goal.PassiveRangedAttackGoal;
import fathertoast.specialmobs.common.entity.projectile.BugSpitEntity;
import fathertoast.specialmobs.common.event.NaturalSpawnManager;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.BlockState;
@ -63,6 +64,11 @@ public class _SpecialCaveSpiderEntity extends CaveSpiderEntity implements IRange
@SpecialMob.AttributeSupplier
public static AttributeModifierMap.MutableAttribute createAttributes() { return CaveSpiderEntity.createAttributes(); }
@SpecialMob.SpawnPlacementRegistrar
public static void registerSpawnPlacement( MobFamily.Species<? extends _SpecialCaveSpiderEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Cave Spider",
@ -167,6 +173,18 @@ public class _SpecialCaveSpiderEntity extends CaveSpiderEntity implements IRange
@Override
public final void setExperience( int xp ) { xpReward = xp; }
/** Converts this entity to one of another type. */
@Nullable
@Override
public <T extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override
@ -205,7 +223,7 @@ public class _SpecialCaveSpiderEntity extends CaveSpiderEntity implements IRange
/** @return The eye height of this entity when standing. */
@Override
protected float getStandingEyeHeight( Pose pose, EntitySize size ) {
return 0.65F * getSpecialData().getBaseScale() * (isBaby() ? 0.53448F : 1.0F); // Use base spider scale instead of super
return super.getStandingEyeHeight( pose, size ) * getSpecialData().getHeightScaleByAge();
}
/** @return Whether this entity is immune to fire damage. */

View file

@ -3,6 +3,16 @@ package fathertoast.specialmobs.common.entity.creeper;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.config.species.SpeciesConfig;
import fathertoast.specialmobs.common.config.util.EnvironmentEntry;
import fathertoast.specialmobs.common.config.util.EnvironmentList;
import fathertoast.specialmobs.common.config.util.environment.biome.BiomeCategory;
import fathertoast.specialmobs.common.entity.ai.AIHelper;
import fathertoast.specialmobs.common.entity.ai.AmphibiousMovementController;
import fathertoast.specialmobs.common.entity.ai.IAmphibiousMob;
import fathertoast.specialmobs.common.entity.ai.goal.AmphibiousGoToWaterGoal;
import fathertoast.specialmobs.common.entity.ai.goal.AmphibiousSwimUpGoal;
import fathertoast.specialmobs.common.event.NaturalSpawnManager;
import fathertoast.specialmobs.common.util.ExplosionHelper;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootEntryItemBuilder;
@ -10,19 +20,29 @@ import fathertoast.specialmobs.datagen.loot.LootPoolBuilder;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.*;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.entity.ai.goal.SwimGoal;
import net.minecraft.entity.passive.fish.PufferfishEntity;
import net.minecraft.item.Items;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.pathfinding.GroundPathNavigator;
import net.minecraft.pathfinding.PathNodeType;
import net.minecraft.pathfinding.SwimmerPathNavigator;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.Explosion;
import net.minecraft.world.IServerWorld;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import java.util.Random;
@SpecialMob
public class DrowningCreeperEntity extends _SpecialCreeperEntity {
public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmphibiousMob {
//--------------- Static Special Mob Hooks ----------------
@ -37,6 +57,34 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity {
.addToAttribute( Attributes.MAX_HEALTH, 10.0 );
}
@SpecialMob.ConfigSupplier
public static SpeciesConfig createConfig( MobFamily.Species<?> species ) {
SpeciesConfig.NEXT_NATURAL_SPAWN_CHANCE_EXCEPTIONS = new EnvironmentList(
EnvironmentEntry.builder( 0.06F ).inBiomeCategory( BiomeCategory.RIVER ).build(),
EnvironmentEntry.builder( 0.02F ).inBiomeCategory( BiomeCategory.OCEAN ).belowSeaDepths().build(),
EnvironmentEntry.builder( 0.0F ).inBiomeCategory( BiomeCategory.OCEAN ).build() );
return _SpecialCreeperEntity.createConfig( species );
}
@SpecialMob.SpawnPlacementRegistrar
public static void registerSpeciesSpawnPlacement( MobFamily.Species<? extends DrowningCreeperEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species, EntitySpawnPlacementRegistry.PlacementType.IN_WATER,
DrowningCreeperEntity::checkSpeciesSpawnRules );
}
public static boolean checkSpeciesSpawnRules( EntityType<? extends DrowningCreeperEntity> type, IServerWorld world,
SpawnReason reason, BlockPos pos, Random random ) {
final Biome.Category biomeCategory = world.getBiome( pos ).getBiomeCategory();
if( biomeCategory == Biome.Category.OCEAN || biomeCategory == Biome.Category.RIVER ) {
return NaturalSpawnManager.checkSpawnRulesWater( type, world, reason, pos, random );
}
return NaturalSpawnManager.checkSpawnRulesDefault( type, world, reason, pos, random );
}
/** @return True if this entity's position is currently obstructed. */
@Override
public boolean checkSpawnObstruction( IWorldReader world ) { return world.isUnobstructed( this ); }
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Drowning Creeper",
@ -66,7 +114,23 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity {
//--------------- Variant-Specific Implementations ----------------
public DrowningCreeperEntity( EntityType<? extends _SpecialCreeperEntity> entityType, World world ) { super( entityType, world ); }
public DrowningCreeperEntity( EntityType<? extends _SpecialCreeperEntity> entityType, World world ) {
super( entityType, world );
moveControl = new AmphibiousMovementController<>( this );
waterNavigation = new SwimmerPathNavigator( this, world );
groundNavigation = new GroundPathNavigator( this, world );
maxUpStep = 1.0F;
setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() );
}
/** Override to change this entity's AI goals. */
@Override
protected void registerVariantGoals() {
AIHelper.removeGoals( goalSelector, SwimGoal.class );
AIHelper.insertGoal( goalSelector, 5, new AmphibiousGoToWaterGoal( this, 1.0 ).alwaysEnabled() );
AIHelper.insertGoal( goalSelector, 6, new AmphibiousSwimUpGoal<>( this, 1.0 ) );
AIHelper.replaceWaterAvoidingRandomWalking( this, 0.8 );
}
/** Override to change this creeper's explosion power multiplier. */
@Override
@ -92,7 +156,7 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity {
final int rMinusOneSq = (radius - 1) * (radius - 1);
final BlockPos center = new BlockPos( explosion.getPos() );
// Track how many pufferfish have been spawned
// Track how many pufferfish have been spawned so we don't spawn a bunch of them
spawnPufferfish( center.above( 1 ) );
int pufferCount = 1;
@ -123,8 +187,7 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity {
// Water fill
level.setBlock( pos, water, References.SetBlockFlags.DEFAULTS );
// Prevent greater radiuses from spawning a bazillion pufferfish
if( random.nextFloat() < 0.01F && pufferCount < 10 ) {
if( random.nextFloat() < 0.0075F && pufferCount < 5 ) {
spawnPufferfish( pos );
pufferCount++;
}
@ -153,4 +216,72 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity {
@Override
public boolean isInWaterRainOrBubble() { return true; }
/** Override to load data from this entity's NBT data. */
@Override
public void readVariantSaveData( CompoundNBT saveTag ) {
setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() );
}
//--------------- IAmphibiousMob Implementation ----------------
private final SwimmerPathNavigator waterNavigation;
private final GroundPathNavigator groundNavigation;
private boolean swimmingUp;
/** Called each tick to update this entity's swimming state. */
@Override
public void updateSwimming() {
if( !level.isClientSide ) {
if( isEffectiveAi() && isUnderWater() && shouldSwim() ) {
setNavigatorToSwim();
setSwimming( true );
}
else {
setNavigatorToGround();
setSwimming( false );
}
}
}
/** Moves this entity in the desired direction. Input magnitude of < 1 scales down movement speed. */
@Override
public void travel( Vector3d input ) {
if( isEffectiveAi() && isUnderWater() && shouldSwim() ) {
moveRelative( 0.01F, input );
move( MoverType.SELF, getDeltaMovement() );
setDeltaMovement( getDeltaMovement().scale( 0.9 ) );
}
else super.travel( input );
}
/** @return Water drag coefficient. */
@Override
protected float getWaterSlowDown() { return 0.9F; }
/** @return True if this mob should use its swimming navigator for its current goal. */
@Override
public boolean shouldSwim() {
if( swimmingUp ) return true;
final LivingEntity target = getTarget();
return target != null && target.isInWater();
}
/** Sets whether this mob should swim upward. */
@Override
public void setSwimmingUp( boolean value ) { swimmingUp = value; }
/** @return True if this mob should swim upward. */
@Override
public boolean isSwimmingUp() { return swimmingUp; }
/** Sets this mob's current navigator to swimming mode. */
@Override
public void setNavigatorToSwim() { navigation = waterNavigation; }
/** Sets this mob's current navigator to ground mode. */
@Override
public void setNavigatorToGround() { navigation = groundNavigation; }
}

View file

@ -142,6 +142,8 @@ public class EnderCreeperEntity extends _SpecialCreeperEntity implements IAngera
entityData.define( DATA_STARED_AT, false );
}
public boolean isCreepy() { return entityData.get( DATA_CREEPY ); }
private boolean hasBeenStaredAt() { return entityData.get( DATA_STARED_AT ); }
private void setBeingStaredAt() { entityData.set( DATA_STARED_AT, true ); }

View file

@ -34,7 +34,7 @@ public class SkeletonCreeperEntity extends _SpecialCreeperEntity {
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0xC1C1C1 ).theme( BestiaryInfo.Theme.FOREST )
.uniqueTextureBaseOnly()
.addExperience( 1 )
.addExperience( 1 ).undead()
.addToAttribute( Attributes.MAX_HEALTH, -4.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 1.2 );
}

View file

@ -9,6 +9,7 @@ import fathertoast.specialmobs.common.entity.ISpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.SpecialMobData;
import fathertoast.specialmobs.common.entity.ai.IExplodingMob;
import fathertoast.specialmobs.common.event.NaturalSpawnManager;
import fathertoast.specialmobs.common.util.ExplosionHelper;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
@ -62,6 +63,11 @@ public class _SpecialCreeperEntity extends CreeperEntity implements IExplodingMo
@SpecialMob.AttributeSupplier
public static AttributeModifierMap.MutableAttribute createAttributes() { return CreeperEntity.createAttributes(); }
@SpecialMob.SpawnPlacementRegistrar
public static void registerSpawnPlacement( MobFamily.Species<? extends _SpecialCreeperEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Creeper",
@ -321,6 +327,19 @@ public class _SpecialCreeperEntity extends CreeperEntity implements IExplodingMo
this.setPathfindingMalus(nodeType, malus);
}
/** Converts this entity to one of another type. */
@Nullable
@Override
public <T extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override
@ -359,10 +378,10 @@ public class _SpecialCreeperEntity extends CreeperEntity implements IExplodingMo
getSpecialData().tick();
}
// /** @return The eye height of this entity when standing. */ - Creepers use auto-scaled eye height
// /** @return The eye height of this entity when standing. */
// @Override
// protected float getStandingEyeHeight( Pose pose, EntitySize size ) {
// return super.getStandingEyeHeight( pose, size ) * getSpecialData().getBaseScale() * (isBaby() ? 0.53448F : 1.0F);
// return super.getStandingEyeHeight( pose, size ) * getSpecialData().getHeightScaleByAge();
// }
/** @return Whether this entity is immune to fire damage. */

View file

@ -0,0 +1,77 @@
package fathertoast.specialmobs.common.entity.drowned;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.core.register.SMEffects;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.ai.AIHelper;
import fathertoast.specialmobs.common.entity.ai.goal.AmphibiousGoToWaterGoal;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.item.Items;
import net.minecraft.potion.Effects;
import net.minecraft.world.World;
@SpecialMob
public class AbyssalDrownedEntity extends _SpecialDrownedEntity {
//--------------- Static Special Mob Hooks ----------------
@SpecialMob.SpeciesReference
public static MobFamily.Species<AbyssalDrownedEntity> SPECIES;
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0x223844 ).weight( BestiaryInfo.DefaultWeight.LOW )
.uniqueTexturesAll()
.addExperience( 2 ).effectImmune( SMEffects.WEIGHT, Effects.LEVITATION )
.addToAttribute( Attributes.MAX_HEALTH, 20.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 1.2 );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Abyssal Drowned",
"", "", "", "", "", "" );//TODO
}
@SpecialMob.LootTableProvider
public static void buildLootTable( LootTableBuilder loot ) {
addBaseLoot( loot );
loot.addUncommonDrop( "uncommon", Items.GOLD_NUGGET, Items.PRISMARINE_SHARD, Items.PRISMARINE_CRYSTALS );
loot.addRareDrop( "rare", Items.GOLD_INGOT );
}
@SpecialMob.Factory
public static EntityType.IFactory<AbyssalDrownedEntity> getVariantFactory() { return AbyssalDrownedEntity::new; }
/** @return This entity's mob species. */
@SpecialMob.SpeciesSupplier
@Override
public MobFamily.Species<? extends AbyssalDrownedEntity> getSpecies() { return SPECIES; }
//--------------- Variant-Specific Implementations ----------------
public AbyssalDrownedEntity( EntityType<? extends _SpecialDrownedEntity> entityType, World world ) { super( entityType, world ); }
/** Override to change this entity's AI goals. */
@Override
protected void registerVariantGoals() {
// Disable all 'night time' behavior changes except for targeting
AIHelper.removeGoals( goalSelector, 1 ); // DrownedEntity.GoToWaterGoal
goalSelector.addGoal( 1, new AmphibiousGoToWaterGoal( this, 1.0 ).alwaysEnabled() );
AIHelper.removeGoals( goalSelector, 5 ); // DrownedEntity.GoToBeachGoal
AIHelper.removeGoals( goalSelector, 6 ); // DrownedEntity.SwimUpGoal
}
/** Override to apply effects when this entity hits a target with a melee attack. */
@Override
protected void onVariantAttack( LivingEntity target ) {
MobHelper.applyEffect( target, SMEffects.WEIGHT.get(), 2 );
}
}

View file

@ -0,0 +1,64 @@
package fathertoast.specialmobs.common.entity.drowned;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.item.Items;
import net.minecraft.world.World;
@SpecialMob
public class BruteDrownedEntity extends _SpecialDrownedEntity {
//--------------- Static Special Mob Hooks ----------------
@SpecialMob.SpeciesReference
public static MobFamily.Species<BruteDrownedEntity> SPECIES;
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0xFFF87E )
.uniqueTextureBaseOnly()
.size( 1.2F, 0.7F, 2.35F )
.addExperience( 2 )
.addToAttribute( Attributes.MAX_HEALTH, 10.0 ).addToAttribute( Attributes.ARMOR, 10.0 );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Drowned Brute",
"", "", "", "", "", "" );//TODO
}
@SpecialMob.LootTableProvider
public static void buildLootTable( LootTableBuilder loot ) {
addBaseLoot( loot );
loot.addCommonDrop( "common", Items.FLINT, 1 );
loot.addRareDrop( "rare", Items.IRON_INGOT );
}
@SpecialMob.Factory
public static EntityType.IFactory<BruteDrownedEntity> getVariantFactory() { return BruteDrownedEntity::new; }
/** @return This entity's mob species. */
@SpecialMob.SpeciesSupplier
@Override
public MobFamily.Species<? extends BruteDrownedEntity> getSpecies() { return SPECIES; }
//--------------- Variant-Specific Implementations ----------------
public BruteDrownedEntity( EntityType<? extends _SpecialDrownedEntity> entityType, World world ) { super( entityType, world ); }
/** Override to apply effects when this entity hits a target with a melee attack. */
@Override
protected void onVariantAttack( LivingEntity target ) {
MobHelper.causeLifeLoss( target, 2.0F );
MobHelper.knockback( this, target, 2.0F, 1.0F );
}
}

View file

@ -0,0 +1,141 @@
package fathertoast.specialmobs.common.entity.drowned;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.config.Config;
import fathertoast.specialmobs.common.config.species.DrownedSpeciesConfig;
import fathertoast.specialmobs.common.config.species.SpeciesConfig;
import fathertoast.specialmobs.common.entity.ai.IAngler;
import fathertoast.specialmobs.common.entity.ai.goal.AnglerGoal;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootEntryItemBuilder;
import fathertoast.specialmobs.datagen.loot.LootHelper;
import fathertoast.specialmobs.datagen.loot.LootPoolBuilder;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.ILivingEntityData;
import net.minecraft.entity.SpawnReason;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.inventory.EquipmentSlotType;
import net.minecraft.item.IDyeableArmorItem;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.network.datasync.DataParameter;
import net.minecraft.network.datasync.DataSerializers;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.IServerWorld;
import net.minecraft.world.World;
import javax.annotation.Nullable;
@SpecialMob
public class FishingDrownedEntity extends _SpecialDrownedEntity implements IAngler {
//--------------- Static Special Mob Hooks ----------------
@SpecialMob.SpeciesReference
public static MobFamily.Species<FishingDrownedEntity> SPECIES;
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0x2D41F4 ).weight( BestiaryInfo.DefaultWeight.LOW ).theme( BestiaryInfo.Theme.FISHING )
.addExperience( 2 ).drownImmune().fluidPushImmune()
.convertThrowToFishing().fishingAttack( 1.0, 40, 15.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 );
}
@SpecialMob.ConfigSupplier
public static SpeciesConfig createConfig( MobFamily.Species<?> species ) {
return new DrownedSpeciesConfig( species, 0.0, 0.0 );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Drowned Fisher",
"", "", "", "", "", "" );//TODO
}
@SpecialMob.LootTableProvider
public static void buildLootTable( LootTableBuilder loot ) {
addBaseLoot( loot );
loot.addPool( new LootPoolBuilder( "common" )
.addEntry( new LootEntryItemBuilder( Items.COD ).setCount( 0, 2 ).addLootingBonus( 0, 1 ).smeltIfBurning().toLootEntry() )
.toLootPool() );
loot.addPool( new LootPoolBuilder( "semicommon" )
.addEntry( new LootEntryItemBuilder( Items.SALMON ).setCount( 0, 1 ).addLootingBonus( 0, 1 ).smeltIfBurning().toLootEntry() )
.toLootPool() );
loot.addPool( new LootPoolBuilder( "rare" ).addConditions( LootHelper.RARE_CONDITIONS )
.addEntry( new LootEntryItemBuilder( Items.FISHING_ROD ).enchant( 30, true ).toLootEntry() )
.toLootPool() );
}
@SpecialMob.Factory
public static EntityType.IFactory<FishingDrownedEntity> getVariantFactory() { return FishingDrownedEntity::new; }
/** @return This entity's mob species. */
@SpecialMob.SpeciesSupplier
@Override
public MobFamily.Species<? extends FishingDrownedEntity> getSpecies() { return SPECIES; }
//--------------- Variant-Specific Implementations ----------------
public FishingDrownedEntity( EntityType<? extends _SpecialDrownedEntity> entityType, World world ) { super( entityType, world ); }
/** Override to change this entity's AI goals. */
@Override
protected void registerVariantGoals() {
// Don't bother removing the trident attack goal, too much effort
goalSelector.addGoal( 2, new AnglerGoal<>( this ) );
}
/** Override to change starting equipment or stats. */
@Override
public void finalizeVariantSpawn( IServerWorld world, DifficultyInstance difficulty, @Nullable SpawnReason spawnReason,
@Nullable ILivingEntityData groupData ) {
setItemSlot( EquipmentSlotType.MAINHAND, new ItemStack( Items.FISHING_ROD ) );
if( getItemBySlot( EquipmentSlotType.FEET ).isEmpty() ) {
ItemStack booties = new ItemStack( Items.LEATHER_BOOTS );
((IDyeableArmorItem) booties.getItem()).setColor( booties, 0xFFFF00 );
setItemSlot( EquipmentSlotType.FEET, booties );
}
setCanPickUpLoot( false );
}
//--------------- IAngler Implementations ----------------
/** The parameter for baby status. */
private static final DataParameter<Boolean> IS_LINE_OUT = EntityDataManager.defineId( FishingDrownedEntity.class, DataSerializers.BOOLEAN );
/** Called from the Entity.class constructor to define data watcher variables. */
@Override
protected void defineSynchedData() {
super.defineSynchedData();
entityData.define( IS_LINE_OUT, false );
}
/** Sets this angler's line as out (or in). */
@Override
public void setLineOut( boolean value ) { getEntityData().set( IS_LINE_OUT, value ); }
/** @return Whether this angler's line is out. */
@Override
public boolean isLineOut() { return getEntityData().get( IS_LINE_OUT ); }
/** @return The item equipped in a particular slot. */
@Override
public ItemStack getItemBySlot( EquipmentSlotType slot ) {
// Display a stick in place of the "cast fishing rod" when the fancy render is disabled
if( level.isClientSide() && !Config.MAIN.GENERAL.fancyFishingMobs.get() && EquipmentSlotType.MAINHAND.equals( slot ) ) {
final ItemStack held = super.getItemBySlot( slot );
if( held.getItem() == Items.FISHING_ROD && isLineOut() ) {
return new ItemStack( Items.STICK );
}
return held;
}
return super.getItemBySlot( slot );
}
}

View file

@ -0,0 +1,73 @@
package fathertoast.specialmobs.common.entity.drowned;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.item.Items;
import net.minecraft.world.World;
@SpecialMob
public class GiantDrownedEntity extends _SpecialDrownedEntity {
//--------------- Static Special Mob Hooks ----------------
@SpecialMob.SpeciesReference
public static MobFamily.Species<GiantDrownedEntity> SPECIES;
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0x799C65 ).theme( BestiaryInfo.Theme.MOUNTAIN )
.size( 1.5F, 0.9F, 2.95F )
.addExperience( 1 )
.addToAttribute( Attributes.MAX_HEALTH, 20.0 )
.addToAttribute( Attributes.ATTACK_DAMAGE, 2.0 );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Drowned Giant",
"", "", "", "", "", "" );//TODO
}
@SpecialMob.LootTableProvider
public static void buildLootTable( LootTableBuilder loot ) {
addBaseLoot( loot );
loot.addGuaranteedDrop( "base", Items.ROTTEN_FLESH, 2 );
}
@SpecialMob.Factory
public static EntityType.IFactory<GiantDrownedEntity> getVariantFactory() { return GiantDrownedEntity::new; }
/** @return This entity's mob species. */
@SpecialMob.SpeciesSupplier
@Override
public MobFamily.Species<? extends GiantDrownedEntity> getSpecies() { return SPECIES; }
//--------------- Variant-Specific Implementations ----------------
public GiantDrownedEntity( EntityType<? extends _SpecialDrownedEntity> entityType, World world ) {
super( entityType, world );
maxUpStep = 1.0F;
}
/** Override to apply effects when this entity hits a target with a melee attack. */
@Override
protected void onVariantAttack( LivingEntity target ) {
MobHelper.knockback( this, target, 4.0F, 0.5F );
}
/** Sets this entity as a baby. */
@Override
public void setBaby( boolean value ) { }
/** @return True if this entity is a baby. */
@Override
public boolean isBaby() { return false; }
}

View file

@ -0,0 +1,93 @@
package fathertoast.specialmobs.common.entity.drowned;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.ILivingEntityData;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.SpawnReason;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Food;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.util.SoundEvents;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.IServerWorld;
import net.minecraft.world.World;
import net.minecraftforge.event.ForgeEventFactory;
import javax.annotation.Nullable;
@SpecialMob
public class HungryDrownedEntity extends _SpecialDrownedEntity {
//--------------- Static Special Mob Hooks ----------------
@SpecialMob.SpeciesReference
public static MobFamily.Species<HungryDrownedEntity> SPECIES;
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0xAB1518 )
.uniqueTextureBaseOnly()
.addExperience( 2 ).regen( 30 ).disableRangedAttack()
.addToAttribute( Attributes.MAX_HEALTH, 10.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 1.3 );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Drowned Hunger",
"", "", "", "", "", "" );//TODO
}
@SpecialMob.LootTableProvider
public static void buildLootTable( LootTableBuilder loot ) {
addBaseLoot( loot );
loot.addCommonDrop( "common", Items.BONE );
loot.addUncommonDrop( "uncommon", Items.BEEF, Items.CHICKEN, Items.MUTTON, Items.PORKCHOP, Items.RABBIT, Items.COOKIE );
}
@SpecialMob.Factory
public static EntityType.IFactory<HungryDrownedEntity> getVariantFactory() { return HungryDrownedEntity::new; }
/** @return This entity's mob species. */
@SpecialMob.SpeciesSupplier
@Override
public MobFamily.Species<? extends HungryDrownedEntity> getSpecies() { return SPECIES; }
//--------------- Variant-Specific Implementations ----------------
public HungryDrownedEntity( EntityType<? extends _SpecialDrownedEntity> entityType, World world ) { super( entityType, world ); }
/** Override to change starting equipment or stats. */
@Override
public void finalizeVariantSpawn( IServerWorld world, DifficultyInstance difficulty, @Nullable SpawnReason spawnReason,
@Nullable ILivingEntityData groupData ) {
setCanPickUpLoot( false );
}
/** Override to apply effects when this entity hits a target with a melee attack. */
@Override
protected void onVariantAttack( LivingEntity target ) {
if( level.isClientSide() ) return;
if( target instanceof PlayerEntity && ForgeEventFactory.getMobGriefingEvent( level, this ) ) {
final ItemStack food = MobHelper.stealRandomFood( (PlayerEntity) target );
if( !food.isEmpty() ) {
final Food foodStats = food.getItem().getFoodProperties();
heal( Math.max( foodStats == null ? 0.0F : foodStats.getNutrition(), 1.0F ) );
playSound( SoundEvents.PLAYER_BURP, 0.5F, random.nextFloat() * 0.1F + 0.9F );
return;
}
}
// Take a bite out of the target if they have no food to eat
MobHelper.stealLife( this, target, 2.0F );
}
}

View file

@ -0,0 +1,84 @@
package fathertoast.specialmobs.common.entity.drowned;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.config.species.DrownedSpeciesConfig;
import fathertoast.specialmobs.common.config.species.SpeciesConfig;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.ILivingEntityData;
import net.minecraft.entity.SpawnReason;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.inventory.EquipmentSlotType;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.IServerWorld;
import net.minecraft.world.World;
import javax.annotation.Nullable;
@SpecialMob
public class KnightDrownedEntity extends _SpecialDrownedEntity {
//--------------- Static Special Mob Hooks ----------------
@SpecialMob.SpeciesReference
public static MobFamily.Species<KnightDrownedEntity> SPECIES;
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0xDDDDDD )
.addExperience( 2 ).multiplyRangedSpread( 1.2 )
.addToAttribute( Attributes.MAX_HEALTH, 10.0 ).addToAttribute( Attributes.ARMOR, 10.0 )
.addToAttribute( Attributes.ATTACK_DAMAGE, 8.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 );
}
@SpecialMob.ConfigSupplier
public static SpeciesConfig createConfig( MobFamily.Species<?> species ) {
return new DrownedSpeciesConfig( species, 0.9, 1.0 );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Drowned Knight",
"", "", "", "", "", "" );//TODO
}
@SpecialMob.LootTableProvider
public static void buildLootTable( LootTableBuilder loot ) {
addBaseLoot( loot );
loot.addCommonDrop( "common", Items.GOLD_NUGGET );
loot.addUncommonDrop( "uncommon", Items.GOLD_INGOT );
}
@SpecialMob.Factory
public static EntityType.IFactory<KnightDrownedEntity> getVariantFactory() { return KnightDrownedEntity::new; }
/** @return This entity's mob species. */
@SpecialMob.SpeciesSupplier
@Override
public MobFamily.Species<? extends KnightDrownedEntity> getSpecies() { return SPECIES; }
//--------------- Variant-Specific Implementations ----------------
public KnightDrownedEntity( EntityType<? extends _SpecialDrownedEntity> entityType, World world ) { super( entityType, world ); }
/** Override to change starting equipment or stats. */
@Override
public void finalizeVariantSpawn( IServerWorld world, DifficultyInstance difficulty, @Nullable SpawnReason spawnReason,
@Nullable ILivingEntityData groupData ) {
final ItemStack heldItem = getItemBySlot( EquipmentSlotType.MAINHAND );
if( heldItem.isEmpty() || heldItem.getItem() == Items.FISHING_ROD ) {
setItemSlot( EquipmentSlotType.MAINHAND, new ItemStack( Items.GOLDEN_SWORD ) );
}
setItemSlot( EquipmentSlotType.HEAD, new ItemStack( Items.CHAINMAIL_HELMET ) );
setItemSlot( EquipmentSlotType.CHEST, new ItemStack( Items.CHAINMAIL_CHESTPLATE ) );
setItemSlot( EquipmentSlotType.LEGS, new ItemStack( Items.CHAINMAIL_LEGGINGS ) );
setItemSlot( EquipmentSlotType.FEET, new ItemStack( Items.CHAINMAIL_BOOTS ) );
}
}

View file

@ -0,0 +1,63 @@
package fathertoast.specialmobs.common.entity.drowned;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.Blocks;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.item.Items;
import net.minecraft.world.World;
@SpecialMob
public class PlagueDrownedEntity extends _SpecialDrownedEntity {
//--------------- Static Special Mob Hooks ----------------
@SpecialMob.SpeciesReference
public static MobFamily.Species<PlagueDrownedEntity> SPECIES;
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0x8AA838 ).theme( BestiaryInfo.Theme.FOREST )
.uniqueTextureBaseOnly()
.addExperience( 1 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 1.1 );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Drowned Plague",
"", "", "", "", "", "" );//TODO
}
@SpecialMob.LootTableProvider
public static void buildLootTable( LootTableBuilder loot ) {
addBaseLoot( loot );
loot.addUncommonDrop( "uncommon", Items.POISONOUS_POTATO, Items.SPIDER_EYE, Items.FERMENTED_SPIDER_EYE,
Blocks.RED_MUSHROOM, Blocks.BROWN_MUSHROOM );
}
@SpecialMob.Factory
public static EntityType.IFactory<PlagueDrownedEntity> getVariantFactory() { return PlagueDrownedEntity::new; }
/** @return This entity's mob species. */
@SpecialMob.SpeciesSupplier
@Override
public MobFamily.Species<? extends PlagueDrownedEntity> getSpecies() { return SPECIES; }
//--------------- Variant-Specific Implementations ----------------
public PlagueDrownedEntity( EntityType<? extends _SpecialDrownedEntity> entityType, World world ) { super( entityType, world ); }
/** Override to apply effects when this entity hits a target with a melee attack. */
@Override
protected void onVariantAttack( LivingEntity target ) {
MobHelper.applyPlagueEffect( target, random );
}
}

View file

@ -0,0 +1,397 @@
package fathertoast.specialmobs.common.entity.drowned;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.config.species.DrownedSpeciesConfig;
import fathertoast.specialmobs.common.config.species.SpeciesConfig;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.SpecialMobData;
import fathertoast.specialmobs.common.entity.ai.AIHelper;
import fathertoast.specialmobs.common.entity.ai.goal.SpecialDrownedAttackGoal;
import fathertoast.specialmobs.common.entity.ai.goal.SpecialHurtByTargetGoal;
import fathertoast.specialmobs.common.entity.ai.goal.SpecialTridentAttackGoal;
import fathertoast.specialmobs.common.event.NaturalSpawnManager;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.BlockState;
import net.minecraft.entity.*;
import net.minecraft.entity.ai.attributes.AttributeModifierMap;
import net.minecraft.entity.monster.DrownedEntity;
import net.minecraft.entity.monster.ZombifiedPiglinEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.projectile.SnowballEntity;
import net.minecraft.entity.projectile.TridentEntity;
import net.minecraft.inventory.EquipmentSlotType;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.datasync.DataParameter;
import net.minecraft.network.datasync.DataSerializers;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.potion.EffectInstance;
import net.minecraft.util.DamageSource;
import net.minecraft.util.SoundEvents;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.IServerWorld;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.Random;
@SpecialMob
public class _SpecialDrownedEntity extends DrownedEntity implements ISpecialMob<_SpecialDrownedEntity> {
//--------------- Static Special Mob Hooks ----------------
@SpecialMob.SpeciesReference
public static MobFamily.Species<_SpecialDrownedEntity> SPECIES;
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0x799C65 )
.vanillaTextureWithOverlay( "textures/entity/zombie/drowned.png", "textures/entity/zombie/drowned_outer_layer.png" )
.experience( 5 ).undead()
.throwAttack( 1.0, 1.0, 40, 10.0 );
}
protected static final double DEFAULT_TRIDENT_CHANCE = 0.0625;
protected static final double DEFAULT_SHIELD_CHANCE = 0.0625;
@SpecialMob.ConfigSupplier
public static SpeciesConfig createConfig( MobFamily.Species<?> species ) {
return new DrownedSpeciesConfig( species, DEFAULT_TRIDENT_CHANCE, DEFAULT_SHIELD_CHANCE );
}
/** @return This entity's species config. */
public DrownedSpeciesConfig getConfig() { return (DrownedSpeciesConfig) getSpecies().config; }
@SpecialMob.AttributeSupplier
public static AttributeModifierMap.MutableAttribute createAttributes() { return DrownedEntity.createAttributes(); }
@SpecialMob.SpawnPlacementRegistrar
public static void registerSpawnPlacement( MobFamily.Species<? extends _SpecialDrownedEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species, EntitySpawnPlacementRegistry.PlacementType.IN_WATER,
_SpecialDrownedEntity::checkFamilySpawnRules );
}
public static boolean checkFamilySpawnRules( EntityType<? extends DrownedEntity> type, IServerWorld world,
SpawnReason reason, BlockPos pos, Random random ) {
//noinspection unchecked
return DrownedEntity.checkDrownedSpawnRules( (EntityType<DrownedEntity>) type, world, reason, pos, random ) &&
NaturalSpawnManager.checkSpawnRulesConfigured( type, world, reason, pos, random );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Drowned",
"", "", "", "", "", "" );//TODO
}
@SpecialMob.LootTableProvider
public static void addBaseLoot( LootTableBuilder loot ) {
loot.addLootTable( "main", EntityType.DROWNED.getDefaultLootTable() );
}
@SpecialMob.Factory
public static EntityType.IFactory<_SpecialDrownedEntity> getFactory() { return _SpecialDrownedEntity::new; }
//--------------- Variant-Specific Breakouts ----------------
/** Called in the MobEntity.class constructor to initialize AI goals. */
@Override
protected void registerGoals() {
super.registerGoals();
// We don't really want to remove the melee attack goal, so just re-add it afterward
AIHelper.removeGoals( goalSelector, 2 ); // DrownedEntity.TridentAttackGoal & DrownedEntity.AttackGoal
goalSelector.addGoal( 2, new SpecialDrownedAttackGoal( this, 1.0, false ) );
AIHelper.replaceHurtByTarget( this, new SpecialHurtByTargetGoal( this, DrownedEntity.class )
.setAlertOthers( ZombifiedPiglinEntity.class ) );
registerVariantGoals();
}
/** Override to change this entity's AI goals. */
protected void registerVariantGoals() { }
/** Override to change starting equipment or stats. */
public void finalizeVariantSpawn( IServerWorld world, DifficultyInstance difficulty, @Nullable SpawnReason spawnReason,
@Nullable ILivingEntityData groupData ) { }
/** Called when this entity successfully damages a target to apply on-hit effects. */
@Override
public void doEnchantDamageEffects( LivingEntity attacker, Entity target ) {
if( target instanceof LivingEntity ) onVariantAttack( (LivingEntity) target );
super.doEnchantDamageEffects( attacker, target );
}
/** Override to apply effects when this entity hits a target with a melee attack. */
protected void onVariantAttack( LivingEntity target ) { }
/** Override to save data to this entity's NBT data. */
public void addVariantSaveData( CompoundNBT saveTag ) { }
/** Override to load data from this entity's NBT data. */
public void readVariantSaveData( CompoundNBT saveTag ) { }
//--------------- Family-Specific Implementations ----------------
/** The parameter for special mob render scale. */
private static final DataParameter<Float> SCALE = EntityDataManager.defineId( _SpecialDrownedEntity.class, DataSerializers.FLOAT );
private boolean needsToBeDeeper;
public _SpecialDrownedEntity( EntityType<? extends _SpecialDrownedEntity> entityType, World world ) {
super( entityType, world );
recalculateAttackGoal();
getSpecialData().initialize();
}
/** Called from the Entity.class constructor to define data watcher variables. */
@Override
protected void defineSynchedData() {
super.defineSynchedData();
specialData = new SpecialMobData<>( this, SCALE );
}
/** Called to update this entity's attack AI based on NBT data. */
public void recalculateAttackGoal() {
if( level != null && !level.isClientSide ) {
AIHelper.removeGoals( goalSelector, SpecialTridentAttackGoal.class );
if( getSpecialData().getRangedAttackMaxRange() > 0.0F ) {
goalSelector.addGoal( 2, new SpecialTridentAttackGoal( this, getSpecialData().getRangedWalkSpeed(),
getSpecialData().getRangedAttackCooldown(), getSpecialData().getRangedAttackMaxRange() ) );
}
}
}
/** Called to attack the target with a ranged attack. */
@Override
public void performRangedAttack( LivingEntity target, float damageMulti ) {
final TridentEntity trident = new TridentEntity( level, this, new ItemStack( Items.TRIDENT ) );
final double dX = target.getX() - getX();
final double dY = target.getY( 0.3333 ) - trident.getY();
final double dZ = target.getZ() - getZ();
final double dH = MathHelper.sqrt( dX * dX + dZ * dZ );
trident.shoot( dX, dY + dH * 0.2, dZ, 1.6F,
getSpecialData().getRangedAttackSpread() * (14 - level.getDifficulty().getId() * 4) );
playSound( SoundEvents.DROWNED_SHOOT, 1.0F, 1.0F / (getRandom().nextFloat() * 0.4F + 0.8F) );
level.addFreshEntity( trident );
}
/** Called each tick to update this entity's swimming state. */
@Override
public void updateSwimming() {
if( !level.isClientSide && isEffectiveAi() ) needsToBeDeeper = true;
super.updateSwimming();
}
/** Moves this entity in the desired direction. Input magnitude of < 1 scales down movement speed. */
@Override
public void travel( Vector3d input ) {
if( isEffectiveAi() ) needsToBeDeeper = true;
super.travel( input );
}
/** @return Water drag coefficient. */
@Override
protected float getWaterSlowDown() { return 0.9F; } // Improve mobility in shallow water
/** @return True if this entity is in water. */
@Override
public boolean isInWater() {
// Hacky way to fix vanilla drowned AI breaking in shallow water
if( needsToBeDeeper ) {
needsToBeDeeper = false;
return isUnderWater();
}
return super.isInWater();
}
//--------------- ISpecialMob Implementation ----------------
private SpecialMobData<_SpecialDrownedEntity> specialData;
/** @return This mob's special data. */
@Override
public SpecialMobData<_SpecialDrownedEntity> getSpecialData() { return specialData; }
/** @return This entity's mob species. */
@SpecialMob.SpeciesSupplier
@Override
public MobFamily.Species<? extends _SpecialDrownedEntity> getSpecies() { return SPECIES; }
/** @return The experience that should be dropped by this entity. */
@Override
public final int getExperience() { return xpReward; }
/** Sets the experience that should be dropped by this entity. */
@Override
public final void setExperience( int xp ) { xpReward = xp; }
/** Converts this entity to one of another type. */
@Nullable
@Override
public <T extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override
public final ILivingEntityData finalizeSpawn( IServerWorld world, DifficultyInstance difficulty, SpawnReason spawnReason,
@Nullable ILivingEntityData groupData, @Nullable CompoundNBT eggTag ) {
return MobHelper.finalizeSpawn( this, world, difficulty, spawnReason,
super.finalizeSpawn( world, difficulty, spawnReason, groupData, eggTag ) );
}
/** Called on spawn to set starting equipment. */
@Override // Seal method to force spawn equipment changes through ISpecialMob
protected final void populateDefaultEquipmentSlots( DifficultyInstance difficulty ) { super.populateDefaultEquipmentSlots( difficulty ); }
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Override
public void finalizeSpecialSpawn( IServerWorld world, DifficultyInstance difficulty, @Nullable SpawnReason spawnReason,
@Nullable ILivingEntityData groupData ) {
// Completely re-roll held item
final ItemStack heldItem = getItemBySlot( EquipmentSlotType.MAINHAND );
if( heldItem.isEmpty() || heldItem.getItem() == Items.TRIDENT || heldItem.getItem() == Items.FISHING_ROD ) {
final double heldItemChoice = random.nextDouble();
if( getSpecialData().getRangedAttackMaxRange() > 0.0F && heldItemChoice < getConfig().DROWNED.tridentEquipChance.get() ) {
setItemSlot( EquipmentSlotType.MAINHAND, new ItemStack( Items.TRIDENT ) );
}
else if( heldItemChoice >= 0.9625 ) { // Vanilla's 3.75% chance; not configurable because not important
setItemSlot( EquipmentSlotType.MAINHAND, new ItemStack( Items.FISHING_ROD ) );
}
else {
setItemSlot( EquipmentSlotType.MAINHAND, ItemStack.EMPTY );
}
}
if( getConfig().DROWNED.shieldEquipChance.rollChance( random ) ) {
setItemSlot( EquipmentSlotType.OFFHAND, new ItemStack( Items.SHIELD ) );
}
finalizeVariantSpawn( world, difficulty, spawnReason, groupData );
}
//--------------- SpecialMobData Hooks ----------------
/** Called each tick to update this entity's movement. */
@Override
public void aiStep() {
super.aiStep();
getSpecialData().tick();
}
/** @return The eye height of this entity when standing. */
@Override
protected float getStandingEyeHeight( Pose pose, EntitySize size ) {
return super.getStandingEyeHeight( pose, size ) * getSpecialData().getHeightScale(); // Age handled in super
}
/** @return Whether this entity is immune to fire damage. */
@Override
public boolean fireImmune() { return getSpecialData().isImmuneToFire(); }
/** Sets this entity on fire for a specific duration. */
@Override
public void setRemainingFireTicks( int ticks ) {
if( !getSpecialData().isImmuneToBurning() ) super.setRemainingFireTicks( ticks );
}
/** @return True if this zombie burns in sunlight. */
@Override
protected boolean isSunSensitive() { return !getSpecialData().isImmuneToFire() && !getSpecialData().isImmuneToBurning(); }
/** @return True if this entity can be leashed. */
@Override
public boolean canBeLeashed( PlayerEntity player ) { return !isLeashed() && getSpecialData().allowLeashing(); }
/** Sets this entity 'stuck' inside a block, such as a cobweb or sweet berry bush. Mod blocks could use this as a speed boost. */
@Override
public void makeStuckInBlock( BlockState block, Vector3d speedMulti ) {
if( getSpecialData().canBeStuckIn( block ) ) super.makeStuckInBlock( block, speedMulti );
}
/** @return Called when this mob falls. Calculates and applies fall damage. Returns false if canceled. */
@Override
public boolean causeFallDamage( float distance, float damageMultiplier ) {
return super.causeFallDamage( distance, damageMultiplier * getSpecialData().getFallDamageMultiplier() );
}
/** @return True if this entity should NOT trigger pressure plates or tripwires. */
@Override
public boolean isIgnoringBlockTriggers() { return getSpecialData().ignorePressurePlates(); }
/** @return True if this entity can breathe underwater. */
@Override
public boolean canBreatheUnderwater() { return getSpecialData().canBreatheInWater(); }
/** @return True if this entity can be pushed by (flowing) fluids. */
@Override
public boolean isPushedByFluid() { return !getSpecialData().ignoreWaterPush(); }
/** @return True if this entity takes damage while wet. */
@Override
public boolean isSensitiveToWater() { return getSpecialData().isDamagedByWater(); }
/** @return Attempts to damage this entity; returns true if the hit was successful. */
@Override
public boolean hurt( DamageSource source, float amount ) {
final Entity entity = source.getDirectEntity();
if( isSensitiveToWater() && entity instanceof SnowballEntity ) {
amount = Math.max( 3.0F, amount );
}
// Shield blocking logic
if( amount > 0.0F && MobHelper.tryBlockAttack( this, source, true ) ) return false;
return super.hurt( source, amount );
}
/** @return True if the effect can be applied to this entity. */
@Override
public boolean canBeAffected( EffectInstance effect ) { return getSpecialData().isPotionApplicable( effect ); }
/** Saves data to this entity's base NBT compound that is specific to its subclass. */
@Override
public void addAdditionalSaveData( CompoundNBT tag ) {
super.addAdditionalSaveData( tag );
final CompoundNBT saveTag = SpecialMobData.getSaveLocation( tag );
getSpecialData().writeToNBT( saveTag );
addVariantSaveData( saveTag );
}
/** Loads data from this entity's base NBT compound that is specific to its subclass. */
@Override
public void readAdditionalSaveData( CompoundNBT tag ) {
super.readAdditionalSaveData( tag );
final CompoundNBT saveTag = SpecialMobData.getSaveLocation( tag );
getSpecialData().readFromNBT( saveTag );
readVariantSaveData( saveTag );
recalculateAttackGoal();
}
}

View file

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

View file

@ -159,7 +159,7 @@ public class IcyEndermanEntity extends _SpecialEndermanEntity {
teleportTo( x, y, z );
if( level.noCollision( this ) && !level.containsAnyLiquid( getBoundingBox() ) ) {
if( spawnParticles ) level.broadcastEntityEvent( this, References.EVENT_TELEPORT_TRAIL_PARTICLES );
if( spawnParticles ) References.EntityEvent.TELEPORT_TRAIL_PARTICLES.broadcast( this );
getNavigation().stop();
return true;
}

View file

@ -6,6 +6,7 @@ import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.SpecialMobData;
import fathertoast.specialmobs.common.event.NaturalSpawnManager;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.BlockState;
@ -44,6 +45,11 @@ public class _SpecialEndermanEntity extends EndermanEntity implements ISpecialMo
@SpecialMob.AttributeSupplier
public static AttributeModifierMap.MutableAttribute createAttributes() { return EndermanEntity.createAttributes(); }
@SpecialMob.SpawnPlacementRegistrar
public static void registerSpawnPlacement( MobFamily.Species<? extends _SpecialEndermanEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Enderman",
@ -132,6 +138,18 @@ public class _SpecialEndermanEntity extends EndermanEntity implements ISpecialMo
@Override
public final void setExperience( int xp ) { xpReward = xp; }
/** Converts this entity to one of another type. */
@Nullable
@Override
public <T extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override
@ -170,7 +188,7 @@ public class _SpecialEndermanEntity extends EndermanEntity implements ISpecialMo
/** @return The eye height of this entity when standing. */
@Override
protected float getStandingEyeHeight( Pose pose, EntitySize size ) {
return super.getStandingEyeHeight( pose, size ) * getSpecialData().getBaseScale() * (isBaby() ? 0.53448F : 1.0F);
return super.getStandingEyeHeight( pose, size ) * getSpecialData().getHeightScaleByAge();
}
/** @return Whether this entity is immune to fire damage. */

View file

@ -116,7 +116,7 @@ public class CorporealShiftGhastEntity extends _SpecialGhastEntity {
return;
}
if( !isSilent() ) level.levelEvent( null, References.EVENT_GHAST_SHOOT, blockPosition(), 0 );
References.LevelEvent.GHAST_SHOOT.play( this );
final float accelVariance = MathHelper.sqrt( distanceTo( target ) ) * 0.5F * getSpecialData().getRangedAttackSpread();
final Vector3d lookVec = getViewVector( 1.0F ).scale( getBbWidth() );

View file

@ -92,7 +92,7 @@ public class QueenGhastEntity extends _SpecialGhastEntity {
final double vH = Math.sqrt( vX * vX + vZ * vZ );
spawnBaby( vX / vH + getDeltaMovement().x * 0.2, vZ / vH + getDeltaMovement().z * 0.2, null );
spawnAnim();
if( !isSilent() ) level.levelEvent( null, References.EVENT_GHAST_SHOOT, blockPosition(), 0 );
References.LevelEvent.GHAST_SHOOT.play( this );
}
else {
super.performRangedAttack( target, damageMulti );
@ -114,7 +114,7 @@ public class QueenGhastEntity extends _SpecialGhastEntity {
groupData = spawnBaby( (random.nextDouble() - 0.5) * 0.3, (random.nextDouble() - 0.5) * 0.3, groupData );
}
spawnAnim();
if( !isSilent() ) level.levelEvent( null, References.EVENT_BLAZE_SHOOT, blockPosition(), 0 );
References.LevelEvent.BLAZE_SHOOT.play( this );
}
super.remove( keepData );
}

View file

@ -11,6 +11,7 @@ import fathertoast.specialmobs.common.entity.ai.SimpleFlyingMovementController;
import fathertoast.specialmobs.common.entity.ai.goal.SpecialGhastFireballAttackGoal;
import fathertoast.specialmobs.common.entity.ai.goal.SpecialGhastLookAroundGoal;
import fathertoast.specialmobs.common.entity.ai.goal.SpecialGhastMeleeAttackGoal;
import fathertoast.specialmobs.common.event.NaturalSpawnManager;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.BlockState;
@ -30,6 +31,7 @@ import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.pathfinding.PathNodeType;
import net.minecraft.potion.EffectInstance;
import net.minecraft.util.DamageSource;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.DifficultyInstance;
@ -37,6 +39,7 @@ import net.minecraft.world.IServerWorld;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.Random;
@SpecialMob
public class _SpecialGhastEntity extends GhastEntity implements IRangedAttackMob, ISpecialMob<_SpecialGhastEntity> {
@ -59,6 +62,18 @@ public class _SpecialGhastEntity extends GhastEntity implements IRangedAttackMob
return GhastEntity.createAttributes().add( Attributes.ATTACK_DAMAGE, 4.0 );
}
@SpecialMob.SpawnPlacementRegistrar
public static void registerSpawnPlacement( MobFamily.Species<? extends _SpecialGhastEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species, _SpecialGhastEntity::checkFamilySpawnRules );
}
public static boolean checkFamilySpawnRules( EntityType<? extends GhastEntity> type, IServerWorld world,
SpawnReason reason, BlockPos pos, Random random ) {
//noinspection unchecked
return GhastEntity.checkGhastSpawnRules( (EntityType<GhastEntity>) type, world, reason, pos, random ) &&
NaturalSpawnManager.checkSpawnRulesConfigured( type, world, reason, pos, random );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Ghast",
@ -107,7 +122,7 @@ public class _SpecialGhastEntity extends GhastEntity implements IRangedAttackMob
/** Called to attack the target with a ranged attack. */
@Override
public void performRangedAttack( LivingEntity target, float damageMulti ) {
if( !isSilent() ) level.levelEvent( null, References.EVENT_GHAST_SHOOT, blockPosition(), 0 );
References.LevelEvent.GHAST_SHOOT.play( this );
final float accelVariance = MathHelper.sqrt( distanceTo( target ) ) * 0.5F * getSpecialData().getRangedAttackSpread();
final Vector3d lookVec = getViewVector( 1.0F ).scale( getBbWidth() );
@ -201,6 +216,18 @@ public class _SpecialGhastEntity extends GhastEntity implements IRangedAttackMob
@Override
public final void setExperience( int xp ) { xpReward = xp; }
/** Converts this entity to one of another type. */
@Nullable
@Override
public <T extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override
@ -240,7 +267,7 @@ public class _SpecialGhastEntity extends GhastEntity implements IRangedAttackMob
/** @return The eye height of this entity when standing. */
@Override
protected float getStandingEyeHeight( Pose pose, EntitySize size ) {
return super.getStandingEyeHeight( pose, size ) * getSpecialData().getBaseScale() * (isBaby() ? 0.53448F : 1.0F);
return super.getStandingEyeHeight( pose, size ) * getSpecialData().getHeightScaleByAge();
}
/** @return Whether this entity is immune to fire damage. */

View file

@ -29,7 +29,7 @@ public class BouncingMagmaCubeEntity extends _SpecialMagmaCubeEntity {
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0xB333B3 ).theme( BestiaryInfo.Theme.MOUNTAIN )
bestiaryInfo.color( 0xB333B3 )
.uniqueTextureBaseOnly()
.addExperience( 1 ).fallImmune()
.addToAttribute( Attributes.MAX_HEALTH, 4.0 )

View file

@ -6,6 +6,7 @@ import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.SpecialMobData;
import fathertoast.specialmobs.common.event.NaturalSpawnManager;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.BlockState;
@ -23,12 +24,14 @@ import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.pathfinding.PathNodeType;
import net.minecraft.potion.EffectInstance;
import net.minecraft.util.DamageSource;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.IServerWorld;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.Random;
@SpecialMob
public class _SpecialMagmaCubeEntity extends MagmaCubeEntity implements ISpecialMob<_SpecialMagmaCubeEntity> {
@ -50,6 +53,18 @@ public class _SpecialMagmaCubeEntity extends MagmaCubeEntity implements ISpecial
return MagmaCubeEntity.createAttributes(); // Slimes define their attributes elsewhere based on size
}
@SpecialMob.SpawnPlacementRegistrar
public static void registerSpawnPlacement( MobFamily.Species<? extends _SpecialMagmaCubeEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species, _SpecialMagmaCubeEntity::checkFamilySpawnRules );
}
public static boolean checkFamilySpawnRules( EntityType<? extends MagmaCubeEntity> type, IServerWorld world,
SpawnReason reason, BlockPos pos, Random random ) {
//noinspection unchecked
return MagmaCubeEntity.checkMagmaCubeSpawnRules( (EntityType<MagmaCubeEntity>) type, world, reason, pos, random ) &&
NaturalSpawnManager.checkSpawnRulesConfigured( type, world, reason, pos, random );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Magma Cube",
@ -164,6 +179,18 @@ public class _SpecialMagmaCubeEntity extends MagmaCubeEntity implements ISpecial
xpReward = getSize() + xp;
}
/** Converts this entity to one of another type. */
@Nullable
@Override
public <T extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override
@ -199,11 +226,11 @@ public class _SpecialMagmaCubeEntity extends MagmaCubeEntity implements ISpecial
getSpecialData().tick();
}
/** @return The eye height of this entity when standing. */
@Override
protected float getStandingEyeHeight( Pose pose, EntitySize size ) {
return super.getStandingEyeHeight( pose, size ) * getSpecialData().getBaseScale() * (isBaby() ? 0.53448F : 1.0F);
}
// /** @return The eye height of this entity when standing. */
// @Override
// protected float getStandingEyeHeight( Pose pose, EntitySize size ) {
// return super.getStandingEyeHeight( pose, size ) * getSpecialData().getHeightScaleByAge();
// }
/** @return Whether this entity is immune to fire damage. */
@Override

View file

@ -0,0 +1,113 @@
package fathertoast.specialmobs.common.entity.silverfish;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.ai.AmphibiousMovementController;
import fathertoast.specialmobs.common.entity.ai.IAmphibiousMob;
import fathertoast.specialmobs.common.event.NaturalSpawnManager;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.MoverType;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.pathfinding.GroundPathNavigator;
import net.minecraft.pathfinding.PathNodeType;
import net.minecraft.pathfinding.SwimmerPathNavigator;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.World;
/**
* A bare-bones implementation of an amphibious silverfish. Just fix the AI and it's good to go.
*/
public abstract class AmphibiousSilverfishEntity extends _SpecialSilverfishEntity implements IAmphibiousMob {
@SpecialMob.SpawnPlacementRegistrar
public static void registerSpeciesSpawnPlacement( MobFamily.Species<? extends AmphibiousSilverfishEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species, EntitySpawnPlacementRegistry.PlacementType.IN_WATER,
_SpecialSilverfishEntity::checkFamilySpawnRules );
}
/** @return True if this entity's position is currently obstructed. */
@Override
public boolean checkSpawnObstruction( IWorldReader world ) { return world.isUnobstructed( this ); }
private final SwimmerPathNavigator waterNavigation;
private final GroundPathNavigator groundNavigation;
private boolean swimmingUp;
public AmphibiousSilverfishEntity( EntityType<? extends _SpecialSilverfishEntity> entityType, World world ) {
super( entityType, world );
moveControl = new AmphibiousMovementController<>( this );
waterNavigation = new SwimmerPathNavigator( this, world );
groundNavigation = new GroundPathNavigator( this, world );
maxUpStep = 1.0F;
setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() );
}
/** Loads data from this entity's base NBT compound that is specific to its subclass. */
@Override
public void readAdditionalSaveData( CompoundNBT tag ) {
super.readAdditionalSaveData( tag );
setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() );
}
//--------------- IAmphibiousMob Implementation ----------------
/** Called each tick to update this entity's swimming state. */
@Override
public void updateSwimming() {
if( !level.isClientSide ) {
if( isEffectiveAi() && isUnderWater() && shouldSwim() ) {
setNavigatorToSwim();
setSwimming( true );
}
else {
setNavigatorToGround();
setSwimming( false );
}
}
}
/** Moves this entity in the desired direction. Input magnitude of < 1 scales down movement speed. */
@Override
public void travel( Vector3d input ) {
if( isEffectiveAi() && isUnderWater() && shouldSwim() ) {
moveRelative( 0.01F, input );
move( MoverType.SELF, getDeltaMovement() );
setDeltaMovement( getDeltaMovement().scale( 0.9 ) );
}
else super.travel( input );
}
/** @return Water drag coefficient. */
@Override
protected float getWaterSlowDown() { return 0.9F; }
/** @return True if this mob should use its swimming navigator for its current goal. */
@Override
public boolean shouldSwim() {
if( swimmingUp ) return true;
final LivingEntity target = getTarget();
return target != null && target.isInWater();
}
/** Sets whether this mob should swim upward. */
@Override
public void setSwimmingUp( boolean value ) { swimmingUp = value; }
/** @return True if this mob should swim upward. */
@Override
public boolean isSwimmingUp() { return swimmingUp; }
/** Sets this mob's current navigator to swimming mode. */
@Override
public void setNavigatorToSwim() { navigation = waterNavigation; }
/** Sets this mob's current navigator to ground mode. */
@Override
public void setNavigatorToGround() { navigation = groundNavigation; }
}

View file

@ -24,7 +24,7 @@ public class BlindingSilverfishEntity extends _SpecialSilverfishEntity {
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0x000000 ).theme( BestiaryInfo.Theme.FOREST )
.uniqueTextureBaseOnly()
.addExperience( 1 ).effectImmune( Effects.BLINDNESS );
.addExperience( 1 );
}
@SpecialMob.LanguageProvider

View file

@ -7,6 +7,8 @@ import fathertoast.specialmobs.common.config.species.SilverfishSpeciesConfig;
import fathertoast.specialmobs.common.config.species.SpeciesConfig;
import fathertoast.specialmobs.common.entity.ai.AIHelper;
import fathertoast.specialmobs.common.entity.ai.IAngler;
import fathertoast.specialmobs.common.entity.ai.goal.AmphibiousGoToShoreGoal;
import fathertoast.specialmobs.common.entity.ai.goal.AmphibiousSwimUpGoal;
import fathertoast.specialmobs.common.entity.ai.goal.AnglerGoal;
import fathertoast.specialmobs.common.entity.ai.goal.PassiveRangedAttackGoal;
import fathertoast.specialmobs.common.util.References;
@ -15,11 +17,12 @@ import fathertoast.specialmobs.datagen.loot.LootPoolBuilder;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.entity.ai.goal.SwimGoal;
import net.minecraft.item.Items;
import net.minecraft.world.World;
@SpecialMob
public class FishingSilverfishEntity extends _SpecialSilverfishEntity implements IAngler {
public class FishingSilverfishEntity extends AmphibiousSilverfishEntity implements IAngler {
//--------------- Static Special Mob Hooks ----------------
@ -77,6 +80,10 @@ public class FishingSilverfishEntity extends _SpecialSilverfishEntity implements
protected void registerVariantGoals() {
AIHelper.removeGoals( goalSelector, PassiveRangedAttackGoal.class ); // Disable spit attack use
goalSelector.addGoal( 4, new AnglerGoal<>( this ) );
AIHelper.removeGoals( goalSelector, SwimGoal.class );
AIHelper.insertGoal( goalSelector, 5, new AmphibiousGoToShoreGoal<>( this, 1.0 ).alwaysEnabled() );
AIHelper.insertGoal( goalSelector, 6, new AmphibiousSwimUpGoal<>( this, 1.0 ).alwaysEnabled() );
}

View file

@ -4,16 +4,19 @@ import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.ai.AIHelper;
import fathertoast.specialmobs.common.entity.ai.goal.AmphibiousGoToWaterGoal;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.goal.SwimGoal;
import net.minecraft.item.Items;
import net.minecraft.potion.Effects;
import net.minecraft.world.World;
@SpecialMob
public class PufferSilverfishEntity extends _SpecialSilverfishEntity {
public class PufferSilverfishEntity extends AmphibiousSilverfishEntity {
//--------------- Static Special Mob Hooks ----------------
@ -22,8 +25,8 @@ public class PufferSilverfishEntity extends _SpecialSilverfishEntity {
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0xE6E861 ).theme( BestiaryInfo.Theme.WATER )
.uniqueTextureBaseOnly()//TODO Change texture or renderer to fix offset
bestiaryInfo.color( 0xE6E861 ).weight( BestiaryInfo.DefaultWeight.LOW ).theme( BestiaryInfo.Theme.WATER )
.uniqueTextureBaseOnly()
.addExperience( 1 ).drownImmune().effectImmune( Effects.POISON );
}
@ -52,7 +55,12 @@ public class PufferSilverfishEntity extends _SpecialSilverfishEntity {
public PufferSilverfishEntity( EntityType<? extends _SpecialSilverfishEntity> entityType, World world ) { super( entityType, world ); }
//TODO swim behavior
/** Override to change this entity's AI goals. */
@Override
protected void registerVariantGoals() {
AIHelper.removeGoals( goalSelector, SwimGoal.class );
AIHelper.insertGoal( goalSelector, 5, new AmphibiousGoToWaterGoal( this, 1.0 ).alwaysEnabled() );
}
/** Override to change the color of this entity's spit attack. */
@Override
@ -63,4 +71,8 @@ public class PufferSilverfishEntity extends _SpecialSilverfishEntity {
protected void onVariantAttack( LivingEntity target ) {
MobHelper.applyEffect( target, Effects.POISON );
}
/** @return Water drag coefficient. */
@Override
protected float getWaterSlowDown() { return 0.95F; }
}

View file

@ -12,6 +12,7 @@ import fathertoast.specialmobs.common.entity.ai.AIHelper;
import fathertoast.specialmobs.common.entity.ai.goal.PassiveRangedAttackGoal;
import fathertoast.specialmobs.common.entity.ai.goal.SpecialHurtByTargetGoal;
import fathertoast.specialmobs.common.entity.projectile.BugSpitEntity;
import fathertoast.specialmobs.common.event.NaturalSpawnManager;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.BlockState;
@ -30,6 +31,7 @@ import net.minecraft.pathfinding.PathNodeType;
import net.minecraft.potion.EffectInstance;
import net.minecraft.util.DamageSource;
import net.minecraft.util.SoundEvents;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.IServerWorld;
@ -37,6 +39,7 @@ import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Random;
@SpecialMob
public class _SpecialSilverfishEntity extends SilverfishEntity implements IRangedAttackMob, ISpecialMob<_SpecialSilverfishEntity> {
@ -67,6 +70,18 @@ public class _SpecialSilverfishEntity extends SilverfishEntity implements IRange
@SpecialMob.AttributeSupplier
public static AttributeModifierMap.MutableAttribute createAttributes() { return SilverfishEntity.createAttributes(); }
@SpecialMob.SpawnPlacementRegistrar
public static void registerSpawnPlacement( MobFamily.Species<? extends _SpecialSilverfishEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species, _SpecialSilverfishEntity::checkFamilySpawnRules );
}
public static boolean checkFamilySpawnRules( EntityType<? extends SilverfishEntity> type, IServerWorld world,
SpawnReason reason, BlockPos pos, Random random ) {
//noinspection unchecked
return SilverfishEntity.checkSliverfishSpawnRules( (EntityType<SilverfishEntity>) type, world, reason, pos, random ) &&
NaturalSpawnManager.checkSpawnRulesConfigured( type, world, reason, pos, random );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Silverfish",
@ -90,6 +105,8 @@ public class _SpecialSilverfishEntity extends SilverfishEntity implements IRange
super.registerGoals();
goalSelector.addGoal( 4, new PassiveRangedAttackGoal<>( this ) );
AIHelper.replaceHurtByTarget( this, new SpecialHurtByTargetGoal( this, SilverfishEntity.class ).setAlertOthers() );
// Someday, it would be nice to replace SilverfishEntity.HideInStoneGoal with one that
// expands the allowed stone types and preserves species on hide/reveal
registerVariantGoals();
}
@ -172,6 +189,18 @@ public class _SpecialSilverfishEntity extends SilverfishEntity implements IRange
@Override
public final void setExperience( int xp ) { xpReward = xp; }
/** Converts this entity to one of another type. */
@Nullable
@Override
public <T extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override
@ -233,7 +262,7 @@ public class _SpecialSilverfishEntity extends SilverfishEntity implements IRange
/** @return The eye height of this entity when standing. */
@Override
protected float getStandingEyeHeight( Pose pose, EntitySize size ) {
return super.getStandingEyeHeight( pose, size ) * getSpecialData().getBaseScale() * (isBaby() ? 0.53448F : 1.0F);
return super.getStandingEyeHeight( pose, size ) * getSpecialData().getHeightScaleByAge();
}
/** @return Whether this entity is immune to fire damage. */

View file

@ -75,7 +75,7 @@ public class SpitfireSkeletonEntity extends _SpecialSkeletonEntity {
/** Called to attack the target with a ranged attack. */
@Override
public void performRangedAttack( LivingEntity target, float damageMulti ) {
if( !isSilent() ) level.levelEvent( null, References.EVENT_BLAZE_SHOOT, blockPosition(), 0 );
References.LevelEvent.BLAZE_SHOOT.play( this );
final float accelVariance = MathHelper.sqrt( distanceTo( target ) ) * 0.5F * getSpecialData().getRangedAttackSpread();

View file

@ -4,23 +4,26 @@ import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.event.NaturalSpawnManager;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.SpawnReason;
import net.minecraft.entity.ai.attributes.AttributeModifierMap;
import net.minecraft.entity.monster.StrayEntity;
import net.minecraft.entity.projectile.AbstractArrowEntity;
import net.minecraft.entity.projectile.ArrowEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.potion.EffectInstance;
import net.minecraft.potion.Effects;
import net.minecraft.util.DamageSource;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.SoundEvents;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IServerWorld;
import net.minecraft.world.World;
import java.util.Random;
@SpecialMob
public class StraySkeletonEntity extends _SpecialSkeletonEntity {
@ -33,12 +36,28 @@ public class StraySkeletonEntity extends _SpecialSkeletonEntity {
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0xDDEAEA ).weight( BestiaryInfo.DefaultWeight.LOW ).theme( BestiaryInfo.Theme.ICE )
.vanillaTextureWithOverlay( "textures/entity/skeleton/stray.png", "textures/entity/skeleton/stray_overlay.png" )
.addExperience( 1 ).effectImmune( Effects.MOVEMENT_SLOWDOWN );
.addExperience( 1 );
}
@SpecialMob.AttributeSupplier
public static AttributeModifierMap.MutableAttribute createAttributes() { return StrayEntity.createAttributes(); }
@SpecialMob.SpawnPlacementRegistrar
public static void registerSpeciesSpawnPlacement( MobFamily.Species<? extends StraySkeletonEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species, StraySkeletonEntity::checkSpeciesSpawnRules );
}
/**
* We cannot call the actual stray method because our stray variant does not extend the vanilla stray.
*
* @see net.minecraft.entity.monster.StrayEntity#checkStraySpawnRules(EntityType, IServerWorld, SpawnReason, BlockPos, Random)
*/
public static boolean checkSpeciesSpawnRules( EntityType<? extends StraySkeletonEntity> type, IServerWorld world,
SpawnReason reason, BlockPos pos, Random random ) {
return NaturalSpawnManager.checkSpawnRulesDefault( type, world, reason, pos, random ) &&
(reason == SpawnReason.SPAWNER || world.canSeeSky( pos ));
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Stray",

View file

@ -8,6 +8,7 @@ import fathertoast.specialmobs.common.config.species.SpeciesConfig;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.SpecialMobData;
import fathertoast.specialmobs.common.event.NaturalSpawnManager;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.BlockState;
@ -76,6 +77,11 @@ public class _SpecialSkeletonEntity extends AbstractSkeletonEntity implements IS
@SpecialMob.AttributeSupplier
public static AttributeModifierMap.MutableAttribute createAttributes() { return SkeletonEntity.createAttributes(); }
@SpecialMob.SpawnPlacementRegistrar
public static void registerSpawnPlacement( MobFamily.Species<? extends _SpecialSkeletonEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Skeleton",
@ -303,6 +309,18 @@ public class _SpecialSkeletonEntity extends AbstractSkeletonEntity implements IS
@Override
public final void setExperience( int xp ) { xpReward = xp; }
/** Converts this entity to one of another type. */
@Nullable
@Override
public <T extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override
@ -352,7 +370,7 @@ public class _SpecialSkeletonEntity extends AbstractSkeletonEntity implements IS
/** @return The eye height of this entity when standing. */
@Override
protected float getStandingEyeHeight( Pose pose, EntitySize size ) {
return super.getStandingEyeHeight( pose, size ) * getSpecialData().getBaseScale() * (isBaby() ? 0.53448F : 1.0F);
return super.getStandingEyeHeight( pose, size ) * getSpecialData().getHeightScaleByAge();
}
/** @return Whether this entity is immune to fire damage. */

View file

@ -3,22 +3,33 @@ package fathertoast.specialmobs.common.entity.slime;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.config.species.SpeciesConfig;
import fathertoast.specialmobs.common.config.util.EnvironmentEntry;
import fathertoast.specialmobs.common.config.util.EnvironmentList;
import fathertoast.specialmobs.common.entity.ai.AIHelper;
import fathertoast.specialmobs.common.entity.ai.FluidPathNavigator;
import fathertoast.specialmobs.common.event.NaturalSpawnManager;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.FlowingFluidBlock;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.SpawnReason;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.fluid.Fluid;
import net.minecraft.item.Items;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.particles.IParticleData;
import net.minecraft.particles.ParticleTypes;
import net.minecraft.pathfinding.PathNavigator;
import net.minecraft.pathfinding.PathNodeType;
import net.minecraft.tags.FluidTags;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.world.IServerWorld;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import java.util.Random;
@SpecialMob
public class BlueberrySlimeEntity extends _SpecialSlimeEntity {
@ -36,6 +47,36 @@ public class BlueberrySlimeEntity extends _SpecialSlimeEntity {
.addToAttribute( Attributes.ATTACK_DAMAGE, 2.0 );
}
@SpecialMob.ConfigSupplier
public static SpeciesConfig createConfig( MobFamily.Species<?> species ) {
SpeciesConfig.NEXT_NATURAL_SPAWN_CHANCE_EXCEPTIONS = new EnvironmentList(
EnvironmentEntry.builder( 0.0F ).atNoMoonLight().build(),
EnvironmentEntry.builder( 0.04F ).atMaxMoonLight().build(),
EnvironmentEntry.builder( 0.01F ).belowHalfMoonLight().build(),
EnvironmentEntry.builder( 0.02F ).atHalfMoonLight().build(),
EnvironmentEntry.builder( 0.03F ).aboveHalfMoonLight().build() );
return new SpeciesConfig( species );
}
@SpecialMob.SpawnPlacementRegistrar
public static void registerSpeciesSpawnPlacement( MobFamily.Species<? extends BlueberrySlimeEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species, EntitySpawnPlacementRegistry.PlacementType.IN_WATER,
BlueberrySlimeEntity::checkSpeciesSpawnRules );
}
public static boolean checkSpeciesSpawnRules( EntityType<? extends BlueberrySlimeEntity> type, IServerWorld world,
SpawnReason reason, BlockPos pos, Random random ) {
final Biome.Category biomeCategory = world.getBiome( pos ).getBiomeCategory();
if( biomeCategory == Biome.Category.OCEAN || biomeCategory == Biome.Category.RIVER ) {
return NaturalSpawnManager.checkSpawnRulesWater( type, world, reason, pos, random );
}
return _SpecialSlimeEntity.checkFamilySpawnRules( type, world, reason, pos, random );
}
/** @return True if this entity's position is currently obstructed. */
@Override
public boolean checkSpawnObstruction( IWorldReader world ) { return world.isUnobstructed( this ); }
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Blueberry Slime",
@ -71,21 +112,28 @@ public class BlueberrySlimeEntity extends _SpecialSlimeEntity {
AIHelper.removeGoals( goalSelector, 1 ); // SlimeEntity.FloatGoal
}
/** @return A new path navigator for this entity to use. */
@Override
protected PathNavigator createNavigation( World world ) {
return new FluidPathNavigator( this, world, true, false );
}
/** @return Whether this entity can stand on a particular type of fluid. */
@Override
public boolean canStandOnFluid( Fluid fluid ) { return fluid.is( FluidTags.WATER ); }
/** Called each tick to update this entity. */
@Override
public void tick() {
super.tick();
MobHelper.floatInFluid( this, 0.06, FluidTags.WATER );
// Hacky way of attacking submerged targets; drop down on them when directly above
double floatAccel = 0.06;
final LivingEntity target = getTarget();
if( target != null && target.getY( 0.5 ) < getY( 0.5 ) ) {
//
final double dX = target.getX() - getX();
final double dZ = target.getZ() - getZ();
final float range = (target.getBbWidth() + getBbWidth() + 0.1F) / 2.0F;
if( dX * dX + dZ * dZ < range * range )
floatAccel = -0.12;
}
if( tickCount > 1 && getFluidHeight( FluidTags.WATER ) > 0.0 ) {
if( !ISelectionContext.of( this ).isAbove( FlowingFluidBlock.STABLE_SHAPE, blockPosition(), true ) ||
level.getFluidState( blockPosition().above() ).is( FluidTags.WATER ) ) {
setDeltaMovement( getDeltaMovement().scale( 0.5 ).add( 0.0, floatAccel, 0.0 ) );
}
}
}
// The below two methods are here to effectively override the private Entity#isInRain to always return true (always wet)
@ -95,6 +143,10 @@ public class BlueberrySlimeEntity extends _SpecialSlimeEntity {
@Override
public boolean isInWaterRainOrBubble() { return true; }
/** @return Water drag coefficient. */
@Override
protected float getWaterSlowDown() { return 0.9F; }
/** Override to load data from this entity's NBT data. */
@Override
public void readVariantSaveData( CompoundNBT saveTag ) {

View file

@ -6,6 +6,7 @@ import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.SpecialMobData;
import fathertoast.specialmobs.common.event.NaturalSpawnManager;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.BlockState;
@ -24,12 +25,14 @@ import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.pathfinding.PathNodeType;
import net.minecraft.potion.EffectInstance;
import net.minecraft.util.DamageSource;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.IServerWorld;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.Random;
@SpecialMob
public class _SpecialSlimeEntity extends SlimeEntity implements ISpecialMob<_SpecialSlimeEntity> {
@ -51,6 +54,18 @@ public class _SpecialSlimeEntity extends SlimeEntity implements ISpecialMob<_Spe
return MonsterEntity.createMonsterAttributes(); // Slimes define their attributes elsewhere based on size
}
@SpecialMob.SpawnPlacementRegistrar
public static void registerSpawnPlacement( MobFamily.Species<? extends _SpecialSlimeEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species, _SpecialSlimeEntity::checkFamilySpawnRules );
}
public static boolean checkFamilySpawnRules( EntityType<? extends SlimeEntity> type, IServerWorld world,
SpawnReason reason, BlockPos pos, Random random ) {
//noinspection unchecked
return SlimeEntity.checkSlimeSpawnRules( (EntityType<SlimeEntity>) type, world, reason, pos, random ) &&
NaturalSpawnManager.checkSpawnRulesConfigured( type, world, reason, pos, random );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Slime",
@ -171,6 +186,18 @@ public class _SpecialSlimeEntity extends SlimeEntity implements ISpecialMob<_Spe
xpReward = getSize() + xp;
}
/** Converts this entity to one of another type. */
@Nullable
@Override
public <T extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override
@ -206,11 +233,11 @@ public class _SpecialSlimeEntity extends SlimeEntity implements ISpecialMob<_Spe
getSpecialData().tick();
}
/** @return The eye height of this entity when standing. */
@Override
protected float getStandingEyeHeight( Pose pose, EntitySize size ) {
return super.getStandingEyeHeight( pose, size ) * getSpecialData().getBaseScale() * (isBaby() ? 0.53448F : 1.0F);
}
// /** @return The eye height of this entity when standing. */
// @Override
// protected float getStandingEyeHeight( Pose pose, EntitySize size ) {
// return super.getStandingEyeHeight( pose, size ) * getSpecialData().getHeightScaleByAge();
// }
/** @return Whether this entity is immune to fire damage. */
@Override

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