Merge branch '1.16.5' of https://github.com/FatherToast/SpecialMobs into 1.16.5
|
@ -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" ) );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -55,11 +55,11 @@ public class MobFamily<T extends LivingEntity> {
|
|||
|
||||
public static final MobFamily<AbstractSkeletonEntity> 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<AbstractSkeletonEntity> 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<SlimeEntity> SLIME = new MobFamily<>(
|
||||
|
@ -90,15 +90,15 @@ public class MobFamily<T extends LivingEntity> {
|
|||
"Blinding", "Icy", "Lightning", "Mini", "Mirage", "Thief"
|
||||
);
|
||||
|
||||
// public static final MobFamily<WitchEntity> 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<WitchEntity> WITCH = new MobFamily<>(
|
||||
"Witch", "witches", 0x340000, new EntityType[] { EntityType.WITCH },
|
||||
"Domination", "Shadows", "Undead", "Wilds", "Wind"
|
||||
);
|
||||
|
||||
// public static final MobFamily<GhastEntity> GHAST = new MobFamily<>(
|
||||
// "Ghast", "ghasts", 0xF9F9F9, new EntityType[] { EntityType.GHAST },
|
||||
// "Baby", "Fighter", "King", "Queen", "Unholy"
|
||||
// );
|
||||
public static final MobFamily<GhastEntity> GHAST = new MobFamily<>(
|
||||
"Ghast", "ghasts", 0xF9F9F9, new EntityType[] { EntityType.GHAST },
|
||||
"Baby", "Fighter", "King", "Queen", "Unholy"
|
||||
);
|
||||
|
||||
public static final MobFamily<BlazeEntity> BLAZE = new MobFamily<>(
|
||||
"Blaze", "blazes", 0xF6B201, new EntityType[] { EntityType.BLAZE },
|
||||
|
|
|
@ -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,8 +63,40 @@ public class ToastConfigSpec {
|
|||
public void initialize() {
|
||||
SpecialMobs.LOG.info( "First-time loading config file {}", CONFIG_FILE.getFile() );
|
||||
firstLoad = true;
|
||||
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. */
|
||||
public void onLoad() {
|
||||
|
@ -70,8 +108,13 @@ public class ToastConfigSpec {
|
|||
// Only rewrite on first load or if one of the load actions requests it
|
||||
if( rewrite || firstLoad ) {
|
||||
firstLoad = false;
|
||||
try {
|
||||
CONFIG_FILE.save();
|
||||
}
|
||||
catch( WritingException ex ) {
|
||||
SpecialMobs.LOG.error( "Failed to save config file {}", CONFIG_FILE.getFile() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Writes the current state of the config to file. */
|
||||
|
@ -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. */
|
||||
|
@ -296,11 +339,11 @@ public class ToastConfigSpec {
|
|||
public void header( List<String> 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<String> 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() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,7 +18,7 @@ public class ToastTomlParser implements ConfigParser<CommentedConfig> {
|
|||
/** 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; }
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
|
||||
|
@ -131,8 +128,8 @@ public class SpecialMobs {
|
|||
// 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 ) {
|
||||
|
|
|
@ -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<? extends Goal> 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() );
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,20 +1,18 @@
|
|||
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<T extends MadScientistZombieEntity> extends Goal {
|
||||
public class ChargeCreeperGoal<T extends MadScientistZombieEntity> extends Goal {
|
||||
|
||||
private final BiPredicate<T, ? super CreeperEntity> targetPredicate;
|
||||
|
||||
|
@ -28,16 +26,16 @@ public class SpecialInjectCreeperGoal<T extends MadScientistZombieEntity> extend
|
|||
private boolean canUseWhileMounted = false;
|
||||
|
||||
|
||||
public SpecialInjectCreeperGoal(T madman, double movementSpeed, double targetRange, BiPredicate<T, ? super CreeperEntity> targetPredicate) {
|
||||
public ChargeCreeperGoal( T madman, double movementSpeed, double targetRange, BiPredicate<T, ? super CreeperEntity> 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<T> canUseWhileMounted() {
|
||||
public ChargeCreeperGoal<T> canUseWhileMounted() {
|
||||
canUseWhileMounted = true;
|
||||
return this;
|
||||
}
|
||||
|
@ -52,11 +50,11 @@ public class SpecialInjectCreeperGoal<T extends MadScientistZombieEntity> extend
|
|||
|
||||
private void findCreeper() {
|
||||
World world = madman.level;
|
||||
List<CreeperEntity> nearbyCreepers = world.getLoadedEntitiesOfClass(CreeperEntity.class, madman.getBoundingBox().inflate(targetRange), null);
|
||||
List<CreeperEntity> nearbyCreepers = world.getLoadedEntitiesOfClass( CreeperEntity.class, madman.getBoundingBox().inflate( targetRange ), null );
|
||||
|
||||
if (!nearbyCreepers.isEmpty()) {
|
||||
for (CreeperEntity creeper : nearbyCreepers) {
|
||||
if (targetPredicate.test(madman, creeper)) {
|
||||
if( !nearbyCreepers.isEmpty() ) {
|
||||
for( CreeperEntity creeper : nearbyCreepers ) {
|
||||
if( targetPredicate.test( madman, creeper ) ) {
|
||||
this.creeper = creeper;
|
||||
break;
|
||||
}
|
||||
|
@ -67,7 +65,7 @@ public class SpecialInjectCreeperGoal<T extends MadScientistZombieEntity> extend
|
|||
/** 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. */
|
||||
|
@ -79,21 +77,21 @@ public class SpecialInjectCreeperGoal<T extends MadScientistZombieEntity> extend
|
|||
/** 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());
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
@ -203,7 +204,7 @@ public class NinjaGoal<T extends MobEntity & INinja> 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. */
|
|
@ -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;
|
|
@ -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) );
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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 ) {
|
||||
|
|
|
@ -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<? extends _SpecialBlazeEntity> 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 = {
|
||||
|
|
|
@ -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<LivingEntity> 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<Float> 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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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<BabyGhastEntity> SPECIES;
|
||||
|
||||
@SpecialMob.BestiaryInfoSupplier
|
||||
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> 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<BabyGhastEntity> getVariantFactory() { return BabyGhastEntity::new; }
|
||||
|
||||
|
||||
//--------------- Variant-Specific Implementations ----------------
|
||||
|
||||
public BabyGhastEntity( EntityType<? extends _SpecialGhastEntity> 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
|
||||
}
|
|
@ -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<FighterGhastEntity> SPECIES;
|
||||
|
||||
@SpecialMob.BestiaryInfoSupplier
|
||||
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> 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<FighterGhastEntity> getVariantFactory() { return FighterGhastEntity::new; }
|
||||
|
||||
|
||||
//--------------- Variant-Specific Implementations ----------------
|
||||
|
||||
public FighterGhastEntity( EntityType<? extends _SpecialGhastEntity> 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; }
|
||||
}
|
|
@ -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<KingGhastEntity> SPECIES;
|
||||
|
||||
@SpecialMob.BestiaryInfoSupplier
|
||||
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> 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<KingGhastEntity> getVariantFactory() { return KingGhastEntity::new; }
|
||||
|
||||
|
||||
//--------------- Variant-Specific Implementations ----------------
|
||||
|
||||
public KingGhastEntity( EntityType<? extends _SpecialGhastEntity> 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; }
|
||||
}
|
|
@ -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<QueenGhastEntity> SPECIES;
|
||||
|
||||
@SpecialMob.BestiaryInfoSupplier
|
||||
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> 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<QueenGhastEntity> 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<? extends _SpecialGhastEntity> 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; }
|
||||
}
|
|
@ -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<UnholyGhastEntity> SPECIES;
|
||||
|
||||
@SpecialMob.BestiaryInfoSupplier
|
||||
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> 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<UnholyGhastEntity> getVariantFactory() { return UnholyGhastEntity::new; }
|
||||
|
||||
|
||||
//--------------- Variant-Specific Implementations ----------------
|
||||
|
||||
public UnholyGhastEntity( EntityType<? extends _SpecialGhastEntity> 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; }
|
||||
}
|
|
@ -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<LivingEntity> 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<? extends _SpecialGhastEntity> 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<Float> 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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<? extends BugSpitEntity> entityType, World world ) {
|
||||
super( entityType, world );
|
||||
|
|
|
@ -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<? extends SpecialFishHookEntity> entityType, World world ) {
|
||||
protected SpecialFishHookEntity( EntityType<? extends SpecialFishHookEntity> entityType, World world ) {
|
||||
super( entityType, world );
|
||||
}
|
||||
|
||||
//TODO
|
||||
public SpecialFishHookEntity( EntityType<? extends SpecialFishHookEntity> 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<? extends SpecialFishHookEntity> entityType, LivingEntity shooter,
|
||||
double dX, double dY, double dZ, World world ) {
|
||||
super( entityType, shooter, dX, dY, dZ, world );
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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<Optional<BlockState>> 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
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<DominationWitchEntity> SPECIES;
|
||||
|
||||
@SpecialMob.BestiaryInfoSupplier
|
||||
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> 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<DominationWitchEntity> getVariantFactory() { return DominationWitchEntity::new; }
|
||||
|
||||
|
||||
//--------------- Variant-Specific Implementations ----------------
|
||||
|
||||
private static final Collection<EffectInstance> 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<? extends _SpecialWitchEntity> 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; }
|
||||
}
|
|
@ -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<ShadowsWitchEntity> SPECIES;
|
||||
|
||||
@SpecialMob.BestiaryInfoSupplier
|
||||
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> 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<ShadowsWitchEntity> getVariantFactory() { return ShadowsWitchEntity::new; }
|
||||
|
||||
|
||||
//--------------- Variant-Specific Implementations ----------------
|
||||
|
||||
private static final Collection<EffectInstance> POTION_SHADOWS = Arrays.asList(
|
||||
new EffectInstance( Effects.BLINDNESS, 300, 0 ),
|
||||
new EffectInstance( Effects.WITHER, 200, 0 )
|
||||
);
|
||||
|
||||
public ShadowsWitchEntity( EntityType<? extends _SpecialWitchEntity> 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; }
|
||||
}
|
|
@ -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<UndeadWitchEntity> SPECIES;
|
||||
|
||||
@SpecialMob.BestiaryInfoSupplier
|
||||
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> 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<UndeadWitchEntity> getVariantFactory() { return UndeadWitchEntity::new; }
|
||||
|
||||
|
||||
//--------------- Variant-Specific Implementations ----------------
|
||||
|
||||
/** The number of skeletons this witch can spawn. */
|
||||
private int summons;
|
||||
|
||||
public UndeadWitchEntity( EntityType<? extends _SpecialWitchEntity> 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; }
|
||||
}
|
|
@ -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<WildsWitchEntity> SPECIES;
|
||||
|
||||
@SpecialMob.BestiaryInfoSupplier
|
||||
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> 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<WildsWitchEntity> 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<? extends _SpecialWitchEntity> 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; }
|
||||
}
|
|
@ -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<WindWitchEntity> SPECIES;
|
||||
|
||||
@SpecialMob.BestiaryInfoSupplier
|
||||
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> 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<WindWitchEntity> getVariantFactory() { return WindWitchEntity::new; }
|
||||
|
||||
|
||||
//--------------- Variant-Specific Implementations ----------------
|
||||
|
||||
/** Ticks before this witch can teleport. */
|
||||
private int teleportDelay;
|
||||
|
||||
public WindWitchEntity( EntityType<? extends _SpecialWitchEntity> 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; }
|
||||
}
|
|
@ -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<? extends _SpecialWitchEntity> 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<Float> 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<EffectInstance> 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<EffectInstance> 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<EffectInstance> 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<EffectInstance> 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<EffectInstance> 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 );
|
||||
}
|
||||
|
|
|
@ -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<Optional<BlockState>> 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
|
||||
|
|
|
@ -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<? extends _SpecialWitherSkeletonEntity> 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. */
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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 ) ) );
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 292 B |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 290 B |
|
@ -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": []
|
||||
}
|
||||
]
|