diff --git a/src/main/java/fathertoast/specialmobs/client/renderer/entity/NinjaSkeletonRenderer.java b/src/main/java/fathertoast/specialmobs/client/renderer/entity/NinjaSkeletonRenderer.java index ec8a401..53bd081 100644 --- a/src/main/java/fathertoast/specialmobs/client/renderer/entity/NinjaSkeletonRenderer.java +++ b/src/main/java/fathertoast/specialmobs/client/renderer/entity/NinjaSkeletonRenderer.java @@ -26,51 +26,35 @@ import javax.annotation.ParametersAreNonnullByDefault; @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault @OnlyIn( Dist.CLIENT ) -public class NinjaSkeletonRenderer extends SkeletonRenderer { - - private final float baseShadowRadius; +public class NinjaSkeletonRenderer extends SpecialSkeletonRenderer { + private final BlockRendererDispatcher blockRenderer; public NinjaSkeletonRenderer( EntityRendererManager rendererManager ) { super( rendererManager ); - baseShadowRadius = shadowRadius = shadowStrength = 0.0F; blockRenderer = Minecraft.getInstance().getBlockRenderer(); - addLayer( new SpecialMobEyesLayer<>( this ) ); - addLayer( new SpecialMobOverlayLayer<>( this, new SkeletonModel<>() ) ); } @Override - public void render( AbstractSkeletonEntity skeletonEntity, float f1, float f2, MatrixStack matrixStack, IRenderTypeBuffer buffer, int i ) { - BlockState disguiseState = ((INinja) skeletonEntity).getDisguiseBlock(); - - if( disguiseState == null ) { - super.render( skeletonEntity, f1, f2, matrixStack, buffer, i ); + public void render( AbstractSkeletonEntity entity, float rotation, float partialTicks, + MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight ) { + final BlockState disguiseBlock = ((INinja) entity).getHiddenDragon(); + if( disguiseBlock == null ) { + super.render( entity, rotation, partialTicks, matrixStack, buffer, packedLight ); } else { - renderBlockDisguise( (AbstractSkeletonEntity & INinja) skeletonEntity, disguiseState, f1, f2, matrixStack, buffer, i ); + shadowRadius = 0.0F; + renderBlockDisguise( entity, disguiseBlock, rotation, partialTicks, matrixStack, buffer, packedLight ); } } - private void renderBlockDisguise( T ninja, BlockState state, float f1, float f2, MatrixStack matrixStack, IRenderTypeBuffer buffer, int i ) { + private void renderBlockDisguise( AbstractSkeletonEntity ninja, BlockState block, float rotation, float partialTicks, + MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight ) { matrixStack.pushPose(); - matrixStack.translate( -0.5D, 0.0D, -0.5D ); - this.blockRenderer.renderBlock( state, matrixStack, buffer, i, OverlayTexture.NO_OVERLAY, EmptyModelData.INSTANCE ); + matrixStack.translate( -0.5, 0.0, -0.5 ); + blockRenderer.renderBlock( block, matrixStack, buffer, packedLight, OverlayTexture.NO_OVERLAY, EmptyModelData.INSTANCE ); matrixStack.popPose(); } - - @Override - public ResourceLocation getTextureLocation( AbstractSkeletonEntity entity ) { - return ((ISpecialMob) entity).getSpecialData().getTexture(); - } - - @Override - protected void scale( AbstractSkeletonEntity entity, MatrixStack matrixStack, float partialTick ) { - super.scale( entity, matrixStack, partialTick ); - - final float scale = ((ISpecialMob) entity).getSpecialData().getRenderScale(); - shadowRadius = baseShadowRadius * scale; - matrixStack.scale( scale, scale, scale ); - } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java index 1a5a25b..97d11d6 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java @@ -47,6 +47,7 @@ public class MobFamily { "Zombie", "zombies", 0x00AFAF, new EntityType[] { EntityType.ZOMBIE, EntityType.HUSK }, "Brute", "Fire", /*"Fishing",*/ "Giant", "Hungry", "Husk", "MadScientist", "Plague" ); + // TODO Drowned family and zombie transform mechanic public static final MobFamily ZOMBIFIED_PIGLIN = new MobFamily<>(//TODO figure out crossbows "ZombifiedPiglin", "zombified piglins", 0xEA9393, new EntityType[] { EntityType.ZOMBIFIED_PIGLIN }, "Brute", /*"Fishing",*/ "Giant", "Hungry", "Knight", "Plague", "Vampire" diff --git a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java index 0c521fc..72ccd92 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java +++ b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java @@ -52,10 +52,10 @@ public class SpecialMobs { * + transformations * - ranged attack AI (using bow) * - use shields - * + drowned - * o zombified piglins - * o ranged attack AI (using bow) - * o use shields + * + drowned + * - zombified piglins + * - ranged attack AI (using bow) + * - use shields * - skeletons * - use shields * - babies @@ -64,7 +64,7 @@ public class SpecialMobs { * - babies * - slimes * - smallest size can deal damage - * o magma cubes + * - magma cubes * - spiders * o ranged attack AI * - cave spiders @@ -84,6 +84,7 @@ public class SpecialMobs { * ? zoglins * ? endermites * ? guardians + * ? vortex * ? shulkers * ? phantoms * + the goat diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/INinja.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/INinja.java index fa97537..9520adb 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/INinja.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/INinja.java @@ -6,20 +6,20 @@ import javax.annotation.Nullable; /** * Monsters must implement this interface to use the ninja goal AI. - * This allows get and set methods for the disguise block and immobile state. + * This allows get and set methods for the disguise block and immovable state. */ public interface INinja { - /** @return Whether this ninja is currently immobile. */ - boolean isHiding(); + /** @return Whether this ninja is currently immovable. */ + boolean isCrouchingTiger(); /** Sets this ninja's immovable state. When activated, the entity is 'snapped' to the nearest block position. */ - void setHiding( boolean value ); + void setCrouchingTiger( boolean value ); /** @return The block being hidden (rendered) as, or null if not hiding. */ @Nullable - BlockState getDisguiseBlock(); + BlockState getHiddenDragon(); /** Sets the block being hidden (rendered) as, set to null to cancel hiding. */ - void setDisguiseBlock( @Nullable BlockState block ); + void setHiddenDragon( @Nullable BlockState block ); } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/NinjaGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/NinjaGoal.java new file mode 100644 index 0000000..5b59019 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/NinjaGoal.java @@ -0,0 +1,186 @@ +package fathertoast.specialmobs.common.entity.ai; + +import net.minecraft.block.BlockRenderType; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.entity.MobEntity; +import net.minecraft.entity.ai.goal.Goal; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.state.properties.BlockStateProperties; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Random; + +/** + * This AI goal tells the ninja when players are looking in its direction so it can 'freeze' in place and act like a block. + *

+ * It also contains static methods to help ninjas choose a block to disguise as. + */ +public class NinjaGoal extends Goal { + + private final T ninja; + + public NinjaGoal( T entity ) { + ninja = entity; + setFlags( EnumSet.of( Goal.Flag.MOVE, Flag.LOOK, Goal.Flag.JUMP ) ); + } + + /** @return Returns true if this AI can be activated. */ + @Override + public boolean canUse() { + if( ninja.getHiddenDragon() == null || ninja.getHiddenDragon().getRenderShape() != BlockRenderType.MODEL ) + return false; + + final List players = new ArrayList<>( ninja.level.players() ); + for( PlayerEntity player : players ) { + final float dX = (float) (ninja.getX() - player.getX()); + final float dZ = (float) (ninja.getZ() - player.getZ()); + final float angleFromPlayer = (float) Math.atan2( dX, -dZ ) * 180.0F / (float) Math.PI; + + if( Math.abs( angleFromPlayer - MathHelper.wrapDegrees( player.yHeadRot ) ) > 90.0F ) { + return true; // A player is looking in the ninja's general direction! + } + } + return false; + } + + /** Called when this AI is activated. */ + @Override + public void start() { + ninja.getNavigation().stop(); + ninja.clearFire(); + ninja.moveTo( Math.floor( ninja.getX() ) + 0.5, Math.floor( ninja.getY() ), Math.floor( ninja.getZ() ) + 0.5 ); + ninja.setDeltaMovement( 0.0, 0.0, 0.0 ); + + ninja.setCrouchingTiger( true ); + } + + /** @return Called each update while active and returns true if this AI can remain active. */ + @Override + public boolean canContinueToUse() { return canUse(); } + + /** @return Whether this AI can be interrupted by a lower priority AI while running. */ + @Override + public boolean isInterruptable() { return false; } + + /** Called when this AI is deactivated. */ + @Override + public void stop() { ninja.setCrouchingTiger( false ); } + + + //--------------- Static Disguise Helper ---------------- + + /** Finds a nearby block for the entity to hide as and flags it to start hiding. */ + public static BlockState pickDisguise( T entity ) { + final Random random = entity.getRandom(); + + // Options available regardless of position + switch( random.nextInt( 200 ) ) { + case 0: return Blocks.CHEST.defaultBlockState().setValue( BlockStateProperties.HORIZONTAL_FACING, + Direction.Plane.HORIZONTAL.getRandomDirection( random ) ); + case 1: return Blocks.OAK_LOG.defaultBlockState(); + case 2: return Blocks.SPONGE.defaultBlockState(); + case 3: return Blocks.DEAD_BUSH.defaultBlockState(); + case 4: return Blocks.OAK_LEAVES.defaultBlockState(); + case 5: return Blocks.BEE_NEST.defaultBlockState(); + case 6: return Blocks.CAKE.defaultBlockState(); + case 7: return Blocks.CRAFTING_TABLE.defaultBlockState(); + case 8: return Blocks.FURNACE.defaultBlockState(); + case 9: return Blocks.ANVIL.defaultBlockState(); + case 10: return Blocks.BREWING_STAND.defaultBlockState(); + } + + final BlockPos posUnderFeet = entity.blockPosition().below(); + final BlockState blockUnderFeet = entity.level.getBlockState( posUnderFeet ); + if( !blockUnderFeet.getBlock().isAir( blockUnderFeet, entity.level, posUnderFeet ) ) { + // Options available based on the block we are standing on + + if( blockUnderFeet.is( Blocks.STONE ) || blockUnderFeet.is( Blocks.STONE_BRICKS ) ) { + // Cave theme + switch( random.nextInt( 30 ) ) { + case 0: return blockUnderFeet; + case 1: return Blocks.MOSSY_COBBLESTONE.defaultBlockState(); + case 2: return Blocks.CLAY.defaultBlockState(); + case 3: return Blocks.GRAVEL.defaultBlockState(); + case 4: return Blocks.COAL_ORE.defaultBlockState(); + case 5: return Blocks.IRON_ORE.defaultBlockState(); + case 6: return Blocks.LAPIS_ORE.defaultBlockState(); + case 7: return Blocks.GOLD_ORE.defaultBlockState(); + case 8: return Blocks.REDSTONE_ORE.defaultBlockState(); + case 9: return Blocks.DIAMOND_ORE.defaultBlockState(); + case 10: return Blocks.EMERALD_ORE.defaultBlockState(); + } + } + else if( blockUnderFeet.is( Blocks.GRASS ) || blockUnderFeet.is( Blocks.DIRT ) || blockUnderFeet.is( Blocks.PODZOL ) ) { + // Field theme + switch( random.nextInt( 15 ) ) { + case 0: return blockUnderFeet; + case 1: return Blocks.OAK_LOG.defaultBlockState(); + case 2: return Blocks.OAK_LEAVES.defaultBlockState(); + case 3: return Blocks.PUMPKIN.defaultBlockState(); + case 4: return Blocks.MELON.defaultBlockState(); + case 5: return Blocks.TALL_GRASS.defaultBlockState(); + case 6: return Blocks.FERN.defaultBlockState(); + case 7: return Blocks.BEE_NEST.defaultBlockState(); + } + } + else if( blockUnderFeet.is( Blocks.SAND ) || blockUnderFeet.is( Blocks.RED_SAND ) || + blockUnderFeet.is( Blocks.SANDSTONE ) || blockUnderFeet.is( Blocks.RED_SANDSTONE ) ) { + // Desert theme + switch( random.nextInt( 5 ) ) { + case 0: return blockUnderFeet; + case 1: return Blocks.CACTUS.defaultBlockState(); + case 2: return Blocks.DEAD_BUSH.defaultBlockState(); + } + } + else if( blockUnderFeet.is( Blocks.NETHERRACK ) || blockUnderFeet.is( Blocks.NETHER_BRICKS ) || + blockUnderFeet.is( Blocks.SOUL_SAND ) || blockUnderFeet.is( Blocks.SOUL_SOIL ) || + blockUnderFeet.is( Blocks.CRIMSON_NYLIUM ) || blockUnderFeet.is( Blocks.WARPED_NYLIUM ) ) { + // Nether theme + switch( random.nextInt( 25 ) ) { + case 0: return blockUnderFeet; + case 1: return Blocks.GRAVEL.defaultBlockState(); + case 2: return Blocks.SOUL_SAND.defaultBlockState(); + case 3: return Blocks.GLOWSTONE.defaultBlockState(); + case 4: return Blocks.NETHER_QUARTZ_ORE.defaultBlockState(); + case 5: return Blocks.NETHER_GOLD_ORE.defaultBlockState(); + case 6: return Blocks.ANCIENT_DEBRIS.defaultBlockState(); + case 7: return Blocks.BROWN_MUSHROOM.defaultBlockState(); + case 8: return Blocks.RED_MUSHROOM.defaultBlockState(); + case 9: return Blocks.CRIMSON_STEM.defaultBlockState(); + case 10: return Blocks.WARPED_STEM.defaultBlockState(); + } + } + else if( blockUnderFeet.is( Blocks.END_STONE ) || blockUnderFeet.is( Blocks.END_STONE_BRICKS ) || + blockUnderFeet.is( Blocks.PURPUR_BLOCK ) ) { + // End theme + switch( random.nextInt( 10 ) ) { + case 0: return blockUnderFeet; + case 1: return Blocks.CHORUS_PLANT.defaultBlockState(); + case 2: return Blocks.PURPUR_PILLAR.defaultBlockState(); + case 3: return Blocks.END_ROD.defaultBlockState(); + } + } + } + + // Pick a random nearby render-able block + final BlockPos.Mutable randPos = new BlockPos.Mutable(); + for( int i = 0; i < 16; i++ ) { + randPos.set( + posUnderFeet.getX() + random.nextInt( 17 ) - 8, + posUnderFeet.getY() + random.nextInt( 5 ) - 2, + posUnderFeet.getZ() + random.nextInt( 17 ) - 8 ); + + final BlockState randBlock = entity.level.getBlockState( randPos ); + if( randBlock.getRenderShape() == BlockRenderType.MODEL ) return randBlock; + } + + // Hide as a log if none of the other options are chosen + return Blocks.OAK_LOG.defaultBlockState(); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/MotherCaveSpiderEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/MotherCaveSpiderEntity.java index 89e6b3d..d9468af 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/MotherCaveSpiderEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/MotherCaveSpiderEntity.java @@ -94,6 +94,7 @@ public class MotherCaveSpiderEntity extends _SpecialCaveSpiderEntity { if( extraBabies > 0 && amount > 1.0F && level instanceof IServerWorld && random.nextFloat() < 0.33F ) { extraBabies--; spawnBaby( 0.66F, null ); + spawnAnim(); playSound( SoundEvents.EGG_THROW, 1.0F, 2.0F / (random.nextFloat() * 0.4F + 0.8F) ); } return true; @@ -112,6 +113,7 @@ public class MotherCaveSpiderEntity extends _SpecialCaveSpiderEntity { for( int i = 0; i < babiesToSpawn; i++ ) { groupData = spawnBaby( 0.33F, groupData ); } + spawnAnim(); playSound( SoundEvents.EGG_THROW, 1.0F, 2.0F / (random.nextFloat() * 0.4F + 0.8F) ); } super.remove( keepData ); diff --git a/src/main/java/fathertoast/specialmobs/common/entity/creeper/SplittingCreeperEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/creeper/SplittingCreeperEntity.java index 189e489..b5c7c65 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/creeper/SplittingCreeperEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/creeper/SplittingCreeperEntity.java @@ -87,6 +87,7 @@ public class SplittingCreeperEntity extends _SpecialCreeperEntity { for( int i = 0; i < babiesToSpawn; i++ ) { groupData = spawnBaby( explosionPower / 3.0F, groupData ); } + spawnAnim(); playSound( SoundEvents.EGG_THROW, 1.0F, 2.0F / (random.nextFloat() * 0.4F + 0.8F) ); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/enderman/MirageEndermanEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/enderman/MirageEndermanEntity.java index f1ab276..932331b 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/enderman/MirageEndermanEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/enderman/MirageEndermanEntity.java @@ -89,6 +89,7 @@ public class MirageEndermanEntity extends _SpecialEndermanEntity { @Override public void tick() { if( isFake && tickCount >= 200 ) { + spawnAnim(); remove(); } super.tick(); diff --git a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/NinjaSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/NinjaSkeletonEntity.java index 385d563..f6cd93d 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/NinjaSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/NinjaSkeletonEntity.java @@ -3,7 +3,9 @@ package fathertoast.specialmobs.common.entity.skeleton; 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.INinja; +import fathertoast.specialmobs.common.entity.ai.NinjaGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; @@ -19,10 +21,13 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.datasync.DataParameter; import net.minecraft.network.datasync.DataSerializers; import net.minecraft.network.datasync.EntityDataManager; +import net.minecraft.potion.EffectInstance; +import net.minecraft.potion.Effects; import net.minecraft.util.ActionResultType; import net.minecraft.util.DamageSource; import net.minecraft.util.Hand; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.World; @@ -78,8 +83,7 @@ public class NinjaSkeletonEntity extends _SpecialSkeletonEntity implements INinj @Override protected void registerVariantGoals() { setRangedAI( 1.0, 10, 9.0F ); - - //TODO AIHelper.insertGoalReverse( goalSelector, getVariantAttackPriority() - 1, null ); + goalSelector.addGoal( -9, new NinjaGoal<>( this ) ); } /** Called during spawn finalization to set starting equipment. */ @@ -95,15 +99,13 @@ public class NinjaSkeletonEntity extends _SpecialSkeletonEntity implements INinj /** Override to apply effects when this entity hits a target with a melee attack. */ @Override - protected void onVariantAttack( Entity target ) { - revealTo( target ); - } + protected void onVariantAttack( Entity target ) { revealTo( target, true ); } /** @return Attempts to damage this entity; returns true if the hit was successful. */ @Override public boolean hurt( DamageSource source, float amount ) { if( super.hurt( source, amount ) ) { - revealTo( source.getEntity() ); + revealTo( source.getEntity(), false ); return true; } return false; @@ -113,21 +115,25 @@ public class NinjaSkeletonEntity extends _SpecialSkeletonEntity implements INinj @Override public ActionResultType mobInteract( PlayerEntity player, Hand hand ) { // Attack if the player tries to right click the "block" - if( !level.isClientSide() && getDisguiseBlock() != null ) revealTo( player ); + if( !level.isClientSide() && getHiddenDragon() != null ) revealTo( player, true ); return super.mobInteract( player, hand ); } + /** Called by the player when it touches this entity. */ + @Override + public void playerTouch( PlayerEntity player ) { + if( !level.isClientSide() && getHiddenDragon() != null && !player.isCreative() ) revealTo( player, true ); + super.playerTouch( player ); + } + /** Called each tick to update this entity. */ @Override public void tick() { - // TODO can this be moved to the ninja AI? if( !level.isClientSide() ) { if( canHide ) { - //EntityAINinja.startHiding( this ); TODO - this.setHiding( true ); - this.setDisguiseBlock( Blocks.DIRT.defaultBlockState() ); + setHiddenDragon( NinjaGoal.pickDisguise( this ) ); } - else if( onGround && getDisguiseBlock() == null && + else if( onGround && getHiddenDragon() == null && (getTarget() == null || getTarget() instanceof PlayerEntity && ((PlayerEntity) getTarget()).isCreative()) ) { canHide = true; } @@ -135,33 +141,48 @@ public class NinjaSkeletonEntity extends _SpecialSkeletonEntity implements INinj super.tick(); } - // // Moves this entity. - // @Override TODO - // public void move( MoverType type, double x, double y, double z ) { - // if( isHiding() && type != MoverType.PISTON ) { - // motionY = 0.0; - // } - // else { - // super.move( type, x, y, z ); - // } - // } + /** Moves this entity to a new position and rotation. */ + @Override + public void moveTo( double x, double y, double z, float yaw, float pitch ) { + if( !isCrouchingTiger() ) super.moveTo( x, y, z, yaw, pitch ); + } + + /** Sets this entity's movement. */ + @Override + public void setDeltaMovement( Vector3d vec ) { + if( !isCrouchingTiger() ) super.setDeltaMovement( vec ); + } /** Returns true if this entity should push and be pushed by other entities when colliding. */ @Override public boolean isPushable() { - return super.isPushable() && !isHiding(); + return super.isPushable() && !isCrouchingTiger(); } /** Sets this entity on fire for a specific duration. */ @Override public void setRemainingFireTicks( int ticks ) { - if( !isHiding() ) super.setRemainingFireTicks( ticks ); + if( !isCrouchingTiger() ) super.setRemainingFireTicks( ticks ); } /** Reveals this ninja and sets its target so that it doesn't immediately re-disguise itself. */ - public void revealTo( @Nullable Entity target ) { - setDisguiseBlock( null ); - if( target instanceof LivingEntity ) setTarget( (LivingEntity) target ); + public void revealTo( @Nullable Entity target, boolean ambush ) { + if( getHiddenDragon() == null ) return; + setHiddenDragon( null ); + + if( target instanceof LivingEntity && !(target instanceof PlayerEntity && ((PlayerEntity) target).isCreative()) ) { + final LivingEntity livingTarget = (LivingEntity) target; + setTarget( livingTarget ); + + if( ambush ) { + final int duration = MobHelper.getDebuffDuration( level.getDifficulty() ); + + livingTarget.addEffect( new EffectInstance( Effects.POISON, duration ) ); + livingTarget.addEffect( new EffectInstance( Effects.MOVEMENT_SLOWDOWN, duration ) ); + livingTarget.addEffect( new EffectInstance( Effects.BLINDNESS, duration ) ); + livingTarget.removeEffect( Effects.NIGHT_VISION ); // Prevent blind + night vision combo (black screen) + } + } } private static final ResourceLocation[] TEXTURES = { @@ -194,37 +215,31 @@ public class NinjaSkeletonEntity extends _SpecialSkeletonEntity implements INinj /** @return Whether this ninja is currently immobile. */ @Override - public boolean isHiding() { return getEntityData().get( IS_HIDING ); } + public boolean isCrouchingTiger() { return getEntityData().get( IS_HIDING ); } /** Sets this ninja's immovable state. When activated, the entity is 'snapped' to the nearest block position. */ @Override - public void setHiding( boolean value ) { - if( value != isHiding() ) { + public void setCrouchingTiger( boolean value ) { + if( value != isCrouchingTiger() ) { getEntityData().set( IS_HIDING, value ); - if( value ) { - clearFire(); - moveTo( Math.floor( getX() ) + 0.5, Math.floor( getY() ), Math.floor( getZ() ) + 0.5 ); - } } } /** @return The block being hidden (rendered) as, or null if not hiding. */ @Nullable @Override - public BlockState getDisguiseBlock() { + public BlockState getHiddenDragon() { if( isAlive() ) return getEntityData().get( HIDING_BLOCK ).orElse( null ); return null; } /** Sets the block being hidden (rendered) as, set to null to cancel hiding. */ @Override - public void setDisguiseBlock( @Nullable BlockState block ) { + public void setHiddenDragon( @Nullable BlockState block ) { getEntityData().set( HIDING_BLOCK, Optional.ofNullable( block ) ); canHide = false; // Smoke puff when emerging from disguise - if( block == null ) { - //spawnExplosionParticle(); TODO - } + if( block == null ) spawnAnim(); } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/spider/MotherSpiderEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/spider/MotherSpiderEntity.java index 7f3bedf..688de24 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/spider/MotherSpiderEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/spider/MotherSpiderEntity.java @@ -94,6 +94,7 @@ public class MotherSpiderEntity extends _SpecialSpiderEntity { if( extraBabies > 0 && amount > 1.0F && level instanceof IServerWorld && random.nextFloat() < 0.33F ) { extraBabies--; spawnBaby( 0.66F, null ); + spawnAnim(); playSound( SoundEvents.EGG_THROW, 1.0F, 2.0F / (random.nextFloat() * 0.4F + 0.8F) ); } return true; @@ -112,6 +113,7 @@ public class MotherSpiderEntity extends _SpecialSpiderEntity { for( int i = 0; i < babiesToSpawn; i++ ) { groupData = spawnBaby( 0.33F, groupData ); } + spawnAnim(); playSound( SoundEvents.EGG_THROW, 1.0F, 2.0F / (random.nextFloat() * 0.4F + 0.8F) ); } super.remove( keepData ); diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/NinjaWitherSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/NinjaWitherSkeletonEntity.java index dd13624..fa492e3 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/NinjaWitherSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/NinjaWitherSkeletonEntity.java @@ -3,7 +3,9 @@ package fathertoast.specialmobs.common.entity.witherskeleton; 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.INinja; +import fathertoast.specialmobs.common.entity.ai.NinjaGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; @@ -19,10 +21,13 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.datasync.DataParameter; import net.minecraft.network.datasync.DataSerializers; import net.minecraft.network.datasync.EntityDataManager; +import net.minecraft.potion.EffectInstance; +import net.minecraft.potion.Effects; import net.minecraft.util.ActionResultType; import net.minecraft.util.DamageSource; import net.minecraft.util.Hand; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.World; @@ -78,8 +83,7 @@ public class NinjaWitherSkeletonEntity extends _SpecialWitherSkeletonEntity impl @Override protected void registerVariantGoals() { setRangedAI( 1.0, 10, 9.0F ); - - //TODO AIHelper.insertGoalReverse( goalSelector, getVariantAttackPriority() - 1, null ); + goalSelector.addGoal( -9, new NinjaGoal<>( this ) ); } /** Called during spawn finalization to set starting equipment. */ @@ -95,15 +99,13 @@ public class NinjaWitherSkeletonEntity extends _SpecialWitherSkeletonEntity impl /** Override to apply effects when this entity hits a target with a melee attack. */ @Override - protected void onVariantAttack( Entity target ) { - revealTo( target ); - } + protected void onVariantAttack( Entity target ) { revealTo( target, true ); } /** @return Attempts to damage this entity; returns true if the hit was successful. */ @Override public boolean hurt( DamageSource source, float amount ) { if( super.hurt( source, amount ) ) { - revealTo( source.getEntity() ); + revealTo( source.getEntity(), false ); return true; } return false; @@ -113,19 +115,25 @@ public class NinjaWitherSkeletonEntity extends _SpecialWitherSkeletonEntity impl @Override public ActionResultType mobInteract( PlayerEntity player, Hand hand ) { // Attack if the player tries to right click the "block" - if( !level.isClientSide() && getDisguiseBlock() != null ) revealTo( player ); + if( !level.isClientSide() && getHiddenDragon() != null ) revealTo( player, true ); return super.mobInteract( player, hand ); } + /** Called by the player when it touches this entity. */ + @Override + public void playerTouch( PlayerEntity player ) { + if( !level.isClientSide() && getHiddenDragon() != null && !player.isCreative() ) revealTo( player, true ); + super.playerTouch( player ); + } + /** Called each tick to update this entity. */ @Override public void tick() { - // TODO can this be moved to the ninja AI? if( !level.isClientSide() ) { if( canHide ) { - //EntityAINinja.startHiding( this ); TODO + setHiddenDragon( NinjaGoal.pickDisguise( this ) ); } - else if( onGround && getDisguiseBlock() == null && + else if( onGround && getHiddenDragon() == null && (getTarget() == null || getTarget() instanceof PlayerEntity && ((PlayerEntity) getTarget()).isCreative()) ) { canHide = true; } @@ -133,31 +141,48 @@ public class NinjaWitherSkeletonEntity extends _SpecialWitherSkeletonEntity impl super.tick(); } - // // Moves this entity. - // @Override TODO - // public void move( MoverType type, double x, double y, double z ) { - // if( isHiding() && type != MoverType.PISTON ) { - // motionY = 0.0; - // } - // else { - // super.move( type, x, y, z ); - // } - // } - // - // // Returns true if this entity should push and be pushed by other entities when colliding. - // @Override - // public boolean canBePushed() { return !isHiding(); } + /** Moves this entity to a new position and rotation. */ + @Override + public void moveTo( double x, double y, double z, float yaw, float pitch ) { + if( !isCrouchingTiger() ) super.moveTo( x, y, z, yaw, pitch ); + } + + /** Sets this entity's movement. */ + @Override + public void setDeltaMovement( Vector3d vec ) { + if( !isCrouchingTiger() ) super.setDeltaMovement( vec ); + } + + /** Returns true if this entity should push and be pushed by other entities when colliding. */ + @Override + public boolean isPushable() { + return super.isPushable() && !isCrouchingTiger(); + } /** Sets this entity on fire for a specific duration. */ @Override public void setRemainingFireTicks( int ticks ) { - if( !isHiding() ) super.setRemainingFireTicks( ticks ); + if( !isCrouchingTiger() ) super.setRemainingFireTicks( ticks ); } /** Reveals this ninja and sets its target so that it doesn't immediately re-disguise itself. */ - public void revealTo( @Nullable Entity target ) { - setDisguiseBlock( null ); - if( target instanceof LivingEntity ) setTarget( (LivingEntity) target ); + public void revealTo( @Nullable Entity target, boolean ambush ) { + if( getHiddenDragon() == null ) return; + setHiddenDragon( null ); + + if( target instanceof LivingEntity && !(target instanceof PlayerEntity && ((PlayerEntity) target).isCreative()) ) { + final LivingEntity livingTarget = (LivingEntity) target; + setTarget( livingTarget ); + + if( ambush ) { + final int duration = MobHelper.getDebuffDuration( level.getDifficulty() ); + + livingTarget.addEffect( new EffectInstance( Effects.POISON, duration ) ); + livingTarget.addEffect( new EffectInstance( Effects.MOVEMENT_SLOWDOWN, duration ) ); + livingTarget.addEffect( new EffectInstance( Effects.BLINDNESS, duration ) ); + livingTarget.removeEffect( Effects.NIGHT_VISION ); // Prevent blind + night vision combo (black screen) + } + } } private static final ResourceLocation[] TEXTURES = { @@ -190,37 +215,31 @@ public class NinjaWitherSkeletonEntity extends _SpecialWitherSkeletonEntity impl /** @return Whether this ninja is currently immobile. */ @Override - public boolean isHiding() { return getEntityData().get( IS_HIDING ); } + public boolean isCrouchingTiger() { return getEntityData().get( IS_HIDING ); } /** Sets this ninja's immovable state. When activated, the entity is 'snapped' to the nearest block position. */ @Override - public void setHiding( boolean value ) { - if( value != isHiding() ) { + public void setCrouchingTiger( boolean value ) { + if( value != isCrouchingTiger() ) { getEntityData().set( IS_HIDING, value ); - if( value ) { - clearFire(); - moveTo( Math.floor( getX() ) + 0.5, Math.floor( getY() ), Math.floor( getZ() ) + 0.5 ); - } } } /** @return The block being hidden (rendered) as, or null if not hiding. */ @Nullable @Override - public BlockState getDisguiseBlock() { + public BlockState getHiddenDragon() { if( isAlive() ) return getEntityData().get( HIDING_BLOCK ).orElse( null ); return null; } /** Sets the block being hidden (rendered) as, set to null to cancel hiding. */ @Override - public void setDisguiseBlock( @Nullable BlockState block ) { + public void setHiddenDragon( @Nullable BlockState block ) { getEntityData().set( HIDING_BLOCK, Optional.ofNullable( block ) ); canHide = false; // Smoke puff when emerging from disguise - if( block == null ) { - //spawnExplosionParticle(); TODO - } + if( block == null ) spawnAnim(); } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/util/AnnotationHelper.java b/src/main/java/fathertoast/specialmobs/common/util/AnnotationHelper.java index e2729ac..1061a02 100644 --- a/src/main/java/fathertoast/specialmobs/common/util/AnnotationHelper.java +++ b/src/main/java/fathertoast/specialmobs/common/util/AnnotationHelper.java @@ -172,7 +172,7 @@ public final class AnnotationHelper { * @throws NoSuchMethodException if the constructor does not exist. */ private static Constructor getConstructor( Class type, Class annotation ) throws NoSuchMethodException { - for( Constructor constructor : type.getConstructors() ) { + for( Constructor constructor : type.getDeclaredConstructors() ) { if( constructor.isAnnotationPresent( annotation ) ) //noinspection unchecked return (Constructor) constructor; diff --git a/src/main/resources/assets/specialmobs/textures/entity/creeper/Skeleton.png b/src/main/resources/assets/specialmobs/textures/entity/creeper/Skeleton.png new file mode 100644 index 0000000..09863cc Binary files /dev/null and b/src/main/resources/assets/specialmobs/textures/entity/creeper/Skeleton.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/creeper/dark.png b/src/main/resources/assets/specialmobs/textures/entity/creeper/dark.png index 7d97db8..dedf549 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entity/creeper/dark.png and b/src/main/resources/assets/specialmobs/textures/entity/creeper/dark.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/creeper/dark_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/creeper/dark_eyes.png index 9c27ac0..58b7377 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entity/creeper/dark_eyes.png and b/src/main/resources/assets/specialmobs/textures/entity/creeper/dark_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/creeper/death.png b/src/main/resources/assets/specialmobs/textures/entity/creeper/death.png index 0de5927..a913856 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entity/creeper/death.png and b/src/main/resources/assets/specialmobs/textures/entity/creeper/death.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/creeper/death_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/creeper/death_eyes.png index 619d6de..f2c37bf 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entity/creeper/death_eyes.png and b/src/main/resources/assets/specialmobs/textures/entity/creeper/death_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/creeper/dirt.png b/src/main/resources/assets/specialmobs/textures/entity/creeper/dirt.png index 472271c..28b237f 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entity/creeper/dirt.png and b/src/main/resources/assets/specialmobs/textures/entity/creeper/dirt.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/creeper/doom.png b/src/main/resources/assets/specialmobs/textures/entity/creeper/doom.png index 1b6ec6c..5f6dff9 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entity/creeper/doom.png and b/src/main/resources/assets/specialmobs/textures/entity/creeper/doom.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/creeper/doom_overlay.png b/src/main/resources/assets/specialmobs/textures/entity/creeper/doom_overlay.png index ba7cde0..42aae81 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entity/creeper/doom_overlay.png and b/src/main/resources/assets/specialmobs/textures/entity/creeper/doom_overlay.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/Skeleton.png b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/Skeleton.png new file mode 100644 index 0000000..09863cc Binary files /dev/null and b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/Skeleton.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/dark.png b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/dark.png index 7d97db8..dedf549 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/dark.png and b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/dark.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/dark_eyes.png b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/dark_eyes.png index 9c27ac0..58b7377 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/dark_eyes.png and b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/dark_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/death.png b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/death.png new file mode 100644 index 0000000..a913856 Binary files /dev/null and b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/death.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/death_eyes.png b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/death_eyes.png new file mode 100644 index 0000000..f2c37bf Binary files /dev/null and b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/death_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/dirt.png b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/dirt.png new file mode 100644 index 0000000..28b237f Binary files /dev/null and b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/dirt.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/doom.png b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/doom.png new file mode 100644 index 0000000..5f6dff9 Binary files /dev/null and b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/doom.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/doom_overlay.png b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/doom_overlay.png new file mode 100644 index 0000000..42aae81 Binary files /dev/null and b/src/main/resources/assets/specialmobs/textures/entityNEW/creeper/doom_overlay.png differ