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