From b9c51a20c21f059883800a27c942af6d1dc67717 Mon Sep 17 00:00:00 2001 From: FatherToast Date: Wed, 10 Aug 2022 20:33:31 -0500 Subject: [PATCH 01/10] Mirror vanilla replacement code --- .../specialmobs/common/core/SpecialMobReplacer.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobReplacer.java b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobReplacer.java index 1206ab3..d0e75b8 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobReplacer.java +++ b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobReplacer.java @@ -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. */ From daaeda3f6eb081099f7f31256f5fbdec9017e3a6 Mon Sep 17 00:00:00 2001 From: FatherToast Date: Thu, 11 Aug 2022 18:27:13 -0500 Subject: [PATCH 02/10] Husk -> Zombie conversion; plus lib stuff --- .../common/bestiary/BestiaryInfo.java | 2 +- .../common/bestiary/MobFamily.java | 9 +- .../common/config/field/DoubleField.java | 12 ++- .../species/HuskZombieSpeciesConfig.java | 40 +++++++++ .../specialmobs/common/core/SpecialMobs.java | 4 +- .../goal/SpecialGhastFireballAttackGoal.java | 2 +- .../entity/blaze/HellfireBlazeEntity.java | 2 +- .../entity/blaze/WildfireBlazeEntity.java | 4 +- .../entity/blaze/_SpecialBlazeEntity.java | 14 +++- .../cavespider/_SpecialCaveSpiderEntity.java | 12 +++ .../entity/creeper/SkeletonCreeperEntity.java | 2 +- .../entity/creeper/_SpecialCreeperEntity.java | 12 +++ .../entity/enderman/IcyEndermanEntity.java | 2 +- .../enderman/_SpecialEndermanEntity.java | 12 +++ .../ghast/CorporealShiftGhastEntity.java | 2 +- .../common/entity/ghast/QueenGhastEntity.java | 4 +- .../entity/ghast/_SpecialGhastEntity.java | 14 +++- .../magmacube/_SpecialMagmaCubeEntity.java | 12 +++ .../silverfish/_SpecialSilverfishEntity.java | 12 +++ .../skeleton/SpitfireSkeletonEntity.java | 2 +- .../skeleton/_SpecialSkeletonEntity.java | 12 +++ .../entity/slime/_SpecialSlimeEntity.java | 12 +++ .../entity/spider/_SpecialSpiderEntity.java | 12 +++ .../common/entity/witch/WindWitchEntity.java | 2 +- .../entity/witch/_SpecialWitchEntity.java | 12 +++ .../SpitfireWitherSkeletonEntity.java | 2 +- .../_SpecialWitherSkeletonEntity.java | 12 +++ .../entity/zombie/HuskZombieEntity.java | 29 +++++++ .../entity/zombie/_SpecialZombieEntity.java | 27 +++++- .../_SpecialZombifiedPiglinEntity.java | 12 +++ .../specialmobs/common/util/References.java | 83 +++++++++++++++++-- 31 files changed, 361 insertions(+), 29 deletions(-) create mode 100644 src/main/java/fathertoast/specialmobs/common/config/species/HuskZombieSpeciesConfig.java diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java b/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java index 2bd6dbd..adb4d1a 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java @@ -431,7 +431,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 ); } diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java index 3469ca8..044a1fa 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java @@ -197,9 +197,14 @@ public class MobFamily { /** Pick a new species from this family, based on the location. */ public Species 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 nextVariant( World world, @Nullable BlockPos pos, @Nullable Function, Boolean> selector, Species fallback ) { + final Species species = config.GENERAL.specialVariantList.next( world.random, world, pos, selector ); //noinspection unchecked - return species == null ? vanillaReplacement : (Species) species; + return species == null ? fallback : (Species) species; } diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/DoubleField.java b/src/main/java/fathertoast/specialmobs/common/config/field/DoubleField.java index c7a13d0..70e16fd 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/DoubleField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/DoubleField.java @@ -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 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 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; diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/HuskZombieSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/HuskZombieSpeciesConfig.java new file mode 100644 index 0000000..a017cf1 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/species/HuskZombieSpeciesConfig.java @@ -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, speciesName ); + } + + 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." ) ) + ); + } + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java index c12ebca..dfdcd67 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java +++ b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java @@ -45,7 +45,7 @@ public class SpecialMobs { * - potions * - vulnerability (opposite of resistance) * ? gravity (opposite of levitation) - * o entities + * - entities * - nbt-driven capabilities (special mob data) * - fish hook * - bug spit @@ -54,7 +54,7 @@ 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) diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastFireballAttackGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastFireballAttackGoal.java index f311a74..c432751 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastFireballAttackGoal.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastFireballAttackGoal.java @@ -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 ); diff --git a/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java index bb6378b..c6c79c0 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java @@ -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; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java index c238078..b996c9a 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java @@ -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 ); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java index 1c7143b..6c097dd 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java @@ -110,7 +110,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; @@ -176,6 +176,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 convertTo( EntityType 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/_SpecialCaveSpiderEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/_SpecialCaveSpiderEntity.java index a886c92..5e93bd1 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/_SpecialCaveSpiderEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/_SpecialCaveSpiderEntity.java @@ -166,6 +166,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 convertTo( EntityType 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/creeper/SkeletonCreeperEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/creeper/SkeletonCreeperEntity.java index a2dc92d..37647fc 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/creeper/SkeletonCreeperEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/creeper/SkeletonCreeperEntity.java @@ -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 ); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java index 9c2d41d..5189fb3 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java @@ -316,6 +316,18 @@ public class _SpecialCreeperEntity extends CreeperEntity implements IExplodingMo @Override public final void setExperience( int xp ) { xpReward = xp; } + /** Converts this entity to one of another type. */ + @Nullable + @Override + public T convertTo( EntityType 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/enderman/IcyEndermanEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/enderman/IcyEndermanEntity.java index 2a9987a..bf15606 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/enderman/IcyEndermanEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/enderman/IcyEndermanEntity.java @@ -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; } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/enderman/_SpecialEndermanEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/enderman/_SpecialEndermanEntity.java index 5a6e641..206d78b 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/enderman/_SpecialEndermanEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/enderman/_SpecialEndermanEntity.java @@ -131,6 +131,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 convertTo( EntityType 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/CorporealShiftGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/CorporealShiftGhastEntity.java index 97cc3a0..ef00d75 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ghast/CorporealShiftGhastEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/CorporealShiftGhastEntity.java @@ -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() ); diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/QueenGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/QueenGhastEntity.java index 8d94e09..64c47b8 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ghast/QueenGhastEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/QueenGhastEntity.java @@ -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 ); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java index 4472bd8..2c184f2 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java @@ -106,7 +106,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() ); @@ -200,6 +200,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 convertTo( EntityType 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/magmacube/_SpecialMagmaCubeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/magmacube/_SpecialMagmaCubeEntity.java index c36e052..4f72ad6 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/magmacube/_SpecialMagmaCubeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/magmacube/_SpecialMagmaCubeEntity.java @@ -163,6 +163,18 @@ public class _SpecialMagmaCubeEntity extends MagmaCubeEntity implements ISpecial xpReward = getSize() + xp; } + /** Converts this entity to one of another type. */ + @Nullable + @Override + public T convertTo( EntityType 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java index 9c74713..87e0e10 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java @@ -171,6 +171,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 convertTo( EntityType 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java index 2ebc906..6f7fdb9 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java @@ -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(); diff --git a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/_SpecialSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/_SpecialSkeletonEntity.java index 77b8b29..ac98278 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/_SpecialSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/_SpecialSkeletonEntity.java @@ -302,6 +302,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 convertTo( EntityType 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/slime/_SpecialSlimeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/slime/_SpecialSlimeEntity.java index 2ba68d3..e0d7c30 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/slime/_SpecialSlimeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/slime/_SpecialSlimeEntity.java @@ -170,6 +170,18 @@ public class _SpecialSlimeEntity extends SlimeEntity implements ISpecialMob<_Spe xpReward = getSize() + xp; } + /** Converts this entity to one of another type. */ + @Nullable + @Override + public T convertTo( EntityType 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/spider/_SpecialSpiderEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/spider/_SpecialSpiderEntity.java index 40386bc..08f398a 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/spider/_SpecialSpiderEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/spider/_SpecialSpiderEntity.java @@ -166,6 +166,18 @@ public class _SpecialSpiderEntity extends SpiderEntity implements IRangedAttackM @Override public final void setExperience( int xp ) { xpReward = xp; } + /** Converts this entity to one of another type. */ + @Nullable + @Override + public T convertTo( EntityType 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/WindWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/WindWitchEntity.java index 17099b8..d73ec6f 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witch/WindWitchEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/WindWitchEntity.java @@ -223,7 +223,7 @@ public class WindWitchEntity extends _SpecialWitchEntity { 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; } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java index 51f4c0a..bebb556 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java @@ -391,6 +391,18 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe @Override public final void setExperience( int xp ) { xpReward = xp; } + /** Converts this entity to one of another type. */ + @Nullable + @Override + public T convertTo( EntityType 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java index 6c94147..21c5fe9 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java @@ -77,7 +77,7 @@ public class SpitfireWitherSkeletonEntity extends _SpecialWitherSkeletonEntity { /** 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(); diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/_SpecialWitherSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/_SpecialWitherSkeletonEntity.java index 428a058..5c4ce1e 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/_SpecialWitherSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/_SpecialWitherSkeletonEntity.java @@ -265,6 +265,18 @@ public class _SpecialWitherSkeletonEntity extends WitherSkeletonEntity implement @Override public final void setExperience( int xp ) { xpReward = xp; } + /** Converts this entity to one of another type. */ + @Nullable + @Override + public T convertTo( EntityType 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/HuskZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/HuskZombieEntity.java index 38b0039..34570d1 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/HuskZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/HuskZombieEntity.java @@ -3,6 +3,8 @@ package fathertoast.specialmobs.common.entity.zombie; import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.config.species.HuskZombieSpeciesConfig; +import fathertoast.specialmobs.common.config.species.SpeciesConfig; import fathertoast.specialmobs.common.entity.MobHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; @@ -18,6 +20,8 @@ import net.minecraft.util.SoundEvent; import net.minecraft.util.SoundEvents; import net.minecraft.world.World; +import java.util.function.Function; + @SpecialMob public class HuskZombieEntity extends _SpecialZombieEntity { @@ -34,6 +38,15 @@ public class HuskZombieEntity extends _SpecialZombieEntity { .addExperience( 1 ); } + @SpecialMob.ConfigSupplier + public static SpeciesConfig createConfig( MobFamily.Species species ) { + return new HuskZombieSpeciesConfig( species, DEFAULT_BOW_CHANCE, DEFAULT_SHIELD_CHANCE ); + } + + /** @return This entity's species config. */ + @Override + public HuskZombieSpeciesConfig getConfig() { return (HuskZombieSpeciesConfig) getSpecies().config; } + @SpecialMob.AttributeSupplier public static AttributeModifierMap.MutableAttribute createAttributes() { return HuskEntity.createAttributes(); } @@ -73,6 +86,22 @@ public class HuskZombieEntity extends _SpecialZombieEntity { return MobHelper.tipArrow( arrow, Effects.HUNGER ); } + /** Returns true if the species is not a husk and not damaged by water. */ + private static final Function, Boolean> HUSK_CONVERSION_SELECTOR = + ( species ) -> species != SPECIES && !species.config.GENERAL.isDamagedByWater.get(); + + /** Performs this zombie's drowning conversion. */ + @Override + protected void doUnderWaterConversion() { + // Select a random non-husk, non-water-sensitive zombie; defaults to a normal zombie + convertToZombieType( + getConfig().HUSK.convertVariantChance.rollChance( random, level, blockPosition() ) ? + MobFamily.ZOMBIE.nextVariant( level, blockPosition(), HUSK_CONVERSION_SELECTOR, _SpecialZombieEntity.SPECIES ).entityType.get() : + _SpecialZombieEntity.SPECIES.entityType.get() + ); + References.LevelEvent.HUSK_CONVERTED_TO_ZOMBIE.play( this ); + } + //--------------- Husk Implementations ---------------- diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java index 8af9f41..8feb943 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java @@ -60,7 +60,7 @@ public class _SpecialZombieEntity extends ZombieEntity implements IRangedAttackM } protected static final double DEFAULT_BOW_CHANCE = 0.05; - protected static final double DEFAULT_SHIELD_CHANCE = 0.05; + protected static final double DEFAULT_SHIELD_CHANCE = 0.02; @SpecialMob.ConfigSupplier public static SpeciesConfig createConfig( MobFamily.Species species ) { @@ -207,6 +207,19 @@ public class _SpecialZombieEntity extends ZombieEntity implements IRangedAttackM } } + /** @return True if this zombie can convert in water. */ + @Override + protected boolean convertsInWater() { return !isSensitiveToWater(); } + + // TODO Drowned transform + // /** Performs this zombie's drowning conversion. */ + // @Override + // protected void doUnderWaterConversion() { + // // Select a random drowned + // convertToZombieType( MobFamily.DROWNED.nextVariant( level, blockPosition() ).entityType.get() ); + // References.LevelEvent.ZOMBIE_CONVERTED_TO_DROWNED.play( this ); + // } + //--------------- ISpecialMob Implementation ---------------- @@ -229,6 +242,18 @@ public class _SpecialZombieEntity extends ZombieEntity implements IRangedAttackM @Override public final void setExperience( int xp ) { xpReward = xp; } + /** Converts this entity to one of another type. */ + @Nullable + @Override + public T convertTo( EntityType 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java index 5758024..e8e952e 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java @@ -227,6 +227,18 @@ public class _SpecialZombifiedPiglinEntity extends ZombifiedPiglinEntity impleme @Override public final void setExperience( int xp ) { xpReward = xp; } + /** Converts this entity to one of another type. */ + @Nullable + @Override + public T convertTo( EntityType 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 diff --git a/src/main/java/fathertoast/specialmobs/common/util/References.java b/src/main/java/fathertoast/specialmobs/common/util/References.java index 381f301..9f43b76 100644 --- a/src/main/java/fathertoast/specialmobs/common/util/References.java +++ b/src/main/java/fathertoast/specialmobs/common/util/References.java @@ -2,13 +2,18 @@ package fathertoast.specialmobs.common.util; import fathertoast.specialmobs.common.core.SpecialMobs; import net.minecraft.block.BlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; import net.minecraft.entity.ai.attributes.AttributeModifier; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.CompoundNBT; import net.minecraft.nbt.ListNBT; import net.minecraft.nbt.StringNBT; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import javax.annotation.Nullable; import java.util.UUID; public final class References { @@ -93,7 +98,7 @@ public final class References { /** Bit flags that can be provided to {@link net.minecraft.world.World#setBlock(BlockPos, BlockState, int)}. */ @SuppressWarnings( "unused" ) - public static class SetBlockFlags { + public static final class SetBlockFlags { /** Triggers a block update. */ public static final int BLOCK_UPDATE = 0b0000_0001; /** On servers, sends the change to clients. On clients, triggers a render update. */ @@ -115,13 +120,77 @@ public final class References { public static final int DEFAULTS = BLOCK_UPDATE | UPDATE_CLIENT; } - // Entity events; used in World#broadcastEntityEvent(Entity, byte) then executed by Entity#handleEntityEvent(byte) - public static final byte EVENT_TELEPORT_TRAIL_PARTICLES = 46; + /** + * Entity events. Sent from the server, executed on the client via {@link Entity#handleEntityEvent(byte)}. + * This only contains event codes for Entity and LivingEntity. + */ + public enum EntityEvent { + // Note: if we want to go deeper, it may be wise to make this generic to only allow an appropriate Entity subclass. + // There's no need to go to MobEntity as its sole event is already nicely abstracted. + + HURT_SOUND( 2 ), HURT_SOUND_THORNS( 33 ), HURT_SOUND_DROWN( 36 ), + HURT_SOUND_BURNING( 37 ), HURT_SOUND_SWEET_BERRY_BUSH( 44 ), + DEATH_SOUND( 3 ), + SHIELD_BLOCK_SOUND( 29 ), SHIELD_BREAK_SOUND( 30 ), + TELEPORT_TRAIL_PARTICLES( 46 ), + ITEM_BREAK_FX_MAIN_HAND( 47 ), ITEM_BREAK_FX_OFF_HAND( 48 ), + ITEM_BREAK_FX_HEAD( 49 ), ITEM_BREAK_FX_CHEST( 50 ), ITEM_BREAK_FX_LEGS( 51 ), ITEM_BREAK_FX_FEET( 52 ), + HONEY_SLIDE_PARTICLES( 53 ) /* This is the only event from Entity. */, HONEY_JUMP_PARTICLES( 54 ), + SWAP_HAND_ITEMS( 55 ); + + private final byte ID; + + EntityEvent( int id ) { ID = (byte) id; } + + /** Sends this event from the given server entity to its client-sided counterpart. */ + public void broadcast( LivingEntity entity ) { entity.level.broadcastEntityEvent( entity, ID ); } + } - // Level events; used in World#levelEvent(PlayerEntity, int, BlockPos, int) then executed by WorldRenderer#levelEvent(PlayerEntity, int, BlockPos, int) - public static final int EVENT_GHAST_WARN = 1015; - public static final int EVENT_GHAST_SHOOT = 1016; - public static final int EVENT_BLAZE_SHOOT = 1018; + /** + * Simple level events (ones that do not use extra metadata). Sent from the server, then executed on the client + * via {@link net.minecraft.client.renderer.WorldRenderer#levelEvent(PlayerEntity, int, BlockPos, int)}. + */ + public enum LevelEvent { + // Note: if metadata events are needed, they will need to be implemented in a separate class + + DISPENSER_DISPENSE( 1000 ), DISPENSER_FAIL( 1001 ), DISPENSER_LAUNCH( 1002 ), + ENDER_EYE_LAUNCH( 1003 ), ENDER_EYE( 2003 ), END_PORTAL_FRAME_FILL( 1503 ), + FIREWORK_ROCKET_SHOOT( 1004 ), + IRON_DOOR_OPEN( 1005 ), WOODEN_DOOR_OPEN( 1006 ), WOODEN_TRAPDOOR_OPEN( 1007 ), FENCE_GATE_OPEN( 1008 ), + IRON_DOOR_CLOSE( 1011 ), WOODEN_DOOR_CLOSE( 1012 ), WOODEN_TRAPDOOR_CLOSE( 1013 ), FENCE_GATE_CLOSE( 1014 ), + IRON_TRAPDOOR_CLOSE( 1036 ), IRON_TRAPDOOR_OPEN( 1037 ), + FIRE_EXTINGUISH( 1009 ), LAVA_EXTINGUISH( 1501 ), REDSTONE_TORCH_BURNOUT( 1502 ), + GHAST_WARN( 1015 ), GHAST_SHOOT( 1016 ), + ENDER_DRAGON_SHOOT( 1017 ), ENDER_DRAGON_GROWL( 3001 ), + BLAZE_SHOOT( 1018 ), + ZOMBIE_ATTACK_WOODEN_DOOR( 1019 ), ZOMBIE_ATTACK_IRON_DOOR( 1020 ), ZOMBIE_BREAK_WOODEN_DOOR( 1021 ), + ZOMBIE_INFECT( 1026 ), ZOMBIE_VILLAGER_CONVERTED( 1027 ), + ZOMBIE_CONVERTED_TO_DROWNED( 1040 ), HUSK_CONVERTED_TO_ZOMBIE( 1041 ), + WITHER_BREAK_BLOCK( 1022 ), WITHER_SHOOT( 1024 ), + BAT_TAKEOFF( 1025 ), + ANVIL_DESTROY( 1029 ), ANVIL_USE( 1030 ), ANVIL_LAND( 1031 ), + BREWING_STAND_BREW( 1035 ), GRINDSTONE_USE( 1042 ), BOOK_PAGE_TURN( 1043 ), SMITHING_TABLE_USE( 1044 ), + PORTAL_TRAVEL( 1032 ), + CHORUS_FLOWER_GROW( 1033 ), CHORUS_FLOWER_DEATH( 1034 ), + PHANTOM_BITE( 1039 ), + SMOKE_AND_FLAME( 2004 ), + EXPLOSION_PARTICLE( 2008 ), CLOUD_PARTICLES( 2009 ), EXPLOSION_EMITTER( 3000 ); + + private final int ID; + + LevelEvent( int id ) { ID = id; } + + /** Plays this event at the entity's position, if the entity is not silenced. */ + public void play( Entity entity ) { + if( !entity.isSilent() ) play( entity.level, entity.blockPosition() ); + } + + /** Plays this event at a particular position. */ + public void play( World world, BlockPos pos ) { play( world, null, pos ); } + + /** Plays this event at a particular position, excluding a particular player. */ + public void play( World world, @Nullable PlayerEntity player, BlockPos pos ) { world.levelEvent( player, ID, pos, 0 ); } + } //--------------- NBT STUFF ---------------- From 653a1224086822a8ac599cf3cfc0f83606bf7f59 Mon Sep 17 00:00:00 2001 From: FatherToast Date: Fri, 12 Aug 2022 09:34:53 -0500 Subject: [PATCH 03/10] Special drowned! + terrible placeholder textures --- .../specialmobs/client/ClientRegister.java | 1 + .../entity/family/SpecialCreeperRenderer.java | 2 +- .../entity/family/SpecialDrownedRenderer.java | 44 +++ .../entity/family/SpecialZombieRenderer.java | 2 +- .../common/bestiary/BestiaryInfo.java | 3 + .../common/bestiary/MobFamily.java | 9 +- .../config/species/DrownedSpeciesConfig.java | 45 +++ .../specialmobs/common/core/SpecialMobs.java | 3 +- .../ai/goal/SpecialDrownedAttackGoal.java | 27 ++ .../entity/ai/goal/SpecialSwellGoal.java | 8 + .../ai/goal/SpecialTridentAttackGoal.java | 43 +++ .../entity/drowned/BruteDrownedEntity.java | 64 ++++ .../entity/drowned/FishingDrownedEntity.java | 141 +++++++ .../entity/drowned/GiantDrownedEntity.java | 73 ++++ .../entity/drowned/HungryDrownedEntity.java | 93 +++++ .../entity/drowned/KnightDrownedEntity.java | 84 +++++ .../entity/drowned/PlagueDrownedEntity.java | 63 ++++ .../entity/drowned/_SpecialDrownedEntity.java | 350 ++++++++++++++++++ .../common/entity/drowned/package-info.java | 7 + .../entity/zombie/BruteZombieEntity.java | 6 + .../entity/zombie/FishingZombieEntity.java | 6 + .../entity/zombie/GiantZombieEntity.java | 6 + .../entity/zombie/HungryZombieEntity.java | 6 + .../entity/zombie/HuskZombieEntity.java | 17 +- .../entity/zombie/PlagueZombieEntity.java | 6 + .../entity/zombie/_SpecialZombieEntity.java | 20 +- .../textures/entity/drowned/brute.png | Bin 0 -> 1349 bytes .../textures/entity/drowned/brute_overlay.png | Bin 0 -> 1349 bytes .../textures/entity/drowned/hungry.png | Bin 0 -> 1428 bytes .../entity/drowned/hungry_overlay.png | Bin 0 -> 1428 bytes .../textures/entity/drowned/plague.png | Bin 0 -> 1351 bytes .../entity/drowned/plague_overlay.png | Bin 0 -> 1351 bytes .../textures/entity/drowned/vanilla.png | Bin 0 -> 665 bytes .../entity/drowned/vanilla_overlay.png | Bin 0 -> 1334 bytes 34 files changed, 1105 insertions(+), 24 deletions(-) create mode 100644 src/main/java/fathertoast/specialmobs/client/renderer/entity/family/SpecialDrownedRenderer.java create mode 100644 src/main/java/fathertoast/specialmobs/common/config/species/DrownedSpeciesConfig.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialDrownedAttackGoal.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialTridentAttackGoal.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/drowned/BruteDrownedEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/drowned/FishingDrownedEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/drowned/GiantDrownedEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/drowned/HungryDrownedEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/drowned/KnightDrownedEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/drowned/PlagueDrownedEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/drowned/_SpecialDrownedEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/drowned/package-info.java create mode 100644 src/main/resources/assets/specialmobs/textures/entity/drowned/brute.png create mode 100644 src/main/resources/assets/specialmobs/textures/entity/drowned/brute_overlay.png create mode 100644 src/main/resources/assets/specialmobs/textures/entity/drowned/hungry.png create mode 100644 src/main/resources/assets/specialmobs/textures/entity/drowned/hungry_overlay.png create mode 100644 src/main/resources/assets/specialmobs/textures/entity/drowned/plague.png create mode 100644 src/main/resources/assets/specialmobs/textures/entity/drowned/plague_overlay.png create mode 100644 src/main/resources/assets/specialmobs/textures/entity/drowned/vanilla.png create mode 100644 src/main/resources/assets/specialmobs/textures/entity/drowned/vanilla_overlay.png diff --git a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java index 520646e..04dfe88 100644 --- a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java +++ b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java @@ -54,6 +54,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 ); diff --git a/src/main/java/fathertoast/specialmobs/client/renderer/entity/family/SpecialCreeperRenderer.java b/src/main/java/fathertoast/specialmobs/client/renderer/entity/family/SpecialCreeperRenderer.java index acf79b7..25dfebc 100644 --- a/src/main/java/fathertoast/specialmobs/client/renderer/entity/family/SpecialCreeperRenderer.java +++ b/src/main/java/fathertoast/specialmobs/client/renderer/entity/family/SpecialCreeperRenderer.java @@ -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 ) ) ); diff --git a/src/main/java/fathertoast/specialmobs/client/renderer/entity/family/SpecialDrownedRenderer.java b/src/main/java/fathertoast/specialmobs/client/renderer/entity/family/SpecialDrownedRenderer.java new file mode 100644 index 0000000..348d81c --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/client/renderer/entity/family/SpecialDrownedRenderer.java @@ -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 ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/client/renderer/entity/family/SpecialZombieRenderer.java b/src/main/java/fathertoast/specialmobs/client/renderer/entity/family/SpecialZombieRenderer.java index d41f8db..73ad521 100644 --- a/src/main/java/fathertoast/specialmobs/client/renderer/entity/family/SpecialZombieRenderer.java +++ b/src/main/java/fathertoast/specialmobs/client/renderer/entity/family/SpecialZombieRenderer.java @@ -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 diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java b/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java index adb4d1a..006de12 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java @@ -566,6 +566,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 ); } diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java index 044a1fa..b695d24 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java @@ -47,11 +47,10 @@ public class MobFamily { "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 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 DROWNED = new MobFamily<>( FamilyConfig::new, + "Drowned", "drowned", 0x8FF1D7, new EntityType[] { EntityType.DROWNED }, + "Brute", "Fishing", "Giant", "Hungry", "Knight", "Plague" //TODO Textures! - brute, hungry, plague, cold ocean, warm ocean + );// ice/fire themes? (cold/warm ocean) - convert from frozen/fire zombies public static final MobFamily 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 diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/DrownedSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/DrownedSpeciesConfig.java new file mode 100644 index 0000000..94170e7 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/species/DrownedSpeciesConfig.java @@ -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, speciesName, 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." ) ); + } + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java index dfdcd67..53d8fcd 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java +++ b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java @@ -56,8 +56,7 @@ public class SpecialMobs { * - chance to spawn charged during thunderstorms * + 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialDrownedAttackGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialDrownedAttackGoal.java new file mode 100644 index 0000000..f27ca0f --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialDrownedAttackGoal.java @@ -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. + *

+ * {@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() ); } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialSwellGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialSwellGoal.java index e285fd1..e8ce1da 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialSwellGoal.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialSwellGoal.java @@ -23,21 +23,29 @@ public class SpecialSwellGoal 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 ); diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialTridentAttackGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialTridentAttackGoal.java new file mode 100644 index 0000000..c539891 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialTridentAttackGoal.java @@ -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. + *

