Amphibious mobs :O

This commit is contained in:
FatherToast 2022-08-12 16:32:56 -05:00
parent 653a122408
commit 43fe9c89d4
17 changed files with 646 additions and 30 deletions

View file

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

View file

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

View file

@ -86,7 +86,7 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
public static final MobFamily<SilverfishEntity, SilverfishFamilyConfig> SILVERFISH = new MobFamily<>( SilverfishFamilyConfig::new,
"Silverfish", "silverfish", 0x6E6E6E, new EntityType[] { EntityType.SILVERFISH },
"Albino", "Blinding", "Desiccated", "Fire", "Fishing", "Flying", "Poison", "Puffer", "Tough"
);//TODO puffer
);
public static final MobFamily<EndermanEntity, FamilyConfig> ENDERMAN = new MobFamily<>( FamilyConfig::new,
"Enderman", "endermen", 0x161616, new EntityType[] { EntityType.ENDERMAN },

View file

@ -82,7 +82,6 @@ public class SpecialMobs {
* + natural spawning
* - silverfish
* - ranged attack AI (spitter)
* + puffer
* - endermen
* - witches
* - ability to equip held items (wonky)

View file

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

View file

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

View file

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

View file

@ -0,0 +1,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.
* <p>
* {@link net.minecraft.entity.monster.DrownedEntity.GoToWaterGoal}
*/
public class AmphibiousGoToWaterGoal<T extends MobEntity & IAmphibiousMob> 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<T> alwaysEnabled() {
disableAtNight = false;
return this;
}
/** @return Returns true if this AI can be activated. */
@Override
public boolean canUse() {
if( disableAtNight && !mob.level.isDay() || mob.isInWater() ) return false;
final Vector3d targetPos = findWaterPos();
if( targetPos == null ) return false;
wantedX = targetPos.x;
wantedY = targetPos.y;
wantedZ = targetPos.z;
return true;
}
/** @return Called each update while active and returns true if this AI can remain active. */
@Override
public boolean canContinueToUse() { return !mob.getNavigation().isDone(); }
/** Called when this AI is activated. */
@Override
public void start() { mob.getNavigation().moveTo( wantedX, wantedY, wantedZ, speedModifier ); }
/** @return A random nearby position of water, or null if none is found after a few tries. */
@Nullable
private Vector3d findWaterPos() {
final Random random = mob.getRandom();
final BlockPos origin = mob.blockPosition();
for( int i = 0; i < 10; i++ ) {
final BlockPos target = origin.offset(
random.nextInt( 20 ) - 10,
2 - random.nextInt( 8 ),
random.nextInt( 20 ) - 10 );
if( mob.level.getBlockState( target ).is( Blocks.WATER ) ) {
return Vector3d.atBottomCenterOf( target );
}
}
return null;
}
}

View file

@ -0,0 +1,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.
* <p>
* {@link net.minecraft.entity.monster.DrownedEntity.AttackGoal}
*/
public class AmphibiousMeleeAttackGoal<T extends CreatureEntity & IAmphibiousMob> 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<T extends ZombieEntity & IAmphibiousMob> 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() ); }
// }
}

View file

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

View file

@ -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<? extends _SpecialCreeperEntity> entityType, World world ) { super( entityType, world ); }
public DrowningCreeperEntity( EntityType<? extends _SpecialCreeperEntity> entityType, World world ) {
super( entityType, world );
moveControl = new AmphibiousMovementController<>( this );
waterNavigation = new SwimmerPathNavigator( this, world );
groundNavigation = new GroundPathNavigator( this, world );
maxUpStep = 1.0F;
setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() );
}
/** Override to change this entity's AI goals. */
@Override
protected void registerVariantGoals() {
AIHelper.removeGoals( goalSelector, SwimGoal.class );
AIHelper.insertGoal( goalSelector, 5, new AmphibiousGoToWaterGoal<>( this, 1.0 ).alwaysEnabled() );
AIHelper.insertGoal( goalSelector, 6, new AmphibiousSwimUpGoal<>( this, 1.0 ) );
AIHelper.replaceWaterAvoidingRandomWalking( this, 0.8 );
}
/** Override to change this creeper's explosion power multiplier. */
@Override
@ -92,7 +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; }
}

