Husk -> Zombie conversion; plus lib stuff

This commit is contained in:
FatherToast 2022-08-11 18:27:13 -05:00
parent b9c51a20c2
commit daaeda3f6e
31 changed files with 361 additions and 29 deletions

View file

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

View file

@ -197,9 +197,14 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
/** Pick a new species from this family, based on the location. */
public Species<? extends T> nextVariant( World world, @Nullable BlockPos pos ) {
final Species<?> species = config.GENERAL.specialVariantList.next( world.random, world, pos );
return nextVariant( world, pos, null, vanillaReplacement );
}
/** Pick a new species from this family, based on the location. */
public Species<? extends T> nextVariant( World world, @Nullable BlockPos pos, @Nullable Function<Species<?>, Boolean> selector, Species<? extends T> fallback ) {
final Species<?> species = config.GENERAL.specialVariantList.next( world.random, world, pos, selector );
//noinspection unchecked
return species == null ? vanillaReplacement : (Species<? extends T>) species;
return species == null ? fallback : (Species<? extends T>) species;
}

View file

@ -10,6 +10,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
/**
* Represents a config field with a double value.
@ -214,12 +215,19 @@ public class DoubleField extends AbstractConfigField {
/** @return Returns a random item from this weighted list. Null if none of the items have a positive weight. */
@Nullable
public T next( Random random, World world, @Nullable BlockPos pos ) {
public T next( Random random, World world, @Nullable BlockPos pos ) { return next( random, world, pos, null ); }
/** @return Returns a random item from this weighted list. Null if none of the items have a positive weight. */
@Nullable
public T next( Random random, World world, @Nullable BlockPos pos, @Nullable Function<T, Boolean> selector ) {
// Due to the 'nebulous' nature of environment-based weights, we must recalculate weights for EVERY call
final double[] weights = new double[UNDERLYING_LIST.size()];
double targetWeight = 0.0;
for( int i = 0; i < weights.length; i++ ) {
targetWeight += weights[i] = UNDERLYING_LIST.get( i ).WEIGHT.get( world, pos );
final Entry<T> entry = UNDERLYING_LIST.get( i );
if( selector == null || selector.apply( entry.VALUE ) ) {
targetWeight += weights[i] = entry.WEIGHT.get( world, pos );
}
}
if( targetWeight <= 0.0 ) return null;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override

View file

@ -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 extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override

View file

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

View file

@ -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 extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override

View file

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

View file

@ -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 extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override

View file

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

View file

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

View file

@ -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 extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override

View file

@ -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 extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override

View file

@ -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 extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override

View file

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

View file

@ -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 extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override

View file

@ -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 extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override

View file

@ -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 extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override

View file

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

View file

@ -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 extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override

View file

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

View file

@ -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 extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override

View file

@ -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<MobFamily.Species<?>, 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 ----------------

View file

@ -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 extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override

View file

@ -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 extends MobEntity> T convertTo( EntityType<T> entityType, boolean keepEquipment ) {
final T replacement = super.convertTo( entityType, keepEquipment );
if( replacement instanceof ISpecialMob && level instanceof IServerWorld ) {
MobHelper.finalizeSpawn( replacement, (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ),
SpawnReason.CONVERSION, null );
}
return replacement;
}
/** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */
@Nullable
@Override

View file

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