+ * {@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 ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/drowned/BruteDrownedEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/drowned/BruteDrownedEntity.java new file mode 100644 index 0000000..9bdee72 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/drowned/BruteDrownedEntity.java @@ -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 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 getVariantFactory() { return BruteDrownedEntity::new; } + + /** @return This entity's mob species. */ + @SpecialMob.SpeciesSupplier + @Override + public MobFamily.Species getSpecies() { return SPECIES; } + + + //--------------- Variant-Specific Implementations ---------------- + + public BruteDrownedEntity( EntityType 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 ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/drowned/FishingDrownedEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/drowned/FishingDrownedEntity.java new file mode 100644 index 0000000..59ada19 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/drowned/FishingDrownedEntity.java @@ -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 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 getVariantFactory() { return FishingDrownedEntity::new; } + + /** @return This entity's mob species. */ + @SpecialMob.SpeciesSupplier + @Override + public MobFamily.Species getSpecies() { return SPECIES; } + + + //--------------- Variant-Specific Implementations ---------------- + + public FishingDrownedEntity( EntityType 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 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 ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/drowned/GiantDrownedEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/drowned/GiantDrownedEntity.java new file mode 100644 index 0000000..85fbded --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/drowned/GiantDrownedEntity.java @@ -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 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 getVariantFactory() { return GiantDrownedEntity::new; } + + /** @return This entity's mob species. */ + @SpecialMob.SpeciesSupplier + @Override + public MobFamily.Species getSpecies() { return SPECIES; } + + + //--------------- Variant-Specific Implementations ---------------- + + public GiantDrownedEntity( EntityType 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; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/drowned/HungryDrownedEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/drowned/HungryDrownedEntity.java new file mode 100644 index 0000000..1a561ef --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/drowned/HungryDrownedEntity.java @@ -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 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 getVariantFactory() { return HungryDrownedEntity::new; } + + /** @return This entity's mob species. */ + @SpecialMob.SpeciesSupplier + @Override + public MobFamily.Species getSpecies() { return SPECIES; } + + + //--------------- Variant-Specific Implementations ---------------- + + public HungryDrownedEntity( EntityType 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 ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/drowned/KnightDrownedEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/drowned/KnightDrownedEntity.java new file mode 100644 index 0000000..09f367f --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/drowned/KnightDrownedEntity.java @@ -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 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 getVariantFactory() { return KnightDrownedEntity::new; } + + /** @return This entity's mob species. */ + @SpecialMob.SpeciesSupplier + @Override + public MobFamily.Species getSpecies() { return SPECIES; } + + + //--------------- Variant-Specific Implementations ---------------- + + public KnightDrownedEntity( EntityType 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 ) ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/drowned/PlagueDrownedEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/drowned/PlagueDrownedEntity.java new file mode 100644 index 0000000..4ad094c --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/drowned/PlagueDrownedEntity.java @@ -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 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 getVariantFactory() { return PlagueDrownedEntity::new; } + + /** @return This entity's mob species. */ + @SpecialMob.SpeciesSupplier + @Override + public MobFamily.Species getSpecies() { return SPECIES; } + + + //--------------- Variant-Specific Implementations ---------------- + + public PlagueDrownedEntity( EntityType 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 ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/drowned/_SpecialDrownedEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/drowned/_SpecialDrownedEntity.java new file mode 100644 index 0000000..2e52f3c --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/drowned/_SpecialDrownedEntity.java @@ -0,0 +1,350 @@ +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.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.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; + +@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.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 SCALE = EntityDataManager.defineId( _SpecialDrownedEntity.class, DataSerializers.FLOAT ); + + public _SpecialDrownedEntity( EntityType 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 ); + } + + + //--------------- 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 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 convertTo( EntityType 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().getBaseScale();// * (isBaby() ? 0.53448F : 1.0F); - 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(); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/drowned/package-info.java b/src/main/java/fathertoast/specialmobs/common/entity/drowned/package-info.java new file mode 100644 index 0000000..fa0c1a6 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/drowned/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package fathertoast.specialmobs.common.entity.drowned; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/BruteZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/BruteZombieEntity.java index e2056f5..25547b5 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/BruteZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/BruteZombieEntity.java @@ -4,11 +4,13 @@ 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.drowned.BruteDrownedEntity; 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.entity.monster.ZombieEntity; import net.minecraft.entity.projectile.AbstractArrowEntity; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; @@ -58,6 +60,10 @@ public class BruteZombieEntity extends _SpecialZombieEntity { public BruteZombieEntity( EntityType entityType, World world ) { super( entityType, world ); } + /** Override to change the entity this converts to when drowned. */ + @Override + protected EntityType getVariantConversionType() { return BruteDrownedEntity.SPECIES.entityType.get(); } + /** Override to apply effects when this entity hits a target with a melee attack. */ @Override protected void onVariantAttack( LivingEntity target ) { diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/FishingZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/FishingZombieEntity.java index 061c702..6a464da 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/FishingZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/FishingZombieEntity.java @@ -9,6 +9,7 @@ import fathertoast.specialmobs.common.config.species.ZombieSpeciesConfig; import fathertoast.specialmobs.common.entity.ai.AIHelper; import fathertoast.specialmobs.common.entity.ai.IAngler; import fathertoast.specialmobs.common.entity.ai.goal.AnglerGoal; +import fathertoast.specialmobs.common.entity.drowned.FishingDrownedEntity; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootEntryItemBuilder; import fathertoast.specialmobs.datagen.loot.LootHelper; @@ -19,6 +20,7 @@ import net.minecraft.entity.ILivingEntityData; import net.minecraft.entity.SpawnReason; import net.minecraft.entity.ai.attributes.Attributes; import net.minecraft.entity.ai.goal.ZombieAttackGoal; +import net.minecraft.entity.monster.ZombieEntity; import net.minecraft.inventory.EquipmentSlotType; import net.minecraft.item.IDyeableArmorItem; import net.minecraft.item.ItemStack; @@ -112,6 +114,10 @@ public class FishingZombieEntity extends _SpecialZombieEntity implements IAngler setCanPickUpLoot( false ); } + /** Override to change the entity this converts to when drowned. */ + @Override + protected EntityType getVariantConversionType() { return FishingDrownedEntity.SPECIES.entityType.get(); } + //--------------- IAngler Implementations ---------------- diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/GiantZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/GiantZombieEntity.java index 63104f6..954ee7d 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/GiantZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/GiantZombieEntity.java @@ -4,11 +4,13 @@ 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.drowned.GiantDrownedEntity; 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.entity.monster.ZombieEntity; import net.minecraft.entity.projectile.AbstractArrowEntity; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; @@ -59,6 +61,10 @@ public class GiantZombieEntity extends _SpecialZombieEntity { maxUpStep = 1.0F; } + /** Override to change the entity this converts to when drowned. */ + @Override + protected EntityType getVariantConversionType() { return GiantDrownedEntity.SPECIES.entityType.get(); } + /** Override to apply effects when this entity hits a target with a melee attack. */ @Override protected void onVariantAttack( LivingEntity target ) { diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/HungryZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/HungryZombieEntity.java index b81235d..177aa5b 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/HungryZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/HungryZombieEntity.java @@ -4,6 +4,7 @@ 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.drowned.HungryDrownedEntity; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import net.minecraft.entity.EntityType; @@ -11,6 +12,7 @@ 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.monster.ZombieEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Food; import net.minecraft.item.ItemStack; @@ -73,6 +75,10 @@ public class HungryZombieEntity extends _SpecialZombieEntity { setCanPickUpLoot( false ); } + /** Override to change the entity this converts to when drowned. */ + @Override + protected EntityType getVariantConversionType() { return HungryDrownedEntity.SPECIES.entityType.get(); } + /** Override to apply effects when this entity hits a target with a melee attack. */ @Override protected void onVariantAttack( LivingEntity target ) { diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/HuskZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/HuskZombieEntity.java index 34570d1..5924617 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/HuskZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/HuskZombieEntity.java @@ -12,6 +12,7 @@ import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.ai.attributes.AttributeModifierMap; import net.minecraft.entity.monster.HuskEntity; +import net.minecraft.entity.monster.ZombieEntity; import net.minecraft.entity.projectile.AbstractArrowEntity; import net.minecraft.item.ItemStack; import net.minecraft.potion.Effects; @@ -93,15 +94,19 @@ public class HuskZombieEntity extends _SpecialZombieEntity { /** Performs this zombie's drowning conversion. */ @Override protected void doUnderWaterConversion() { - // Select a random non-husk, non-water-sensitive zombie; defaults to a normal zombie - convertToZombieType( - getConfig().HUSK.convertVariantChance.rollChance( random, level, blockPosition() ) ? - MobFamily.ZOMBIE.nextVariant( level, blockPosition(), HUSK_CONVERSION_SELECTOR, _SpecialZombieEntity.SPECIES ).entityType.get() : - _SpecialZombieEntity.SPECIES.entityType.get() - ); + convertToZombieType( getVariantConversionType() ); References.LevelEvent.HUSK_CONVERTED_TO_ZOMBIE.play( this ); } + /** Override to change the entity this converts to when drowned. */ + @Override + protected EntityType getVariantConversionType() { + // Select a random non-husk, non-water-sensitive zombie; defaults to a normal zombie + return getConfig().HUSK.convertVariantChance.rollChance( random, level, blockPosition() ) ? + MobFamily.ZOMBIE.nextVariant( level, blockPosition(), HUSK_CONVERSION_SELECTOR, _SpecialZombieEntity.SPECIES ).entityType.get() : + _SpecialZombieEntity.SPECIES.entityType.get(); + } + //--------------- Husk Implementations ---------------- diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/PlagueZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/PlagueZombieEntity.java index 368a789..84c2d56 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/PlagueZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/PlagueZombieEntity.java @@ -4,12 +4,14 @@ 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.drowned.PlagueDrownedEntity; 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.entity.monster.ZombieEntity; import net.minecraft.entity.projectile.AbstractArrowEntity; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; @@ -57,6 +59,10 @@ public class PlagueZombieEntity extends _SpecialZombieEntity { public PlagueZombieEntity( EntityType entityType, World world ) { super( entityType, world ); } + /** Override to change the entity this converts to when drowned. */ + @Override + protected EntityType getVariantConversionType() { return PlagueDrownedEntity.SPECIES.entityType.get(); } + /** Override to apply effects when this entity hits a target with a melee attack. */ @Override protected void onVariantAttack( LivingEntity target ) { diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java index 8feb943..4eefec5 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java @@ -10,6 +10,7 @@ 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.SpecialHurtByTargetGoal; +import fathertoast.specialmobs.common.entity.drowned._SpecialDrownedEntity; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import net.minecraft.block.BlockState; @@ -111,6 +112,16 @@ public class _SpecialZombieEntity extends ZombieEntity implements IRangedAttackM public void finalizeVariantSpawn( IServerWorld world, DifficultyInstance difficulty, @Nullable SpawnReason spawnReason, @Nullable ILivingEntityData groupData ) { } + /** Performs this zombie's drowning conversion. */ + @Override + protected void doUnderWaterConversion() { + convertToZombieType( getVariantConversionType() ); + References.LevelEvent.ZOMBIE_CONVERTED_TO_DROWNED.play( this ); + } + + /** Override to change the entity this converts to when drowned. */ + protected EntityType getVariantConversionType() { return _SpecialDrownedEntity.SPECIES.entityType.get(); } + /** Called to attack the target with a ranged attack. */ @Override public void performRangedAttack( LivingEntity target, float damageMulti ) { @@ -211,15 +222,6 @@ public class _SpecialZombieEntity extends ZombieEntity implements IRangedAttackM @Override protected boolean convertsInWater() { return !isSensitiveToWater(); } - // TODO Drowned transform - // /** Performs this zombie's drowning conversion. */ - // @Override - // protected void doUnderWaterConversion() { - // // Select a random drowned - // convertToZombieType( MobFamily.DROWNED.nextVariant( level, blockPosition() ).entityType.get() ); - // References.LevelEvent.ZOMBIE_CONVERTED_TO_DROWNED.play( this ); - // } - //--------------- ISpecialMob Implementation ---------------- diff --git a/src/main/resources/assets/specialmobs/textures/entity/drowned/brute.png b/src/main/resources/assets/specialmobs/textures/entity/drowned/brute.png new file mode 100644 index 0000000000000000000000000000000000000000..f1bfe2a22c8f9727a44f2a6481c3e02647aef6a5 GIT binary patch literal 1349 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`1o3GfMVwae7CPSW=*H1(@Cvj|d~pBItY9+o~KtTVy4a!zXD)ad-FF;;PU zQPmz&Qc`cfJu>o8x^QPtnCBS{4Go5Q^UR`D4K#`v)~{zcbH>WB)6HtBnqo9VeLX{T zG{cG&3^QjkWMqVwx*K~b+uPe`XJ`BP`1JSpSCyA1CMHJMYNxmvst0;o$Hi7e+63ff zc^j+vPHG5isSDXz1a#n&AI}qjlu1dDU+{m@0fTqAQYuhCXMsm#F#`kN0T5=)y4K0b zz`*>;)5S3)!u{>k-10|e0!GUAne6$)HQvWBU)@h=2dlI*W1 z;~iG={?5#a9k*CD)|$lI$yw>dAN6#QT+3VXwlHRY+hhlp+X@Ta9qJD`iYO)+eDGw< zRchuA`oJXbf3UrON!NT4g<7u#6~ClrZKx4-XT0y|E0p5so+rMb{7U?;|GrM$jena= zTxM##sTMCvOOW35U+nlL&y~vBUyjrt+E=c~*qX5D$@cq~?S<~2C|IU6C&st6U`DTx zNd5FX`;V6dWu0{2a^-4Xg<19DH}CX%-@h~3vZ1-vW6R>`pb4{zSsq2awrG=Gb>P60 zDTn$WKjwK<{iM0~_~MThI_nlsoFXP)VQu;2^AC$=kw7NHM~tfS(|Eqgt-H?|zhwV` z`v=xH?D=p)oiVX8{r&s3Ke*P*<#V4XmFU%Dd3W&jwYmt0bLkade-v;9E&T9mVT!Zm zTtD^AH+vGJLa*-8I^u9}%c@UzB45YE+1i9C6-Y4u_eprf`f68Mn>_2M2Z9_ z-%RBBv$sKjpQ%8L-CV0DHO^X_Z%y;^+_bf6i_Wp0+UwBi5b5{4)OP#S-k#8N=VSA9 z<72bG3hef|S~X9zTxZFwDeR3xD?KC6KcA2@@%Aahxwm6jin^AE@T8n^cl6HT|4uTx|9Joxn7$)A3k zOpIeLKQZ)U`oEOHIKVd0WxnaueKM(nglhz>uBBHJP!2_L}KaN~g%a&z!ZSD0=dA zzIoyT|9|dGsSs;C$-}-VBFOWcTcp^rjR#b_PAEJ)uID8eZ&AT^dj9F9J^!0d=G1TS z(Bl7Wr0!Ywa*(+17XFNFl!2j@stx=}A*S9__z5d%U`~|ihJfglx0^c=(@qoQ*-2C*42dqajpvv9?0cWv6?pf>aNK3 z*KJg7BzEQPeK*@K$NG^I!>_o+-eX*R!FGE;NT(&<7M#wMGik+&3q0=^yp1@>=V^L; z3tz=yM}0Pz1KL}1@0;-oWoMpF)z)W?s;KSmT_)PdS9O_Br~Fcfv`hy}-0o$sIrb}= z?Kyiu!|6Kf2FoqdO}s8gwzk~kyRFOh=xLSULf*q{hc+xOmUX-Jw(K|e&b3!Qh&A@@ z-JNS*P<3)=s)U)e&xiKtAPK9&{nBhV{lBgFDYETM#=)<84@7fP_J_oZw8h^}yj(it xmBvNwJ*o}?I?hL$kEE)(PIk44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`1o3GfMVwae7CPSW=*H1(@Cvj|d~pBItY9+o~KtTVy4a!zXD)ad-FF;;PU zQPmz&Qc`cfJu>o8x^QPtnCBS{4Go5Q^UR`D4K#`v)~{zcbH>WB)6HtBnqo9VeLX{T zG{cG&3^QjkWMqVwx*K~b+uPe`XJ`BP`1JSpSCyA1CMHJMYNxmvst0;o$Hi7e+63ff zc^j+vPHG5isSDXz1a#n&AI}qjlu1dDU+{m@0fTqAQYuhCXMsm#F#`kN0T5=)y4K0b zz`*>;)5S3)!u{>k-10|e0!GUAne6$)HQvWBU)@h=2dlI*W1 z;~iG={?5#a9k*CD)|$lI$yw>dAN6#QT+3VXwlHRY+hhlp+X@Ta9qJD`iYO)+eDGw< zRchuA`oJXbf3UrON!NT4g<7u#6~ClrZKx4-XT0y|E0p5so+rMb{7U?;|GrM$jena= zTxM##sTMCvOOW35U+nlL&y~vBUyjrt+E=c~*qX5D$@cq~?S<~2C|IU6C&st6U`DTx zNd5FX`;V6dWu0{2a^-4Xg<19DH}CX%-@h~3vZ1-vW6R>`pb4{zSsq2awrG=Gb>P60 zDTn$WKjwK<{iM0~_~MThI_nlsoFXP)VQu;2^AC$=kw7NHM~tfS(|Eqgt-H?|zhwV` z`v=xH?D=p)oiVX8{r&s3Ke*P*<#V4XmFU%Dd3W&jwYmt0bLkade-v;9E&T9mVT!Zm zTtD^AH+vGJLa*-8I^u9}%c@UzB45YE+1i9C6-Y4u_eprf`f68Mn>_2M2Z9_ z-%RBBv$sKjpQ%8L-CV0DHO^X_Z%y;^+_bf6i_Wp0+UwBi5b5{4)OP#S-k#8N=VSA9 z<72bG3hef|S~X9zTxZFwDeR3xD?KC6KcA2@@%Aahxwm6jin^AE@T8n^cl6HT|4uTx|9Joxn7$)A3k zOpIeLKQZ)U`oEOHIKVd0WxnaueKM(nglhz>uBBHJP!2_L}KaN~g%a&z!ZSD0=dA zzIoyT|9|dGsSs;C$-}-VBFOWcTcp^rjR#b_PAEJ)uID8eZ&AT^dj9F9J^!0d=G1TS z(Bl7Wr0!Ywa*(+17XFNFl!2j@stx=}A*S9__z5d%U`~|ihJfglx0^c=(@qoQ*-2C*42dqajpvv9?0cWv6?pf>aNK3 z*KJg7BzEQPeK*@K$NG^I!>_o+-eX*R!FGE;NT(&<7M#wMGik+&3q0=^yp1@>=V^L; z3tz=yM}0Pz1KL}1@0;-oWoMpF)z)W?s;KSmT_)PdS9O_Br~Fcfv`hy}-0o$sIrb}= z?Kyiu!|6Kf2FoqdO}s8gwzk~kyRFOh=xLSULf*q{hc+xOmUX-Jw(K|e&b3!Qh&A@@ z-JNS*P<3)=s)U)e&xiKtAPK9&{nBhV{lBgFDYETM#=)<84@7fP_J_oZw8h^}yj(it xmBvNwJ*o}?I?hL$kEE)(PIt;|CdHuiAGw%9P{(3nl0u8lJDP?<~-iisF%Qb$8>5TkF~fB)g2w`l!_YZ}0Yzg-7yxJ%Xl*l96~5coJHP`S;^-Q1oD_|w zay;y@hZ3(wCl$u>gb8_fdFeuqVk$$(S=^A0PWJ4#)+e!0uEr*T#6cJg1`G~5oU*oq zae$^K=9Mrq8}DkL>*-p1*n$RJy5vlPJCosva9g0Vl6i$u*(C(Z%NN!scXxMzm>A%} z0~Zn;pwXDgR1-EkQ1D^j_k9@O(uK=1$A^R)B|4#wE7n^a4EX{L-;6wn>>G>h0wi~QP z#qA(O93~(LzgWV$C_SN@#Vfug?G=;0e`fgqlBw-DR7*=!Dc#*|+Ay`i#v2p~c%0wP z^kZiR%Z+n`Xet9@_ReO^A5s5U=_r?;8Q0;`rV``^I?8aiy5_jbhB~wjuB@Zx4r3r1 zFKbaol$U)lS5s>94j7p8PBFfJbm9)K5|Q9pozGSnBkpvX(gnP{cB`Ei&S*8)ZibF6 zAiyLm%TI_?MC;muh7oC$^?59w9F( zZyFs)MLf#U%W1Alr`mnX-;-=c{;C;T^R;^!-IOtaa)fWGMV;SIr)NtCI$}F)%*T3U zbj^@V;bUHF#%&*2{daNq*lY}#Vj-(%_GhQs?OVIrXVGU){N2&C_Qra&(P8s}*OGqN z+oDI0aG;IqNtFI9i%$sksq-(1VwO#xr{g4ZPZ4i%$3R=}S5lO*U$m}&;3AD>-i|1N zXFbBNdKLweiqO~5(7pEplA1H-KCvrjWDy@byOs*DC98XtYdZA{l>x$OrcsuvtVZLSWzCY9g-GxA zQ6o#ezu;=-<8_ z-+Arj<-}o6Xy353%xk)c|4ty9|B6gl0hYQXlKxFwG`h8mSkvyE+> zC8`~n&lZIB*RWf>4~_FC{!WK3@LNF|d67A(g>8d1ITVp_hVeqKWNoAE4*N?tL-8>H Mq6Zn*h-GB{2jbnLQ2+n{ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/specialmobs/textures/entity/drowned/hungry_overlay.png b/src/main/resources/assets/specialmobs/textures/entity/drowned/hungry_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..faa740495103d47bfb03124c081c532b2ad8b420 GIT binary patch literal 1428 zcmbu9{WsGK9LK+yr-{tdjYde>t;|CdHuiAGw%9P{(3nl0u8lJDP?<~-iisF%Qb$8>5TkF~fB)g2w`l!_YZ}0Yzg-7yxJ%Xl*l96~5coJHP`S;^-Q1oD_|w zay;y@hZ3(wCl$u>gb8_fdFeuqVk$$(S=^A0PWJ4#)+e!0uEr*T#6cJg1`G~5oU*oq zae$^K=9Mrq8}DkL>*-p1*n$RJy5vlPJCosva9g0Vl6i$u*(C(Z%NN!scXxMzm>A%} z0~Zn;pwXDgR1-EkQ1D^j_k9@O(uK=1$A^R)B|4#wE7n^a4EX{L-;6wn>>G>h0wi~QP z#qA(O93~(LzgWV$C_SN@#Vfug?G=;0e`fgqlBw-DR7*=!Dc#*|+Ay`i#v2p~c%0wP z^kZiR%Z+n`Xet9@_ReO^A5s5U=_r?;8Q0;`rV``^I?8aiy5_jbhB~wjuB@Zx4r3r1 zFKbaol$U)lS5s>94j7p8PBFfJbm9)K5|Q9pozGSnBkpvX(gnP{cB`Ei&S*8)ZibF6 zAiyLm%TI_?MC;muh7oC$^?59w9F( zZyFs)MLf#U%W1Alr`mnX-;-=c{;C;T^R;^!-IOtaa)fWGMV;SIr)NtCI$}F)%*T3U zbj^@V;bUHF#%&*2{daNq*lY}#Vj-(%_GhQs?OVIrXVGU){N2&C_Qra&(P8s}*OGqN z+oDI0aG;IqNtFI9i%$sksq-(1VwO#xr{g4ZPZ4i%$3R=}S5lO*U$m}&;3AD>-i|1N zXFbBNdKLweiqO~5(7pEplA1H-KCvrjWDy@byOs*DC98XtYdZA{l>x$OrcsuvtVZLSWzCY9g-GxA zQ6o#ezu;=-<8_ z-+Arj<-}o6Xy353%xk)c|4ty9|B6gl0hYQXlKxFwG`h8mSkvyE+> zC8`~n&lZIB*RWf>4~_FC{!WK3@LNF|d67A(g>8d1ITVp_hVeqKWNoAE4*N?tL-8>H Mq6Zn*h-GB{2jbnLQ2+n{ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/specialmobs/textures/entity/drowned/plague.png b/src/main/resources/assets/specialmobs/textures/entity/drowned/plague.png new file mode 100644 index 0000000000000000000000000000000000000000..0140656cfcd98f9708086cc7262d29e31089f9f5 GIT binary patch literal 1351 zcmb7^{WlYO9LK*l!`2aFU5`bgmZz)2)$AHFvoN!mr*!Rxd6vgU!zL70C?iiT^N@0D zjE-*9(9J0Fcq8tev~oKRi@ID76Le2Ok^j78-_Ukh~bF zlu!p;4m;os+3swRJraq;=;)T+Nab?V8yndC`-P2l?8f@++{@t{+WFB}_n7GvDsc#n zMuVXtD|8}gX~7)Jcf~XwJ{SbDvOrJ};Bvv;yP&cXL_}m}{EWllqNAfdJUohvi_pKbnQQ9y{XgZhtPA5r8GfMY*3a_PoKO=4ARjPLv<$Hqh!NhjlWAbY}Pe4{-N0 z3X}KY7u*7p0NC66O$xtVkCp-eT_w6W`S=s0WztlMt$Loxpuv4{C#eHg&DyU#Zm|Pt zS>B!9Dnz!ziJo8DkDMwpF{`4_R$g~ho#arfa1Qm6UD!x1*7=8%mi+o#Q%y^dl|rg8 zPjY@s6M@B%MTT>wK#FlN)lX4mVqwxw*EC9>7X%W^fulwQc_K@%8J*hX+A=+cXrn#; z?T{2aP+e=1?JcX%{s`06uoJb*W^EdoTD^Hop4wI3+2fXL9A`z-R(TrbDybrm#dr5- zxrpps)a=yRi?yN0_mK_=pwiqI!TKZNye24#!_@bSq_^oYE(se-hQbpnjPo1j+>uz? zrBY?YCL$|Q?RQxv;%QHAk7;ZQG|636UwWzFyUcZZyKFhRbW7kC;9uiFM*L(%N!#V_ z$d3D3lQ}x|;z-z+4Nch|J-^m8s~INkm8IGOlN&XXw6quz5B-4I*x46S^fXR-MWLot zD`oQnf*JYO^wbz9W}tj8$mQ#OHeWzYu%gCU`Lz|=grnDSicR-&E3x%y0Q7Us9fbN&sU2qE6XDqb(O!c)%)rkr71OcwE~Qpt zoik6TKD(0!J8A5DYXUQ^hX_O(vaXY-VuO7R{~a%1q@IIDZO`cH_*$@()|)^1$2A~- zK&vYTRyRkgbRV+Kyi(-tH~~Ua*VrNZk=_|ZeXSjDr~SMSBe5qZ%rUq@5gZZGk!9bFxY=uOrdE!ZF0?Gvmb9ueAG ytJdkBUVS+WQAfS7t8e2Ok^j78-_Ukh~bF zlu!p;4m;os+3swRJraq;=;)T+Nab?V8yndC`-P2l?8f@++{@t{+WFB}_n7GvDsc#n zMuVXtD|8}gX~7)Jcf~XwJ{SbDvOrJ};Bvv;yP&cXL_}m}{EWllqNAfdJUohvi_pKbnQQ9y{XgZhtPA5r8GfMY*3a_PoKO=4ARjPLv<$Hqh!NhjlWAbY}Pe4{-N0 z3X}KY7u*7p0NC66O$xtVkCp-eT_w6W`S=s0WztlMt$Loxpuv4{C#eHg&DyU#Zm|Pt zS>B!9Dnz!ziJo8DkDMwpF{`4_R$g~ho#arfa1Qm6UD!x1*7=8%mi+o#Q%y^dl|rg8 zPjY@s6M@B%MTT>wK#FlN)lX4mVqwxw*EC9>7X%W^fulwQc_K@%8J*hX+A=+cXrn#; z?T{2aP+e=1?JcX%{s`06uoJb*W^EdoTD^Hop4wI3+2fXL9A`z-R(TrbDybrm#dr5- zxrpps)a=yRi?yN0_mK_=pwiqI!TKZNye24#!_@bSq_^oYE(se-hQbpnjPo1j+>uz? zrBY?YCL$|Q?RQxv;%QHAk7;ZQG|636UwWzFyUcZZyKFhRbW7kC;9uiFM*L(%N!#V_ z$d3D3lQ}x|;z-z+4Nch|J-^m8s~INkm8IGOlN&XXw6quz5B-4I*x46S^fXR-MWLot zD`oQnf*JYO^wbz9W}tj8$mQ#OHeWzYu%gCU`Lz|=grnDSicR-&E3x%y0Q7Us9fbN&sU2qE6XDqb(O!c)%)rkr71OcwE~Qpt zoik6TKD(0!J8A5DYXUQ^hX_O(vaXY-VuO7R{~a%1q@IIDZO`cH_*$@()|)^1$2A~- zK&vYTRyRkgbRV+Kyi(-tH~~Ua*VrNZk=_|ZeXSjDr~SMSBe5qZ%rUq@5gZZGk!9bFxY=uOrdE!ZF0?Gvmb9ueAG ytJdkBUVS+WQAfS7t8?L|X4wu=YgJ_6zIeG`FL?lh2=HQA zFpkH*>;WKzSpdXx8n9TuLii})Vp&84ut$C9+ZY|B_dbWY-{%Wu_5d*Hqt$ZlVVH@x z?y)WReYh7_1KJ834AH=;;R~ft0S~FGrOqk>Q0y>quKGS27!GO!1o;$5@r3G60Ajv~ zD>sAzh@}HVVUKePwAGVM5s*a+n8>yi;EvA#z>|In>^p#OH2Z?P6qP*000000NkvXXu0mjfvriOx literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/specialmobs/textures/entity/drowned/vanilla_overlay.png b/src/main/resources/assets/specialmobs/textures/entity/drowned/vanilla_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..d124f2e6a48ff54a012eb4d52a407a5f768c04a3 GIT binary patch literal 1334 zcmV-61M3JM}RXh)7jL>xJ48gT{U8r*}c zi&VoWAEzrXd2Kf@c4tuB{O6uir|wVcW;i)H8FtyTU*E>E+uw%q_4nh`?dLyE#^cXN zr^g<=JKAY;&mYI}pf)Te0N}5FfBCy#+XeF0%cref2?ELtx=64pt_1;BLP@_!D65dT z@&uHZ)B9Gzl_%id&tWWshzy5oK|p~=_6C3B^KktXfC>tH9slpUK|D*mSET`L@KxgZ zvKOB|wAXaTxgrfU)pnH!Cjd{;qE`tKRJ_1eg&7D{St_U!P+X5PxYyw88lHo#f`IaV zfy69fDqP3u`cMqlavqQnQu`28rLV91Yy02?0J{2jlR#w+9Pr-mh%;FBO_a~Ah5(FE z06g9*1Gx9O#)M4~Y2fUyf`F`)!vaq~`SS5E@Y*h`bDeUKcSl5?J2(N3N8;FCO|RSg z4uMYI8Cu-y>sLX5E1!m^@w&a=NqkNP5WE@%q-Wnex--6ie0Qv}3~w^Jn;Y0Xy?=Au zWlJYtQ}*@k!_)fZWGq#-)LH^e0z=xRL)$5!7fFCgDA3exN&RLCs0UjCIlf;)zNddOmxS!#8}J?^bl_7J zt~?dx83~SeB)J`-ZinzHK(hgKLv1B235mfk!b=W;DFU$0W4<5xx`E|dKikiD379H9 zW~tdIU!;*Gp-p)27W85)oLLD9s3ws&h*$4v$5*Pzy7YGR+jm{DP__~qG zINkuu!`V)Go4k$yhlL3+*d(L2bF#&-+!MEsgr&X0U~u&N?*4Kx zW3Mk6T-n3arFwvD8_wMeB)B9Y&*e*~aP|o2nJwmUnGo=p-`^)tssU%WxF`e?oE4F6 zz`nD?xjDhya5%py5r*PlAr&)K;kk8aB?F*rOy$NxrMoJe5)iN7g+3*sBmhn zY;FO>Y8BumHv25F$lT)G5}1P%kRH#ZNG3hI&*56yhcF3@&4UPn;&nH2vAi$EOQn`S!%h}oU zCM(w{n}k%#DjLDe-RJ)W?!WVAj9lsEKS{^{J7Jp*;Hn7d{xNWp)x+9u{ju-&kC~YU z&;0+v3CN6W{%?>==(`miPJw3# Date: Fri, 12 Aug 2022 16:32:56 -0500 Subject: [PATCH 04/10] Amphibious mobs :O --- .../specialmobs/client/ClientRegister.java | 8 +- .../species/ShortSilverfishRenderer.java | 27 +++++ .../common/bestiary/MobFamily.java | 2 +- .../specialmobs/common/core/SpecialMobs.java | 1 - .../ai/AmphibiousMovementController.java | 57 ++++++++++ .../common/entity/ai/IAmphibiousMob.java | 21 ++++ .../ai/goal/AmphibiousGoToShoreGoal.java | 51 +++++++++ .../ai/goal/AmphibiousGoToWaterGoal.java | 81 +++++++++++++ .../ai/goal/AmphibiousMeleeAttackGoal.java | 47 ++++++++ .../entity/ai/goal/AmphibiousSwimUpGoal.java | 82 ++++++++++++++ .../entity/creeper/DrowningCreeperEntity.java | 106 +++++++++++++++++- .../entity/drowned/_SpecialDrownedEntity.java | 31 +++++ .../AmphibiousSilverfishEntity.java | 97 ++++++++++++++++ .../silverfish/FishingSilverfishEntity.java | 9 +- .../silverfish/PufferSilverfishEntity.java | 14 ++- .../silverfish/_SpecialSilverfishEntity.java | 2 + .../entity/slime/BlueberrySlimeEntity.java | 40 ++++--- 17 files changed, 646 insertions(+), 30 deletions(-) create mode 100644 src/main/java/fathertoast/specialmobs/client/renderer/entity/species/ShortSilverfishRenderer.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ai/AmphibiousMovementController.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ai/IAmphibiousMob.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousGoToShoreGoal.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousGoToWaterGoal.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousMeleeAttackGoal.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousSwimUpGoal.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/silverfish/AmphibiousSilverfishEntity.java diff --git a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java index 04dfe88..ad29efe 100644 --- a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java +++ b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java @@ -3,15 +3,13 @@ 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.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; @@ -77,6 +75,8 @@ public class ClientRegister { registerSpeciesRenderer( PotionSlimeEntity.SPECIES, PotionSlimeRenderer::new ); + registerSpeciesRenderer( PufferSilverfishEntity.SPECIES, ShortSilverfishRenderer::new ); + registerSpeciesRenderer( CorporealShiftGhastEntity.SPECIES, CorporealShiftGhastRenderer::new ); // Other diff --git a/src/main/java/fathertoast/specialmobs/client/renderer/entity/species/ShortSilverfishRenderer.java b/src/main/java/fathertoast/specialmobs/client/renderer/entity/species/ShortSilverfishRenderer.java new file mode 100644 index 0000000..b4e4fd3 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/client/renderer/entity/species/ShortSilverfishRenderer.java @@ -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 ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java index b695d24..fbc5cd0 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java @@ -86,7 +86,7 @@ public class MobFamily { public static final MobFamily 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 ENDERMAN = new MobFamily<>( FamilyConfig::new, "Enderman", "endermen", 0x161616, new EntityType[] { EntityType.ENDERMAN }, diff --git a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java index 53d8fcd..7a8d174 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java +++ b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java @@ -82,7 +82,6 @@ public class SpecialMobs { * + natural spawning * - silverfish * - ranged attack AI (spitter) - * + puffer * - endermen * - witches * - ability to equip held items (wonky) diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/AmphibiousMovementController.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/AmphibiousMovementController.java new file mode 100644 index 0000000..7e40a66 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/AmphibiousMovementController.java @@ -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. + *

+ * {@link net.minecraft.entity.monster.DrownedEntity.MoveHelperController} + */ +public class AmphibiousMovementController 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(); + } + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/IAmphibiousMob.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/IAmphibiousMob.java new file mode 100644 index 0000000..ecf11a0 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/IAmphibiousMob.java @@ -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(); +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousGoToShoreGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousGoToShoreGoal.java new file mode 100644 index 0000000..6d012e2 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousGoToShoreGoal.java @@ -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. + *

