mirror of
https://github.com/FatherToast/SpecialMobs.git
synced 2025-08-07 19:01:23 +00:00
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:
commit
471f4b4378
137 changed files with 3387 additions and 282 deletions
|
@ -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
|
||||
|
|
|
@ -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 ) ) );
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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 ); }
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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). */
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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." ) );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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." ) )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ); }
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package fathertoast.specialmobs.common.config.field;
|
||||
package fathertoast.specialmobs.common.config.util;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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; }
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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 ); }
|
||||
|
||||
|
|
|
@ -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 ); }
|
||||
|
||||
|
|
|
@ -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 ); }
|
||||
|
||||
|
|
|
@ -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 ); }
|
||||
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -35,7 +35,7 @@ public class SMEntities {
|
|||
|
||||
public static final RegistryObject<EntityType<BoneShrapnelEntity>> BONE_SHRAPNEL = register( "bone_shrapnel",
|
||||
EntityType.Builder.<BoneShrapnelEntity>of( BoneShrapnelEntity::new, EntityClassification.MISC ).noSave().noSummon()
|
||||
.sized(0.25F, 0.25F ).clientTrackingRange( 4 ).updateInterval( 5 ) );
|
||||
.sized( 0.25F, 0.25F ).clientTrackingRange( 4 ).updateInterval( 5 ) );
|
||||
|
||||
|
||||
/** Registers an entity type to the deferred register. */
|
||||
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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() ); }
|
||||
// }
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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() ); }
|
||||
}
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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; }
|
||||
}
|
|
@ -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 ); }
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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 ) );
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
@MethodsReturnNonnullByDefault
|
||||
@ParametersAreNonnullByDefault
|
||||
package fathertoast.specialmobs.common.entity.drowned;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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() );
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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() );
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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; }
|
||||
}
|
|
@ -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. */
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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
Loading…
Add table
Reference in a new issue