Merge branch '1.16.5' of https://github.com/FatherToast/SpecialMobs into 1.16.5

This commit is contained in:
Sarinsa 2022-07-03 15:08:05 +02:00
commit e3bd4627a6
72 changed files with 2676 additions and 181 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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. */

View file

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

View file

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

View file

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

View file

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

View file

@ -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. */

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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. */

View file

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

View file

@ -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. */

View file

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

View file

@ -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. */

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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. */

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

View file

@ -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": []
}
]