+ * {@link net.minecraft.entity.monster.DrownedEntity.GoToBeachGoal} + */ +public class AmphibiousGoToShoreGoal 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 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(); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousGoToWaterGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousGoToWaterGoal.java new file mode 100644 index 0000000..201d53b --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousGoToWaterGoal.java @@ -0,0 +1,81 @@ +package fathertoast.specialmobs.common.entity.ai.goal; + +import fathertoast.specialmobs.common.entity.ai.IAmphibiousMob; +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. + *

+ * {@link net.minecraft.entity.monster.DrownedEntity.GoToWaterGoal} + */ +public class AmphibiousGoToWaterGoal extends Goal { + + private final T mob; + private final double speedModifier; + + private boolean disableAtNight = true; + + private double wantedX; + private double wantedY; + private double wantedZ; + + public AmphibiousGoToWaterGoal( T 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; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousMeleeAttackGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousMeleeAttackGoal.java new file mode 100644 index 0000000..3815033 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousMeleeAttackGoal.java @@ -0,0 +1,47 @@ +package fathertoast.specialmobs.common.entity.ai.goal; + +import fathertoast.specialmobs.common.entity.ai.IAmphibiousMob; +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. + *

+ * {@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( T 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( T 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() ); } + // } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousSwimUpGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousSwimUpGoal.java new file mode 100644 index 0000000..900a01c --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousSwimUpGoal.java @@ -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. + *

+ * {@link net.minecraft.entity.monster.DrownedEntity.SwimUpGoal} + */ +public class AmphibiousSwimUpGoal 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 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; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java index 1494442..06e7f8c 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java @@ -3,6 +3,11 @@ 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.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.util.ExplosionHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootEntryItemBuilder; @@ -11,18 +16,26 @@ 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.LivingEntity; +import net.minecraft.entity.MoverType; 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.World; @SpecialMob -public class DrowningCreeperEntity extends _SpecialCreeperEntity { +public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmphibiousMob { //--------------- Static Special Mob Hooks ---------------- @@ -66,7 +79,23 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity { //--------------- Variant-Specific Implementations ---------------- - public DrowningCreeperEntity( EntityType entityType, World world ) { super( entityType, world ); } + public DrowningCreeperEntity( EntityType 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 +121,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 +152,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 +181,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; } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/drowned/_SpecialDrownedEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/drowned/_SpecialDrownedEntity.java index 2e52f3c..7741ac0 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/drowned/_SpecialDrownedEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/drowned/_SpecialDrownedEntity.java @@ -131,6 +131,8 @@ public class _SpecialDrownedEntity extends DrownedEntity implements ISpecialMob< /** The parameter for special mob render scale. */ private static final DataParameter SCALE = EntityDataManager.defineId( _SpecialDrownedEntity.class, DataSerializers.FLOAT ); + private boolean needsToBeDeeper; + public _SpecialDrownedEntity( EntityType entityType, World world ) { super( entityType, world ); recalculateAttackGoal(); @@ -172,6 +174,35 @@ public class _SpecialDrownedEntity extends DrownedEntity implements ISpecialMob< 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 ---------------- diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/AmphibiousSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/AmphibiousSilverfishEntity.java new file mode 100644 index 0000000..ded9134 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/AmphibiousSilverfishEntity.java @@ -0,0 +1,97 @@ +package fathertoast.specialmobs.common.entity.silverfish; + +import fathertoast.specialmobs.common.entity.ai.AmphibiousMovementController; +import fathertoast.specialmobs.common.entity.ai.IAmphibiousMob; +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.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 { + + private final SwimmerPathNavigator waterNavigation; + private final GroundPathNavigator groundNavigation; + + private boolean swimmingUp; + + public AmphibiousSilverfishEntity( EntityType 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; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/FishingSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/FishingSilverfishEntity.java index 6095dc1..5414294 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/FishingSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/FishingSilverfishEntity.java @@ -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() ); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java index a0fe554..8488c8a 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java @@ -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 ---------------- @@ -23,7 +26,7 @@ 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 + .uniqueTextureBaseOnly() .addExperience( 1 ).drownImmune().effectImmune( Effects.POISON ); } @@ -52,7 +55,12 @@ public class PufferSilverfishEntity extends _SpecialSilverfishEntity { public PufferSilverfishEntity( EntityType 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java index 87e0e10..3657031 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java @@ -89,6 +89,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(); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/slime/BlueberrySlimeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/slime/BlueberrySlimeEntity.java index 182b004..1679516 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/slime/BlueberrySlimeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/slime/BlueberrySlimeEntity.java @@ -3,21 +3,20 @@ 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.entity.ai.AIHelper; -import fathertoast.specialmobs.common.entity.ai.FluidPathNavigator; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import net.minecraft.block.FlowingFluidBlock; import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; 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.shapes.ISelectionContext; import net.minecraft.world.World; @SpecialMob @@ -71,21 +70,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 +101,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 ) { From 75df9a23c2873486bd56d3d92a3061eee4a1abb7 Mon Sep 17 00:00:00 2001 From: FatherToast Date: Fri, 12 Aug 2022 21:03:16 -0500 Subject: [PATCH 05/10] minor cleanup --- .../fathertoast/specialmobs/common/bestiary/MobFamily.java | 4 ++-- .../java/fathertoast/specialmobs/common/core/SpecialMobs.java | 4 +++- .../common/entity/silverfish/PufferSilverfishEntity.java | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java index fbc5cd0..a6c0e7c 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java @@ -49,8 +49,8 @@ public class MobFamily { ); public static final MobFamily DROWNED = new MobFamily<>( FamilyConfig::new, "Drowned", "drowned", 0x8FF1D7, new EntityType[] { EntityType.DROWNED }, - "Brute", "Fishing", "Giant", "Hungry", "Knight", "Plague" //TODO Textures! - brute, hungry, plague, cold ocean, warm ocean - );// ice/fire themes? (cold/warm ocean) - convert from frozen/fire zombies + "Brute", "Fishing", /*"Frozen",*/ "Giant", "Hungry", "Knight", "Plague"//, "Tropical" + ); //TODO Textures! - brute, hungry, plague, frozen, tropical public static final MobFamily 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 diff --git a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java index 7a8d174..8e02883 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java +++ b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java @@ -59,7 +59,9 @@ public class SpecialMobs { * - 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) diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java index 8488c8a..19009a1 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java @@ -25,7 +25,7 @@ public class PufferSilverfishEntity extends AmphibiousSilverfishEntity { @SpecialMob.BestiaryInfoSupplier public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) { - bestiaryInfo.color( 0xE6E861 ).theme( BestiaryInfo.Theme.WATER ) + bestiaryInfo.color( 0xE6E861 ).weight( BestiaryInfo.DefaultWeight.LOW ).theme( BestiaryInfo.Theme.WATER ) .uniqueTextureBaseOnly() .addExperience( 1 ).drownImmune().effectImmune( Effects.POISON ); } From 46f1a660c8a49b3e7e73b58cd5b809514cb11fcb Mon Sep 17 00:00:00 2001 From: FatherToast Date: Sat, 13 Aug 2022 22:33:19 -0500 Subject: [PATCH 06/10] Natural spawning stuff + eye height fixes --- .../common/bestiary/MobFamily.java | 24 +++ .../common/bestiary/SpecialMob.java | 16 ++ .../specialmobs/common/config/MainConfig.java | 37 +++++ .../config/field/AbstractConfigField.java | 4 +- .../common/config/file/ToastConfigSpec.java | 17 ++- .../common/config/species/SpeciesConfig.java | 14 ++ .../common/config/util/RestartNote.java | 19 +++ .../specialmobs/common/core/SpecialMobs.java | 37 ++--- .../common/core/register/SMEntities.java | 15 +- .../common/entity/SpecialMobData.java | 13 +- .../entity/blaze/_SpecialBlazeEntity.java | 10 +- .../cavespider/BabyCaveSpiderEntity.java | 8 + .../cavespider/_SpecialCaveSpiderEntity.java | 8 +- .../entity/creeper/DrowningCreeperEntity.java | 12 ++ .../entity/creeper/_SpecialCreeperEntity.java | 10 +- .../entity/drowned/_SpecialDrownedEntity.java | 18 ++- .../enderman/_SpecialEndermanEntity.java | 8 +- .../entity/ghast/_SpecialGhastEntity.java | 17 ++- .../magmacube/_SpecialMagmaCubeEntity.java | 25 ++- .../AmphibiousSilverfishEntity.java | 16 ++ .../silverfish/PufferSilverfishEntity.java | 4 + .../silverfish/_SpecialSilverfishEntity.java | 17 ++- .../entity/skeleton/StraySkeletonEntity.java | 25 ++- .../skeleton/_SpecialSkeletonEntity.java | 8 +- .../entity/slime/BlueberrySlimeEntity.java | 13 ++ .../entity/slime/_SpecialSlimeEntity.java | 25 ++- .../entity/spider/_SpecialSpiderEntity.java | 8 +- .../entity/witch/_SpecialWitchEntity.java | 8 +- .../_SpecialWitherSkeletonEntity.java | 8 +- .../entity/zombie/HuskZombieEntity.java | 21 +++ .../entity/zombie/_SpecialZombieEntity.java | 8 +- .../_SpecialZombifiedPiglinEntity.java | 17 ++- .../specialmobs/common/event/BiomeEvents.java | 28 ---- .../common/event/NaturalSpawnManager.java | 143 ++++++++++++++++++ .../common/util/AnnotationHelper.java | 10 ++ 35 files changed, 582 insertions(+), 89 deletions(-) create mode 100644 src/main/java/fathertoast/specialmobs/common/config/util/RestartNote.java delete mode 100644 src/main/java/fathertoast/specialmobs/common/event/BiomeEvents.java create mode 100644 src/main/java/fathertoast/specialmobs/common/event/NaturalSpawnManager.java diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java index a6c0e7c..713204a 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java @@ -222,6 +222,13 @@ public class MobFamily { * @see MobFamily */ public static class Species { + /** Maps each species's entity type back to the species. */ + private static final Map, 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 family; /** The name of this special variant; or null for vanilla replacement species. */ @@ -242,6 +249,9 @@ public class MobFamily { /** 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 parentFamily, String packageRoot, @Nullable String variantName ) { final boolean vanillaReplacement = variantName == null; @@ -301,5 +311,19 @@ public class MobFamily { .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 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; + } } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/SpecialMob.java b/src/main/java/fathertoast/specialmobs/common/bestiary/SpecialMob.java index 4e29366..85f5d91 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/SpecialMob.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/SpecialMob.java @@ -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. + *

+ * The annotated method must have a signature that follows the pattern: + *

+ * {@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. diff --git a/src/main/java/fathertoast/specialmobs/common/config/MainConfig.java b/src/main/java/fathertoast/specialmobs/common/config/MainConfig.java index 07bb625..844741c 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/MainConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/MainConfig.java @@ -2,14 +2,20 @@ 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.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,6 +24,7 @@ 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 { @@ -63,4 +70,34 @@ 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; + + 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." ); + + 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 this is set to 0, this added cave spider spawn feature is completely disabled.", + "Finer tuning can be done with the spawn chances below." ), RestartNote.GAME ); + 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(); + } + } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/AbstractConfigField.java b/src/main/java/fathertoast/specialmobs/common/config/field/AbstractConfigField.java index 7120589..bc90e2f 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/AbstractConfigField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/AbstractConfigField.java @@ -3,6 +3,7 @@ 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.RestartNote; import javax.annotation.Nullable; import java.util.ArrayList; @@ -46,8 +47,9 @@ public abstract class AbstractConfigField { public final List 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) COMMENT).trimToSize(); COMMENT = Collections.unmodifiableList( COMMENT ); diff --git a/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java b/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java index 27e0183..a37df7a 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java +++ b/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java @@ -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; @@ -315,10 +317,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. */ @@ -352,14 +354,21 @@ public class ToastConfigSpec { * @param field The field to define in this config spec. * @return The same field for convenience in constructing. */ - public T define( T field ) { + public T define( T field ) { return define( field, null ); } + + /** + * @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 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; } diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/SpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/SpeciesConfig.java index 108b22b..479f7b9 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/SpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/SpeciesConfig.java @@ -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; @@ -46,6 +47,8 @@ public class SpeciesConfig extends Config.AbstractConfig { public static class General extends Config.AbstractCategory { + public final DoubleField.EnvironmentSensitive naturalSpawnChance; + public final DoubleField randomScaling; public final AttributeListField attributeChanges; @@ -76,6 +79,17 @@ 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", new EnvironmentList() + .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", diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/RestartNote.java b/src/main/java/fathertoast/specialmobs/common/config/util/RestartNote.java new file mode 100644 index 0000000..4a038b8 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/config/util/RestartNote.java @@ -0,0 +1,19 @@ +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 *" ), + GAME( " * Requires a game restart to take effect *" ); + + /** Adds the note's comment, if any. */ + public static void appendComment( List comment, @Nullable RestartNote note ) { + if( note != null ) comment.add( note.COMMENT ); + } + + private final String COMMENT; + + RestartNote( String comment ) { COMMENT = comment; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java index 8e02883..d8c87f3 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java +++ b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java @@ -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; @@ -118,24 +119,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 ) { diff --git a/src/main/java/fathertoast/specialmobs/common/core/register/SMEntities.java b/src/main/java/fathertoast/specialmobs/common/core/register/SMEntities.java index 7da1b5f..b0cc7d0 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/register/SMEntities.java +++ b/src/main/java/fathertoast/specialmobs/common/core/register/SMEntities.java @@ -24,19 +24,19 @@ public class SMEntities { public static final RegistryObject> BUG_SPIT = register( "bug_spit", EntityType.Builder.of( BugSpitEntity::new, EntityClassification.MISC ) .sized( 0.25F, 0.25F ).clientTrackingRange( 4 ).updateInterval( 10 ) ); - + public static final RegistryObject> CORPOREAL_FIREBALL = register( "incorporeal_fireball", EntityType.Builder.of( IncorporealFireballEntity::new, EntityClassification.MISC ) .sized( 1.0F, 1.0F ).clientTrackingRange( 4 ).updateInterval( 3 ) ); - + public static final RegistryObject> FISHING_BOBBER = register( "fishing_bobber", EntityType.Builder.of( SpecialFishingBobberEntity::new, EntityClassification.MISC ).noSave().noSummon() .sized( 0.25F, 0.25F ).clientTrackingRange( 4 ).updateInterval( 5 ) ); - + public static final RegistryObject> BONE_SHRAPNEL = register( "bone_shrapnel", EntityType.Builder.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. */ public static RegistryObject> register( String name, EntityType.Builder builder ) { @@ -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 - } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/SpecialMobData.java b/src/main/java/fathertoast/specialmobs/common/entity/SpecialMobData.java index 93ebf95..e1786a1 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/SpecialMobData.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/SpecialMobData.java @@ -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> { //** @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() { diff --git a/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java index 6c097dd..575f44b 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java @@ -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; @@ -61,6 +62,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 species ) { + NaturalSpawnManager.registerSpawnPlacement( species, NaturalSpawnManager::checkSpawnRulesIgnoreLight ); + } + @SpecialMob.LanguageProvider public static String[] getTranslations( String langKey ) { return References.translations( langKey, "Blaze", @@ -218,10 +224,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. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/BabyCaveSpiderEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/BabyCaveSpiderEntity.java index aec198c..860a568 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/BabyCaveSpiderEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/BabyCaveSpiderEntity.java @@ -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 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 + } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/_SpecialCaveSpiderEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/_SpecialCaveSpiderEntity.java index 5e93bd1..bcc6439 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/_SpecialCaveSpiderEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/_SpecialCaveSpiderEntity.java @@ -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; @@ -62,6 +63,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 species ) { + NaturalSpawnManager.registerSpawnPlacement( species ); + } + @SpecialMob.LanguageProvider public static String[] getTranslations( String langKey ) { return References.translations( langKey, "Cave Spider", @@ -211,7 +217,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. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java index 06e7f8c..2f662ee 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java @@ -8,6 +8,7 @@ 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; @@ -15,6 +16,7 @@ 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.EntitySpawnPlacementRegistry; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.MoverType; @@ -32,6 +34,7 @@ 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; @SpecialMob @@ -50,6 +53,15 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmp .addToAttribute( Attributes.MAX_HEALTH, 10.0 ); } + @SpecialMob.SpawnPlacementRegistrar + public static void registerSpeciesSpawnPlacement( MobFamily.Species species ) { + NaturalSpawnManager.registerSpawnPlacement( species, EntitySpawnPlacementRegistry.PlacementType.IN_WATER ); + } + + /** @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", diff --git a/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java index 5189fb3..6cfde3d 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java @@ -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 species ) { + NaturalSpawnManager.registerSpawnPlacement( species ); + } + @SpecialMob.LanguageProvider public static String[] getTranslations( String langKey ) { return References.translations( langKey, "Creeper", @@ -366,10 +372,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. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/drowned/_SpecialDrownedEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/drowned/_SpecialDrownedEntity.java index 7741ac0..56ca60e 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/drowned/_SpecialDrownedEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/drowned/_SpecialDrownedEntity.java @@ -12,6 +12,7 @@ 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; @@ -32,6 +33,7 @@ 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; @@ -39,6 +41,7 @@ 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> { @@ -70,6 +73,19 @@ public class _SpecialDrownedEntity extends DrownedEntity implements ISpecialMob< @SpecialMob.AttributeSupplier public static AttributeModifierMap.MutableAttribute createAttributes() { return DrownedEntity.createAttributes(); } + @SpecialMob.SpawnPlacementRegistrar + public static void registerSpawnPlacement( MobFamily.Species species ) { + NaturalSpawnManager.registerSpawnPlacement( species, EntitySpawnPlacementRegistry.PlacementType.IN_WATER, + _SpecialDrownedEntity::checkFamilySpawnRules ); + } + + public static boolean checkFamilySpawnRules( EntityType type, IServerWorld world, + SpawnReason reason, BlockPos pos, Random random ) { + //noinspection unchecked + return DrownedEntity.checkDrownedSpawnRules( (EntityType) 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", @@ -289,7 +305,7 @@ public class _SpecialDrownedEntity extends DrownedEntity implements ISpecialMob< /** @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); - Handled in super + return super.getStandingEyeHeight( pose, size ) * getSpecialData().getHeightScale(); // Age handled in super } /** @return Whether this entity is immune to fire damage. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/enderman/_SpecialEndermanEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/enderman/_SpecialEndermanEntity.java index 206d78b..6eb564e 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/enderman/_SpecialEndermanEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/enderman/_SpecialEndermanEntity.java @@ -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; @@ -43,6 +44,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 species ) { + NaturalSpawnManager.registerSpawnPlacement( species ); + } + @SpecialMob.LanguageProvider public static String[] getTranslations( String langKey ) { return References.translations( langKey, "Enderman", @@ -176,7 +182,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. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java index 2c184f2..c36f8a6 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java @@ -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; @@ -29,6 +30,7 @@ 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.math.BlockPos; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.DifficultyInstance; @@ -36,6 +38,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> { @@ -58,6 +61,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 species ) { + NaturalSpawnManager.registerSpawnPlacement( species, _SpecialGhastEntity::checkFamilySpawnRules ); + } + + public static boolean checkFamilySpawnRules( EntityType type, IServerWorld world, + SpawnReason reason, BlockPos pos, Random random ) { + //noinspection unchecked + return GhastEntity.checkGhastSpawnRules( (EntityType) 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", @@ -246,7 +261,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. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/magmacube/_SpecialMagmaCubeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/magmacube/_SpecialMagmaCubeEntity.java index 4f72ad6..94ecc9a 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/magmacube/_SpecialMagmaCubeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/magmacube/_SpecialMagmaCubeEntity.java @@ -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; @@ -22,12 +23,14 @@ 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.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> { @@ -49,6 +52,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 species ) { + NaturalSpawnManager.registerSpawnPlacement( species, _SpecialMagmaCubeEntity::checkFamilySpawnRules ); + } + + public static boolean checkFamilySpawnRules( EntityType type, IServerWorld world, + SpawnReason reason, BlockPos pos, Random random ) { + //noinspection unchecked + return MagmaCubeEntity.checkMagmaCubeSpawnRules( (EntityType) 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", @@ -205,11 +220,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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/AmphibiousSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/AmphibiousSilverfishEntity.java index ded9134..29e25c8 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/AmphibiousSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/AmphibiousSilverfishEntity.java @@ -1,7 +1,11 @@ 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; @@ -10,6 +14,7 @@ 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; /** @@ -17,6 +22,17 @@ import net.minecraft.world.World; */ public abstract class AmphibiousSilverfishEntity extends _SpecialSilverfishEntity implements IAmphibiousMob { + @SpecialMob.SpawnPlacementRegistrar + public static void registerSpeciesSpawnPlacement( MobFamily.Species 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; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java index 19009a1..34c8072 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java @@ -71,4 +71,8 @@ public class PufferSilverfishEntity extends AmphibiousSilverfishEntity { protected void onVariantAttack( LivingEntity target ) { MobHelper.applyEffect( target, Effects.POISON ); } + + /** @return Water drag coefficient. */ + @Override + protected float getWaterSlowDown() { return 0.95F; } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java index 3657031..f55d41b 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java @@ -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; @@ -29,6 +30,7 @@ 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.vector.Vector3d; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.IServerWorld; @@ -36,6 +38,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> { @@ -66,6 +69,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 species ) { + NaturalSpawnManager.registerSpawnPlacement( species, _SpecialSilverfishEntity::checkFamilySpawnRules ); + } + + public static boolean checkFamilySpawnRules( EntityType type, IServerWorld world, + SpawnReason reason, BlockPos pos, Random random ) { + //noinspection unchecked + return SilverfishEntity.checkSliverfishSpawnRules( (EntityType) 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", @@ -241,7 +256,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. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/StraySkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/StraySkeletonEntity.java index 0c18186..e10705d 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/StraySkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/StraySkeletonEntity.java @@ -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 { @@ -39,6 +42,22 @@ public class StraySkeletonEntity extends _SpecialSkeletonEntity { @SpecialMob.AttributeSupplier public static AttributeModifierMap.MutableAttribute createAttributes() { return StrayEntity.createAttributes(); } + @SpecialMob.SpawnPlacementRegistrar + public static void registerSpeciesSpawnPlacement( MobFamily.Species 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 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", diff --git a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/_SpecialSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/_SpecialSkeletonEntity.java index ac98278..4010ad3 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/_SpecialSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/_SpecialSkeletonEntity.java @@ -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; @@ -75,6 +76,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 species ) { + NaturalSpawnManager.registerSpawnPlacement( species ); + } + @SpecialMob.LanguageProvider public static String[] getTranslations( String langKey ) { return References.translations( langKey, "Skeleton", @@ -358,7 +364,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. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/slime/BlueberrySlimeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/slime/BlueberrySlimeEntity.java index 1679516..edd5da1 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/slime/BlueberrySlimeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/slime/BlueberrySlimeEntity.java @@ -4,9 +4,11 @@ import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; import fathertoast.specialmobs.common.entity.ai.AIHelper; +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.ai.attributes.Attributes; @@ -17,6 +19,7 @@ import net.minecraft.particles.ParticleTypes; import net.minecraft.pathfinding.PathNodeType; import net.minecraft.tags.FluidTags; import net.minecraft.util.math.shapes.ISelectionContext; +import net.minecraft.world.IWorldReader; import net.minecraft.world.World; @SpecialMob @@ -35,6 +38,16 @@ public class BlueberrySlimeEntity extends _SpecialSlimeEntity { .addToAttribute( Attributes.ATTACK_DAMAGE, 2.0 ); } + @SpecialMob.SpawnPlacementRegistrar + public static void registerSpeciesSpawnPlacement( MobFamily.Species species ) { + NaturalSpawnManager.registerSpawnPlacement( species, EntitySpawnPlacementRegistry.PlacementType.IN_WATER, + _SpecialSlimeEntity::checkFamilySpawnRules ); + } + + /** @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", diff --git a/src/main/java/fathertoast/specialmobs/common/entity/slime/_SpecialSlimeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/slime/_SpecialSlimeEntity.java index e0d7c30..b2623dc 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/slime/_SpecialSlimeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/slime/_SpecialSlimeEntity.java @@ -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.DataSerializers; import net.minecraft.network.datasync.EntityDataManager; 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> { @@ -50,6 +53,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 species ) { + NaturalSpawnManager.registerSpawnPlacement( species, _SpecialSlimeEntity::checkFamilySpawnRules ); + } + + public static boolean checkFamilySpawnRules( EntityType type, IServerWorld world, + SpawnReason reason, BlockPos pos, Random random ) { + //noinspection unchecked + return SlimeEntity.checkSlimeSpawnRules( (EntityType) 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", @@ -212,11 +227,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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/spider/_SpecialSpiderEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/spider/_SpecialSpiderEntity.java index 08f398a..f1f2ac2 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/spider/_SpecialSpiderEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/spider/_SpecialSpiderEntity.java @@ -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; @@ -62,6 +63,11 @@ public class _SpecialSpiderEntity extends SpiderEntity implements IRangedAttackM @SpecialMob.AttributeSupplier public static AttributeModifierMap.MutableAttribute createAttributes() { return SpiderEntity.createAttributes(); } + @SpecialMob.SpawnPlacementRegistrar + public static void registerSpawnPlacement( MobFamily.Species species ) { + NaturalSpawnManager.registerSpawnPlacement( species ); + } + @SpecialMob.LanguageProvider public static String[] getTranslations( String langKey ) { return References.translations( langKey, "Spider", @@ -211,7 +217,7 @@ public class _SpecialSpiderEntity extends SpiderEntity implements IRangedAttackM /** @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. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java index bebb556..b3901f7 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java @@ -8,6 +8,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.AIHelper; +import fathertoast.specialmobs.common.event.NaturalSpawnManager; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import net.minecraft.block.BlockState; @@ -64,6 +65,11 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe @SpecialMob.AttributeSupplier public static AttributeModifierMap.MutableAttribute createAttributes() { return WitchEntity.createAttributes(); } + @SpecialMob.SpawnPlacementRegistrar + public static void registerSpawnPlacement( MobFamily.Species species ) { + NaturalSpawnManager.registerSpawnPlacement( species ); + } + @SpecialMob.LanguageProvider public static String[] getTranslations( String langKey ) { return References.translations( langKey, "Witch", @@ -451,7 +457,7 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe /** @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. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/_SpecialWitherSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/_SpecialWitherSkeletonEntity.java index 5c4ce1e..fc96bf1 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/_SpecialWitherSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/_SpecialWitherSkeletonEntity.java @@ -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; @@ -73,6 +74,11 @@ public class _SpecialWitherSkeletonEntity extends WitherSkeletonEntity implement @SpecialMob.AttributeSupplier public static AttributeModifierMap.MutableAttribute createAttributes() { return WitherSkeletonEntity.createAttributes(); } + @SpecialMob.SpawnPlacementRegistrar + public static void registerSpawnPlacement( MobFamily.Species species ) { + NaturalSpawnManager.registerSpawnPlacement( species ); + } + @SpecialMob.LanguageProvider public static String[] getTranslations( String langKey ) { return References.translations( langKey, "Wither Skeleton", @@ -320,7 +326,7 @@ public class _SpecialWitherSkeletonEntity extends WitherSkeletonEntity implement /** @return The eye height of this entity when standing. */ @Override protected float getStandingEyeHeight( Pose pose, EntitySize size ) { - return 1.74F * getSpecialData().getBaseScale() * (isBaby() ? 0.53448F : 1.0F); // Use base skeleton scale instead of super + return super.getStandingEyeHeight( pose, size ) * getSpecialData().getHeightScaleByAge(); } /** @return Whether this entity is immune to fire damage. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/HuskZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/HuskZombieEntity.java index 5924617..1b9d7be 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/HuskZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/HuskZombieEntity.java @@ -6,10 +6,12 @@ import fathertoast.specialmobs.common.bestiary.SpecialMob; import fathertoast.specialmobs.common.config.species.HuskZombieSpeciesConfig; import fathertoast.specialmobs.common.config.species.SpeciesConfig; 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.EntityType; import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.SpawnReason; import net.minecraft.entity.ai.attributes.AttributeModifierMap; import net.minecraft.entity.monster.HuskEntity; import net.minecraft.entity.monster.ZombieEntity; @@ -19,8 +21,11 @@ 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; import java.util.function.Function; @SpecialMob @@ -51,6 +56,22 @@ public class HuskZombieEntity extends _SpecialZombieEntity { @SpecialMob.AttributeSupplier public static AttributeModifierMap.MutableAttribute createAttributes() { return HuskEntity.createAttributes(); } + @SpecialMob.SpawnPlacementRegistrar + public static void registerSpeciesSpawnPlacement( MobFamily.Species species ) { + NaturalSpawnManager.registerSpawnPlacement( species, HuskZombieEntity::checkSpeciesSpawnRules ); + } + + /** + * We cannot call the actual husk method because our husk variant does not extend the vanilla husk. + * + * @see net.minecraft.entity.monster.HuskEntity#checkHuskSpawnRules(EntityType, IServerWorld, SpawnReason, BlockPos, Random) + */ + public static boolean checkSpeciesSpawnRules( EntityType 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, "Husk", diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java index 4eefec5..0ca6b8b 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java @@ -11,6 +11,7 @@ import fathertoast.specialmobs.common.entity.SpecialMobData; import fathertoast.specialmobs.common.entity.ai.AIHelper; import fathertoast.specialmobs.common.entity.ai.goal.SpecialHurtByTargetGoal; import fathertoast.specialmobs.common.entity.drowned._SpecialDrownedEntity; +import fathertoast.specialmobs.common.event.NaturalSpawnManager; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import net.minecraft.block.BlockState; @@ -74,6 +75,11 @@ public class _SpecialZombieEntity extends ZombieEntity implements IRangedAttackM @SpecialMob.AttributeSupplier public static AttributeModifierMap.MutableAttribute createAttributes() { return ZombieEntity.createAttributes(); } + @SpecialMob.SpawnPlacementRegistrar + public static void registerSpawnPlacement( MobFamily.Species species ) { + NaturalSpawnManager.registerSpawnPlacement( species ); + } + @SpecialMob.LanguageProvider public static String[] getTranslations( String langKey ) { return References.translations( langKey, "Zombie", @@ -297,7 +303,7 @@ public class _SpecialZombieEntity extends ZombieEntity implements IRangedAttackM /** @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); - Handled in super + return super.getStandingEyeHeight( pose, size ) * getSpecialData().getHeightScale(); // Age handled in super } /** @return Whether this entity is immune to fire damage. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java index e8e952e..86dfef2 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java @@ -10,6 +10,7 @@ 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.SpecialHurtByTargetGoal; +import fathertoast.specialmobs.common.event.NaturalSpawnManager; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import net.minecraft.block.BlockState; @@ -34,6 +35,7 @@ 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; @@ -41,6 +43,7 @@ import net.minecraft.world.IServerWorld; import net.minecraft.world.World; import javax.annotation.Nullable; +import java.util.Random; @SpecialMob public class _SpecialZombifiedPiglinEntity extends ZombifiedPiglinEntity implements IRangedAttackMob, ISpecialMob<_SpecialZombifiedPiglinEntity> { @@ -72,6 +75,18 @@ public class _SpecialZombifiedPiglinEntity extends ZombifiedPiglinEntity impleme @SpecialMob.AttributeSupplier public static AttributeModifierMap.MutableAttribute createAttributes() { return ZombifiedPiglinEntity.createAttributes(); } + @SpecialMob.SpawnPlacementRegistrar + public static void registerSpawnPlacement( MobFamily.Species species ) { + NaturalSpawnManager.registerSpawnPlacement( species, _SpecialZombifiedPiglinEntity::checkFamilySpawnRules ); + } + + public static boolean checkFamilySpawnRules( EntityType type, IServerWorld world, + SpawnReason reason, BlockPos pos, Random random ) { + //noinspection unchecked + return ZombifiedPiglinEntity.checkZombifiedPiglinSpawnRules( (EntityType) type, world, reason, pos, random ) && + NaturalSpawnManager.checkSpawnRulesConfigured( type, world, reason, pos, random ); + } + @SpecialMob.LanguageProvider public static String[] getTranslations( String langKey ) { return References.translations( langKey, "Zombified Piglin", @@ -280,7 +295,7 @@ public class _SpecialZombifiedPiglinEntity extends ZombifiedPiglinEntity impleme /** @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); - Handled in super + return super.getStandingEyeHeight( pose, size ) * getSpecialData().getHeightScale(); // Age handled in super } /** @return Whether this entity is immune to fire damage. */ diff --git a/src/main/java/fathertoast/specialmobs/common/event/BiomeEvents.java b/src/main/java/fathertoast/specialmobs/common/event/BiomeEvents.java deleted file mode 100644 index e02922b..0000000 --- a/src/main/java/fathertoast/specialmobs/common/event/BiomeEvents.java +++ /dev/null @@ -1,28 +0,0 @@ -package fathertoast.specialmobs.common.event; - -import net.minecraft.entity.EntityType; -import net.minecraft.world.biome.MobSpawnInfo; -import net.minecraftforge.common.world.MobSpawnInfoBuilder; -import net.minecraftforge.event.world.BiomeLoadingEvent; -import net.minecraftforge.eventbus.api.EventPriority; -import net.minecraftforge.eventbus.api.SubscribeEvent; - -public class BiomeEvents { - - @SubscribeEvent(priority = EventPriority.HIGH) - public void onBiomeLoad(BiomeLoadingEvent event) { - // Vanilla biome weirdness. Registry name can be null - if (event.getName() == null) - return; - - // TODO - This is the place to add spawns to biomes if needed, rather than replacing - - //MobSpawnInfoBuilder spawnInfoBuilder = event.getSpawns(); - - //for (EntityType entityType : spawnInfoBuilder.getEntityTypes()) { - // if (Thing.hasMobVariant(entityType)) { - // spawnInfoBuilder.addSpawn(entityType.getCategory(), new MobSpawnInfo.Spawners(woah, ohMan, aaaugh, HAAAAUGGHHH)); - // } - //} - } -} diff --git a/src/main/java/fathertoast/specialmobs/common/event/NaturalSpawnManager.java b/src/main/java/fathertoast/specialmobs/common/event/NaturalSpawnManager.java new file mode 100644 index 0000000..42168af --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/event/NaturalSpawnManager.java @@ -0,0 +1,143 @@ +package fathertoast.specialmobs.common.event; + +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.config.Config; +import net.minecraft.entity.*; +import net.minecraft.entity.monster.CaveSpiderEntity; +import net.minecraft.entity.monster.MonsterEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IServerWorld; +import net.minecraft.world.World; +import net.minecraft.world.biome.MobSpawnInfo; +import net.minecraft.world.gen.Heightmap; +import net.minecraftforge.common.world.MobSpawnInfoBuilder; +import net.minecraftforge.event.world.BiomeLoadingEvent; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public final class NaturalSpawnManager { + + //--------------- Spawn Placement Registration ---------------- + + /** Sets the natural spawn placement rules for entity types. */ + public static void registerSpawnPlacements() { + // Bestiary-generated entities + for( MobFamily.Species species : MobFamily.getAllSpecies() ) { + species.registerSpawnPlacement(); + } + + // Additional entries + if( Config.MAIN.NATURAL_SPAWNING.caveSpiderSpawnMultiplier.get() > 0.0 ) { + try { + EntitySpawnPlacementRegistry.register( EntityType.CAVE_SPIDER, EntitySpawnPlacementRegistry.PlacementType.ON_GROUND, + Heightmap.Type.MOTION_BLOCKING_NO_LEAVES, NaturalSpawnManager::checkSpawnRulesCaveSpider ); + } + catch( IllegalStateException ex ) { + // Overwriting the vanilla entry with our own throws this exception, but we can just ignore it :^) + } + } + } + + public static void registerSpawnPlacement( MobFamily.Species species ) { + registerSpawnPlacement( species, EntitySpawnPlacementRegistry.PlacementType.ON_GROUND ); + } + + public static void registerSpawnPlacement( MobFamily.Species species, + EntitySpawnPlacementRegistry.PlacementType type ) { + registerSpawnPlacement( species, type, NaturalSpawnManager::checkSpawnRulesDefault ); + } + + public static void registerSpawnPlacement( MobFamily.Species species, + EntitySpawnPlacementRegistry.IPlacementPredicate predicate ) { + registerSpawnPlacement( species, EntitySpawnPlacementRegistry.PlacementType.ON_GROUND, predicate ); + } + + public static void registerSpawnPlacement( MobFamily.Species species, + EntitySpawnPlacementRegistry.PlacementType type, + EntitySpawnPlacementRegistry.IPlacementPredicate predicate ) { + EntitySpawnPlacementRegistry.register( species.entityType.get(), type, Heightmap.Type.MOTION_BLOCKING_NO_LEAVES, predicate ); + } + + public static boolean checkSpawnRulesDefault( EntityType type, IServerWorld world, + SpawnReason reason, BlockPos pos, Random random ) { + return MonsterEntity.checkMonsterSpawnRules( type, world, reason, pos, random ) && + checkSpawnRulesConfigured( type, world, reason, pos, random ); + } + + public static boolean checkSpawnRulesIgnoreLight( EntityType type, IServerWorld world, + SpawnReason reason, BlockPos pos, Random random ) { + return MonsterEntity.checkAnyLightMonsterSpawnRules( type, world, reason, pos, random ) && + checkSpawnRulesConfigured( type, world, reason, pos, random ); + } + + public static boolean checkSpawnRulesConfigured( EntityType type, IServerWorld world, + SpawnReason reason, BlockPos pos, Random random ) { + if( reason == SpawnReason.NATURAL ) { + final MobFamily.Species species = MobFamily.Species.of( type ); + if( species != null && world instanceof World ) { + return species.config.GENERAL.naturalSpawnChance.rollChance( random, (World) world, pos ); + } + } + return true; + } + + public static boolean checkSpawnRulesCaveSpider( EntityType type, IServerWorld world, + SpawnReason reason, BlockPos pos, Random random ) { + if( reason == SpawnReason.NATURAL && world instanceof World && + !Config.MAIN.NATURAL_SPAWNING.caveSpiderSpawnChance.rollChance( random, (World) world, pos ) ) { + return false; + } + return MonsterEntity.checkMonsterSpawnRules( type, world, reason, pos, random ); + } + + + //--------------- Added Natural Spawns ---------------- + + public static void onBiomeLoad( BiomeLoadingEvent event ) { + final MobSpawnInfoBuilder spawnInfoBuilder = event.getSpawns(); + + if( Config.MAIN.NATURAL_SPAWNING.caveSpiderSpawnMultiplier.get() > 0.0 ) { + addCaveSpiderSpawns( spawnInfoBuilder ); + } + } + + /** Adds cave spiders to the spawn list by copying regular spider spawn entries. Does nothing if cave spiders are already added. */ + private static void addCaveSpiderSpawns( MobSpawnInfoBuilder builder ) { + // First, we figure out what needs to be added and make sure cave spiders do not already naturally spawn + final List spiderSpawners = new ArrayList<>(); + final List spawners = builder.getSpawner( EntityClassification.MONSTER ); + for( MobSpawnInfo.Spawners spawner : spawners ) { + if( spawner.type == EntityType.CAVE_SPIDER && spawner.weight > 0 ) return; + if( spawner.type == EntityType.SPIDER && spawner.weight > 0 ) spiderSpawners.add( spawner ); + } + + // Then, we actually add any needed spawns + if( !spiderSpawners.isEmpty() ) { + for( MobSpawnInfo.Spawners spawner : spiderSpawners ) { + builder.addSpawn( EntityClassification.MONSTER, new MobSpawnInfo.Spawners( EntityType.CAVE_SPIDER, + Math.max( 1, (int) (spawner.weight * Config.MAIN.NATURAL_SPAWNING.caveSpiderSpawnMultiplier.get()) ), + spawner.minCount, spawner.maxCount ) ); + } + + final MobSpawnInfo.SpawnCosts spiderCost = builder.getCost( EntityType.SPIDER ); + if( spiderCost != null ) { + // Just leave them the same as a regular spider, config can be added if anyone actually wants it + builder.addMobCharge( EntityType.CAVE_SPIDER, spiderCost.getCharge(), spiderCost.getEnergyBudget() ); + } + } + } + + private static void addSpawn( MobSpawnInfoBuilder builder, EntityType entity, int weight ) { + builder.addSpawn( entity.getCategory(), new MobSpawnInfo.Spawners( entity, weight, 4, 4 ) ); + } + + private static void addSpawn( MobSpawnInfoBuilder builder, EntityType entity, int weight, int maxCount ) { + builder.addSpawn( entity.getCategory(), new MobSpawnInfo.Spawners( entity, weight, 1, maxCount ) ); + } + + private static void addSpawn( MobSpawnInfoBuilder builder, EntityType entity, int weight, int minCount, int maxCount ) { + builder.addSpawn( entity.getCategory(), new MobSpawnInfo.Spawners( entity, weight, minCount, maxCount ) ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/util/AnnotationHelper.java b/src/main/java/fathertoast/specialmobs/common/util/AnnotationHelper.java index 163f5f6..eb77e94 100644 --- a/src/main/java/fathertoast/specialmobs/common/util/AnnotationHelper.java +++ b/src/main/java/fathertoast/specialmobs/common/util/AnnotationHelper.java @@ -82,6 +82,16 @@ public final class AnnotationHelper { } } + /** Registers the entity spawn placement for a special mob species. Throws an exception if anything goes wrong. */ + public static void registerSpawnPlacement( MobFamily.Species species ) { + try { + getMethodOrSuper( species.entityClass, SpecialMob.SpawnPlacementRegistrar.class ).invoke( null, species ); + } + catch( NoSuchMethodException | InvocationTargetException | IllegalAccessException ex ) { + throw new RuntimeException( "Entity class for " + species.name + " has invalid spawn placement registration method", ex ); + } + } + /** Gets the translations from a special mob species. Throws an exception if anything goes wrong. */ public static String[] getTranslations( MobFamily.Species species ) { try { From e5b551b34c317dd90d9986d704d151a31bec4cb8 Mon Sep 17 00:00:00 2001 From: FatherToast Date: Sun, 14 Aug 2022 17:20:21 -0500 Subject: [PATCH 07/10] More better config doc --- .../common/config/file/ToastConfigSpec.java | 183 ++++++++++++++---- 1 file changed, 141 insertions(+), 42 deletions(-) diff --git a/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java b/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java index a37df7a..0560e8a 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java +++ b/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java @@ -21,8 +21,8 @@ import java.util.Objects; /** * A config spec maps read and write functions to the runtime variables used to hold them. *

- * 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 { @@ -199,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. */ @@ -245,18 +243,16 @@ public class ToastConfigSpec { /** The appendix comment. */ private final List COMMENT; - /** Create a new header action that will insert the opening file comment. */ - private AppendixHeader( List comment ) { - COMMENT = comment; - } + /** Create a new appendix header action that will insert a closing file comment. */ + private AppendixHeader( List 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 ); @@ -283,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 COMMENT; + + /** Create a new subcategory action that will insert the subcategory comment. */ + private Subcategory( String subcategoryName, List 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 ); } } @@ -350,13 +370,28 @@ 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. + *

+ * 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 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. + *

+ * 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. @@ -372,15 +407,26 @@ public class ToastConfigSpec { 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). + *

+ * 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 ); } @@ -390,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 comment ) { ACTIONS.add( new Comment( comment ) ); } - - /** @param comment The file comment to insert. */ - public void header( List 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 comment ) { ACTIONS.add( new Comment( comment ) ); } + + + /** + * Adds a subcategory header, optionally including a comment to describe/summarize the contents of the file. + *

+ * 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 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. */ From 9f150863636fec96484de538485c1ebf078a5a3a Mon Sep 17 00:00:00 2001 From: FatherToast Date: Sun, 14 Aug 2022 20:07:15 -0500 Subject: [PATCH 08/10] Finished natural spawns; Config improvements/fixes --- .../common/bestiary/BestiaryInfo.java | 6 +- .../common/bestiary/MobFamily.java | 13 ++ .../specialmobs/common/config/MainConfig.java | 91 +++++++++- .../config/field/AbstractConfigField.java | 1 + .../common/config/file/TomlHelper.java | 17 +- .../config/species/BlazeSpeciesConfig.java | 2 +- .../config/species/CreeperSpeciesConfig.java | 2 +- .../DesiccatedSilverfishSpeciesConfig.java | 2 +- .../config/species/DrownedSpeciesConfig.java | 2 +- .../species/HuskZombieSpeciesConfig.java | 2 +- .../MadScientistZombieSpeciesConfig.java | 2 +- .../species/MotherSpiderSpeciesConfig.java | 2 +- .../species/PotionSlimeSpeciesConfig.java | 2 +- .../species/QueenGhastSpeciesConfig.java | 2 +- .../species/SilverfishSpeciesConfig.java | 2 +- .../config/species/SkeletonSpeciesConfig.java | 2 +- .../common/config/species/SpeciesConfig.java | 31 ++-- .../config/species/SpiderSpeciesConfig.java | 2 +- .../SplittingCreeperSpeciesConfig.java | 2 +- .../species/UndeadWitchSpeciesConfig.java | 2 +- .../species/WebSpiderSpeciesConfig.java | 2 +- .../species/WildfireBlazeSpeciesConfig.java | 2 +- .../species/WildsWitchSpeciesConfig.java | 2 +- .../config/species/ZombieSpeciesConfig.java | 2 +- .../common/config/util/AttributeList.java | 1 - .../common/config/util/BlockList.java | 1 - .../common/config/util/EntityList.java | 1 - .../common/config/util/EnvironmentEntry.java | 29 ++- .../common/config/util/EnvironmentList.java | 1 - .../config/{field => util}/IStringArray.java | 2 +- .../common/config/util/RegistryEntryList.java | 1 - .../common/config/util/RestartNote.java | 8 +- .../DynamicRegistryEnvironment.java | 2 +- .../DynamicRegistryGroupEnvironment.java | 2 +- .../environment/biome/BiomeEnvironment.java | 2 +- .../biome/BiomeGroupEnvironment.java | 2 +- .../dimension/DimensionTypeEnvironment.java | 2 +- .../DimensionTypeGroupEnvironment.java | 2 +- .../specialmobs/common/core/SpecialMobs.java | 17 +- .../entity/creeper/DrowningCreeperEntity.java | 33 +++- .../magmacube/BouncingMagmaCubeEntity.java | 2 +- .../entity/slime/BlueberrySlimeEntity.java | 31 +++- .../GiantWitherSkeletonEntity.java | 2 +- .../SpitfireWitherSkeletonEntity.java | 2 +- .../GiantZombifiedPiglinEntity.java | 2 +- .../common/event/NaturalSpawnManager.java | 169 +++++++++++++++--- 46 files changed, 405 insertions(+), 104 deletions(-) rename src/main/java/fathertoast/specialmobs/common/config/{field => util}/IStringArray.java (83%) diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java b/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java index 006de12..c555e6e 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java @@ -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( diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java index 713204a..b3e4cc2 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java @@ -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; @@ -318,6 +319,18 @@ public class MobFamily { 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 ) { diff --git a/src/main/java/fathertoast/specialmobs/common/config/MainConfig.java b/src/main/java/fathertoast/specialmobs/common/config/MainConfig.java index 844741c..e3b6db7 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/MainConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/MainConfig.java @@ -3,6 +3,7 @@ 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; @@ -30,6 +31,7 @@ public class MainConfig extends Config.AbstractConfig { public static class General extends Config.AbstractCategory { public final BooleanField enableMobReplacement; + public final BooleanField enableNaturalSpawning; public final BooleanField masterVanillaReplacement; public final DoubleField masterRandomScaling; @@ -46,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(); @@ -76,16 +80,38 @@ public class MainConfig extends Config.AbstractConfig { 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 this is set to 0, this added cave spider spawn feature is completely disabled.", - "Finer tuning can be done with the spawn chances below." ), RestartNote.GAME ); + "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." ) ), @@ -97,7 +123,66 @@ public class MainConfig extends Config.AbstractConfig { "The chance for added cave spider natural spawn attempts to succeed when specific environmental conditions are met." ) ) ); - //SPEC.newLine(); + 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(); } } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/AbstractConfigField.java b/src/main/java/fathertoast/specialmobs/common/config/field/AbstractConfigField.java index bc90e2f..0321ced 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/AbstractConfigField.java +++ b/src/main/java/fathertoast/specialmobs/common/config/field/AbstractConfigField.java @@ -3,6 +3,7 @@ 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; diff --git a/src/main/java/fathertoast/specialmobs/common/config/file/TomlHelper.java b/src/main/java/fathertoast/specialmobs/common/config/file/TomlHelper.java index 53da30d..4d3a9e7 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/file/TomlHelper.java +++ b/src/main/java/fathertoast/specialmobs/common/config/file/TomlHelper.java @@ -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 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). */ diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/BlazeSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/BlazeSpeciesConfig.java index 6fb1adb..d811ed8 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/BlazeSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/BlazeSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/CreeperSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/CreeperSpeciesConfig.java index 4f247a3..3e8763e 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/CreeperSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/CreeperSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/DesiccatedSilverfishSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/DesiccatedSilverfishSpeciesConfig.java index 0fe038c..5545a1e 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/DesiccatedSilverfishSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/DesiccatedSilverfishSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/DrownedSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/DrownedSpeciesConfig.java index 94170e7..183b10d 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/DrownedSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/DrownedSpeciesConfig.java @@ -17,7 +17,7 @@ public class DrownedSpeciesConfig extends SpeciesConfig { public DrownedSpeciesConfig( MobFamily.Species species, double tridentChance, double shieldChance ) { super( species ); - DROWNED = new Drowned( SPEC, species, speciesName, tridentChance, shieldChance ); + DROWNED = new Drowned( SPEC, species, species.getConfigName(), tridentChance, shieldChance ); } public static class Drowned extends Config.AbstractCategory { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/HuskZombieSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/HuskZombieSpeciesConfig.java index a017cf1..0f54a2e 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/HuskZombieSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/HuskZombieSpeciesConfig.java @@ -17,7 +17,7 @@ public class HuskZombieSpeciesConfig extends ZombieSpeciesConfig { public HuskZombieSpeciesConfig( MobFamily.Species species, double bowChance, double shieldChance ) { super( species, bowChance, shieldChance ); - HUSK = new Husk( SPEC, species, speciesName ); + HUSK = new Husk( SPEC, species, species.getConfigName() ); } public static class Husk extends Config.AbstractCategory { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/MadScientistZombieSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/MadScientistZombieSpeciesConfig.java index 228ce89..59fecbb 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/MadScientistZombieSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/MadScientistZombieSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/MotherSpiderSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/MotherSpiderSpeciesConfig.java index bd74a12..2a89b5c 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/MotherSpiderSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/MotherSpiderSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/PotionSlimeSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/PotionSlimeSpeciesConfig.java index d192e2c..8ecba31 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/PotionSlimeSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/PotionSlimeSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/QueenGhastSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/QueenGhastSpeciesConfig.java index 0450a82..5908e50 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/QueenGhastSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/QueenGhastSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/SilverfishSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/SilverfishSpeciesConfig.java index c0a8a5c..72ac6a4 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/SilverfishSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/SilverfishSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/SkeletonSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/SkeletonSpeciesConfig.java index 7073d11..9eb6d19 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/SkeletonSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/SkeletonSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/SpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/SpeciesConfig.java index 479f7b9..6ab40ae 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/SpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/SpeciesConfig.java @@ -16,33 +16,25 @@ 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 { @@ -83,8 +75,7 @@ public class SpeciesConfig extends Config.AbstractConfig { 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", new EnvironmentList() - .setRange( DoubleField.Range.PERCENT ), + 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." ) ) ); @@ -151,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; + } } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/SpiderSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/SpiderSpeciesConfig.java index be69e97..b18ef77 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/SpiderSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/SpiderSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/SplittingCreeperSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/SplittingCreeperSpeciesConfig.java index ba6485d..983b7d8 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/SplittingCreeperSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/SplittingCreeperSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/UndeadWitchSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/UndeadWitchSpeciesConfig.java index e01521c..403875d 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/UndeadWitchSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/UndeadWitchSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/WebSpiderSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/WebSpiderSpeciesConfig.java index 6f733cf..b4a70fc 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/WebSpiderSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/WebSpiderSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/WildfireBlazeSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/WildfireBlazeSpeciesConfig.java index bb162f7..3a085ad 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/WildfireBlazeSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/WildfireBlazeSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/WildsWitchSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/WildsWitchSpeciesConfig.java index 5abe327..5208438 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/WildsWitchSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/WildsWitchSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/ZombieSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/ZombieSpeciesConfig.java index 5d36082..3790103 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/ZombieSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/ZombieSpeciesConfig.java @@ -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 { diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/AttributeList.java b/src/main/java/fathertoast/specialmobs/common/config/util/AttributeList.java index 1d8df71..1c5a822 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/AttributeList.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/AttributeList.java @@ -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; diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/BlockList.java b/src/main/java/fathertoast/specialmobs/common/config/util/BlockList.java index 2180d44..248390b 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/BlockList.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/BlockList.java @@ -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; diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/EntityList.java b/src/main/java/fathertoast/specialmobs/common/config/util/EntityList.java index b790b12..6be2fc8 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/EntityList.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/EntityList.java @@ -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; diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/EnvironmentEntry.java b/src/main/java/fathertoast/specialmobs/common/config/util/EnvironmentEntry.java index b73fa1d..79cf159 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/EnvironmentEntry.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/EnvironmentEntry.java @@ -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 ) { return in( new BiomeEnvironment( biome, false ) ); } + + /** Check if the biome is a specific one. */ + public Builder notInBiome( RegistryKey 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 ); } diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/EnvironmentList.java b/src/main/java/fathertoast/specialmobs/common/config/util/EnvironmentList.java index 1fc2de7..c8da48a 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/EnvironmentList.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/EnvironmentList.java @@ -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; diff --git a/src/main/java/fathertoast/specialmobs/common/config/field/IStringArray.java b/src/main/java/fathertoast/specialmobs/common/config/util/IStringArray.java similarity index 83% rename from src/main/java/fathertoast/specialmobs/common/config/field/IStringArray.java rename to src/main/java/fathertoast/specialmobs/common/config/util/IStringArray.java index cf84438..947f17c 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/field/IStringArray.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/IStringArray.java @@ -1,4 +1,4 @@ -package fathertoast.specialmobs.common.config.field; +package fathertoast.specialmobs.common.config.util; import java.util.List; diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/RegistryEntryList.java b/src/main/java/fathertoast/specialmobs/common/config/util/RegistryEntryList.java index e3c5613..147c5f7 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/RegistryEntryList.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/RegistryEntryList.java @@ -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; diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/RestartNote.java b/src/main/java/fathertoast/specialmobs/common/config/util/RestartNote.java index 4a038b8..c99476b 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/RestartNote.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/RestartNote.java @@ -5,8 +5,12 @@ 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 *" ), - GAME( " * Requires a 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 comment, @Nullable RestartNote note ) { diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/DynamicRegistryEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/DynamicRegistryEnvironment.java index f4e1bb4..57ebc0a 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/environment/DynamicRegistryEnvironment.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/DynamicRegistryEnvironment.java @@ -68,7 +68,7 @@ public abstract class DynamicRegistryEnvironment 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; diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/DynamicRegistryGroupEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/DynamicRegistryGroupEnvironment.java index b093904..44bf1a3 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/environment/DynamicRegistryGroupEnvironment.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/DynamicRegistryGroupEnvironment.java @@ -76,7 +76,7 @@ public abstract class DynamicRegistryGroupEnvironment 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 ); } diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeEnvironment.java index 9e3c704..f10f0f2 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeEnvironment.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeEnvironment.java @@ -13,7 +13,7 @@ import javax.annotation.Nullable; public class BiomeEnvironment extends DynamicRegistryEnvironment { - public BiomeEnvironment( RegistryKey biome, boolean invert ) { super( biome.getRegistryName(), invert ); } + public BiomeEnvironment( RegistryKey biome, boolean invert ) { super( biome.location(), invert ); } public BiomeEnvironment( AbstractConfigField field, String line ) { super( field, line ); } diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeGroupEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeGroupEnvironment.java index ede1a0f..efb9c2a 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeGroupEnvironment.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/biome/BiomeGroupEnvironment.java @@ -15,7 +15,7 @@ import java.util.List; public class BiomeGroupEnvironment extends DynamicRegistryGroupEnvironment { - public BiomeGroupEnvironment( RegistryKey biome, boolean invert ) { this( biome.getRegistryName(), invert ); } + public BiomeGroupEnvironment( RegistryKey biome, boolean invert ) { this( biome.location(), invert ); } public BiomeGroupEnvironment( ResourceLocation regKey, boolean invert ) { super( regKey, invert ); } diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionTypeEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionTypeEnvironment.java index e3eecb6..58d6a9f 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionTypeEnvironment.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionTypeEnvironment.java @@ -13,7 +13,7 @@ import javax.annotation.Nullable; public class DimensionTypeEnvironment extends DynamicRegistryEnvironment { - public DimensionTypeEnvironment( RegistryKey dimType, boolean invert ) { super( dimType.getRegistryName(), invert ); } + public DimensionTypeEnvironment( RegistryKey dimType, boolean invert ) { super( dimType.location(), invert ); } public DimensionTypeEnvironment( AbstractConfigField field, String line ) { super( field, line ); } diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionTypeGroupEnvironment.java b/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionTypeGroupEnvironment.java index 516dd03..abc3adc 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionTypeGroupEnvironment.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/environment/dimension/DimensionTypeGroupEnvironment.java @@ -15,7 +15,7 @@ import java.util.List; public class DimensionTypeGroupEnvironment extends DynamicRegistryGroupEnvironment { - public DimensionTypeGroupEnvironment( RegistryKey dimType, boolean invert ) { this( dimType.getRegistryName(), invert ); } + public DimensionTypeGroupEnvironment( RegistryKey dimType, boolean invert ) { this( dimType.location(), invert ); } public DimensionTypeGroupEnvironment( ResourceLocation regKey, boolean invert ) { super( regKey, invert ); } diff --git a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java index d8c87f3..e1f7fe3 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java +++ b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java @@ -39,10 +39,18 @@ 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) @@ -82,7 +90,6 @@ public class SpecialMobs { * - ranged attack AI (spitter) * - cave spiders * - ranged attack AI (spitter) - * + natural spawning * - silverfish * - ranged attack AI (spitter) * - endermen diff --git a/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java index 2f662ee..d5c2d55 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java @@ -3,6 +3,10 @@ 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; @@ -16,10 +20,7 @@ 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.EntitySpawnPlacementRegistry; -import net.minecraft.entity.EntityType; -import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.MoverType; +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; @@ -36,6 +37,9 @@ 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 implements IAmphibiousMob { @@ -53,9 +57,28 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmp .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 species ) { - NaturalSpawnManager.registerSpawnPlacement( species, EntitySpawnPlacementRegistry.PlacementType.IN_WATER ); + NaturalSpawnManager.registerSpawnPlacement( species, EntitySpawnPlacementRegistry.PlacementType.IN_WATER, + DrowningCreeperEntity::checkSpeciesSpawnRules ); + } + + public static boolean checkSpeciesSpawnRules( EntityType 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. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/magmacube/BouncingMagmaCubeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/magmacube/BouncingMagmaCubeEntity.java index bb8b965..86ad6fe 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/magmacube/BouncingMagmaCubeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/magmacube/BouncingMagmaCubeEntity.java @@ -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 ) diff --git a/src/main/java/fathertoast/specialmobs/common/entity/slime/BlueberrySlimeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/slime/BlueberrySlimeEntity.java index edd5da1..c1405c4 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/slime/BlueberrySlimeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/slime/BlueberrySlimeEntity.java @@ -3,6 +3,9 @@ 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.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.event.NaturalSpawnManager; import fathertoast.specialmobs.common.util.References; @@ -11,6 +14,7 @@ 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.item.Items; import net.minecraft.nbt.CompoundNBT; @@ -18,9 +22,14 @@ import net.minecraft.particles.IParticleData; import net.minecraft.particles.ParticleTypes; 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 { @@ -38,10 +47,30 @@ 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 species ) { NaturalSpawnManager.registerSpawnPlacement( species, EntitySpawnPlacementRegistry.PlacementType.IN_WATER, - _SpecialSlimeEntity::checkFamilySpawnRules ); + BlueberrySlimeEntity::checkSpeciesSpawnRules ); + } + + public static boolean checkSpeciesSpawnRules( EntityType 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. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/GiantWitherSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/GiantWitherSkeletonEntity.java index d1bf538..4aead19 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/GiantWitherSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/GiantWitherSkeletonEntity.java @@ -24,7 +24,7 @@ public class GiantWitherSkeletonEntity extends _SpecialWitherSkeletonEntity { @SpecialMob.BestiaryInfoSupplier public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) { - bestiaryInfo.color( 0x474D4D ).theme( BestiaryInfo.Theme.MOUNTAIN ) + bestiaryInfo.color( 0x474D4D ) .size( 1.8F, 0.95F, 3.6F ) .addExperience( 1 ) .addToAttribute( Attributes.MAX_HEALTH, 20.0 ) diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java index 21c5fe9..a9e10f8 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java @@ -26,7 +26,7 @@ public class SpitfireWitherSkeletonEntity extends _SpecialWitherSkeletonEntity { @SpecialMob.BestiaryInfoSupplier public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) { - bestiaryInfo.color( 0xDC1A00 ).weight( BestiaryInfo.DefaultWeight.LOW ).theme( BestiaryInfo.Theme.FIRE ) + bestiaryInfo.color( 0xDC1A00 ).weight( BestiaryInfo.DefaultWeight.LOW ) .uniqueTextureWithEyes() .size( 1.8F, 0.95F, 3.6F ) .addExperience( 2 ).waterSensitive().rangedDamage( 0.0 ) diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/GiantZombifiedPiglinEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/GiantZombifiedPiglinEntity.java index 356c895..0973b6b 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/GiantZombifiedPiglinEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/GiantZombifiedPiglinEntity.java @@ -24,7 +24,7 @@ public class GiantZombifiedPiglinEntity extends _SpecialZombifiedPiglinEntity { @SpecialMob.BestiaryInfoSupplier public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) { - bestiaryInfo.color( 0x4C7129 ).theme( BestiaryInfo.Theme.MOUNTAIN ) + bestiaryInfo.color( 0x4C7129 ) .size( 1.5F, 0.9F, 2.95F ) .addExperience( 1 ) .addToAttribute( Attributes.MAX_HEALTH, 20.0 ) diff --git a/src/main/java/fathertoast/specialmobs/common/event/NaturalSpawnManager.java b/src/main/java/fathertoast/specialmobs/common/event/NaturalSpawnManager.java index 42168af..6d162e7 100644 --- a/src/main/java/fathertoast/specialmobs/common/event/NaturalSpawnManager.java +++ b/src/main/java/fathertoast/specialmobs/common/event/NaturalSpawnManager.java @@ -2,17 +2,31 @@ package fathertoast.specialmobs.common.event; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.config.Config; +import fathertoast.specialmobs.common.entity.creeper.DrowningCreeperEntity; +import fathertoast.specialmobs.common.entity.creeper.EnderCreeperEntity; +import fathertoast.specialmobs.common.entity.creeper.FireCreeperEntity; +import fathertoast.specialmobs.common.entity.slime.BlueberrySlimeEntity; +import fathertoast.specialmobs.common.entity.spider.FireSpiderEntity; +import fathertoast.specialmobs.common.entity.zombie.FireZombieEntity; import net.minecraft.entity.*; import net.minecraft.entity.monster.CaveSpiderEntity; import net.minecraft.entity.monster.MonsterEntity; +import net.minecraft.tags.FluidTags; +import net.minecraft.util.RegistryKey; +import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.Difficulty; import net.minecraft.world.IServerWorld; import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.biome.Biomes; import net.minecraft.world.biome.MobSpawnInfo; import net.minecraft.world.gen.Heightmap; import net.minecraftforge.common.world.MobSpawnInfoBuilder; import net.minecraftforge.event.world.BiomeLoadingEvent; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -23,6 +37,8 @@ public final class NaturalSpawnManager { /** Sets the natural spawn placement rules for entity types. */ public static void registerSpawnPlacements() { + if( !Config.MAIN.GENERAL.enableNaturalSpawning.get() ) return; + // Bestiary-generated entities for( MobFamily.Species species : MobFamily.getAllSpecies() ) { species.registerSpawnPlacement(); @@ -72,6 +88,20 @@ public final class NaturalSpawnManager { checkSpawnRulesConfigured( type, world, reason, pos, random ); } + @SuppressWarnings( "unused" ) + public static boolean checkSpawnRulesBasic( EntityType type, IServerWorld world, + SpawnReason reason, BlockPos pos, Random random ) { + return world.getDifficulty() != Difficulty.PEACEFUL && MobEntity.checkMobSpawnRules( type, world, reason, pos, random ) && + checkSpawnRulesConfigured( type, world, reason, pos, random ); + } + + public static boolean checkSpawnRulesWater( EntityType type, IServerWorld world, + SpawnReason reason, BlockPos pos, Random random ) { + return world.getDifficulty() != Difficulty.PEACEFUL && MonsterEntity.isDarkEnoughToSpawn( world, pos, random ) && + (reason == SpawnReason.SPAWNER || world.getFluidState( pos ).is( FluidTags.WATER )) && + checkSpawnRulesConfigured( type, world, reason, pos, random ); + } + public static boolean checkSpawnRulesConfigured( EntityType type, IServerWorld world, SpawnReason reason, BlockPos pos, Random random ) { if( reason == SpawnReason.NATURAL ) { @@ -96,48 +126,129 @@ public final class NaturalSpawnManager { //--------------- Added Natural Spawns ---------------- public static void onBiomeLoad( BiomeLoadingEvent event ) { + if( !Config.MAIN.GENERAL.enableNaturalSpawning.get() ) return; + final MobSpawnInfoBuilder spawnInfoBuilder = event.getSpawns(); - if( Config.MAIN.NATURAL_SPAWNING.caveSpiderSpawnMultiplier.get() > 0.0 ) { - addCaveSpiderSpawns( spawnInfoBuilder ); + addCopiedSpawns( spawnInfoBuilder ); + addBiomeCategorySpawns( spawnInfoBuilder, event.getCategory(), event.getName() ); + } + + /** Adds enabled spawn-copier mobs to the spawn list. */ + private static void addCopiedSpawns( MobSpawnInfoBuilder builder ) { + addCopiedSpawns( builder, EntityType.SPIDER, EntityType.CAVE_SPIDER, + Config.MAIN.NATURAL_SPAWNING.caveSpiderSpawnMultiplier.get() ); + + addCopiedSpawns( builder, EntityType.ENDERMAN, EnderCreeperEntity.SPECIES.entityType.get(), + Config.MAIN.NATURAL_SPAWNING.enderCreeperSpawnMultiplier.get() ); + } + + /** Adds an entity type to the spawn list by copying another type's spawn entries. Does nothing if the entity type is already added. */ + private static void addCopiedSpawns( MobSpawnInfoBuilder builder, EntityType typeToCopy, EntityType typeToAdd, double multi ) { + if( multi <= 0.0 ) return; + + final List spawnersToCopy = new ArrayList<>(); + final List spawners = builder.getSpawner( EntityClassification.MONSTER ); + for( MobSpawnInfo.Spawners spawner : spawners ) { + if( spawner.type == typeToAdd && spawner.weight > 0 ) return; + if( spawner.type == typeToCopy && spawner.weight > 0 ) spawnersToCopy.add( spawner ); + } + + // Currently, we simply copy pack size and spawn costs directly; configs can be added later for these if needed + if( !spawnersToCopy.isEmpty() ) { + for( MobSpawnInfo.Spawners spawner : spawnersToCopy ) { + addSpawn( builder, typeToAdd, Math.max( 1, MathHelper.floor( spawner.weight * multi ) ), + spawner.minCount, spawner.maxCount ); + } + + final MobSpawnInfo.SpawnCosts costsToCopy = builder.getCost( typeToCopy ); + if( costsToCopy != null ) { + builder.addMobCharge( typeToAdd, costsToCopy.getCharge(), costsToCopy.getEnergyBudget() ); + } } } - /** Adds cave spiders to the spawn list by copying regular spider spawn entries. Does nothing if cave spiders are already added. */ - private static void addCaveSpiderSpawns( MobSpawnInfoBuilder builder ) { - // First, we figure out what needs to be added and make sure cave spiders do not already naturally spawn - final List spiderSpawners = new ArrayList<>(); - final List spawners = builder.getSpawner( EntityClassification.MONSTER ); - for( MobSpawnInfo.Spawners spawner : spawners ) { - if( spawner.type == EntityType.CAVE_SPIDER && spawner.weight > 0 ) return; - if( spawner.type == EntityType.SPIDER && spawner.weight > 0 ) spiderSpawners.add( spawner ); + /** Adds enabled biome-category-based mobs to the spawn list. */ + private static void addBiomeCategorySpawns( MobSpawnInfoBuilder builder, Biome.Category category, @Nullable ResourceLocation name ) { + switch( category ) { + case OCEAN: + addSpawn( builder, DrowningCreeperEntity.SPECIES.entityType.get(), + Config.MAIN.NATURAL_SPAWNING.drowningCreeperOceanWeight.get() ); + addSpawn( builder, BlueberrySlimeEntity.SPECIES.entityType.get(), + Config.MAIN.NATURAL_SPAWNING.blueberrySlimeOceanWeight.get() ); + break; + case RIVER: + addSpawn( builder, DrowningCreeperEntity.SPECIES.entityType.get(), + Config.MAIN.NATURAL_SPAWNING.drowningCreeperRiverWeight.get() ); + addSpawn( builder, BlueberrySlimeEntity.SPECIES.entityType.get(), + Config.MAIN.NATURAL_SPAWNING.blueberrySlimeRiverWeight.get() ); + break; + + case NETHER: + addNetherSpawns( builder, name ); + break; + } + } + + /** Adds enabled extra nether mobs to the spawn list. */ + private static void addNetherSpawns( MobSpawnInfoBuilder builder, @Nullable ResourceLocation name ) { + // Soul sand valley and warped forest biomes have unique spawn setups + if( isBiome( name, Biomes.WARPED_FOREST ) ) { + // Add warped variants here once they are created + return; + } + if( isBiome( name, Biomes.SOUL_SAND_VALLEY ) ) { + addSpawn( builder, EntityType.WITHER_SKELETON, + Config.MAIN.NATURAL_SPAWNING.witherSkeletonSoulSandValleyWeight.get(), 5, 5, + 0.7, 0.15 ); + return; } - // Then, we actually add any needed spawns - if( !spiderSpawners.isEmpty() ) { - for( MobSpawnInfo.Spawners spawner : spiderSpawners ) { - builder.addSpawn( EntityClassification.MONSTER, new MobSpawnInfo.Spawners( EntityType.CAVE_SPIDER, - Math.max( 1, (int) (spawner.weight * Config.MAIN.NATURAL_SPAWNING.caveSpiderSpawnMultiplier.get()) ), - spawner.minCount, spawner.maxCount ) ); - } - - final MobSpawnInfo.SpawnCosts spiderCost = builder.getCost( EntityType.SPIDER ); - if( spiderCost != null ) { - // Just leave them the same as a regular spider, config can be added if anyone actually wants it - builder.addMobCharge( EntityType.CAVE_SPIDER, spiderCost.getCharge(), spiderCost.getEnergyBudget() ); - } + // if( isBiome( name, Biomes.CRIMSON_FOREST ) ) { + // // Add crimson variants here once they are created + // // Do not return here - this biome has normal spawns! + // } + + addSpawn( builder, EntityType.WITHER_SKELETON, + Config.MAIN.NATURAL_SPAWNING.witherSkeletonNetherWeight.get(), 5, 5 ); + + if( isBiome( name, Biomes.BASALT_DELTAS ) ) { + addSpawn( builder, EntityType.BLAZE, + Config.MAIN.NATURAL_SPAWNING.blazeBasaltDeltasWeight.get(), 2, 3 ); } + else { + addSpawn( builder, EntityType.BLAZE, + Config.MAIN.NATURAL_SPAWNING.blazeNetherWeight.get(), 2, 3 ); + } + + addSpawn( builder, FireCreeperEntity.SPECIES.entityType.get(), + Config.MAIN.NATURAL_SPAWNING.fireCreeperNetherWeight.get(), 4, 4 ); + addSpawn( builder, FireZombieEntity.SPECIES.entityType.get(), + Config.MAIN.NATURAL_SPAWNING.fireZombieNetherWeight.get(), 4, 4 ); + addSpawn( builder, FireSpiderEntity.SPECIES.entityType.get(), + Config.MAIN.NATURAL_SPAWNING.fireSpiderNetherWeight.get(), 4, 4 ); + } + + /** @return True if the name represents a particular biome. */ + private static boolean isBiome( @Nullable ResourceLocation name, RegistryKey biome ) { + return biome.location().equals( name ); } private static void addSpawn( MobSpawnInfoBuilder builder, EntityType entity, int weight ) { - builder.addSpawn( entity.getCategory(), new MobSpawnInfo.Spawners( entity, weight, 4, 4 ) ); - } - - private static void addSpawn( MobSpawnInfoBuilder builder, EntityType entity, int weight, int maxCount ) { - builder.addSpawn( entity.getCategory(), new MobSpawnInfo.Spawners( entity, weight, 1, maxCount ) ); + addSpawn( builder, entity, weight, 1, 1 ); } private static void addSpawn( MobSpawnInfoBuilder builder, EntityType entity, int weight, int minCount, int maxCount ) { - builder.addSpawn( entity.getCategory(), new MobSpawnInfo.Spawners( entity, weight, minCount, maxCount ) ); + if( weight > 0 ) { + builder.addSpawn( entity.getCategory(), new MobSpawnInfo.Spawners( entity, weight, minCount, maxCount ) ); + } + } + + private static void addSpawn( MobSpawnInfoBuilder builder, EntityType entity, int weight, int minCount, int maxCount, + double charge, double budget ) { + if( weight > 0 ) { + builder.addSpawn( entity.getCategory(), new MobSpawnInfo.Spawners( entity, weight, minCount, maxCount ) ); + builder.addMobCharge( entity, charge, budget ); + } } } \ No newline at end of file From e6ab11cbab8d880006c679c8b49960749312f909 Mon Sep 17 00:00:00 2001 From: FatherToast Date: Mon, 15 Aug 2022 11:48:26 -0500 Subject: [PATCH 09/10] Ender creeper vibes --- .../specialmobs/client/ClientRegister.java | 3 ++ .../entity/species/EnderCreeperRenderer.java | 30 +++++++++++++++++++ .../entity/creeper/EnderCreeperEntity.java | 2 ++ 3 files changed, 35 insertions(+) create mode 100644 src/main/java/fathertoast/specialmobs/client/renderer/entity/species/EnderCreeperRenderer.java diff --git a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java index ad29efe..fc90ef0 100644 --- a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java +++ b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java @@ -8,6 +8,7 @@ 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; @@ -67,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 ); diff --git a/src/main/java/fathertoast/specialmobs/client/renderer/entity/species/EnderCreeperRenderer.java b/src/main/java/fathertoast/specialmobs/client/renderer/entity/species/EnderCreeperRenderer.java new file mode 100644 index 0000000..4578e81 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/client/renderer/entity/species/EnderCreeperRenderer.java @@ -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 ); + } + +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/creeper/EnderCreeperEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/creeper/EnderCreeperEntity.java index e699483..635a204 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/creeper/EnderCreeperEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/creeper/EnderCreeperEntity.java @@ -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 ); } From ec20e305be248ebf44e4c9a9f981498df5d4545e Mon Sep 17 00:00:00 2001 From: FatherToast Date: Mon, 15 Aug 2022 15:18:20 -0500 Subject: [PATCH 10/10] Abyssal drowned + Weight effect/potion pass --- .../common/bestiary/BestiaryInfo.java | 26 +++--- .../common/bestiary/MobFamily.java | 2 +- .../species/PotionSlimeSpeciesConfig.java | 2 +- .../config/util/LazyRegistryEntryList.java | 1 - .../specialmobs/common/core/SpecialMobs.java | 2 +- .../common/core/register/SMEffects.java | 2 + .../ai/goal/AmphibiousGoToWaterGoal.java | 9 +- .../ai/goal/AmphibiousMeleeAttackGoal.java | 9 +- .../cavespider/DesertCaveSpiderEntity.java | 2 +- .../cavespider/PaleCaveSpiderEntity.java | 2 +- .../entity/creeper/DrowningCreeperEntity.java | 2 +- .../entity/drowned/AbyssalDrownedEntity.java | 77 ++++++++++++++++++ .../silverfish/BlindingSilverfishEntity.java | 2 +- .../silverfish/PufferSilverfishEntity.java | 2 +- .../entity/skeleton/StraySkeletonEntity.java | 2 +- .../entity/spider/DesertSpiderEntity.java | 2 +- .../entity/spider/PaleSpiderEntity.java | 2 +- .../specialmobs/common/event/GameEvents.java | 14 ++++ .../common/potion/WeightEffect.java | 36 ++++++++ .../common/potion/package-info.java | 7 ++ .../datagen/SMLanguageProvider.java | 2 + .../textures/entity/drowned/abyssal.png | Bin 0 -> 640 bytes .../textures/entity/drowned/abyssal_eyes.png | Bin 0 -> 361 bytes .../entity/drowned/abyssal_overlay.png | Bin 0 -> 1161 bytes .../textures/mob_effect/weight.png | Bin 0 -> 285 bytes 25 files changed, 173 insertions(+), 32 deletions(-) create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/drowned/AbyssalDrownedEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/potion/WeightEffect.java create mode 100644 src/main/java/fathertoast/specialmobs/common/potion/package-info.java create mode 100644 src/main/resources/assets/specialmobs/textures/entity/drowned/abyssal.png create mode 100644 src/main/resources/assets/specialmobs/textures/entity/drowned/abyssal_eyes.png create mode 100644 src/main/resources/assets/specialmobs/textures/entity/drowned/abyssal_overlay.png create mode 100644 src/main/resources/assets/specialmobs/textures/mob_effect/weight.png diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java b/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java index c555e6e..b0764f3 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/BestiaryInfo.java @@ -152,7 +152,7 @@ public class BestiaryInfo { private BestiaryInfo( int eggColor, float scale, DefaultWeight weight, Theme spawnTheme, List 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; @@ -175,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; @@ -215,8 +215,8 @@ public class BestiaryInfo { private boolean isDamagedByWater; private boolean allowLeashing; private boolean ignorePressurePlates; - private final ArrayList immuneToStickyBlocks = new ArrayList<>(); - private final ArrayList immuneToPotions = new ArrayList<>(); + private final ArrayList immuneToStickyBlocks = new ArrayList<>(); + private final ArrayList immuneToPotions = new ArrayList<>(); private double rangedAttackDamage = -1.0; private double rangedAttackSpread = -1.0; private double rangedWalkSpeed = -1.0; @@ -272,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 ); } @@ -523,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}, {@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}, {@code ResourceLocation}, or {@code String}. + */ + public Builder effectImmune( Object... effects ) { immuneToPotions.addAll( Arrays.asList( effects ) ); return this; } diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java index b3e4cc2..d4db547 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java @@ -50,7 +50,7 @@ public class MobFamily { ); public static final MobFamily DROWNED = new MobFamily<>( FamilyConfig::new, "Drowned", "drowned", 0x8FF1D7, new EntityType[] { EntityType.DROWNED }, - "Brute", "Fishing", /*"Frozen",*/ "Giant", "Hungry", "Knight", "Plague"//, "Tropical" + "Abyssal", "Brute", "Fishing", /*"Frozen",*/ "Giant", "Hungry", "Knight", "Plague"//, "Tropical" ); //TODO Textures! - brute, hungry, plague, frozen, tropical public static final MobFamily ZOMBIFIED_PIGLIN = new MobFamily<>( FamilyConfig::new, "ZombifiedPiglin", "zombified piglins", 0xEA9393, new EntityType[] { EntityType.ZOMBIFIED_PIGLIN }, diff --git a/src/main/java/fathertoast/specialmobs/common/config/species/PotionSlimeSpeciesConfig.java b/src/main/java/fathertoast/specialmobs/common/config/species/PotionSlimeSpeciesConfig.java index 8ecba31..f3aba43 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/species/PotionSlimeSpeciesConfig.java +++ b/src/main/java/fathertoast/specialmobs/common/config/species/PotionSlimeSpeciesConfig.java @@ -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, diff --git a/src/main/java/fathertoast/specialmobs/common/config/util/LazyRegistryEntryList.java b/src/main/java/fathertoast/specialmobs/common/config/util/LazyRegistryEntryList.java index f7def70..1ce8f50 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/util/LazyRegistryEntryList.java +++ b/src/main/java/fathertoast/specialmobs/common/config/util/LazyRegistryEntryList.java @@ -17,7 +17,6 @@ import java.util.Set; *

* See also: {@link net.minecraftforge.registries.ForgeRegistries} */ -@SuppressWarnings( "unused" ) public class LazyRegistryEntryList> extends RegistryEntryList { /** The field containing this list. We save a reference to help improve error/warning reports. */ private final AbstractConfigField FIELD; diff --git a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java index e1f7fe3..ceb1322 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java +++ b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java @@ -53,7 +53,7 @@ public class SpecialMobs { * ? warped/crimson mobs * - potions * - vulnerability (opposite of resistance) - * ? gravity (opposite of levitation) + * - weight (opposite of levitation) * - entities * - nbt-driven capabilities (special mob data) * - fish hook diff --git a/src/main/java/fathertoast/specialmobs/common/core/register/SMEffects.java b/src/main/java/fathertoast/specialmobs/common/core/register/SMEffects.java index 74b6e6f..226b2f0 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/register/SMEffects.java +++ b/src/main/java/fathertoast/specialmobs/common/core/register/SMEffects.java @@ -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 REGISTRY = DeferredRegister.create( ForgeRegistries.POTIONS, SpecialMobs.MOD_ID ); public static final RegistryObject VULNERABILITY = register( "vulnerability", EffectType.HARMFUL, 0x96848D ); + public static final RegistryObject WEIGHT = register( "weight", () -> new WeightEffect( EffectType.HARMFUL, 0x353A6B ) ); /** Registers a simple effect to the deferred register. */ public static RegistryObject register( String name, EffectType type, int color ) { diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousGoToWaterGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousGoToWaterGoal.java index 201d53b..f4fa3c3 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousGoToWaterGoal.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousGoToWaterGoal.java @@ -1,6 +1,5 @@ package fathertoast.specialmobs.common.entity.ai.goal; -import fathertoast.specialmobs.common.entity.ai.IAmphibiousMob; import net.minecraft.block.Blocks; import net.minecraft.entity.MobEntity; import net.minecraft.entity.ai.goal.Goal; @@ -16,9 +15,9 @@ import java.util.Random; *

* {@link net.minecraft.entity.monster.DrownedEntity.GoToWaterGoal} */ -public class AmphibiousGoToWaterGoal extends Goal { +public class AmphibiousGoToWaterGoal extends Goal { - private final T mob; + private final MobEntity mob; private final double speedModifier; private boolean disableAtNight = true; @@ -27,14 +26,14 @@ public class AmphibiousGoToWaterGoal exten private double wantedY; private double wantedZ; - public AmphibiousGoToWaterGoal( T entity, double speed ) { + 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() { + public AmphibiousGoToWaterGoal alwaysEnabled() { disableAtNight = false; return this; } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousMeleeAttackGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousMeleeAttackGoal.java index 3815033..8a9789c 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousMeleeAttackGoal.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/AmphibiousMeleeAttackGoal.java @@ -1,6 +1,5 @@ package fathertoast.specialmobs.common.entity.ai.goal; -import fathertoast.specialmobs.common.entity.ai.IAmphibiousMob; import net.minecraft.entity.CreatureEntity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.ai.goal.MeleeAttackGoal; @@ -13,14 +12,14 @@ import javax.annotation.Nullable; *

* {@link net.minecraft.entity.monster.DrownedEntity.AttackGoal} */ -public class AmphibiousMeleeAttackGoal extends MeleeAttackGoal { +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( T entity, double speed, boolean longMemory ) { super( entity, speed, longMemory ); } + public AmphibiousMeleeAttackGoal( CreatureEntity entity, double speed, boolean longMemory ) { super( entity, speed, longMemory ); } /** @return Returns true if this AI can be activated. */ @Override @@ -32,9 +31,9 @@ public class AmphibiousMeleeAttackGoal extends ZombieAttackGoal { + // public static class Zombie extends ZombieAttackGoal { // - // public Zombie( T entity, double speed, boolean longMemory ) { super( entity, speed, longMemory ); } + // public Zombie( ZombieEntity entity, double speed, boolean longMemory ) { super( entity, speed, longMemory ); } // // /** @return Returns true if this AI can be activated. */ // @Override diff --git a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/DesertCaveSpiderEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/DesertCaveSpiderEntity.java index 2a1efa9..d89d317 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/DesertCaveSpiderEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/DesertCaveSpiderEntity.java @@ -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 ); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/PaleCaveSpiderEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/PaleCaveSpiderEntity.java index 7d74fa8..18cbe18 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/PaleCaveSpiderEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/PaleCaveSpiderEntity.java @@ -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 ); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java index d5c2d55..3fb757d 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/creeper/DrowningCreeperEntity.java @@ -127,7 +127,7 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmp @Override protected void registerVariantGoals() { AIHelper.removeGoals( goalSelector, SwimGoal.class ); - AIHelper.insertGoal( goalSelector, 5, new AmphibiousGoToWaterGoal<>( this, 1.0 ).alwaysEnabled() ); + AIHelper.insertGoal( goalSelector, 5, new AmphibiousGoToWaterGoal( this, 1.0 ).alwaysEnabled() ); AIHelper.insertGoal( goalSelector, 6, new AmphibiousSwimUpGoal<>( this, 1.0 ) ); AIHelper.replaceWaterAvoidingRandomWalking( this, 0.8 ); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/drowned/AbyssalDrownedEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/drowned/AbyssalDrownedEntity.java new file mode 100644 index 0000000..26a50ee --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/drowned/AbyssalDrownedEntity.java @@ -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 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 getVariantFactory() { return AbyssalDrownedEntity::new; } + + /** @return This entity's mob species. */ + @SpecialMob.SpeciesSupplier + @Override + public MobFamily.Species getSpecies() { return SPECIES; } + + + //--------------- Variant-Specific Implementations ---------------- + + public AbyssalDrownedEntity( EntityType 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 ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/BlindingSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/BlindingSilverfishEntity.java index 43cd177..cea6bae 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/BlindingSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/BlindingSilverfishEntity.java @@ -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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java index 34c8072..6a38786 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/PufferSilverfishEntity.java @@ -59,7 +59,7 @@ public class PufferSilverfishEntity extends AmphibiousSilverfishEntity { @Override protected void registerVariantGoals() { AIHelper.removeGoals( goalSelector, SwimGoal.class ); - AIHelper.insertGoal( goalSelector, 5, new AmphibiousGoToWaterGoal<>( this, 1.0 ).alwaysEnabled() ); + AIHelper.insertGoal( goalSelector, 5, new AmphibiousGoToWaterGoal( this, 1.0 ).alwaysEnabled() ); } /** Override to change the color of this entity's spit attack. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/StraySkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/StraySkeletonEntity.java index e10705d..ad14ce2 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/StraySkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/StraySkeletonEntity.java @@ -36,7 +36,7 @@ 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 diff --git a/src/main/java/fathertoast/specialmobs/common/entity/spider/DesertSpiderEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/spider/DesertSpiderEntity.java index 695fc73..6bd5d35 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/spider/DesertSpiderEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/spider/DesertSpiderEntity.java @@ -28,7 +28,7 @@ public class DesertSpiderEntity extends _SpecialSpiderEntity { bestiaryInfo.color( 0xE6DDAC ).theme( BestiaryInfo.Theme.DESERT ) .uniqueTextureWithEyes() .size( 0.8F, 0.95F, 0.7F ) - .addExperience( 2 ) + .addExperience( 2 ).effectImmune( Effects.MOVEMENT_SLOWDOWN, SMEffects.VULNERABILITY ) .addToAttribute( Attributes.MAX_HEALTH, 4.0 ); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/spider/PaleSpiderEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/spider/PaleSpiderEntity.java index 25a3d01..635f696 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/spider/PaleSpiderEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/spider/PaleSpiderEntity.java @@ -25,7 +25,7 @@ public class PaleSpiderEntity extends _SpecialSpiderEntity { 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 ); } diff --git a/src/main/java/fathertoast/specialmobs/common/event/GameEvents.java b/src/main/java/fathertoast/specialmobs/common/event/GameEvents.java index 791336c..7f6997f 100644 --- a/src/main/java/fathertoast/specialmobs/common/event/GameEvents.java +++ b/src/main/java/fathertoast/specialmobs/common/event/GameEvents.java @@ -3,11 +3,13 @@ package fathertoast.specialmobs.common.event; import fathertoast.specialmobs.common.core.register.SMEffects; import net.minecraft.potion.EffectInstance; import net.minecraft.util.DamageSource; +import net.minecraftforge.event.entity.living.LivingFallEvent; import net.minecraftforge.event.entity.living.LivingHurtEvent; import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.SubscribeEvent; public class GameEvents { + @SubscribeEvent( priority = EventPriority.NORMAL ) public void onLivingHurt( LivingHurtEvent event ) { if( event.getEntityLiving() != null && event.getSource() != DamageSource.OUT_OF_WORLD && !event.getSource().isBypassMagic() && @@ -20,4 +22,16 @@ public class GameEvents { event.setAmount( Math.max( event.getAmount() * (1.0F + 0.25F * (vulnerability.getAmplifier() + 1)), 0.0F ) ); } } + + @SubscribeEvent( priority = EventPriority.NORMAL ) + public void onLivingFall( LivingFallEvent event ) { + if( event.getEntityLiving() != null && event.getEntityLiving().hasEffect( SMEffects.WEIGHT.get() ) ) { + + final EffectInstance weight = event.getEntityLiving().getEffect( SMEffects.WEIGHT.get() ); + if( weight == null ) return; + + // Increase effective fall distance by ~33% per effect level + event.setDamageMultiplier( event.getDamageMultiplier() * (1.0F + 0.3334F * (weight.getAmplifier() + 1)) ); + } + } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/potion/WeightEffect.java b/src/main/java/fathertoast/specialmobs/common/potion/WeightEffect.java new file mode 100644 index 0000000..4fb32fc --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/potion/WeightEffect.java @@ -0,0 +1,36 @@ +package fathertoast.specialmobs.common.potion; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.potion.Effect; +import net.minecraft.potion.EffectType; +import net.minecraft.util.math.vector.Vector3d; + +/** + * This effect applies downward acceleration to the entity and multiplies fall distance by amplifier. + * Only functions when the entity is at least one block above a solid block so that it doesn't hinder basic movement. + *

+ * The pull effect is equivalent to one downward bubble column per effect level. + * + * @see net.minecraft.entity.Entity#onInsideBubbleColumn(boolean) + */ +public class WeightEffect extends Effect { + + public WeightEffect( EffectType type, int color ) { super( type, color ); } + + /** @return True if the duration tick should apply this effect. */ + public boolean isDurationEffectTick( int duration, int amplifier ) { return true; } + + /** Applies this effect to the entity. */ + public void applyEffectTick( LivingEntity entity, int amplifier ) { + // We only want to apply this if the entity is at least one block above solid ground + if( entity.level == null || entity.isOnGround() || + entity.level.getBlockState( entity.blockPosition().below() ).getMaterial().blocksMotion() || + entity instanceof PlayerEntity && ((PlayerEntity) entity).abilities.flying ) return; + + final Vector3d v = entity.getDeltaMovement(); + final double vTick = -0.03 * (amplifier + 1); + final double vLimit = 10.0 * vTick; + if( v.y > vLimit ) entity.setDeltaMovement( v.x, Math.max( vLimit, v.y + vTick ), v.z ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/potion/package-info.java b/src/main/java/fathertoast/specialmobs/common/potion/package-info.java new file mode 100644 index 0000000..628d791 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/potion/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package fathertoast.specialmobs.common.potion; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/datagen/SMLanguageProvider.java b/src/main/java/fathertoast/specialmobs/datagen/SMLanguageProvider.java index 16a2a00..1e1c481 100644 --- a/src/main/java/fathertoast/specialmobs/datagen/SMLanguageProvider.java +++ b/src/main/java/fathertoast/specialmobs/datagen/SMLanguageProvider.java @@ -81,6 +81,8 @@ public class SMLanguageProvider extends LanguageProvider { // Misc translationList.add( References.translations( SMEffects.VULNERABILITY.get().getDescriptionId(), "Vulnerability", "", "", "", "", "", "" ) ); //TODO + translationList.add( References.translations( SMEffects.WEIGHT.get().getDescriptionId(), "Weight", + "", "", "", "", "", "" ) ); //TODO TRANSLATIONS = translationList.toArray( new String[0][0] ); diff --git a/src/main/resources/assets/specialmobs/textures/entity/drowned/abyssal.png b/src/main/resources/assets/specialmobs/textures/entity/drowned/abyssal.png new file mode 100644 index 0000000000000000000000000000000000000000..7d839cbd6b65184d16698d905ace47837bd755c6 GIT binary patch literal 640 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`1E2l#}z8fcko+S@u@{_oIw#bMvyq!0hST>ZlQW9-Zw!~K$@gY!&ut$}K1 zKRlcTqE{-7)?r*2xE^0Oq za9(-yhWFwB_4%sqA6XvjdfuL%X5s9+L3QIkpJg+{3`UzL;#rFaRzMJprBroBfsN=Rv;A9ez!6Q7gP#|lwE0FtOZ%o?1mm3e+ymV4| z8WiP~*3bB&O0hw;gf-&OKM!#w-2=b3u`XcmztkGi$m2W5`()p-+F3HayUMMMcr;E2 z8R_kk?cFt>|H17*X007l7rcAUY~-!6^TXRcS8VxSoIEmd!;b6yJAQZ`=Kaae7%=^* z@yukwm#sH1Wb9D|d{NRr3K)+}$1BTYIXeFHo$PPq1LW8J<_Ws=Hmp*;oG!)8A~v zz4M=+*f9UwA-xJWbAJVKhU*tCFZ?||hcTVOlKGyw!+ptIpZ?^v@1&RfU(a}{&EuK{ za~tFA|6%1G*CbeKeltIt*k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`212l#}zI$Zwm(0avT-`}JU|AAts(@SOnDb|u8zu^Bs!0>w;UocRBv%n*= zn1O-sFbFdq&tH)O6uji=;usR){&rd;UyA~VgW%u)`F0<7Ob(H_m6vUPFD&@9c*42` zcb+&hdEB}2WPVnGm#E5hr52J#` zHFu5;MQ0BPHVDWFF~t@{I%bJlOnwS?>IShyNo_vkaqzgWqo)2LC3}SvC6>)V-!XW)`njxgN@xNA#EgMr literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/specialmobs/textures/entity/drowned/abyssal_overlay.png b/src/main/resources/assets/specialmobs/textures/entity/drowned/abyssal_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..d1ef9012be0287886afeb8fffb71f3d0e4ca51be GIT binary patch literal 1161 zcmV;41a|w0P)Px#1ZP1_K>z@;j|==^1poj5IZ#YgMO#yDC_ztGPGe75Qa(39TU>fDDmN}nS|T_^ zN<~gqPh3VmPengUNkdQ{DK8{1HX0)+EI2{{005C2b?5*9026dlPE-H?|NsC0|NsC0 z|NsC0|NsC00J44D2mk;832;bRa{vGi!vFvd!vV){sAK>D1G-5>K~z{r?U(D8+aL^t zfnW^m1`O~2u>A(Jxzw8`P5rlvx=_+xV2lqTI^TjY#KL1 z>>90&>(qNF#{2BE^>$4$o4nYS`-nC6KIWLG-a|3Ir<9m~Yfdh^o@`Fh!ztPbWPcip zN%53YOg?^metvvL7gO>cTR^gIr`|&`_)ppU1lO!z%FzRuyxoze-a|2Hq1mxJ_kND~ zBK3Zva3@;l45hpu#{4V2mKs@&Et=1qiuHj*;4PBH)1O0*{+0{X6fRdzIgI5m8HXJTt08loCumhCB*a)d9w_N~? z^9eODWftcJag#_R6=ECC(G>uM;NOUZIB>lwW^6)sSyc?-W|5g~3q|3TErczIId}tr zH~_#_7=>BwGjm%xxZUy$c7>3&`$THAh?t9>d{IAwAQhx^6hRkOymzcs9N{Ki0f>sF zthFZ!0H_*$IB&RDA;;_r>@#UmCVs1e?1Z3;{wZCB*acgO=Jcj*i#@~1Sf!jQ+Lf_` zg>nG5yLmn=`AM`vub0L<&#@&oV#Ao&6Ke*xH-X+zhIlP&hpRKMhV06K~G z`1v?G&FwyTD-Re5;Odc9*R)~I$2)nzfFsY%fkV9#0T8s`EX80ez(_^Ec0Zx}QY5WqkPf!1At z*i@EdDUSejdfGiSDt%wc)Lmkj=EM8URJ+I#KoB~^jFuk?zA2zfTm|$T0GcW2I5|2X z{=w2o8U%59ok;pq0A?T%q5QaFeV{wqX6T`Z5GB1G~mUKs7M+SzC z{oH>NS%G}f0G|+7NohSH5fx*zAQ$(XoZKcwWhfSvq7o(s0i&Z+6v59!tvY<6Rq z=*N4@hN-Hn>EPCxErQHXv^Ex;u$ZAaMbEm_XT_wYGj_h-w_xr2-TGDgH~eP2$j2Hb T#@yHpbP0o}tDnm{r-UW|^2lHK literal 0 HcmV?d00001