diff --git a/src/main/java/fathertoast/specialmobs/client/ClientEventHandler.java b/src/main/java/fathertoast/specialmobs/client/ClientEventHandler.java new file mode 100644 index 0000000..d63e104 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/client/ClientEventHandler.java @@ -0,0 +1,47 @@ +package fathertoast.specialmobs.client; + +import fathertoast.specialmobs.common.config.Config; +import fathertoast.specialmobs.common.core.SpecialMobs; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.util.Util; +import net.minecraft.util.text.StringTextComponent; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.GuiOpenEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.ExtensionPoint; +import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.common.Mod; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@Mod.EventBusSubscriber( value = Dist.CLIENT, modid = SpecialMobs.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE ) +public class ClientEventHandler { + + static void registerConfigGUIFactory() { + ModLoadingContext.get().registerExtensionPoint( ExtensionPoint.CONFIGGUIFACTORY, + () -> ClientEventHandler.OpenConfigFolderScreen::new ); + } + + @SubscribeEvent + public static void onGuiOpen( GuiOpenEvent event ) { + if( event.getGui() instanceof OpenConfigFolderScreen ) { + event.setCanceled( true ); + Util.getPlatform().openFile( Config.CONFIG_DIR ); + } + } + + /** + * This screen is effectively a redirect. It is opened when the "mod config" button is pressed with the goal of behaving + * like the "mods folder" button; i.e. just opens the appropriate folder. + */ + private static class OpenConfigFolderScreen extends Screen { + private OpenConfigFolderScreen( Minecraft game, Screen parent ) { + // We don't need to localize the name or do anything since the opening of this screen is always canceled + super( new StringTextComponent( "Opening mod config folder" ) ); + } + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java index 58e8008..4e3df63 100644 --- a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java +++ b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java @@ -23,6 +23,7 @@ public class ClientRegister { @SubscribeEvent public static void onClientSetup( FMLClientSetupEvent event ) { + ClientEventHandler.registerConfigGUIFactory(); registerEntityRenderers(); } @@ -39,7 +40,8 @@ public class ClientRegister { registerFamilyRenderers( MobFamily.CAVE_SPIDER, SpecialSpiderRenderer::new ); registerFamilyRenderers( MobFamily.SILVERFISH, SpecialSilverfishRenderer::new ); registerFamilyRenderers( MobFamily.ENDERMAN, SpecialEndermanRenderer::new ); - //registerFamilyRenderers( MobFamily.WITCH, SpecialWitchRenderer::new ); + registerFamilyRenderers( MobFamily.WITCH, SpecialWitchRenderer::new ); + registerFamilyRenderers( MobFamily.GHAST, SpecialGhastRenderer::new ); registerFamilyRenderers( MobFamily.BLAZE, SpecialBlazeRenderer::new ); // Species overrides diff --git a/src/main/java/fathertoast/specialmobs/client/renderer/entity/SpecialGhastRenderer.java b/src/main/java/fathertoast/specialmobs/client/renderer/entity/SpecialGhastRenderer.java new file mode 100644 index 0000000..ea724e9 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/client/renderer/entity/SpecialGhastRenderer.java @@ -0,0 +1,46 @@ +package fathertoast.specialmobs.client.renderer.entity; + +import com.mojang.blaze3d.matrix.MatrixStack; +import fathertoast.specialmobs.common.entity.ISpecialMob; +import fathertoast.specialmobs.common.entity.SpecialMobData; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.client.renderer.entity.EntityRendererManager; +import net.minecraft.client.renderer.entity.GhastRenderer; +import net.minecraft.entity.monster.GhastEntity; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@OnlyIn( Dist.CLIENT ) +public class SpecialGhastRenderer extends GhastRenderer { + + private final float baseShadowRadius; + + public SpecialGhastRenderer( EntityRendererManager rendererManager ) { + super( rendererManager ); + baseShadowRadius = shadowRadius; + // Would require some big changes to make glowing eyes animate with the base texture + //addLayer( new SpecialMobEyesLayer<>( this ) ); + // Model doesn't support size parameter - overlay texture is applied to the animation instead + //addLayer( new SpecialMobOverlayLayer<>( this, new GhastModel<>( 0.25F ) ) ); + } + + @Override + public ResourceLocation getTextureLocation( GhastEntity entity ) { + final SpecialMobData data = ((ISpecialMob) entity).getSpecialData(); + return entity.isCharging() && data.hasOverlayTexture() ? data.getTextureOverlay() : data.getTexture(); + } + + @Override + protected void scale( GhastEntity 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 1a6e869..97ff7c1 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java @@ -55,11 +55,11 @@ public class MobFamily { public static final MobFamily SKELETON = new MobFamily<>( "Skeleton", "skeletons", 0xC1C1C1, new EntityType[] { EntityType.SKELETON, EntityType.STRAY }, - "Brute", "Fire", "Gatling", "Giant", "Knight", "Ninja", "Poison", "Sniper", /*"Spitfire",*/ "Stray" + "Brute", "Fire", "Gatling", "Giant", "Knight", "Ninja", "Poison", "Sniper", "Spitfire", "Stray" ); public static final MobFamily WITHER_SKELETON = new MobFamily<>( "WitherSkeleton", "wither skeletons", 0x141414, new EntityType[] { EntityType.WITHER_SKELETON }, - "Brute", "Gatling", "Giant", "Knight", "Ninja", "Sniper"//, "Spitfire" + "Brute", "Gatling", "Giant", "Knight", "Ninja", "Sniper", "Spitfire" ); public static final MobFamily SLIME = new MobFamily<>( @@ -90,15 +90,15 @@ public class MobFamily { "Blinding", "Icy", "Lightning", "Mini", "Mirage", "Thief" ); - // public static final MobFamily WITCH = new MobFamily<>( - // "Witch", "witches", 0x340000, new EntityType[] { EntityType.WITCH }, - // "Domination"//, "Shadows", "Undead", "Wilds", "Wind"//Note - should wind be able to walk on water? - // ); + public static final MobFamily WITCH = new MobFamily<>( + "Witch", "witches", 0x340000, new EntityType[] { EntityType.WITCH }, + "Domination", "Shadows", "Undead", "Wilds", "Wind" + ); - // public static final MobFamily GHAST = new MobFamily<>( - // "Ghast", "ghasts", 0xF9F9F9, new EntityType[] { EntityType.GHAST }, - // "Baby", "Fighter", "King", "Queen", "Unholy" - // ); + public static final MobFamily GHAST = new MobFamily<>( + "Ghast", "ghasts", 0xF9F9F9, new EntityType[] { EntityType.GHAST }, + "Baby", "Fighter", "King", "Queen", "Unholy" + ); public static final MobFamily BLAZE = new MobFamily<>( "Blaze", "blazes", 0xF6B201, new EntityType[] { EntityType.BLAZE }, diff --git a/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java b/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java index a5aece4..bc80ecb 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java +++ b/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java @@ -2,12 +2,16 @@ package fathertoast.specialmobs.common.config.file; import com.electronwill.nightconfig.core.file.FileConfig; import com.electronwill.nightconfig.core.file.FileConfigBuilder; +import com.electronwill.nightconfig.core.file.FileWatcher; import com.electronwill.nightconfig.core.io.CharacterOutput; +import com.electronwill.nightconfig.core.io.ParsingException; +import com.electronwill.nightconfig.core.io.WritingException; import fathertoast.specialmobs.common.config.field.AbstractConfigField; import fathertoast.specialmobs.common.config.field.GenericField; import fathertoast.specialmobs.common.core.SpecialMobs; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -34,6 +38,8 @@ public class ToastConfigSpec { /** Used to make sure the file is always rewritten when the config is initialized. */ private boolean firstLoad; + /** True while this config spec is currently writing. */ + boolean writing; /** Creates a new config spec at a specified location with only the basic 'start of file' action. */ public ToastConfigSpec( File dir, String fileName ) { @@ -49,7 +55,7 @@ public class ToastConfigSpec { // Create the config file format final FileConfigBuilder builder = FileConfig.builder( new File( dir, fileName + ToastConfigFormat.FILE_EXT ), new ToastConfigFormat( this ) ); - builder.sync().autoreload(); + builder.sync();//.autoreload(); TODO test alternative reloading CONFIG_FILE = builder.build(); } @@ -57,7 +63,39 @@ public class ToastConfigSpec { public void initialize() { SpecialMobs.LOG.info( "First-time loading config file {}", CONFIG_FILE.getFile() ); firstLoad = true; - CONFIG_FILE.load(); + try { + CONFIG_FILE.load(); + } + catch( ParsingException ex ) { + SpecialMobs.LOG.error( "Failed first-time loading of config file {} - this is bad!", CONFIG_FILE.getFile() ); + } + + //TODO test alternative reloading + try { + FileWatcher.defaultInstance().addWatch( CONFIG_FILE.getFile(), this::onFileChanged ); + SpecialMobs.LOG.info( "Started watching config file {} for updates", CONFIG_FILE.getFile() ); + } + catch( IOException ex ) { + SpecialMobs.LOG.error( "Failed to watch config file {} - this file will NOT update in-game until restarted!", + CONFIG_FILE.getFile() ); + } + } + + /** Called when a change to the config file is detected. */ + public void onFileChanged() { + if( writing ) { + SpecialMobs.LOG.info( "Attempted to reload config file {} while it was still saving - this is probably okay", + CONFIG_FILE.getFile() ); + } + else { + try { + SpecialMobs.LOG.info( "Reloading config file {}", CONFIG_FILE.getFile() ); + CONFIG_FILE.load(); + } + catch( ParsingException ex ) { + SpecialMobs.LOG.error( "Failed to reload config file {}", CONFIG_FILE.getFile() ); + } + } } /** Called after the config is loaded to update cached values. */ @@ -70,7 +108,12 @@ public class ToastConfigSpec { // Only rewrite on first load or if one of the load actions requests it if( rewrite || firstLoad ) { firstLoad = false; - CONFIG_FILE.save(); + try { + CONFIG_FILE.save(); + } + catch( WritingException ex ) { + SpecialMobs.LOG.error( "Failed to save config file {}", CONFIG_FILE.getFile() ); + } } } @@ -209,7 +252,7 @@ public class ToastConfigSpec { /** Called when the config is saved. */ @Override - public final void write( ToastTomlWriter writer, CharacterOutput output ) {} // Read callback actions do not affect file writing + public final void write( ToastTomlWriter writer, CharacterOutput output ) { } // Read callback actions do not affect file writing } /** Represents a spec action that reads and writes to a field. */ @@ -294,15 +337,15 @@ public class ToastConfigSpec { /** @param comment The file comment to insert. */ public void header( List comment ) { ACTIONS.add( new Header( this, comment ) ); } - + /** Inserts a detailed description of how to use the given field. */ - public void verboseFieldDesc(GenericField field) { + public void verboseFieldDesc( GenericField field ) { final List description = field.verboseDescription(); - - if (description != null && !description.isEmpty()) - ACTIONS.add(new Comment(field.verboseDescription())); + + if( description != null && !description.isEmpty() ) + ACTIONS.add( new Comment( field.verboseDescription() ) ); } - + /** * @param name The category name. * @param comment The category comment to insert. diff --git a/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlParser.java b/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlParser.java index 9abc77e..8c396df 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlParser.java +++ b/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlParser.java @@ -18,7 +18,7 @@ public class ToastTomlParser implements ConfigParser { /** The actual parser. */ private final TomlParser WRAPPED_PARSER = new TomlParser(); - /** The config spec that drives this writer. */ + /** The config spec that drives this parser. */ private final ToastConfigSpec CONFIG_SPEC; ToastTomlParser( ToastConfigSpec spec ) { CONFIG_SPEC = spec; } diff --git a/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlWriter.java b/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlWriter.java index 378760b..be9c68d 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlWriter.java +++ b/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlWriter.java @@ -42,10 +42,12 @@ public class ToastTomlWriter implements ConfigWriter { */ @Override public void write( UnmodifiableConfig config, Writer writer ) { + CONFIG_SPEC.writing = true; SpecialMobs.LOG.debug( "Writing config file! ({}{})", CONFIG_SPEC.NAME, ToastConfigFormat.FILE_EXT ); CharacterOutput output = new WriterOutput( writer ); currentIndentLevel = 0; CONFIG_SPEC.write( this, output ); + CONFIG_SPEC.writing = false; } /** Increases the indent level by 1. */ diff --git a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java index 9b96e1b..908d07a 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java +++ b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java @@ -10,15 +10,11 @@ import fathertoast.specialmobs.common.network.PacketHandler; import net.minecraft.util.ResourceLocation; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.IEventBus; -import net.minecraftforge.fml.DeferredWorkQueue; import net.minecraftforge.fml.InterModComms; import net.minecraftforge.fml.ModList; -import net.minecraftforge.fml.ModLoader; import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.lifecycle.FMLConstructModEvent; import net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent; -import net.minecraftforge.fml.event.lifecycle.ParallelDispatchEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.registries.ForgeRegistryEntry; import org.apache.logging.log4j.LogManager; @@ -79,10 +75,11 @@ public class SpecialMobs { * ? ranged attack AI * + puffer * - endermen - * o witches - * o ability to equip held items - * o ghasts - * o melee attack AI + * - witches + * - ability to equip held items + * - uses splash speed instead of regular + * - ghasts + * - melee attack AI * - blazes * - melee attack AI * ? piglins @@ -127,12 +124,12 @@ public class SpecialMobs { MobFamily.initBestiary(); } - + // TODO - This could very well help out the config malformation issue // Only problem here is that this event is never fired apparently. // Perhaps DeferredWorkQueue.runLater() could work (ignore deprecation, simply marked for removal) - public void onParallelDispatch(FMLConstructModEvent event) { - event.enqueueWork(Config::initialize); + public void onParallelDispatch( FMLConstructModEvent event ) { + event.enqueueWork( Config::initialize ); } public void sendIMCMessages( InterModEnqueueEvent event ) { diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/AIHelper.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/AIHelper.java index d4aa3a5..13e0255 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/AIHelper.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/AIHelper.java @@ -1,8 +1,11 @@ package fathertoast.specialmobs.common.entity.ai; +import fathertoast.specialmobs.common.core.SpecialMobs; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialHurtByTargetGoal; import net.minecraft.entity.CreatureEntity; import net.minecraft.entity.ai.goal.*; +import javax.annotation.Nullable; import java.util.ArrayList; /** @@ -40,6 +43,26 @@ public final class AIHelper { } } + /** @return A goal with the specified priority; null if none are found. */ + @Nullable + public static Goal getGoal( GoalSelector ai, int priority ) { + for( PrioritizedGoal task : new ArrayList<>( ai.availableGoals ) ) { + if( task.getPriority() == priority ) return task.getGoal(); + } + SpecialMobs.LOG.warn( "Attempted to get '{}'-priority goal, but none exists!", priority ); + return null; + } + + /** @return A goal of the specified type; null if none are found. */ + @Nullable + public static Goal getGoal( GoalSelector ai, Class goalType ) { + for( PrioritizedGoal task : new ArrayList<>( ai.availableGoals ) ) { + if( task.getGoal().getClass().equals( goalType ) ) return task.getGoal(); + } + SpecialMobs.LOG.warn( "Attempted to get '{}' goal, but none exists!", goalType.getSimpleName() ); + return null; + } + /** Replaces the entity's water avoiding random walking goal with an equivalent non-water-avoiding goal. */ public static void replaceWaterAvoidingRandomWalking( CreatureEntity entity, double speedModifier ) { for( PrioritizedGoal task : new ArrayList<>( entity.goalSelector.availableGoals ) ) { @@ -50,6 +73,7 @@ public final class AIHelper { return; } } + SpecialMobs.LOG.warn( "Attempted to replace random walking goal for {}, but none exists!", entity.getClass().getSimpleName() ); } /** Replaces the entity's hurt by target goal with an equivalent replacement more compatible with special mobs. */ @@ -62,5 +86,6 @@ public final class AIHelper { return; } } + SpecialMobs.LOG.warn( "Attempted to replace hurt by target goal for {}, but none exists!", entity.getClass().getSimpleName() ); } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/SimpleFlyingMovementController.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/SimpleFlyingMovementController.java new file mode 100644 index 0000000..288f806 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/SimpleFlyingMovementController.java @@ -0,0 +1,88 @@ +package fathertoast.specialmobs.common.entity.ai; + +import mcp.MethodsReturnNonnullByDefault; +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.AxisAlignedBB; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.vector.Vector3d; + +import javax.annotation.ParametersAreNonnullByDefault; + +/** + * Simple movement controller that can be used by flying entities, which takes their movement speed attribute into account. + */ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public class SimpleFlyingMovementController extends MovementController { + + private int floatDuration; + + public SimpleFlyingMovementController( MobEntity entity ) { super( entity ); } + + /** Called each tick while this move controller is active. */ + @Override + public void tick() { + if( operation == MovementController.Action.MOVE_TO ) { + if( floatDuration-- <= 0 ) { + floatDuration = mob.getRandom().nextInt( 5 ) + 2; + + Vector3d moveVec = new Vector3d( + wantedX - mob.getX(), + wantedY - mob.getY(), + wantedZ - mob.getZ() ); + final int distance = MathHelper.ceil( moveVec.length() ); + moveVec = moveVec.normalize(); + + if( mob.getRandom().nextBoolean() || canReach( moveVec, distance ) ) { // Skip the "boxcast" sometimes + mob.setDeltaMovement( mob.getDeltaMovement().add( moveVec.scale( getScaledMoveSpeed() ) ) ); + } + else { + operation = MovementController.Action.WAIT; + } + } + } + } + + public double getScaledMoveSpeed() { + return 0.1 * speedModifier * mob.getAttributeValue( Attributes.MOVEMENT_SPEED ) / Attributes.MOVEMENT_SPEED.getDefaultValue(); + } + + public double getDistanceSqToWantedPosition() { + return mob.distanceToSqr( getWantedX(), getWantedY(), getWantedZ() ); + } + + public boolean isWantedPositionStale( LivingEntity target ) { + return !hasWanted() || + target.distanceToSqr( + getWantedX(), + getWantedY() - target.getBbHeight() / 2.0F, + getWantedZ() + ) > 1.0; + } + + public boolean canReachWantedPosition() { + return hasWanted() && canReachPosition( getWantedX(), getWantedY(), getWantedZ() ); + } + + public boolean canReachPosition( LivingEntity target ) { + return canReachPosition( target.getX(), target.getY( 0.5 ), target.getZ() ); + } + + public boolean canReachPosition( double x, double y, double z ) { + final Vector3d targetVec = new Vector3d( x - mob.getX(), y - mob.getY(), z - mob.getZ() ); + final int distance = MathHelper.ceil( targetVec.length() ); + return canReach( targetVec.normalize(), distance ); + } + + private boolean canReach( Vector3d direction, int distance ) { + AxisAlignedBB boundingBox = mob.getBoundingBox(); + for( int i = 1; i < distance; i++ ) { + boundingBox = boundingBox.move( direction ); + if( !mob.level.noCollision( mob, boundingBox ) ) return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialInjectCreeperGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/ChargeCreeperGoal.java similarity index 58% rename from src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialInjectCreeperGoal.java rename to src/main/java/fathertoast/specialmobs/common/entity/ai/goal/ChargeCreeperGoal.java index 64018e8..e04e289 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialInjectCreeperGoal.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/ChargeCreeperGoal.java @@ -1,47 +1,45 @@ -package fathertoast.specialmobs.common.entity.ai; +package fathertoast.specialmobs.common.entity.ai.goal; import fathertoast.specialmobs.common.entity.creeper._SpecialCreeperEntity; import fathertoast.specialmobs.common.entity.zombie.MadScientistZombieEntity; import net.minecraft.entity.ai.goal.Goal; -import net.minecraft.entity.ai.goal.LookAtGoal; import net.minecraft.entity.monster.CreeperEntity; import net.minecraft.util.SoundCategory; import net.minecraft.util.SoundEvents; -import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.world.World; import java.util.EnumSet; import java.util.List; import java.util.function.BiPredicate; -public class SpecialInjectCreeperGoal extends Goal { - +public class ChargeCreeperGoal extends Goal { + private final BiPredicate targetPredicate; - + private final T madman; private final double movementSpeed; private final double targetRange; - + /** The creeper to target for power-up injection **/ private CreeperEntity creeper; - + private boolean canUseWhileMounted = false; - - - public SpecialInjectCreeperGoal(T madman, double movementSpeed, double targetRange, BiPredicate targetPredicate) { + + + public ChargeCreeperGoal( T madman, double movementSpeed, double targetRange, BiPredicate targetPredicate ) { this.madman = madman; this.movementSpeed = movementSpeed; this.targetRange = targetRange; this.targetPredicate = targetPredicate; - this.setFlags(EnumSet.of(Flag.MOVE)); + this.setFlags( EnumSet.of( Flag.MOVE ) ); } - + /** Builder that enables the entity to leap while mounted. */ - public SpecialInjectCreeperGoal canUseWhileMounted() { + public ChargeCreeperGoal canUseWhileMounted() { canUseWhileMounted = true; return this; } - + /** @return Returns true if this AI can be activated. */ @Override public boolean canUse() { @@ -49,53 +47,53 @@ public class SpecialInjectCreeperGoal extend findCreeper(); return creeper != null; } - + private void findCreeper() { World world = madman.level; - List nearbyCreepers = world.getLoadedEntitiesOfClass(CreeperEntity.class, madman.getBoundingBox().inflate(targetRange), null); - - if (!nearbyCreepers.isEmpty()) { - for (CreeperEntity creeper : nearbyCreepers) { - if (targetPredicate.test(madman, creeper)) { + List nearbyCreepers = world.getLoadedEntitiesOfClass( CreeperEntity.class, madman.getBoundingBox().inflate( targetRange ), null ); + + if( !nearbyCreepers.isEmpty() ) { + for( CreeperEntity creeper : nearbyCreepers ) { + if( targetPredicate.test( madman, creeper ) ) { this.creeper = creeper; break; } } } } - + /** Called when this AI is activated. */ @Override public void start() { - madman.getNavigation().moveTo(creeper, movementSpeed); + madman.getNavigation().moveTo( creeper, movementSpeed ); } - + /** @return Called each update while active and returns true if this AI can remain active. */ @Override public boolean canContinueToUse() { return !madman.isOnGround() && !madman.isPassenger() && !madman.isInWaterOrBubble() && !madman.isInLava(); } - + /** Called each tick while this AI is active. */ @Override public void tick() { - if (creeper == null || !targetPredicate.test(madman, creeper)) { + if( creeper == null || !targetPredicate.test( madman, creeper ) ) { findCreeper(); } else { - madman.getNavigation().moveTo(creeper, movementSpeed); - madman.getLookControl().setLookAt(this.creeper.getX(), this.creeper.getEyeY(), this.creeper.getZ()); - - if (madman.distanceTo(creeper) < 1.5D) { - creeper.getEntityData().set(CreeperEntity.DATA_IS_POWERED, true); - + madman.getNavigation().moveTo( creeper, movementSpeed ); + madman.getLookControl().setLookAt( this.creeper.getX(), this.creeper.getEyeY(), this.creeper.getZ() ); + + if( madman.distanceTo( creeper ) < 1.5D ) { + creeper.getEntityData().set( CreeperEntity.DATA_IS_POWERED, true ); + // HEE HEE HEE HAW - if (creeper instanceof _SpecialCreeperEntity && creeper.level.random.nextDouble() < 0.1F) { - ((_SpecialCreeperEntity)creeper).setSupercharged( true ); + if( creeper instanceof _SpecialCreeperEntity && creeper.level.random.nextDouble() < 0.1 ) { // TODO config + ((_SpecialCreeperEntity) creeper).setSupercharged( true ); } - madman.level.playSound(null, creeper.getX() + 0.5D, creeper.getY(), creeper.getZ() + 0.5D, SoundEvents.BEE_STING, SoundCategory.HOSTILE, 0.9F, 1.0F); + madman.level.playSound( null, creeper.getX() + 0.5D, creeper.getY(), creeper.getZ() + 0.5D, SoundEvents.BEE_STING, SoundCategory.HOSTILE, 0.9F, 1.0F ); creeper = null; } } } -} +} \ 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/goal/NinjaGoal.java similarity index 98% rename from src/main/java/fathertoast/specialmobs/common/entity/ai/NinjaGoal.java rename to src/main/java/fathertoast/specialmobs/common/entity/ai/goal/NinjaGoal.java index 1c42980..361b590 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/NinjaGoal.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/NinjaGoal.java @@ -1,5 +1,6 @@ -package fathertoast.specialmobs.common.entity.ai; +package fathertoast.specialmobs.common.entity.ai.goal; +import fathertoast.specialmobs.common.entity.ai.INinja; import net.minecraft.block.BlockRenderType; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; @@ -81,7 +82,7 @@ public class NinjaGoal extends Goal { /** 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(); - + // Random blocks to be picked regardless of position switch( random.nextInt( 200 ) ) { case 0: return Blocks.TNT.defaultBlockState(); @@ -100,7 +101,7 @@ public class NinjaGoal extends Goal { case 12: return randomPottedFlower( random ); case 13: return Blocks.SWEET_BERRY_BUSH.defaultBlockState().setValue(SweetBerryBushBlock.AGE, 3); } - + final BlockPos posUnderFeet = entity.blockPosition().below(); final BlockState blockUnderFeet = entity.level.getBlockState( posUnderFeet ); if( !blockUnderFeet.getBlock().isAir( blockUnderFeet, entity.level, posUnderFeet ) ) { @@ -203,12 +204,12 @@ public class NinjaGoal extends Goal { /** @return A random flower. */ private static BlockState randomFlower( Random random ) { - return BlockTags.SMALL_FLOWERS.getRandomElement(random).defaultBlockState(); + return BlockTags.SMALL_FLOWERS.getRandomElement( random ).defaultBlockState(); } /** @return A random potted flower. */ private static BlockState randomPottedFlower( Random random ) { - + switch( random.nextInt( 12 ) ) { case 0: return Blocks.POTTED_DANDELION.defaultBlockState(); case 1: return Blocks.POTTED_POPPY.defaultBlockState(); diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialBlazeAttackGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialBlazeAttackGoal.java similarity index 98% rename from src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialBlazeAttackGoal.java rename to src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialBlazeAttackGoal.java index 2c3b2ef..c171cf3 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialBlazeAttackGoal.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialBlazeAttackGoal.java @@ -1,4 +1,4 @@ -package fathertoast.specialmobs.common.entity.ai; +package fathertoast.specialmobs.common.entity.ai.goal; import fathertoast.specialmobs.common.entity.SpecialMobData; import fathertoast.specialmobs.common.entity.blaze._SpecialBlazeEntity; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastFireballAttackGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastFireballAttackGoal.java new file mode 100644 index 0000000..354f1dc --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastFireballAttackGoal.java @@ -0,0 +1,56 @@ +package fathertoast.specialmobs.common.entity.ai.goal; + +import fathertoast.specialmobs.common.entity.SpecialMobData; +import fathertoast.specialmobs.common.entity.ghast._SpecialGhastEntity; +import fathertoast.specialmobs.common.util.References; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.goal.Goal; + +/** + * Implementation of GhastEntity.FireballAttackGoal that is visible to special ghasts and allows variants to + * decide how to execute the actual attack. + */ +public class SpecialGhastFireballAttackGoal extends Goal { + + private final _SpecialGhastEntity ghast; + + private int chargeTime; + + public SpecialGhastFireballAttackGoal( _SpecialGhastEntity entity ) { ghast = entity; } + + /** @return Returns true if this AI can be activated. */ + @Override + public boolean canUse() { return ghast.getTarget() != null; } + + /** Called when this AI is activated. */ + @Override + public void start() { chargeTime = 0; } + + /** Called when this AI is deactivated. */ + @Override + public void stop() { ghast.setCharging( false ); } + + /** Called each tick while this AI is active. */ + @Override + public void tick() { + final LivingEntity target = ghast.getTarget(); + if( target == null ) return; + + final SpecialMobData<_SpecialGhastEntity> data = ghast.getSpecialData(); + + if( target.distanceToSqr( ghast ) < data.rangedAttackMaxRange * data.rangedAttackMaxRange && ghast.canSee( target ) ) { + chargeTime++; + if( chargeTime == (data.rangedAttackCooldown >> 1) && !ghast.isSilent() ) { + ghast.level.levelEvent( null, References.EVENT_GHAST_WARN, ghast.blockPosition(), 0 ); + } + if( chargeTime >= data.rangedAttackCooldown ) { + ghast.performRangedAttack( target, 1.0F ); + chargeTime = data.rangedAttackCooldown - data.rangedAttackMaxCooldown; + } + } + else if( chargeTime > 0 ) { + chargeTime--; + } + ghast.setCharging( chargeTime > (data.rangedAttackCooldown >> 1) ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastLookAroundGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastLookAroundGoal.java new file mode 100644 index 0000000..61673a0 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastLookAroundGoal.java @@ -0,0 +1,47 @@ +package fathertoast.specialmobs.common.entity.ai.goal; + +import fathertoast.specialmobs.common.entity.ghast._SpecialGhastEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.goal.Goal; +import net.minecraft.util.math.MathHelper; + +import java.util.EnumSet; + +/** + * Implementation of GhastEntity.LookAroundGoal that is visible to special ghasts and sensitive to special mob data. + */ +public class SpecialGhastLookAroundGoal extends Goal { + + private final _SpecialGhastEntity ghast; + + public SpecialGhastLookAroundGoal( _SpecialGhastEntity entity ) { + ghast = entity; + setFlags( EnumSet.of( Goal.Flag.LOOK ) ); + } + + /** @return Returns true if this AI can be activated. */ + @Override + public boolean canUse() { return true; } + + /** Called each tick while this AI is active. */ + @Override + public void tick() { + final LivingEntity target = ghast.getTarget(); + if( target != null ) { + final float range = ghast.getSpecialData().rangedAttackMaxRange > 0.0F ? + ghast.getSpecialData().rangedAttackMaxRange : + 16.0F; // Range for melee ghast to face target + + if( target.distanceToSqr( ghast ) < range * range ) { + setFacing( target.getX() - ghast.getX(), target.getZ() - ghast.getZ() ); + return; + } + } + // Allow move direction facing even if target exists out of attack range + setFacing( ghast.getDeltaMovement().x, ghast.getDeltaMovement().z ); + } + + private void setFacing( double x, double z ) { + ghast.yBodyRot = ghast.yRot = (float) MathHelper.atan2( x, z ) * -180.0F / (float) Math.PI; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastMeleeAttackGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastMeleeAttackGoal.java new file mode 100644 index 0000000..48e0aa7 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastMeleeAttackGoal.java @@ -0,0 +1,100 @@ +package fathertoast.specialmobs.common.entity.ai.goal; + +import fathertoast.specialmobs.common.entity.ai.SimpleFlyingMovementController; +import fathertoast.specialmobs.common.entity.ghast._SpecialGhastEntity; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.controller.MovementController; +import net.minecraft.entity.ai.goal.Goal; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.EnumSet; + +/** + * Melee attack goal modified to function for ghasts. + */ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public class SpecialGhastMeleeAttackGoal extends Goal { + + private final _SpecialGhastEntity ghast; + + private int attackTimer; + + private int pathUpdateCooldown; + private boolean wasPathingToTarget; + + public SpecialGhastMeleeAttackGoal( _SpecialGhastEntity entity ) { + ghast = entity; + setFlags( EnumSet.of( Goal.Flag.MOVE ) ); + } + + /** @return Returns true if this AI can be activated. */ + @Override + public boolean canUse() { return ghast.getTarget() != null; } + + /** Called when this AI is activated. */ + @Override + public void start() { + attackTimer = 0; + + final LivingEntity target = ghast.getTarget(); + if( target != null ) { + setWantedPosition( target ); + wasPathingToTarget = !(ghast.getMoveControl() instanceof SimpleFlyingMovementController) || + ((SimpleFlyingMovementController) ghast.getMoveControl()).canReachWantedPosition(); + pathUpdateCooldown = 5; + } + } + + /** Called each tick while this AI is active. */ + @Override + public void tick() { + final LivingEntity target = ghast.getTarget(); + if( target == null ) return; + + // Move towards the target + final MovementController moveControl = ghast.getMoveControl(); + if( pathUpdateCooldown-- <= 0 || !moveControl.hasWanted() ) { + pathUpdateCooldown = ghast.getRandom().nextInt( 5 ) + 3; + + final SimpleFlyingMovementController flyingControl = moveControl instanceof SimpleFlyingMovementController ? + (SimpleFlyingMovementController) moveControl : null; + if( flyingControl == null || flyingControl.isWantedPositionStale( target ) ) { + if( flyingControl == null || flyingControl.canReachPosition( target ) ) { + setWantedPosition( target ); + wasPathingToTarget = true; + } + else if( !wasPathingToTarget || !moveControl.hasWanted() || flyingControl.getDistanceSqToWantedPosition() < 2.0 ) { + setWantedPosition(); + wasPathingToTarget = false; + } + } + } + + // Attack the target when able + if( attackTimer > 0 ) { + attackTimer--; + } + else { + final double reachSq = (ghast.getBbWidth() * ghast.getBbWidth() + target.getBbWidth() * target.getBbWidth()) / 4.0 + 5.0; + if( target.distanceToSqr( ghast ) < reachSq ) { + attackTimer = 20; + ghast.doHurtTarget( target ); + } + } + } + + private void setWantedPosition( LivingEntity target ) { + ghast.getMoveControl().setWantedPosition( target.getX(), target.getY( 0.5 ), target.getZ(), 1.2 ); + } + + private void setWantedPosition() { + final float diameter = 8.0F; + ghast.getMoveControl().setWantedPosition( + ghast.getX() + (ghast.getRandom().nextFloat() - 0.5F) * diameter, + ghast.getY() + (ghast.getRandom().nextFloat() - 0.5F) * diameter, + ghast.getZ() + (ghast.getRandom().nextFloat() - 0.5F) * diameter, + 1.0 ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialHurtByTargetGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialHurtByTargetGoal.java similarity index 97% rename from src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialHurtByTargetGoal.java rename to src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialHurtByTargetGoal.java index 1bb6da6..fa8f05a 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialHurtByTargetGoal.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialHurtByTargetGoal.java @@ -1,4 +1,4 @@ -package fathertoast.specialmobs.common.entity.ai; +package fathertoast.specialmobs.common.entity.ai.goal; import mcp.MethodsReturnNonnullByDefault; import net.minecraft.entity.CreatureEntity; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialLeapAtTargetGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialLeapAtTargetGoal.java similarity index 98% rename from src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialLeapAtTargetGoal.java rename to src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialLeapAtTargetGoal.java index c1dc276..735715e 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialLeapAtTargetGoal.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialLeapAtTargetGoal.java @@ -1,4 +1,4 @@ -package fathertoast.specialmobs.common.entity.ai; +package fathertoast.specialmobs.common.entity.ai.goal; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.MobEntity; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java index eafac4e..e83878d 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java @@ -11,11 +11,9 @@ import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.ai.attributes.AttributeModifierMap; import net.minecraft.entity.ai.attributes.Attributes; -import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.projectile.FireballEntity; import net.minecraft.item.Items; import net.minecraft.nbt.CompoundNBT; -import net.minecraft.util.DamageSource; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; @@ -82,7 +80,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, 1018, blockPosition(), 0 ); + if( !isSilent() ) level.levelEvent( null, References.EVENT_BLAZE_SHOOT, blockPosition(), 0 ); final float accelVariance = MathHelper.sqrt( distanceTo( target ) ) * 0.5F * getSpecialData().rangedAttackSpread; final double dX = target.getX() - getX() + getRandom().nextGaussian() * accelVariance; @@ -95,18 +93,6 @@ public class HellfireBlazeEntity extends _SpecialBlazeEntity { level.addFreshEntity( fireball ); } - /** @return Attempts to damage this entity; returns true if the hit was successful. */ - @Override - public boolean hurt( DamageSource source, float amount ) { - if( isInvulnerableTo( source ) ) return false; - - if( source.getDirectEntity() instanceof FireballEntity && source.getEntity() instanceof PlayerEntity ) { - super.hurt( source, 1000.0F ); // Die from returned fireballs (like ghasts) - return true; - } - return super.hurt( source, amount ); - } - /** Override to save data to this entity's NBT data. */ @Override public void addVariantSaveData( CompoundNBT saveTag ) { diff --git a/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java index 60a7cf3..1c0fe85 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java @@ -64,7 +64,7 @@ public class WildfireBlazeEntity extends _SpecialBlazeEntity { /** The number of babies spawned on death. */ private int babies; /** The number of extra babies that can be spawned by attacks. */ - private int extraBabies; + private int summons; public WildfireBlazeEntity( EntityType entityType, World world ) { super( entityType, world ); @@ -72,8 +72,8 @@ public class WildfireBlazeEntity extends _SpecialBlazeEntity { getSpecialData().setRegenerationTime( 40 ); xpReward += 2; - babies = 2 + random.nextInt( 3 ); - extraBabies = 3 + random.nextInt( 4 ); + babies = 3 + random.nextInt( 4 ); + summons = 4 + random.nextInt( 7 ); } /** Override to change this entity's AI goals. */ @@ -91,15 +91,15 @@ public class WildfireBlazeEntity extends _SpecialBlazeEntity { /** Called to attack the target with a ranged attack. */ @Override public void performRangedAttack( LivingEntity target, float damageMulti ) { - if( !level.isClientSide() && extraBabies > 0 && random.nextInt( 2 ) == 0 ) { - extraBabies--; + if( !level.isClientSide() && summons > 0 && random.nextInt( 2 ) == 0 ) { + summons--; final double vX = target.getX() - getX(); final double vZ = target.getZ() - getZ(); 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, 1018, blockPosition(), 0 ); + if( !isSilent() ) level.levelEvent( null, References.EVENT_BLAZE_SHOOT, blockPosition(), 0 ); } else { super.performRangedAttack( target, damageMulti ); @@ -112,13 +112,12 @@ public class WildfireBlazeEntity extends _SpecialBlazeEntity { //noinspection deprecation if( isDeadOrDying() && !removed && level instanceof IServerWorld ) { // Same conditions as slime splitting // Spawn babies on death - final int babiesToSpawn = babies + extraBabies; ILivingEntityData groupData = null; - for( int i = 0; i < babiesToSpawn; i++ ) { + for( int i = 0; i < babies; i++ ) { groupData = spawnBaby( (random.nextDouble() - 0.5) * 0.3, (random.nextDouble() - 0.5) * 0.3, groupData ); } spawnAnim(); - if( !isSilent() ) level.levelEvent( null, 1018, blockPosition(), 0 ); + if( !isSilent() ) level.levelEvent( null, References.EVENT_BLAZE_SHOOT, blockPosition(), 0 ); } super.remove( keepData ); } @@ -147,7 +146,7 @@ public class WildfireBlazeEntity extends _SpecialBlazeEntity { @Override public void addVariantSaveData( CompoundNBT saveTag ) { saveTag.putByte( References.TAG_BABIES, (byte) babies ); - saveTag.putByte( References.TAG_EXTRA_BABIES, (byte) extraBabies ); + saveTag.putByte( References.TAG_SUMMONS, (byte) summons ); } /** Override to load data from this entity's NBT data. */ @@ -155,8 +154,8 @@ public class WildfireBlazeEntity extends _SpecialBlazeEntity { public void readVariantSaveData( CompoundNBT saveTag ) { if( saveTag.contains( References.TAG_BABIES, References.NBT_TYPE_NUMERICAL ) ) babies = saveTag.getByte( References.TAG_BABIES ); - if( saveTag.contains( References.TAG_EXTRA_BABIES, References.NBT_TYPE_NUMERICAL ) ) - extraBabies = saveTag.getByte( References.TAG_EXTRA_BABIES ); + if( saveTag.contains( References.TAG_SUMMONS, References.NBT_TYPE_NUMERICAL ) ) + summons = saveTag.getByte( References.TAG_SUMMONS ); } private static final ResourceLocation[] TEXTURES = { diff --git a/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java index f587129..d73addc 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java @@ -7,8 +7,8 @@ import fathertoast.specialmobs.common.core.SpecialMobs; import fathertoast.specialmobs.common.entity.ISpecialMob; import fathertoast.specialmobs.common.entity.SpecialMobData; import fathertoast.specialmobs.common.entity.ai.AIHelper; -import fathertoast.specialmobs.common.entity.ai.SpecialBlazeAttackGoal; -import fathertoast.specialmobs.common.entity.ai.SpecialHurtByTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialBlazeAttackGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialHurtByTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; @@ -47,7 +47,7 @@ public class _SpecialBlazeEntity extends BlazeEntity implements IRangedAttackMob @SpecialMob.BestiaryInfoSupplier public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { - return new BestiaryInfo( 0xFFF87E ); //TODO - TEMP: base size = 0.6F, 1.8F + return new BestiaryInfo( 0xFFF87E ); } @SpecialMob.AttributeCreator @@ -131,9 +131,9 @@ public class _SpecialBlazeEntity extends BlazeEntity implements IRangedAttackMob /** The parameter for special mob render scale. */ private static final DataParameter SCALE = EntityDataManager.defineId( _SpecialBlazeEntity.class, DataSerializers.FLOAT ); - // The amount of fireballs in each burst. + /** The amount of fireballs in each burst. */ public int fireballBurstCount; - // The ticks between each shot in a burst. + /** The ticks between each shot in a burst. */ public int fireballBurstDelay; /** Called from the Entity.class constructor to define data watcher variables. */ @@ -146,7 +146,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, 1018, blockPosition(), 0 ); + if( !isSilent() ) level.levelEvent( null, References.EVENT_BLAZE_SHOOT, blockPosition(), 0 ); final float accelVariance = MathHelper.sqrt( distanceTo( target ) ) * 0.5F * getSpecialData().rangedAttackSpread; final double dX = target.getX() - getX() + getRandom().nextGaussian() * accelVariance; @@ -154,7 +154,7 @@ public class _SpecialBlazeEntity extends BlazeEntity implements IRangedAttackMob final double dZ = target.getZ() - getZ() + getRandom().nextGaussian() * accelVariance; final SmallFireballEntity fireball = new SmallFireballEntity( level, this, dX, dY, dZ ); - fireball.setPos( fireball.getX(), getY( 0.5 ) + 0.5, fireball.getZ() ); + fireball.setPos( getX(), getY( 0.5 ) + 0.5, getZ() ); level.addFreshEntity( fireball ); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/FlyingCaveSpiderEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/FlyingCaveSpiderEntity.java index ae866d9..b95b155 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/FlyingCaveSpiderEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/FlyingCaveSpiderEntity.java @@ -3,7 +3,7 @@ package fathertoast.specialmobs.common.entity.cavespider; import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; -import fathertoast.specialmobs.common.entity.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/creeper/JumpingCreeperEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/creeper/JumpingCreeperEntity.java index 26506bf..b34536e 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/creeper/JumpingCreeperEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/creeper/JumpingCreeperEntity.java @@ -4,7 +4,7 @@ import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; import fathertoast.specialmobs.common.entity.ai.AIHelper; -import fathertoast.specialmobs.common.entity.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java index d10b0f2..60ff144 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java @@ -30,7 +30,6 @@ import net.minecraft.world.DifficultyInstance; import net.minecraft.world.IServerWorld; import net.minecraft.world.World; import net.minecraft.world.server.ServerWorld; -import net.minecraftforge.common.util.Constants; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; @@ -189,7 +188,7 @@ public class _SpecialCreeperEntity extends CreeperEntity implements ISpecialMob< /** Called when this entity is struck by lightning. */ @Override public void thunderHit( ServerWorld world, LightningBoltEntity lightningBolt ) { - if( !isPowered() && random.nextDouble() < 0.1D ) + if( !isPowered() && random.nextDouble() < 0.1 ) // TODO config setSupercharged( true ); super.thunderHit( world, lightningBolt ); diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/BabyGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/BabyGhastEntity.java new file mode 100644 index 0000000..5b87bed --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/BabyGhastEntity.java @@ -0,0 +1,76 @@ +package fathertoast.specialmobs.common.entity.ghast; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.util.AttributeHelper; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.item.Items; +import net.minecraft.util.SoundEvent; +import net.minecraft.world.World; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class BabyGhastEntity extends _SpecialGhastEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + entityType.sized( 1.0F, 1.0F ); + return new BestiaryInfo( 0xFFC0CB, BestiaryInfo.BaseWeight.DISABLED ); + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialGhastEntity.createAttributes() ) + .addAttribute( Attributes.ATTACK_DAMAGE, -1.0 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Baby Ghast", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + loot.addCommonDrop( "common", Items.GUNPOWDER, 1 ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return BabyGhastEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + public BabyGhastEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().setBaseScale( 0.25F ); + xpReward = 1; + } + + /** Override to change this entity's AI goals. */ + @Override + protected void registerVariantGoals() { + getSpecialData().rangedAttackDamage -= 1.0F; + disableRangedAI(); + } + + /** @return The sound this entity makes idly. */ + @Override + protected SoundEvent getAmbientSound() { return null; } // There could be a lot of these, need to be less annoying +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/FighterGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/FighterGhastEntity.java new file mode 100644 index 0000000..bc04729 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/FighterGhastEntity.java @@ -0,0 +1,95 @@ +package fathertoast.specialmobs.common.entity.ghast; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.entity.MobHelper; +import fathertoast.specialmobs.common.util.AttributeHelper; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.item.Items; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class FighterGhastEntity extends _SpecialGhastEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + entityType.sized( 2.0F, 2.0F ); + return new BestiaryInfo( 0x7A1300 ); + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialGhastEntity.createAttributes() ) + .addAttribute( Attributes.MAX_HEALTH, 20.0 ) + .addAttribute( Attributes.ARMOR, 10.0 ) + .addAttribute( Attributes.ATTACK_DAMAGE, 2.0 ) + .multAttribute( Attributes.MOVEMENT_SPEED, 0.8 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Fighter Ghast", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addUncommonDrop( "uncommon", Items.IRON_INGOT ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return FighterGhastEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + public FighterGhastEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().setBaseScale( 0.5F ); + xpReward += 1; + } + + /** Override to change this entity's AI goals. */ + @Override + protected void registerVariantGoals() { + getSpecialData().rangedAttackDamage += 2.0F; + disableRangedAI(); + } + + /** Override to apply effects when this entity hits a target with a melee attack. */ + protected void onVariantAttack( Entity target ) { + if( target instanceof LivingEntity ) { + MobHelper.causeLifeLoss( (LivingEntity) target, 2.0F ); + } + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "fighter" ), + null, + GET_TEXTURE_PATH( "fighter_shooting" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/KingGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/KingGhastEntity.java new file mode 100644 index 0000000..88c2911 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/KingGhastEntity.java @@ -0,0 +1,91 @@ +package fathertoast.specialmobs.common.entity.ghast; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.util.AttributeHelper; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.item.Items; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class KingGhastEntity extends _SpecialGhastEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + entityType.sized( 6.0F, 6.0F ); + return new BestiaryInfo( 0xE8C51A, BestiaryInfo.BaseWeight.LOW ); + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialGhastEntity.createAttributes() ) + .addAttribute( Attributes.MAX_HEALTH, 20.0 ) + .addAttribute( Attributes.ARMOR, 10.0 ) + .addAttribute( Attributes.ATTACK_DAMAGE, 4.0 ) + .multAttribute( Attributes.MOVEMENT_SPEED, 0.6 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "King Ghast", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addSemicommonDrop( "semicommon", Items.GOLD_INGOT ); + loot.addUncommonDrop( "uncommon", Items.EMERALD ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return KingGhastEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + public KingGhastEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().setBaseScale( 1.5F ); + getSpecialData().setRegenerationTime( 30 ); + xpReward += 4; + } + + /** Override to change this entity's AI goals. */ + @Override + protected void registerVariantGoals() { + getSpecialData().rangedAttackDamage += 4.0F; + } + + /** Override to change this ghast's explosion power multiplier. */ + @Override + protected int getVariantExplosionPower( int radius ) { return Math.round( radius * 2.5F ); } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "king" ), + null, + GET_TEXTURE_PATH( "king_shooting" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/QueenGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/QueenGhastEntity.java new file mode 100644 index 0000000..d6dbfdb --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/QueenGhastEntity.java @@ -0,0 +1,173 @@ +package fathertoast.specialmobs.common.entity.ghast; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.util.AttributeHelper; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.ILivingEntityData; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.SpawnReason; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.IServerWorld; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class QueenGhastEntity extends _SpecialGhastEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + entityType.sized( 5.0F, 5.0F ); + return new BestiaryInfo( 0xCE0Aff ); + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialGhastEntity.createAttributes() ) + .addAttribute( Attributes.MAX_HEALTH, 20.0 ) + .addAttribute( Attributes.ATTACK_DAMAGE, 2.0 ) + .multAttribute( Attributes.MOVEMENT_SPEED, 0.6 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Queen Ghast", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addSemicommonDrop( "semicommon", Items.GOLD_INGOT ); + loot.addUncommonDrop( "uncommon", Items.GHAST_SPAWN_EGG ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return QueenGhastEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + /** The number of babies spawned on death. */ + private int babies; + /** The number of extra babies that can be spawned by attacks. */ + private int summons; + + public QueenGhastEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().setBaseScale( 1.25F ); + getSpecialData().setRegenerationTime( 20 ); + xpReward += 2; + + babies = 3 + random.nextInt( 4 ); + summons = 4 + random.nextInt( 7 ); + } + + /** Override to change this entity's AI goals. */ + @Override + protected void registerVariantGoals() { + getSpecialData().rangedAttackDamage += 2.0F; + } + + /** Called to attack the target with a ranged attack. */ + @Override + public void performRangedAttack( LivingEntity target, float damageMulti ) { + if( !level.isClientSide() && summons > 0 && random.nextInt( 2 ) == 0 ) { + summons--; + + final double vX = target.getX() - getX(); + final double vZ = target.getZ() - getZ(); + 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 ); + } + else { + super.performRangedAttack( target, damageMulti ); + } + } + + /** Override to change this ghast's explosion power multiplier. */ + @Override + protected int getVariantExplosionPower( int radius ) { return Math.round( radius * 1.5F ); } + + /** Called to remove this entity from the world. Includes death, unloading, interdimensional travel, etc. */ + @Override + public void remove( boolean keepData ) { + //noinspection deprecation + if( isDeadOrDying() && !removed && level instanceof IServerWorld ) { // Same conditions as slime splitting + // Spawn babies on death + ILivingEntityData groupData = null; + for( int i = 0; i < babies; i++ ) { + 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 ); + } + super.remove( keepData ); + } + + /** Helper method to simplify spawning babies. */ + @Nullable + private ILivingEntityData spawnBaby( double vX, double vZ, @Nullable ILivingEntityData groupData ) { + final BabyGhastEntity baby = BabyGhastEntity.SPECIES.entityType.get().create( level ); + if( baby == null ) return groupData; + + baby.copyPosition( this ); + baby.yHeadRot = yRot; + baby.yBodyRot = yRot; + groupData = baby.finalizeSpawn( (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ), + SpawnReason.MOB_SUMMONED, groupData, null ); + baby.setTarget( getTarget() ); + + baby.setDeltaMovement( vX, 0.0, vZ ); + baby.setOnGround( false ); + + level.addFreshEntity( baby ); + return groupData; + } + + /** Override to save data to this entity's NBT data. */ + @Override + public void addVariantSaveData( CompoundNBT saveTag ) { + saveTag.putByte( References.TAG_BABIES, (byte) babies ); + saveTag.putByte( References.TAG_SUMMONS, (byte) summons ); + } + + /** Override to load data from this entity's NBT data. */ + @Override + public void readVariantSaveData( CompoundNBT saveTag ) { + if( saveTag.contains( References.TAG_BABIES, References.NBT_TYPE_NUMERICAL ) ) + babies = saveTag.getByte( References.TAG_BABIES ); + if( saveTag.contains( References.TAG_SUMMONS, References.NBT_TYPE_NUMERICAL ) ) + summons = saveTag.getByte( References.TAG_SUMMONS ); + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "queen" ), + null, + GET_TEXTURE_PATH( "queen_shooting" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/UnholyGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/UnholyGhastEntity.java new file mode 100644 index 0000000..b8caa89 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/UnholyGhastEntity.java @@ -0,0 +1,133 @@ +package fathertoast.specialmobs.common.entity.ghast; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.entity.MobHelper; +import fathertoast.specialmobs.common.util.AttributeHelper; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.CreatureAttribute; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.inventory.EquipmentSlotType; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.DamageSource; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class UnholyGhastEntity extends _SpecialGhastEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + entityType.sized( 2.0F, 2.0F ); + return new BestiaryInfo( 0x7AC754, BestiaryInfo.BaseWeight.LOW ); + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialGhastEntity.createAttributes() ) + .addAttribute( Attributes.MAX_HEALTH, 10.0 ) + .addAttribute( Attributes.ATTACK_DAMAGE, 2.0 ) + .multAttribute( Attributes.MOVEMENT_SPEED, 0.7 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Unholy Ghast", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addCommonDrop( "common", Items.BONE ); + loot.addSemicommonDrop( "semicommon", Items.QUARTZ ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return UnholyGhastEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + public UnholyGhastEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().setBaseScale( 0.5F ); + xpReward += 4; + } + + /** Override to change this entity's AI goals. */ + @Override + protected void registerVariantGoals() { + getSpecialData().rangedAttackDamage += 2.0F; + disableRangedAI(); + } + + /** Override to apply effects when this entity hits a target with a melee attack. */ + protected void onVariantAttack( Entity target ) { + if( target instanceof LivingEntity ) { + MobHelper.stealLife( this, (LivingEntity) target, 2.0F ); + } + } + + /** @return Attempts to damage this entity; returns true if the hit was successful. */ + @Override + public boolean hurt( DamageSource source, float amount ) { + if( MobHelper.isDamageSourceIneffectiveAgainstVampires( source ) ) { + amount = Math.min( 2.0F, amount ); + } + return super.hurt( source, amount ); + } + + /** @return This entity's creature type. */ + @Override + public CreatureAttribute getMobType() { return CreatureAttribute.UNDEAD; } + + /** Called each tick to update this entity's movement. */ + @Override + public void aiStep() { + if( isSunBurnTick() ) { + final ItemStack hat = getItemBySlot( EquipmentSlotType.HEAD ); + if( !hat.isEmpty() ) { + if( hat.isDamageableItem() ) { + hat.setDamageValue( hat.getDamageValue() + random.nextInt( 2 ) ); + if( hat.getDamageValue() >= hat.getMaxDamage() ) { + broadcastBreakEvent( EquipmentSlotType.HEAD ); + setItemSlot( EquipmentSlotType.HEAD, ItemStack.EMPTY ); + } + } + } + else { + setSecondsOnFire( 8 ); + } + } + super.aiStep(); + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "unholy" ), + null, + GET_TEXTURE_PATH( "unholy_shooting" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java new file mode 100644 index 0000000..69cbb3b --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java @@ -0,0 +1,327 @@ +package fathertoast.specialmobs.common.entity.ghast; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.core.SpecialMobs; +import fathertoast.specialmobs.common.entity.ISpecialMob; +import fathertoast.specialmobs.common.entity.SpecialMobData; +import fathertoast.specialmobs.common.entity.ai.AIHelper; +import fathertoast.specialmobs.common.entity.ai.SimpleFlyingMovementController; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialGhastFireballAttackGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialGhastLookAroundGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialGhastMeleeAttackGoal; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.block.BlockState; +import net.minecraft.entity.*; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.entity.ai.goal.Goal; +import net.minecraft.entity.ai.goal.NearestAttackableTargetGoal; +import net.minecraft.entity.monster.GhastEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.projectile.FireballEntity; +import net.minecraft.entity.projectile.SnowballEntity; +import net.minecraft.inventory.EquipmentSlotType; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.network.datasync.DataParameter; +import net.minecraft.network.datasync.DataSerializers; +import net.minecraft.network.datasync.EntityDataManager; +import net.minecraft.potion.EffectInstance; +import net.minecraft.util.DamageSource; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.IServerWorld; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class _SpecialGhastEntity extends GhastEntity implements IRangedAttackMob, ISpecialMob<_SpecialGhastEntity> { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species<_SpecialGhastEntity> SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + return new BestiaryInfo( 0xBCBCBC ); + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return GhastEntity.createAttributes().add( Attributes.ATTACK_DAMAGE, 4.0 ); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Ghast", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void addBaseLoot( LootTableBuilder loot ) { + loot.addLootTable( "main", EntityType.GHAST.getDefaultLootTable() ); + } + + @SpecialMob.Factory + public static EntityType.IFactory<_SpecialGhastEntity> getFactory() { return _SpecialGhastEntity::new; } + + + //--------------- Variant-Specific Breakouts ---------------- + + public _SpecialGhastEntity( EntityType entityType, World world ) { + super( entityType, world ); + moveControl = new SimpleFlyingMovementController( this ); + reassessAttackGoal(); + getSpecialData().initialize(); + } + + /** Called in the MobEntity.class constructor to initialize AI goals. */ + @Override + protected void registerGoals() { + super.registerGoals(); + // We actually do want to replace both of these, even though we don't have the option of only replacing one + AIHelper.removeGoals( goalSelector, 7 ); // GhastEntity.LookAroundGoal & GhastEntity.FireballAttackGoal + goalSelector.addGoal( 7, new SpecialGhastLookAroundGoal( this ) ); + + // Allow ghasts to target things not directly horizontal to them (why was this ever added?) TODO config + AIHelper.removeGoals( targetSelector, NearestAttackableTargetGoal.class ); + targetSelector.addGoal( 1, new NearestAttackableTargetGoal<>( this, PlayerEntity.class, true ) ); + + getSpecialData().rangedAttackDamage = 2.0F; + getSpecialData().rangedAttackSpread = 0.0F; + getSpecialData().rangedAttackCooldown = 20; + getSpecialData().rangedAttackMaxCooldown = 60; + getSpecialData().rangedAttackMaxRange = 64.0F; + registerVariantGoals(); + } + + /** Override to change this entity's AI goals. */ + protected void registerVariantGoals() { } + + /** Helper method to set the ranged attack AI more easily. */ + protected void disableRangedAI() { getSpecialData().rangedAttackMaxRange = 0.0F; } + + /** Override to change this entity's attack goal priority. */ + protected int getVariantAttackPriority() { return 4; } + + /** 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 ); + + final float accelVariance = MathHelper.sqrt( distanceTo( target ) ) * 0.5F * getSpecialData().rangedAttackSpread; + final Vector3d lookVec = getViewVector( 1.0F ).scale( getBbWidth() ); + double dX = target.getX() - (getX() + lookVec.x) + getRandom().nextGaussian() * accelVariance; + double dY = target.getY( 0.5 ) - (0.5 + getY( 0.5 )); + double dZ = target.getZ() - (getZ() + lookVec.z) + getRandom().nextGaussian() * accelVariance; + + final FireballEntity fireball = new FireballEntity( level, this, dX, dY, dZ ); + fireball.explosionPower = getVariantExplosionPower( getExplosionPower() ); + fireball.setPos( + getX() + lookVec.x, + getY( 0.5 ) + 0.5, + getZ() + lookVec.z ); + level.addFreshEntity( fireball ); + } + + /** Override to change this ghast's explosion power multiplier. */ + protected int getVariantExplosionPower( int radius ) { return radius; } + + /** Called when this entity successfully damages a target to apply on-hit effects. */ + @Override + public void doEnchantDamageEffects( LivingEntity attacker, Entity target ) { + onVariantAttack( target ); + super.doEnchantDamageEffects( attacker, target ); + } + + /** Override to apply effects when this entity hits a target with a melee attack. */ + protected void onVariantAttack( Entity target ) { } + + /** Override to save data to this entity's NBT data. */ + public void addVariantSaveData( CompoundNBT saveTag ) { } + + /** Override to load data from this entity's NBT data. */ + public void readVariantSaveData( CompoundNBT saveTag ) { } + + + //--------------- Family-Specific Implementations ---------------- + + /** The parameter for special mob render scale. */ + private static final DataParameter SCALE = EntityDataManager.defineId( _SpecialGhastEntity.class, DataSerializers.FLOAT ); + + /** This entity's attack AI. */ + private Goal currentAttackAI; + + /** Called from the Entity.class constructor to define data watcher variables. */ + @Override + protected void defineSynchedData() { + super.defineSynchedData(); + specialData = new SpecialMobData<>( this, SCALE, 1.0F ); + } + + /** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */ + @Nullable + public ILivingEntityData finalizeSpawn( IServerWorld world, DifficultyInstance difficulty, SpawnReason spawnReason, + @Nullable ILivingEntityData groupData, @Nullable CompoundNBT eggTag ) { + groupData = super.finalizeSpawn( world, difficulty, spawnReason, groupData, eggTag ); + reassessAttackGoal(); + return groupData; + } + + /** Called to set the item equipped in a particular slot. */ + @Override + public void setItemSlot( EquipmentSlotType slot, ItemStack item ) { + super.setItemSlot( slot, item ); + if( !level.isClientSide ) reassessAttackGoal(); + } + + /** Called to set this entity's attack AI based on current equipment. */ + public void reassessAttackGoal() { + if( level != null && !level.isClientSide ) { + if( currentAttackAI != null ) goalSelector.removeGoal( currentAttackAI ); + + currentAttackAI = getSpecialData().rangedAttackMaxRange > 0.0F ? + new SpecialGhastFireballAttackGoal( this ) : + new SpecialGhastMeleeAttackGoal( this ); + + goalSelector.addGoal( getVariantAttackPriority(), currentAttackAI ); + } + } + + + //--------------- ISpecialMob Implementation ---------------- + + private SpecialMobData<_SpecialGhastEntity> specialData; + + /** @return This mob's special data. */ + @Override + public SpecialMobData<_SpecialGhastEntity> getSpecialData() { return specialData; } + + /** @return The experience that should be dropped by this entity. */ + @Override + public final int getExperience() { return xpReward; } + + /** Sets the experience that should be dropped by this entity. */ + @Override + public final void setExperience( int xp ) { xpReward = xp; } + + static ResourceLocation GET_TEXTURE_PATH( String type ) { + return SpecialMobs.resourceLoc( SpecialMobs.TEXTURE_PATH + "ghast/" + type + ".png" ); + } + + private static final ResourceLocation[] TEXTURES = { + new ResourceLocation( "textures/entity/ghast/ghast.png" ), + null, + new ResourceLocation( "textures/entity/ghast/ghast_shooting.png" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } + + + //--------------- SpecialMobData Hooks ---------------- + + /** Called each tick to update this entity's movement. */ + @Override + public void aiStep() { + super.aiStep(); + getSpecialData().tick(); + } + + /** @return The eye height of this entity when standing. */ + @Override + protected float getStandingEyeHeight( Pose pose, EntitySize size ) { + return super.getStandingEyeHeight( pose, size ) * getSpecialData().getBaseScale() * (isBaby() ? 0.53448F : 1.0F); + } + + /** @return Whether this entity is immune to fire damage. */ + @Override + public boolean fireImmune() { return getSpecialData().isImmuneToFire(); } + + /** Sets this entity on fire for a specific duration. */ + @Override + public void setRemainingFireTicks( int ticks ) { + if( !getSpecialData().isImmuneToBurning() ) super.setRemainingFireTicks( ticks ); + } + + /** @return True if this entity can be leashed. */ + @Override + public boolean canBeLeashed( PlayerEntity player ) { return !isLeashed() && getSpecialData().allowLeashing(); } + + /** Sets this entity 'stuck' inside a block, such as a cobweb or sweet berry bush. Mod blocks could use this as a speed boost. */ + @Override + public void makeStuckInBlock( BlockState block, Vector3d speedMulti ) { + if( getSpecialData().canBeStuckIn( block ) ) super.makeStuckInBlock( block, speedMulti ); + } + + /** @return Called when this mob falls. Calculates and applies fall damage. Returns false if canceled. */ + @Override + public boolean causeFallDamage( float distance, float damageMultiplier ) { + return super.causeFallDamage( distance, damageMultiplier * getSpecialData().getFallDamageMultiplier() ); + } + + /** @return True if this entity should NOT trigger pressure plates or tripwires. */ + @Override + public boolean isIgnoringBlockTriggers() { return getSpecialData().ignorePressurePlates(); } + + /** @return True if this entity can breathe underwater. */ + @Override + public boolean canBreatheUnderwater() { return getSpecialData().canBreatheInWater(); } + + /** @return True if this entity can be pushed by (flowing) fluids. */ + @Override + public boolean isPushedByFluid() { return !getSpecialData().ignoreWaterPush(); } + + /** @return True if this entity takes damage while wet. */ + @Override + public boolean isSensitiveToWater() { return getSpecialData().isDamagedByWater(); } + + /** @return Attempts to damage this entity; returns true if the hit was successful. */ + @Override + public boolean hurt( DamageSource source, float amount ) { + if( isSensitiveToWater() && source.getDirectEntity() instanceof SnowballEntity ) { + amount = Math.max( 3.0F, amount ); + } + return super.hurt( source, amount ); + } + + /** @return True if the effect can be applied to this entity. */ + @Override + public boolean canBeAffected( EffectInstance effect ) { return getSpecialData().isPotionApplicable( effect ); } + + /** Saves data to this entity's base NBT compound that is specific to its subclass. */ + @Override + public void addAdditionalSaveData( CompoundNBT tag ) { + super.addAdditionalSaveData( tag ); + + final CompoundNBT saveTag = SpecialMobData.getSaveLocation( tag ); + + getSpecialData().writeToNBT( saveTag ); + addVariantSaveData( saveTag ); + } + + /** Loads data from this entity's base NBT compound that is specific to its subclass. */ + @Override + public void readAdditionalSaveData( CompoundNBT tag ) { + super.readAdditionalSaveData( tag ); + + final CompoundNBT saveTag = SpecialMobData.getSaveLocation( tag ); + + getSpecialData().readFromNBT( saveTag ); + readVariantSaveData( saveTag ); + + reassessAttackGoal(); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/magmacube/BouncingMagmaCubeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/magmacube/BouncingMagmaCubeEntity.java index c033fe5..ab9a059 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/magmacube/BouncingMagmaCubeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/magmacube/BouncingMagmaCubeEntity.java @@ -6,7 +6,7 @@ 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.entity.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/magmacube/HardenedMagmaCubeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/magmacube/HardenedMagmaCubeEntity.java index ee8eadb..072a323 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/magmacube/HardenedMagmaCubeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/magmacube/HardenedMagmaCubeEntity.java @@ -3,7 +3,7 @@ package fathertoast.specialmobs.common.entity.magmacube; import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; -import fathertoast.specialmobs.common.entity.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/projectile/BugSpitEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/projectile/BugSpitEntity.java index 7ca4d83..df46c2a 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/projectile/BugSpitEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/projectile/BugSpitEntity.java @@ -2,9 +2,10 @@ package fathertoast.specialmobs.common.entity.projectile; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; +import net.minecraft.entity.projectile.ThrowableEntity; import net.minecraft.world.World; -public abstract class BugSpitEntity extends Entity { +public abstract class BugSpitEntity extends ThrowableEntity { public BugSpitEntity( EntityType entityType, World world ) { super( entityType, world ); diff --git a/src/main/java/fathertoast/specialmobs/common/entity/projectile/SpecialFishHookEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/projectile/SpecialFishHookEntity.java index df85c43..c7511e6 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/projectile/SpecialFishHookEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/projectile/SpecialFishHookEntity.java @@ -1,14 +1,23 @@ package fathertoast.specialmobs.common.entity.projectile; -import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.projectile.DamagingProjectileEntity; import net.minecraft.world.World; -public abstract class SpecialFishHookEntity extends Entity { +public class SpecialFishHookEntity extends DamagingProjectileEntity { - public SpecialFishHookEntity( EntityType entityType, World world ) { + protected SpecialFishHookEntity( EntityType entityType, World world ) { super( entityType, world ); } - //TODO + public SpecialFishHookEntity( EntityType entityType, double x, double y, double z, + double dX, double dY, double dZ, World world ) { + super( entityType, x, y, z, dX, dY, dZ, world ); + } + + public SpecialFishHookEntity( EntityType entityType, LivingEntity shooter, + double dX, double dY, double dZ, World world ) { + super( entityType, shooter, dX, dY, dZ, world ); + } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/FlyingSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/FlyingSilverfishEntity.java index d37010b..dfaf187 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/FlyingSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/FlyingSilverfishEntity.java @@ -3,7 +3,7 @@ package fathertoast.specialmobs.common.entity.silverfish; import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; -import fathertoast.specialmobs.common.entity.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/ToughSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/ToughSilverfishEntity.java index 7c1a12b..9b6ac10 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/ToughSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/ToughSilverfishEntity.java @@ -75,7 +75,8 @@ public class ToughSilverfishEntity extends _SpecialSilverfishEntity { } private static final ResourceLocation[] TEXTURES = { - GET_TEXTURE_PATH( "tough" ) + GET_TEXTURE_PATH( "tough" ), + GET_TEXTURE_PATH( "tough_eyes" ) }; /** @return All default textures for this entity. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java index 3c49aa3..4d40a27 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java @@ -7,7 +7,7 @@ import fathertoast.specialmobs.common.core.SpecialMobs; import fathertoast.specialmobs.common.entity.ISpecialMob; import fathertoast.specialmobs.common.entity.SpecialMobData; import fathertoast.specialmobs.common.entity.ai.AIHelper; -import fathertoast.specialmobs.common.entity.ai.SpecialHurtByTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialHurtByTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/FireSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/FireSkeletonEntity.java index e006a4d..3c965b6 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/FireSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/FireSkeletonEntity.java @@ -73,7 +73,8 @@ public class FireSkeletonEntity extends _SpecialSkeletonEntity { } private static final ResourceLocation[] TEXTURES = { - GET_TEXTURE_PATH( "fire" ) + GET_TEXTURE_PATH( "fire" ), + GET_TEXTURE_PATH( "fire_eyes" ) }; /** @return All default textures for this entity. */ 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 d42a37b..0998c8c 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/NinjaSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/NinjaSkeletonEntity.java @@ -5,7 +5,7 @@ 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.entity.ai.goal.NinjaGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; @@ -23,7 +23,6 @@ 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.tileentity.TileEntity; import net.minecraft.util.*; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.vector.Vector3d; @@ -142,16 +141,13 @@ public class NinjaSkeletonEntity extends _SpecialSkeletonEntity implements INinj super.tick(); } + /** Plays an appropriate step sound for this entity based on the floor block. */ @Override - protected void playStepSound( BlockPos pos, BlockState state ) { - // Nope - } + protected void playStepSound( BlockPos pos, BlockState state ) { } // Disable /** @return The sound this entity makes idly. */ @Override - protected SoundEvent getAmbientSound() { - return getHiddenDragon() == null ? null : SoundEvents.SKELETON_AMBIENT; - } + protected SoundEvent getAmbientSound() { return isCrouchingTiger() ? null : super.getAmbientSound(); } /** Moves this entity to a new position and rotation. */ @Override @@ -216,7 +212,6 @@ public class NinjaSkeletonEntity extends _SpecialSkeletonEntity implements INinj private static final DataParameter> HIDING_BLOCK = EntityDataManager.defineId( NinjaSkeletonEntity.class, DataSerializers.BLOCK_STATE ); private boolean canHide = true; - private TileEntity cachedTileEntity = null; /** Called from the Entity.class constructor to define data watcher variables. */ @Override diff --git a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java index 7bd6a74..208c296 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java @@ -12,8 +12,10 @@ import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.ai.attributes.AttributeModifierMap; import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.entity.projectile.SmallFireballEntity; import net.minecraft.item.Items; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; import javax.annotation.ParametersAreNonnullByDefault; @@ -73,9 +75,12 @@ public class SpitfireSkeletonEntity extends _SpecialSkeletonEntity { @Override protected void registerVariantGoals() { getSpecialData().rangedAttackDamage += 2.0F; - getSpecialData().rangedAttackSpread *= 0.5F; } + /** Override to change this entity's chance to spawn with a melee weapon. */ + @Override + protected double getVariantMeleeChance() { return 0.0; } + /** Override to apply effects when this entity hits a target with a melee attack. */ @Override protected void onVariantAttack( Entity target ) { @@ -85,11 +90,32 @@ public class SpitfireSkeletonEntity extends _SpecialSkeletonEntity { /** Called to attack the target with a ranged attack. */ @Override public void performRangedAttack( LivingEntity target, float damageMulti ) { - //TODO + if( !isSilent() ) level.levelEvent( null, References.EVENT_BLAZE_SHOOT, blockPosition(), 0 ); + + final float accelVariance = MathHelper.sqrt( distanceTo( target ) ) * 0.5F * getSpecialData().rangedAttackSpread; + + for( int i = 0; i < 3; i++ ) { + final double dX = target.getX() - getX() + getRandom().nextGaussian() * accelVariance; + final double dY = target.getEyeY() - getEyeY(); + final double dZ = target.getZ() - getZ() + getRandom().nextGaussian() * accelVariance; + + final SmallFireballEntity fireball = new SmallFireballEntity( level, this, dX, dY, dZ ); + fireball.setPos( fireball.getX(), getEyeY() - 0.1, fireball.getZ() ); + level.addFreshEntity( fireball ); + } } + /** Sets this entity as a baby. */ + @Override + public void setBaby( boolean value ) { } + + /** @return True if this entity is a baby. */ + @Override + public boolean isBaby() { return false; } + private static final ResourceLocation[] TEXTURES = { - GET_TEXTURE_PATH( "fire" ) + GET_TEXTURE_PATH( "fire" ), + GET_TEXTURE_PATH( "fire_eyes" ) }; /** @return All default textures for this entity. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/slime/GrapeSlimeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/slime/GrapeSlimeEntity.java index 95a91ad..11c79b7 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/slime/GrapeSlimeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/slime/GrapeSlimeEntity.java @@ -3,7 +3,7 @@ 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.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/slime/WatermelonSlimeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/slime/WatermelonSlimeEntity.java index 64401c7..f3a89b4 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/slime/WatermelonSlimeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/slime/WatermelonSlimeEntity.java @@ -3,7 +3,7 @@ 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.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/spider/FlyingSpiderEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/spider/FlyingSpiderEntity.java index cf20878..d659754 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/spider/FlyingSpiderEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/spider/FlyingSpiderEntity.java @@ -3,7 +3,7 @@ package fathertoast.specialmobs.common.entity.spider; import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; -import fathertoast.specialmobs.common.entity.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/DominationWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/DominationWitchEntity.java new file mode 100644 index 0000000..b10b9cc --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/DominationWitchEntity.java @@ -0,0 +1,122 @@ +package fathertoast.specialmobs.common.entity.witch; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.util.AttributeHelper; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.potion.EffectInstance; +import net.minecraft.potion.Effects; +import net.minecraft.potion.Potions; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.world.World; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.Collection; +import java.util.Collections; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class DominationWitchEntity extends _SpecialWitchEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + return new BestiaryInfo( 0xFFF87E, BestiaryInfo.BaseWeight.LOW ); + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialWitchEntity.createAttributes() ) + .multAttribute( Attributes.MOVEMENT_SPEED, 0.8 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Witch of Domination", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addCommonDrop( "common", Items.EXPERIENCE_BOTTLE ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return DominationWitchEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + private static final Collection LEVITATION_EFFECTS = Collections.singletonList( + new EffectInstance( Effects.LEVITATION, 140, 0 ) ); + + /** Ticks before this witch can use its pull ability. */ + private int pullDelay; + + public DominationWitchEntity( EntityType entityType, World world ) { + super( entityType, world ); + xpReward += 2; + } + + /** Override to modify potion attacks. Return an empty item stack to cancel the potion throw. */ + @Override + protected ItemStack pickVariantThrownPotion( ItemStack originalPotion, LivingEntity target, float damageMulti, float distance ) { + if( !target.hasEffect( Effects.WEAKNESS ) ) { + return makeSplashPotion( Potions.WEAKNESS ); + } + else if( distance > 5.0F && !target.hasEffect( Effects.LEVITATION ) && random.nextFloat() < 0.5F ) { + return makeSplashPotion( LEVITATION_EFFECTS ); + } + return originalPotion; + } + + /** Called each tick to update this entity's movement. */ + @Override + public void aiStep() { + final LivingEntity target = getTarget(); + if( !level.isClientSide() && isAlive() && pullDelay-- <= 0 && target != null && random.nextInt( 20 ) == 0 ) { + + // Pull the player toward this entity if they are vulnerable + final double distanceSq = target.distanceToSqr( this ); + if( distanceSq > 100.0 && distanceSq < 196.0 && + (target.hasEffect( Effects.WEAKNESS ) || target.hasEffect( Effects.LEVITATION )) && canSee( target ) ) { + pullDelay = 100; + + target.setDeltaMovement( new Vector3d( + getX() - target.getX(), + getY() - target.getY(), + getZ() - target.getZ() ) + .scale( 0.32 ) + .add( 0.0, Math.sqrt( Math.sqrt( distanceSq ) ) * 0.1, 0.0 ) ); + target.hurtMarked = true; + } + } + super.aiStep(); + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "domination" ), + GET_TEXTURE_PATH( "domination_eyes" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/ShadowsWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/ShadowsWitchEntity.java new file mode 100644 index 0000000..dc42e5a --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/ShadowsWitchEntity.java @@ -0,0 +1,97 @@ +package fathertoast.specialmobs.common.entity.witch; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.potion.EffectInstance; +import net.minecraft.potion.Effects; +import net.minecraft.potion.PotionUtils; +import net.minecraft.potion.Potions; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.Arrays; +import java.util.Collection; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class ShadowsWitchEntity extends _SpecialWitchEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + return new BestiaryInfo( 0x000000 ); + //TODO theme - forest + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Witch of Shadows", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addCommonDrop( "common", Items.INK_SAC ); + loot.addRareDrop( "rare", PotionUtils.setPotion( new ItemStack( Items.POTION ), Potions.NIGHT_VISION ) ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return ShadowsWitchEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + private static final Collection POTION_SHADOWS = Arrays.asList( + new EffectInstance( Effects.BLINDNESS, 300, 0 ), + new EffectInstance( Effects.WITHER, 200, 0 ) + ); + + public ShadowsWitchEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().addPotionImmunity( Effects.BLINDNESS, Effects.WITHER ); + xpReward += 2; + } + + /** Override to modify potion attacks. Return an empty item stack to cancel the potion throw. */ + @Override + protected ItemStack pickVariantThrownPotion( ItemStack originalPotion, LivingEntity target, float damageMulti, float distance ) { + if( target.getHealth() >= 4.0F && (!target.hasEffect( Effects.BLINDNESS ) || !target.hasEffect( Effects.WITHER )) ) { + return makeSplashPotion( POTION_SHADOWS ); + } + return originalPotion; + } + + /** Called each tick to update this entity's movement. */ + @Override + public void aiStep() { + final LivingEntity target = getTarget(); + if( !level.isClientSide() && isAlive() && target != null && target.hasEffect( Effects.BLINDNESS ) && random.nextInt( 10 ) == 0 ) { + target.removeEffect( Effects.NIGHT_VISION ); // Prevent blind + night vision combo (black screen) + } + super.aiStep(); + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "shadows" ), + GET_TEXTURE_PATH( "shadows_eyes" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/UndeadWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/UndeadWitchEntity.java new file mode 100644 index 0000000..30a999a --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/UndeadWitchEntity.java @@ -0,0 +1,138 @@ +package fathertoast.specialmobs.common.entity.witch; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.entity.skeleton._SpecialSkeletonEntity; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.CreatureAttribute; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.SpawnReason; +import net.minecraft.entity.monster.AbstractSkeletonEntity; +import net.minecraft.inventory.EquipmentSlotType; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.potion.Potions; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundEvents; +import net.minecraft.world.IServerWorld; +import net.minecraft.world.World; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class UndeadWitchEntity extends _SpecialWitchEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + return new BestiaryInfo( 0x799C65 ); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Lich", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addLootTable( "common", EntityType.ZOMBIE.getDefaultLootTable() ); + loot.addUncommonDrop( "uncommon", Items.SKELETON_SPAWN_EGG ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return UndeadWitchEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + /** The number of skeletons this witch can spawn. */ + private int summons; + + public UndeadWitchEntity( EntityType entityType, World world ) { + super( entityType, world ); + xpReward += 2; + + summons = 3 + random.nextInt( 4 ); + } + + /** Override to modify potion attacks. Return an empty item stack to cancel the potion throw. */ + @Override + protected ItemStack pickVariantThrownPotion( ItemStack originalPotion, LivingEntity target, float damageMulti, float distance ) { + if( summons > 0 && random.nextFloat() < (isNearSkeletons() ? 0.25F : 0.75F) ) { + summons--; + + final _SpecialSkeletonEntity skeleton = _SpecialSkeletonEntity.SPECIES.entityType.get().create( level ); + if( skeleton != null ) { + skeleton.copyPosition( this ); + skeleton.yHeadRot = yRot; + skeleton.yBodyRot = yRot; + skeleton.finalizeSpawn( (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ), + SpawnReason.MOB_SUMMONED, null, null ); + skeleton.setItemSlot( EquipmentSlotType.MAINHAND, new ItemStack( Items.IRON_SWORD ) ); + skeleton.setItemSlot( EquipmentSlotType.HEAD, new ItemStack( Items.CHAINMAIL_HELMET ) ); + skeleton.setTarget( getTarget() ); + + final double vX = target.getX() - getX(); + final double vZ = target.getZ() - getZ(); + final double vH = Math.sqrt( vX * vX + vZ * vZ ); + skeleton.setDeltaMovement( + vX / vH * 0.7 + getDeltaMovement().x * 0.2, + 0.4, // Used to cause floor clip bug; remove if it happens again + vZ / vH * 0.7 + getDeltaMovement().z * 0.2 ); + skeleton.setOnGround( false ); + + level.addFreshEntity( skeleton ); + playSound( SoundEvents.BLAZE_SHOOT, 1.0F, 2.0F / (random.nextFloat() * 0.4F + 0.8F) ); + skeleton.spawnAnim(); + + return ItemStack.EMPTY; + } + } + + // Only throw harming potions - heals self and minions, while probably damaging the target + return random.nextFloat() < 0.2F ? makeLingeringPotion( Potions.HARMING ) : makeSplashPotion( Potions.HARMING ); + } + + /** @return True if there are any skeletons near this entity. */ + private boolean isNearSkeletons() { + return level.getEntitiesOfClass( AbstractSkeletonEntity.class, getBoundingBox().inflate( 11.0 ) ).size() > 0; + } + + /** @return This entity's creature type. */ + @Override + public CreatureAttribute getMobType() { return CreatureAttribute.UNDEAD; } + + /** Override to save data to this entity's NBT data. */ + @Override + public void addVariantSaveData( CompoundNBT saveTag ) { + saveTag.putByte( References.TAG_SUMMONS, (byte) summons ); + } + + /** Override to load data from this entity's NBT data. */ + @Override + public void readVariantSaveData( CompoundNBT saveTag ) { + if( saveTag.contains( References.TAG_SUMMONS, References.NBT_TYPE_NUMERICAL ) ) + summons = saveTag.getByte( References.TAG_SUMMONS ); + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "undead" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/WildsWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/WildsWitchEntity.java new file mode 100644 index 0000000..087adcb --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/WildsWitchEntity.java @@ -0,0 +1,226 @@ +package fathertoast.specialmobs.common.entity.witch; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.entity.spider.BabySpiderEntity; +import fathertoast.specialmobs.common.entity.spider._SpecialSpiderEntity; +import fathertoast.specialmobs.common.util.AttributeHelper; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.block.Blocks; +import net.minecraft.entity.*; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.potion.Effects; +import net.minecraft.potion.Potion; +import net.minecraft.potion.PotionUtils; +import net.minecraft.potion.Potions; +import net.minecraft.tags.FluidTags; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundEvents; +import net.minecraft.world.IServerWorld; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class WildsWitchEntity extends _SpecialWitchEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + return new BestiaryInfo( 0xA80E0E ); + //TODO theme - forest + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialWitchEntity.createAttributes() ) + .multAttribute( Attributes.MOVEMENT_SPEED, 0.7 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Witch of the Wilds", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addUncommonDrop( "uncommon", Items.SPIDER_SPAWN_EGG ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return WildsWitchEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + /** The number of spider mounts this witch can spawn. */ + private int spiderMounts; + /** The number of spider swarm attacks this witch can cast. */ + private int spiderSwarms; + /** The number of baby spiders to spawn in each spider swarm attack. */ + private int spiderSwarmSize; + + public WildsWitchEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().addStickyBlockImmunity( Blocks.COBWEB ); + getSpecialData().addPotionImmunity( Effects.POISON ); + xpReward += 1; + + spiderMounts = 1 + random.nextInt( 3 ); + spiderSwarms = 3 + random.nextInt( 4 ); + spiderSwarmSize = 3; + } + + /** Override to modify potion attacks. Return an empty item stack to cancel the potion throw. */ + @Override + protected ItemStack pickVariantThrownPotion( ItemStack originalPotion, LivingEntity target, float damageMulti, float distance ) { + if( spiderSwarms > 0 && random.nextFloat() < 0.33F ) { + spiderSwarms--; + + ILivingEntityData groupData = null; + for( int i = 0; i < spiderSwarmSize; i++ ) { + groupData = spawnBaby( groupData ); + } + spawnAnim(); + playSound( SoundEvents.EGG_THROW, 1.0F, 2.0F / (random.nextFloat() * 0.4F + 0.8F) ); + + return ItemStack.EMPTY; + } + if( !target.hasEffect( Effects.POISON ) ) { + return makeSplashPotion( Potions.STRONG_POISON ); + } + // Save the spiders + final Potion originalType = PotionUtils.getPotion( originalPotion ); + if( originalType == Potions.HARMING || originalType == Potions.STRONG_HARMING ) { + return makeSplashPotion( Potions.STRONG_POISON ); + } + return originalPotion; + } + + /** Helper method to simplify spawning babies. */ + @Nullable + private ILivingEntityData spawnBaby( @Nullable ILivingEntityData groupData ) { + final BabySpiderEntity baby = BabySpiderEntity.SPECIES.entityType.get().create( level ); + if( baby == null ) return groupData; + + baby.copyPosition( this ); + baby.yHeadRot = yRot; + baby.yBodyRot = yRot; + groupData = baby.finalizeSpawn( (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ), + SpawnReason.MOB_SUMMONED, groupData, null ); + baby.setTarget( getTarget() ); + + baby.setDeltaMovement( + (random.nextDouble() - 0.5) * 0.33, + random.nextDouble() * 0.5, // Used to cause floor clip bug; remove if it happens again + (random.nextDouble() - 0.5) * 0.33 ); + baby.setOnGround( false ); + + level.addFreshEntity( baby ); + return groupData; + } + + /** Override to add additional potions this witch can drink if none of the base potions are chosen. */ + @Override + protected void tryVariantUsingPotion() { + final LivingEntity mount = getVehicle() instanceof LivingEntity ? (LivingEntity) getVehicle() : null; + + if( mount != null && random.nextFloat() < 0.15F && mount.isEyeInFluid( FluidTags.WATER ) && + !mount.hasEffect( Effects.WATER_BREATHING ) ) { + usePotion( makeSplashPotion( Potions.WATER_BREATHING ) ); + } + else if( mount != null && random.nextFloat() < 0.15F && (mount.isOnFire() || mount.getLastDamageSource() != null && + mount.getLastDamageSource().isFire()) && !hasEffect( Effects.FIRE_RESISTANCE ) ) { + usePotion( makeSplashPotion( Potions.FIRE_RESISTANCE ) ); + } + else if( mount != null && random.nextFloat() < 0.05F && mount.getMobType() != CreatureAttribute.UNDEAD && + mount.getHealth() < mount.getMaxHealth() ) { + usePotion( makeSplashPotion( Potions.HEALING ) ); + } + // else if( mount != null && random.nextFloat() < 0.5F && getTarget() != null && !mount.hasEffect( Effects.MOVEMENT_SPEED ) && + // getTarget().distanceToSqr( this ) > 121.0 ) { + // usePotion( makeSplashPotion( Potions.SWIFTNESS ) ); // TODO config + // } + else if( spiderMounts > 0 && random.nextFloat() < 0.15F && getVehicle() == null && getTarget() != null && + getTarget().distanceToSqr( this ) > 100.0 ) { + final _SpecialSpiderEntity spider = _SpecialSpiderEntity.SPECIES.entityType.get().create( level ); + if( spider != null ) { + spider.copyPosition( this ); + spider.yHeadRot = yRot; + spider.yBodyRot = yRot; + + if( level.noCollision( spider.getBoundingBox() ) ) { + spiderMounts--; + potionUseCooldownTimer = 40; + + spider.setTarget( getTarget() ); + spider.finalizeSpawn( (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ), + SpawnReason.MOB_SUMMONED, null, null ); + level.addFreshEntity( spider ); + spider.spawnAnim(); + playSound( SoundEvents.BLAZE_SHOOT, 1.0F, 2.0F / (random.nextFloat() * 0.4F + 0.8F) ); + + startRiding( spider, true ); + } + else { + // Cancel spawn; spider is in too small of a space + spider.remove(); + } + } + } + } + + /** Called each tick to update this entity's movement. */ + @Override + public void aiStep() { + // With changes to mount/rider mechanics, is this still needed? + // if( getVehicle() instanceof MobEntity && getTarget() != null && random.nextInt( 10 ) == 0 ) { + // ((MobEntity) getVehicle()).setTarget( getTarget() ); + // } + super.aiStep(); + } + + /** Override to save data to this entity's NBT data. */ + @Override + public void addVariantSaveData( CompoundNBT saveTag ) { + saveTag.putByte( References.TAG_SUMMONS, (byte) spiderMounts ); + saveTag.putByte( References.TAG_BABIES, (byte) spiderSwarms ); + saveTag.putByte( References.TAG_EXTRA_BABIES, (byte) spiderSwarmSize ); + } + + /** Override to load data from this entity's NBT data. */ + @Override + public void readVariantSaveData( CompoundNBT saveTag ) { + if( saveTag.contains( References.TAG_SUMMONS, References.NBT_TYPE_NUMERICAL ) ) + spiderMounts = saveTag.getByte( References.TAG_SUMMONS ); + if( saveTag.contains( References.TAG_BABIES, References.NBT_TYPE_NUMERICAL ) ) + spiderSwarms = saveTag.getByte( References.TAG_BABIES ); + if( saveTag.contains( References.TAG_EXTRA_BABIES, References.NBT_TYPE_NUMERICAL ) ) + spiderSwarmSize = saveTag.getByte( References.TAG_EXTRA_BABIES ); + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "wilds" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/WindWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/WindWitchEntity.java new file mode 100644 index 0000000..ff17bba --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/WindWitchEntity.java @@ -0,0 +1,257 @@ +package fathertoast.specialmobs.common.entity.witch; + +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.AttributeHelper; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.block.BlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +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.pathfinding.PathNavigator; +import net.minecraft.pathfinding.PathNodeType; +import net.minecraft.potion.EffectInstance; +import net.minecraft.potion.Effects; +import net.minecraft.tags.FluidTags; +import net.minecraft.util.*; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.world.World; +import net.minecraftforge.event.ForgeEventFactory; +import net.minecraftforge.event.entity.living.EntityTeleportEvent; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class WindWitchEntity extends _SpecialWitchEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + return new BestiaryInfo( 0x6388B2 ); + //TODO theme - mountain + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialWitchEntity.createAttributes() ) + .addAttribute( Attributes.ATTACK_DAMAGE, 2.0 ) + .multAttribute( Attributes.MOVEMENT_SPEED, 1.2 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Witch of the Wind", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addCommonDrop( "common", Items.FEATHER ); + loot.addSemicommonDrop( "semicommon", Items.ENDER_PEARL ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return WindWitchEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + /** Ticks before this witch can teleport. */ + private int teleportDelay; + + public WindWitchEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().setFallDamageMultiplier( 0.0F ); + xpReward += 2; + + setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() ); + } + + /** Override to change this entity's AI goals. */ + protected void registerVariantGoals() { + AIHelper.replaceWaterAvoidingRandomWalking( this, 1.0 ); + } + + /** @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 ); + } + + /** Called each tick to update this entity's movement. */ + @Override + public void aiStep() { + if( !level.isClientSide() && isAlive() && teleportDelay-- <= 0 && getTarget() != null && random.nextInt( 20 ) == 0 ) { + if( getTarget().distanceToSqr( this ) > 64.0 ) { + for( int i = 0; i < 16; i++ ) { + if( teleportTowards( getTarget() ) ) { + teleportDelay = 60; + removeEffect( Effects.INVISIBILITY ); + break; + } + } + } + else { + addEffect( new EffectInstance( Effects.INVISIBILITY, 30 ) ); + for( int i = 0; i < 16; i++ ) { + if( teleport() ) { + teleportDelay = 30; + break; + } + } + } + } + super.aiStep(); + } + + /** @return Attempts to damage this entity; returns true if the hit was successful. */ + @Override + public boolean hurt( DamageSource source, float amount ) { + if( isInvulnerableTo( source ) || fireImmune() && source.isFire() ) return false; + + if( source instanceof IndirectEntityDamageSource ) { + for( int i = 0; i < 64; i++ ) { + if( teleport() ) return true; + } + return false; + } + + final boolean success = super.hurt( source, amount ); + if( !level.isClientSide() && getHealth() > 0.0F ) { + if( source.getEntity() instanceof LivingEntity ) { + teleportDelay -= 15; + if( teleportDelay <= 0 && random.nextFloat() < 0.5F ) { + for( int i = 0; i < 16; i++ ) { + if( teleport() ) break; + } + } + else { + removeEffect( Effects.INVISIBILITY ); + } + } + else if( random.nextInt( 10 ) != 0 ) { + teleport(); + } + } + return success; + } + + /** @return Teleports this "enderman" to a random nearby position; returns true if successful. */ + protected boolean teleport() { + if( level.isClientSide() || !isAlive() ) return false; + + final double x = getX() + (random.nextDouble() - 0.5) * 20.0; + final double y = getY() + (double) (random.nextInt( 12 ) - 4); + final double z = getZ() + (random.nextDouble() - 0.5) * 20.0; + return teleport( x, y, z ); + } + + /** @return Teleports this "enderman" towards another entity; returns true if successful. */ + protected boolean teleportTowards( Entity target ) { + final Vector3d directionFromTarget = new Vector3d( + getX() - target.getX(), + getY( 0.5 ) - target.getEyeY(), + getZ() - target.getZ() ) + .normalize(); + + final double x = getX() + (random.nextDouble() - 0.5) * 8.0 - directionFromTarget.x * 10.0; + final double y = getY() + (double) (random.nextInt( 8 ) - 2) - directionFromTarget.y * 10.0; + final double z = getZ() + (random.nextDouble() - 0.5) * 8.0 - directionFromTarget.z * 10.0; + return teleport( x, y, z ); + } + + /** @return Teleports this "enderman" to a new position; returns true if successful. */ + protected boolean teleport( double x, double y, double z ) { + final BlockPos.Mutable pos = new BlockPos.Mutable( x, y, z ); + + while( pos.getY() > 0 ) { + // Allow wind witch to teleport on top of water + final BlockState block = level.getBlockState( pos ); + if( block.getMaterial().blocksMotion() || block.getFluidState().is( FluidTags.WATER ) ) { + + final EntityTeleportEvent.EnderEntity event = ForgeEventFactory.onEnderTeleport( this, x, y + 1, z ); + if( event.isCanceled() ) return false; + + final boolean success = uncheckedTeleport( event.getTargetX(), event.getTargetY(), event.getTargetZ(), false ); + if( success && !isSilent() ) { + level.playSound( null, xo, yo, zo, SoundEvents.GHAST_SHOOT, getSoundSource(), + 1.0F, 1.0F ); + playSound( SoundEvents.GHAST_SHOOT, 1.0F, 1.0F ); + } + return success; + } + else { + pos.move( Direction.DOWN ); + y--; + } + } + return false; + } + + /** This is #randomTeleport, but uses a pre-determined y-coord. */ + @SuppressWarnings( "SameParameterValue" ) // Don't care; maintain vanilla's method signature + private boolean uncheckedTeleport( double x, double y, double z, boolean spawnParticles ) { + final double xI = getX(); + final double yI = getY(); + final double zI = getZ(); + + //noinspection deprecation + if( level.hasChunkAt( new BlockPos( x, y, z ) ) ) { + teleportTo( x, y, z ); + + if( level.noCollision( this ) && !level.containsAnyLiquid( getBoundingBox() ) ) { + if( spawnParticles ) level.broadcastEntityEvent( this, References.EVENT_TELEPORT_TRAIL_PARTICLES ); + getNavigation().stop(); + return true; + } + } + teleportTo( xI, yI, zI ); + return false; + } + + /** Override to load data from this entity's NBT data. */ + @Override + public void readVariantSaveData( CompoundNBT saveTag ) { + setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() ); + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "wind" ), + GET_TEXTURE_PATH( "wind_eyes" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java index 0dbf63c..a4354e2 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java @@ -11,21 +11,36 @@ import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; import net.minecraft.block.BlockState; import net.minecraft.entity.*; +import net.minecraft.entity.ai.attributes.AttributeModifier; import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.entity.ai.attributes.ModifiableAttributeInstance; +import net.minecraft.entity.monster.AbstractRaiderEntity; import net.minecraft.entity.monster.WitchEntity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.projectile.PotionEntity; import net.minecraft.entity.projectile.SnowballEntity; +import net.minecraft.inventory.EquipmentSlotType; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; import net.minecraft.nbt.CompoundNBT; import net.minecraft.network.datasync.DataParameter; import net.minecraft.network.datasync.DataSerializers; import net.minecraft.network.datasync.EntityDataManager; -import net.minecraft.potion.EffectInstance; +import net.minecraft.potion.*; +import net.minecraft.tags.FluidTags; import net.minecraft.util.DamageSource; +import net.minecraft.util.IItemProvider; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundEvents; +import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.World; import javax.annotation.ParametersAreNonnullByDefault; +import java.util.Collection; +import java.util.List; +import java.util.UUID; @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault @@ -66,6 +81,7 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe public _SpecialWitchEntity( EntityType entityType, World world ) { super( entityType, world ); + usingTime = Integer.MAX_VALUE; // Effectively disable vanilla witch potion drinking logic in combo with "fake drinking" getSpecialData().initialize(); } @@ -89,7 +105,106 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe /** Override to apply effects when this entity hits a target with a melee attack. */ protected void onVariantAttack( Entity target ) { } - //TODO + /** Called to attack the target with a ranged attack. */ + @Override + public void performRangedAttack( LivingEntity target, float damageMulti ) { + if( isDrinkingPotion() ) return; + + final Vector3d vTarget = target.getDeltaMovement(); + final double dX = target.getX() + vTarget.x - getX(); + final double dY = target.getEyeY() - 1.1 - getY(); + final double dZ = target.getZ() + vTarget.z - getZ(); + final float dH = MathHelper.sqrt( dX * dX + dZ * dZ ); + + final ItemStack potion = pickThrownPotion( target, damageMulti, dH ); + if( potion.isEmpty() ) return; + + final PotionEntity thrownPotion = new PotionEntity( level, this ); + thrownPotion.setItem( potion ); + thrownPotion.xRot += 20.0F; + thrownPotion.shoot( dX, dY + (double) (dH * 0.2F), dZ, 0.75F, 8.0F ); + if( !isSilent() ) { + level.playSound( null, getX(), getY(), getZ(), SoundEvents.WITCH_THROW, getSoundSource(), + 1.0F, 0.8F + random.nextFloat() * 0.4F ); + } + level.addFreshEntity( thrownPotion ); + } + + /** @return A throwable potion item depending on the situation. */ + protected ItemStack pickThrownPotion( LivingEntity target, float damageMulti, float distance ) { + final ItemStack potion; + + // Healing an ally + if( target instanceof AbstractRaiderEntity ) { + if( target.getMobType() == CreatureAttribute.UNDEAD ) { + potion = makeSplashPotion( Potions.HARMING ); + } + else if( target.getHealth() <= 4.0F ) { + potion = makeSplashPotion( Potions.HEALING ); + } + else { + potion = makeSplashPotion( Potions.REGENERATION ); + } + setTarget( null ); + + // Let the variant change the choice or cancel potion throwing + return pickVariantSupportPotion( potion, (AbstractRaiderEntity) target, distance ); + } + + // Attack potions + if( distance >= 8.0F && !target.hasEffect( Effects.MOVEMENT_SLOWDOWN ) ) { + potion = makeSplashPotion( Potions.SLOWNESS ); + } + else if( target.getHealth() >= 8.0F && !target.hasEffect( Effects.POISON ) ) { + potion = makeSplashPotion( Potions.POISON ); + } + else if( distance <= 3.0F && !target.hasEffect( Effects.WEAKNESS ) && random.nextFloat() < 0.25F ) { + potion = makeSplashPotion( Potions.WEAKNESS ); + } + else if( target.getMobType() == CreatureAttribute.UNDEAD ) { + potion = makeSplashPotion( Potions.HEALING ); + } + else { + potion = makeSplashPotion( Potions.HARMING ); + } + // Let the variant change the choice or cancel potion throwing + return pickVariantThrownPotion( potion, target, damageMulti, distance ); + } + + /** Override to modify potion support. Return an empty item stack to cancel the potion throw. */ + protected ItemStack pickVariantSupportPotion( ItemStack originalPotion, AbstractRaiderEntity target, float distance ) { + return originalPotion; + } + + /** Override to modify potion attacks. Return an empty item stack to cancel the potion throw. */ + protected ItemStack pickVariantThrownPotion( ItemStack originalPotion, LivingEntity target, float damageMulti, float distance ) { + return originalPotion; + } + + /** Called each tick while this witch is capable of using a potion on itself. */ + protected void tryUsingPotion() { + if( random.nextFloat() < 0.15F && isEyeInFluid( FluidTags.WATER ) && !hasEffect( Effects.WATER_BREATHING ) ) { + usePotion( makePotion( Potions.WATER_BREATHING ) ); + } + else if( random.nextFloat() < 0.15F && (isOnFire() || getLastDamageSource() != null && getLastDamageSource().isFire()) && + !hasEffect( Effects.FIRE_RESISTANCE ) ) { + usePotion( makePotion( Potions.FIRE_RESISTANCE ) ); + } + else if( random.nextFloat() < 0.05F && getHealth() < getMaxHealth() ) { + usePotion( makePotion( getMobType() == CreatureAttribute.UNDEAD ? Potions.HARMING : Potions.HEALING ) ); + } + else if( random.nextFloat() < 0.5F && getTarget() != null && !hasEffect( Effects.MOVEMENT_SPEED ) && + getTarget().distanceToSqr( this ) > 121.0 ) { + //usePotion( ____ ? makeSplashPotion( Potions.SWIFTNESS ) : makePotion( Potions.SWIFTNESS ) ); + usePotion( makeSplashPotion( Potions.SWIFTNESS ) ); // TODO config + } + else { + tryVariantUsingPotion(); + } + } + + /** Override to add additional potions this witch can drink if none of the base potions are chosen. */ + protected void tryVariantUsingPotion() { } /** Override to save data to this entity's NBT data. */ public void addVariantSaveData( CompoundNBT saveTag ) { } @@ -100,9 +215,24 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe //--------------- Family-Specific Implementations ---------------- + /** The speed penalty to apply while drinking. */ + private static final AttributeModifier DRINKING_SPEED_PENALTY = new AttributeModifier( UUID.fromString( "5CD17E52-A79A-43D3-A529-90FDE04B181E" ), + "Drinking speed penalty", -0.25, AttributeModifier.Operation.ADDITION ); + /** The parameter for special mob render scale. */ private static final DataParameter SCALE = EntityDataManager.defineId( _SpecialWitchEntity.class, DataSerializers.FLOAT ); + /** Used to prevent vanilla code from handling potion-drinking. */ + private boolean fakeDrinkingPotion; + + /** Ticks until this witch finishes drinking. */ + protected int potionDrinkTimer; + /** Ticks until this witch can use another potion on itself. */ + protected int potionUseCooldownTimer; + + /** While the witch is drinking a potion, it stores its 'actual' held item here. */ + public ItemStack sheathedItem = ItemStack.EMPTY; + /** Called from the Entity.class constructor to define data watcher variables. */ @Override protected void defineSynchedData() { @@ -110,7 +240,112 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe specialData = new SpecialMobData<>( this, SCALE, 1.0F ); } - //TODO + /** Called each AI tick to update potion-drinking behavior. */ + public void drinkPotionUpdate() { + potionUseCooldownTimer--; + + if( isDrinkingPotion() ) { + // Complete potion drinking + if( potionDrinkTimer-- <= 0 ) { + final ItemStack drinkingItem = getMainHandItem(); + usePotion( ItemStack.EMPTY ); + + if( drinkingItem.getItem() == Items.POTION ) { + final List effects = PotionUtils.getMobEffects( drinkingItem ); + for( EffectInstance effect : effects ) { + addEffect( new EffectInstance( effect ) ); + } + } + } + } + else if( potionUseCooldownTimer <= 0 ) { + tryUsingPotion(); + } + } + + /** Have this witch use the potion item on itself, or stop drinking if the given 'potion' is an empty stack. */ + public void usePotion( ItemStack potion ) { + // Cancel any current drinking before using a new potion so we don't accidentally delete the sheathed item + if( isDrinkingPotion() && !potion.isEmpty() ) usePotion( ItemStack.EMPTY ); + + if( potion.isEmpty() ) { + // Cancel drinking the current potion and re-equip the sheathed item + if( isDrinkingPotion() ) { + setUsingItem( false ); + potionDrinkTimer = 0; + + final ModifiableAttributeInstance attribute = getAttribute( Attributes.MOVEMENT_SPEED ); + if( attribute != null ) attribute.removeModifier( DRINKING_SPEED_PENALTY ); + + setItemSlot( EquipmentSlotType.MAINHAND, sheathedItem ); + sheathedItem = ItemStack.EMPTY; + } + } + else if( potion.getItem() == Items.POTION ) { + // It is a normal potion, start drinking and sheathe the held item + sheathedItem = getMainHandItem(); + + setItemSlot( EquipmentSlotType.MAINHAND, potion ); + setUsingItem( true ); + potionDrinkTimer = getMainHandItem().getUseDuration(); + + if( !isSilent() ) { + level.playSound( null, getX(), getY(), getZ(), SoundEvents.WITCH_DRINK, getSoundSource(), + 1.0F, 0.8F + random.nextFloat() * 0.4F ); + } + + final ModifiableAttributeInstance attribute = getAttribute( Attributes.MOVEMENT_SPEED ); + if( attribute != null ) { + attribute.removeModifier( DRINKING_SPEED_PENALTY ); + attribute.addTransientModifier( DRINKING_SPEED_PENALTY ); + } + } + else if( potion.getItem() == Items.SPLASH_POTION || potion.getItem() == Items.LINGERING_POTION ) { + // It is a splash or lingering potion, throw it straight down to apply to self + potionUseCooldownTimer = 40; + + final PotionEntity thrownPotion = new PotionEntity( level, this ); + thrownPotion.setItem( potion ); + thrownPotion.xRot += 20.0F; + thrownPotion.shoot( 0.0, -1.0, 0.0, 0.2F, 0.0F ); + if( !isSilent() ) { + level.playSound( null, getX(), getY(), getZ(), SoundEvents.WITCH_THROW, getSoundSource(), + 1.0F, 0.8F + random.nextFloat() * 0.4F ); + } + level.addFreshEntity( thrownPotion ); + } + else { + SpecialMobs.LOG.warn( "Witch {} attempted to use '{}' as a potion! Gross!", getClass().getSimpleName(), potion ); + } + } + + /** @return A new regular potion with standard effects. */ + public ItemStack makePotion( Potion type ) { return newPotion( Items.POTION, type ); } + + /** @return A new regular potion with custom effects. */ + public ItemStack makePotion( Collection effects ) { return newPotion( Items.POTION, effects ); } + + /** @return A new splash potion with standard effects. */ + public ItemStack makeSplashPotion( Potion type ) { return newPotion( Items.SPLASH_POTION, type ); } + + /** @return A new splash potion on self with custom effects. */ + public ItemStack makeSplashPotion( Collection effects ) { return newPotion( Items.SPLASH_POTION, effects ); } + + /** @return A new lingering splash potion with standard effects. */ + public ItemStack makeLingeringPotion( Potion type ) { return newPotion( Items.LINGERING_POTION, type ); } + + /** @return A new lingering splash potion with custom effects. */ + public ItemStack makeLingeringPotion( Collection effects ) { return newPotion( Items.LINGERING_POTION, effects ); } + + /** @return A new potion with standard effects. */ + private ItemStack newPotion( IItemProvider item, Potion type ) { + return PotionUtils.setPotion( new ItemStack( item ), type ); + } + + /** @return A new potion with custom effects. */ + private ItemStack newPotion( IItemProvider item, Collection effects ) { + return PotionUtils.setCustomEffects( new ItemStack( item ), effects ); + } //--------------- ISpecialMob Implementation ---------------- @@ -147,10 +382,25 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe /** Called each tick to update this entity's movement. */ @Override public void aiStep() { + if( !level.isClientSide() && isAlive() ) { + drinkPotionUpdate(); + fakeDrinkingPotion = true; + } super.aiStep(); getSpecialData().tick(); } + /** @return True if this witch is currently drinking a potion. */ + @Override + public boolean isDrinkingPotion() { + // Effectively disable vanilla witch potion drinking logic in combo with "infinite using time" + if( fakeDrinkingPotion ) { + fakeDrinkingPotion = false; + return true; + } + return super.isDrinkingPotion(); + } + /** @return The eye height of this entity when standing. */ @Override protected float getStandingEyeHeight( Pose pose, EntitySize size ) { @@ -219,6 +469,11 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe final CompoundNBT saveTag = SpecialMobData.getSaveLocation( tag ); + final CompoundNBT itemTag = new CompoundNBT(); + if( !sheathedItem.isEmpty() ) sheathedItem.save( itemTag ); + saveTag.put( References.TAG_SHEATHED_ITEM, itemTag ); + saveTag.putShort( References.TAG_POTION_USE_TIME, (short) potionDrinkTimer ); + getSpecialData().writeToNBT( saveTag ); addVariantSaveData( saveTag ); } @@ -230,6 +485,11 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe final CompoundNBT saveTag = SpecialMobData.getSaveLocation( tag ); + if( saveTag.contains( References.TAG_SHEATHED_ITEM, References.NBT_TYPE_COMPOUND ) ) + sheathedItem = ItemStack.of( saveTag.getCompound( References.TAG_SHEATHED_ITEM ) ); + if( saveTag.contains( References.TAG_POTION_USE_TIME, References.NBT_TYPE_NUMERICAL ) ) + potionDrinkTimer = saveTag.getShort( References.TAG_POTION_USE_TIME ); + getSpecialData().readFromNBT( saveTag ); readVariantSaveData( saveTag ); } 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 3e8cac6..2824176 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/NinjaWitherSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/NinjaWitherSkeletonEntity.java @@ -5,7 +5,7 @@ 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.entity.ai.goal.NinjaGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; @@ -23,11 +23,8 @@ 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.tileentity.TileEntity; -import net.minecraft.util.ActionResultType; -import net.minecraft.util.DamageSource; -import net.minecraft.util.Hand; -import net.minecraft.util.ResourceLocation; +import net.minecraft.util.*; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.World; @@ -144,6 +141,14 @@ public class NinjaWitherSkeletonEntity extends _SpecialWitherSkeletonEntity impl super.tick(); } + /** Plays an appropriate step sound for this entity based on the floor block. */ + @Override + protected void playStepSound( BlockPos pos, BlockState state ) { } // Disable + + /** @return The sound this entity makes idly. */ + @Override + protected SoundEvent getAmbientSound() { return isCrouchingTiger() ? null : super.getAmbientSound(); } + /** Moves this entity to a new position and rotation. */ @Override public void moveTo( double x, double y, double z, float yaw, float pitch ) { @@ -207,7 +212,6 @@ public class NinjaWitherSkeletonEntity extends _SpecialWitherSkeletonEntity impl private static final DataParameter> HIDING_BLOCK = EntityDataManager.defineId( NinjaWitherSkeletonEntity.class, DataSerializers.BLOCK_STATE ); private boolean canHide = true; - private TileEntity cachedTileEntity = null; /** Called from the Entity.class constructor to define data watcher variables. */ @Override @@ -242,7 +246,7 @@ public class NinjaWitherSkeletonEntity extends _SpecialWitherSkeletonEntity impl public void setHiddenDragon( @Nullable BlockState block ) { getEntityData().set( HIDING_BLOCK, Optional.ofNullable( block ) ); canHide = false; - + // Smoke puff when emerging from disguise if( block == null ) { spawnAnim(); diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java index dbd0bb7..9402f8a 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java @@ -12,8 +12,10 @@ import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.ai.attributes.AttributeModifierMap; import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.entity.projectile.SmallFireballEntity; import net.minecraft.item.Items; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; import javax.annotation.ParametersAreNonnullByDefault; @@ -62,7 +64,7 @@ public class SpitfireWitherSkeletonEntity extends _SpecialWitherSkeletonEntity { public SpitfireWitherSkeletonEntity( EntityType entityType, World world ) { super( entityType, world ); - getSpecialData().setBaseScale( 1.5F ); + getSpecialData().setBaseScale( 1.8F ); getSpecialData().setDamagedByWater( true ); maxUpStep = 1.0F; xpReward += 2; @@ -72,9 +74,12 @@ public class SpitfireWitherSkeletonEntity extends _SpecialWitherSkeletonEntity { @Override protected void registerVariantGoals() { getSpecialData().rangedAttackDamage += 2.0F; - getSpecialData().rangedAttackSpread *= 0.5F; } + /** Override to change this entity's chance to spawn with a bow. */ + @Override + protected double getVariantBowChance() { return 1.0; } + /** Override to apply effects when this entity hits a target with a melee attack. */ @Override protected void onVariantAttack( Entity target ) { @@ -84,11 +89,32 @@ public class SpitfireWitherSkeletonEntity extends _SpecialWitherSkeletonEntity { /** Called to attack the target with a ranged attack. */ @Override public void performRangedAttack( LivingEntity target, float damageMulti ) { - //TODO + if( !isSilent() ) level.levelEvent( null, References.EVENT_BLAZE_SHOOT, blockPosition(), 0 ); + + final float accelVariance = MathHelper.sqrt( distanceTo( target ) ) * 0.5F * getSpecialData().rangedAttackSpread; + + for( int i = 0; i < 4; i++ ) { + final double dX = target.getX() - getX() + getRandom().nextGaussian() * accelVariance; + final double dY = target.getEyeY() - getEyeY(); + final double dZ = target.getZ() - getZ() + getRandom().nextGaussian() * accelVariance; + + final SmallFireballEntity fireball = new SmallFireballEntity( level, this, dX, dY, dZ ); + fireball.setPos( fireball.getX(), getEyeY() - 0.1, fireball.getZ() ); + level.addFreshEntity( fireball ); + } } + /** Sets this entity as a baby. */ + @Override + public void setBaby( boolean value ) { } + + /** @return True if this entity is a baby. */ + @Override + public boolean isBaby() { return false; } + private static final ResourceLocation[] TEXTURES = { - GET_TEXTURE_PATH( "fire" ) + GET_TEXTURE_PATH( "fire" ), + GET_TEXTURE_PATH( "fire_eyes" ) }; /** @return All default textures for this entity. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/FireZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/FireZombieEntity.java index 4ee921b..33e6060 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/FireZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/FireZombieEntity.java @@ -75,6 +75,10 @@ public class FireZombieEntity extends _SpecialZombieEntity { return arrow; } + /** @return True if this entity should appear to be on fire. */ + @Override + public boolean isOnFire() { return isAlive() && !isInWaterRainOrBubble(); } + /** @return The sound this entity makes idly. */ @Override protected SoundEvent getAmbientSound() { return SoundEvents.HUSK_AMBIENT; } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/MadScientistZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/MadScientistZombieEntity.java index 25c9fe9..d07045e 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/MadScientistZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/MadScientistZombieEntity.java @@ -6,7 +6,7 @@ import fathertoast.specialmobs.common.bestiary.SpecialMob; import fathertoast.specialmobs.common.core.register.SMItems; import fathertoast.specialmobs.common.entity.MobHelper; import fathertoast.specialmobs.common.entity.ai.AIHelper; -import fathertoast.specialmobs.common.entity.ai.SpecialInjectCreeperGoal; +import fathertoast.specialmobs.common.entity.ai.goal.ChargeCreeperGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; @@ -76,8 +76,8 @@ public class MadScientistZombieEntity extends _SpecialZombieEntity { protected void registerVariantGoals() { disableRangedAI(); - AIHelper.insertGoal( goalSelector, 2, new SpecialInjectCreeperGoal<>( - this, getAttributeValue(Attributes.MOVEMENT_SPEED) * 1.25D, 20.0D, + AIHelper.insertGoal( goalSelector, 2, new ChargeCreeperGoal<>( + this, getAttributeValue( Attributes.MOVEMENT_SPEED ) * 1.25D, 20.0D, ( madman, creeper ) -> creeper.isAlive() && !creeper.isPowered() && madman.getSensing().canSee( creeper ) ) ); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java index d699396..dde2b9d 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java @@ -8,7 +8,7 @@ import fathertoast.specialmobs.common.entity.ISpecialMob; import fathertoast.specialmobs.common.entity.MobHelper; import fathertoast.specialmobs.common.entity.SpecialMobData; import fathertoast.specialmobs.common.entity.ai.AIHelper; -import fathertoast.specialmobs.common.entity.ai.SpecialHurtByTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialHurtByTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java index d71a7d4..e34541f 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java @@ -8,7 +8,7 @@ import fathertoast.specialmobs.common.entity.ISpecialMob; import fathertoast.specialmobs.common.entity.MobHelper; import fathertoast.specialmobs.common.entity.SpecialMobData; import fathertoast.specialmobs.common.entity.ai.AIHelper; -import fathertoast.specialmobs.common.entity.ai.SpecialHurtByTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialHurtByTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/java/fathertoast/specialmobs/common/util/References.java b/src/main/java/fathertoast/specialmobs/common/util/References.java index d979f81..d194192 100644 --- a/src/main/java/fathertoast/specialmobs/common/util/References.java +++ b/src/main/java/fathertoast/specialmobs/common/util/References.java @@ -19,11 +19,16 @@ public final class References { public static final int SET_BLOCK_FLAGS = 0b00000011; - //--------------- ENTITY EVENTS ---------------- - // Used in World#broadcastEntityEvent(Entity, byte) then executed by Entity#handleEntityEvent(byte) + //--------------- EVENT CODES ---------------- + // Entity events; used in World#broadcastEntityEvent(Entity, byte) then executed by Entity#handleEntityEvent(byte) public static final byte EVENT_TELEPORT_TRAIL_PARTICLES = 46; + // 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; + //--------------- NBT STUFF ---------------- @@ -65,6 +70,10 @@ public final class References { public static final String TAG_WHEN_BURNING_EXPLODE = "ExplodesWhileBurning"; public static final String TAG_WHEN_SHOT_EXPLODE = "ExplodesWhenShot"; + // Witches + public static final String TAG_SHEATHED_ITEM = "SheathedItem"; + public static final String TAG_POTION_USE_TIME = "PotionUseTimer"; + // Blazes public static final String TAG_BURST_COUNT = "FireballBurstCount"; public static final String TAG_BURST_DELAY = "FireballBurstDelay"; @@ -73,8 +82,9 @@ public final class References { public static final String TAG_IS_BABY = "IsBaby"; // Spawner mobs TODO drowning creeper pufferfish cap? - public static final String TAG_BABIES = "Babies"; // Mother (Cave) Spider - public static final String TAG_EXTRA_BABIES = "ExtraBabies"; // Splitting Creeper, Mother (Cave) Spider + public static final String TAG_BABIES = "Babies"; // Mother (Cave) Spider, Wilds Witch, Queen Ghast, Wildfire Blaze + public static final String TAG_EXTRA_BABIES = "ExtraBabies"; // Splitting Creeper, Mother (Cave) Spider, Wilds Witch + public static final String TAG_SUMMONS = "Summons"; // Undead Witch, Wilds Witch, Queen Ghast, Wildfire Blaze // Growing mobs public static final String TAG_GROWTH_LEVEL = "GrowthLevel"; // Hungry Spider, Conflagration Blaze diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index a985722..0120535 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -23,5 +23,8 @@ protected net.minecraft.entity.monster.CreeperEntity func_190741_do()V # spawnLi protected net.minecraft.entity.monster.EndermanEntity func_70816_c(Lnet/minecraft/entity/Entity;)Z # teleportTowards(Entity) protected net.minecraft.entity.monster.EndermanEntity func_70825_j(DDD)Z # teleport(x,y,z) +# Witches +protected net.minecraft.entity.monster.WitchEntity field_82200_e # usingTime + # Blazes public net.minecraft.entity.monster.BlazeEntity func_70844_e(Z)V # setCharged(value) \ No newline at end of file diff --git a/src/main/resources/assets/specialmobs/textures/entity/creeper/ender_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/creeper/ender_eyes.png index ae525d4..a462311 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entity/creeper/ender_eyes.png and b/src/main/resources/assets/specialmobs/textures/entity/creeper/ender_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/creeper/splitting_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/creeper/splitting_eyes.png index c20ae3d..156890c 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entity/creeper/splitting_eyes.png and b/src/main/resources/assets/specialmobs/textures/entity/creeper/splitting_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/enderman/blinding_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/enderman/blinding_eyes.png index 8258ff2..398f290 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entity/enderman/blinding_eyes.png and b/src/main/resources/assets/specialmobs/textures/entity/enderman/blinding_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/enderman/icy_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/enderman/icy_eyes.png index c44f354..8fc0cdc 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entity/enderman/icy_eyes.png and b/src/main/resources/assets/specialmobs/textures/entity/enderman/icy_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/enderman/lightning_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/enderman/lightning_eyes.png index 6b66d66..0955b06 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entity/enderman/lightning_eyes.png and b/src/main/resources/assets/specialmobs/textures/entity/enderman/lightning_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/enderman/mirage_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/enderman/mirage_eyes.png index 136aba9..ff3d6c0 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entity/enderman/mirage_eyes.png and b/src/main/resources/assets/specialmobs/textures/entity/enderman/mirage_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/enderman/thief_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/enderman/thief_eyes.png index bc545f5..fb6096d 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entity/enderman/thief_eyes.png and b/src/main/resources/assets/specialmobs/textures/entity/enderman/thief_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/silverfish/tough_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/silverfish/tough_eyes.png new file mode 100644 index 0000000..871b3a2 Binary files /dev/null and b/src/main/resources/assets/specialmobs/textures/entity/silverfish/tough_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/skeleton/fire_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/skeleton/fire_eyes.png new file mode 100644 index 0000000..8475359 Binary files /dev/null and b/src/main/resources/assets/specialmobs/textures/entity/skeleton/fire_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/witch/domination_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/witch/domination_eyes.png new file mode 100644 index 0000000..d5438ae Binary files /dev/null and b/src/main/resources/assets/specialmobs/textures/entity/witch/domination_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/witch/shadows_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/witch/shadows_eyes.png new file mode 100644 index 0000000..3f72c22 Binary files /dev/null and b/src/main/resources/assets/specialmobs/textures/entity/witch/shadows_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/witch/wind.png b/src/main/resources/assets/specialmobs/textures/entity/witch/wind.png index ab1a16f..2551d42 100644 Binary files a/src/main/resources/assets/specialmobs/textures/entity/witch/wind.png and b/src/main/resources/assets/specialmobs/textures/entity/witch/wind.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/witch/wind_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/witch/wind_eyes.png new file mode 100644 index 0000000..de46535 Binary files /dev/null and b/src/main/resources/assets/specialmobs/textures/entity/witch/wind_eyes.png differ diff --git a/src/main/resources/assets/specialmobs/textures/entity/witherskeleton/fire_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/witherskeleton/fire_eyes.png new file mode 100644 index 0000000..1e05e3d Binary files /dev/null and b/src/main/resources/assets/specialmobs/textures/entity/witherskeleton/fire_eyes.png differ diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info deleted file mode 100644 index b8966d7..0000000 --- a/src/main/resources/mcmod.info +++ /dev/null @@ -1,16 +0,0 @@ -[ -{ - "modid": "specialmobs", - "name": "Special Mobs", - "description": "Adds variety to vanilla monsters to make enemies more interesting, without breaking the 'Minecraft' feel.", - "version": "${version}", - "mcversion": "${mcversion}", - "url": "", - "updateUrl": "", - "authorList": ["Father Toast"], - "credits": "Mother Toast, for dealing with my shit", - "logoFile": "", - "screenshots": [], - "dependencies": [] -} -]