View file

@ -131,6 +131,8 @@ public class _SpecialDrownedEntity extends DrownedEntity implements ISpecialMob<
/** The parameter for special mob render scale. */
private static final DataParameter<Float> SCALE = EntityDataManager.defineId( _SpecialDrownedEntity.class, DataSerializers.FLOAT );
private boolean needsToBeDeeper;
public _SpecialDrownedEntity( EntityType<? extends _SpecialDrownedEntity> entityType, World world ) {
super( entityType, world );
recalculateAttackGoal();
@ -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 ----------------

View file

@ -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<? extends _SpecialSilverfishEntity> entityType, World world ) {
super( entityType, world );
moveControl = new AmphibiousMovementController<>( this );
waterNavigation = new SwimmerPathNavigator( this, world );
groundNavigation = new GroundPathNavigator( this, world );
maxUpStep = 1.0F;
setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() );
}
/** Loads data from this entity's base NBT compound that is specific to its subclass. */
@Override
public void readAdditionalSaveData( CompoundNBT tag ) {
super.readAdditionalSaveData( tag );
setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() );
}
//--------------- IAmphibiousMob Implementation ----------------
/** Called each tick to update this entity's swimming state. */
@Override
public void updateSwimming() {
if( !level.isClientSide ) {
if( isEffectiveAi() && isUnderWater() && shouldSwim() ) {
setNavigatorToSwim();
setSwimming( true );
}
else {
setNavigatorToGround();
setSwimming( false );
}
}
}
/** Moves this entity in the desired direction. Input magnitude of < 1 scales down movement speed. */
@Override
public void travel( Vector3d input ) {
if( isEffectiveAi() && isUnderWater() && shouldSwim() ) {
moveRelative( 0.01F, input );
move( MoverType.SELF, getDeltaMovement() );
setDeltaMovement( getDeltaMovement().scale( 0.9 ) );
}
else super.travel( input );
}
/** @return Water drag coefficient. */
@Override
protected float getWaterSlowDown() { return 0.9F; }
/** @return True if this mob should use its swimming navigator for its current goal. */
@Override
public boolean shouldSwim() {
if( swimmingUp ) return true;
final LivingEntity target = getTarget();
return target != null && target.isInWater();
}
/** Sets whether this mob should swim upward. */
@Override
public void setSwimmingUp( boolean value ) { swimmingUp = value; }
/** @return True if this mob should swim upward. */
@Override
public boolean isSwimmingUp() { return swimmingUp; }
/** Sets this mob's current navigator to swimming mode. */
@Override
public void setNavigatorToSwim() { navigation = waterNavigation; }
/** Sets this mob's current navigator to ground mode. */
@Override
public void setNavigatorToGround() { navigation = groundNavigation; }
}

View file

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

View file

@ -4,16 +4,19 @@ import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.ai.AIHelper;
import fathertoast.specialmobs.common.entity.ai.goal.AmphibiousGoToWaterGoal;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.goal.SwimGoal;
import net.minecraft.item.Items;
import net.minecraft.potion.Effects;
import net.minecraft.world.World;
@SpecialMob
public class PufferSilverfishEntity extends _SpecialSilverfishEntity {
public class PufferSilverfishEntity extends AmphibiousSilverfishEntity {
//--------------- Static Special Mob Hooks ----------------
@ -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<? extends _SpecialSilverfishEntity> entityType, World world ) { super( entityType, world ); }
//TODO swim behavior
/** Override to change this entity's AI goals. */
@Override
protected void registerVariantGoals() {
AIHelper.removeGoals( goalSelector, SwimGoal.class );
AIHelper.insertGoal( goalSelector, 5, new AmphibiousGoToWaterGoal<>( this, 1.0 ).alwaysEnabled() );
}
/** Override to change the color of this entity's spit attack. */
@Override

View file

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

View file

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