mirror of
https://github.com/FatherToast/SpecialMobs.git
synced 2025-04-25 06:45:11 +00:00
Angler & ninja boilerplates
This commit is contained in:
parent
5bf6e82a87
commit
2130486ff1
11 changed files with 795 additions and 1 deletions
|
@ -0,0 +1,34 @@
|
|||
package fathertoast.specialmobs.client;
|
||||
|
||||
import fathertoast.specialmobs.common.entity.ai.IAngler;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
import net.minecraft.entity.LivingEntity;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.item.FishingRodItem;
|
||||
import net.minecraft.item.IItemPropertyGetter;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Item property getter override that allows the fishing rod item animation to function for any entity implementing IAngler.
|
||||
*/
|
||||
@OnlyIn( Dist.CLIENT )
|
||||
public class FishingRodItemPropertyGetter implements IItemPropertyGetter {
|
||||
|
||||
@Override
|
||||
public float call( @Nonnull ItemStack stack, @Nullable ClientWorld world, @Nullable LivingEntity entity ) {
|
||||
if( entity == null ) return 0.0F;
|
||||
|
||||
boolean inMainHand = entity.getMainHandItem() == stack;
|
||||
boolean inOffhand = entity.getOffhandItem() == stack && !(entity.getMainHandItem().getItem() instanceof FishingRodItem);
|
||||
|
||||
return (inMainHand || inOffhand) && (
|
||||
entity instanceof PlayerEntity && ((PlayerEntity) entity).fishing != null ||
|
||||
entity instanceof IAngler && ((IAngler) entity).isLineOut() // Line added to vanilla logic
|
||||
) ? 1.0F : 0.0F;
|
||||
}
|
||||
}
|
|
@ -68,6 +68,7 @@ public class SpecialMobs {
|
|||
* o ranged attack AI
|
||||
* - silverfish
|
||||
* ? ranged attack AI
|
||||
* + puffer
|
||||
* - endermen
|
||||
* o witches
|
||||
* o ability to equip held items
|
||||
|
|
|
@ -11,7 +11,7 @@ import java.util.ArrayList;
|
|||
*/
|
||||
public final class AIHelper {
|
||||
|
||||
/** Inserts an AI goal at the specified priority. Existing goals have their priority adjusted accordingly. */
|
||||
/** Inserts an AI goal at the specified priority. Existing goals have their priority incremented accordingly. */
|
||||
public static void insertGoal( GoalSelector ai, int priority, Goal goal ) {
|
||||
for( PrioritizedGoal task : new ArrayList<>( ai.availableGoals ) ) {
|
||||
if( task.getPriority() >= priority ) task.priority++;
|
||||
|
@ -19,6 +19,14 @@ public final class AIHelper {
|
|||
ai.addGoal( priority, goal );
|
||||
}
|
||||
|
||||
/** Inserts an AI goal at the specified priority. Existing goals have their priority decremented accordingly. */
|
||||
public static void insertGoalReverse( GoalSelector ai, int priority, Goal goal ) {
|
||||
for( PrioritizedGoal task : new ArrayList<>( ai.availableGoals ) ) {
|
||||
if( task.getPriority() <= priority ) task.priority--;
|
||||
}
|
||||
ai.addGoal( priority, goal );
|
||||
}
|
||||
|
||||
/** Removes all goals with the specified priority. */
|
||||
public static void removeGoals( GoalSelector ai, int priority ) {
|
||||
for( PrioritizedGoal task : new ArrayList<>( ai.availableGoals ) ) {
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package fathertoast.specialmobs.common.entity.ai;
|
||||
|
||||
/**
|
||||
* Monsters must implement this interface to shoot fish hooks.
|
||||
* This allows get and set methods for the fish hook so that the server can communicate rendering info to the client.
|
||||
*/
|
||||
public interface IAngler {
|
||||
|
||||
/** Sets this angler's line as out (or in). */
|
||||
void setLineOut( boolean value );
|
||||
|
||||
/** @return Whether this angler's line is out. */
|
||||
boolean isLineOut();
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package fathertoast.specialmobs.common.entity.ai;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Monsters must implement this interface to use the ninja goal AI.
|
||||
* This allows get and set methods for the disguise block and immobile state.
|
||||
*/
|
||||
public interface INinja {
|
||||
|
||||
/** @return Whether this ninja is currently immobile. */
|
||||
boolean isImmobile();
|
||||
|
||||
/** Sets this ninja's immovable state. When activated, the entity is 'snapped' to the nearest block position. */
|
||||
void setImmobile( boolean value );
|
||||
|
||||
/** @return The block being hidden (rendered) as, or null if not hiding. */
|
||||
@Nullable
|
||||
BlockState getDisguiseBlock();
|
||||
|
||||
/** Sets the block being hidden (rendered) as, set to null to cancel hiding. */
|
||||
void setDisguiseBlock( @Nullable BlockState block );
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package fathertoast.specialmobs.common.entity.projectile;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityType;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public abstract class BugSpitEntity extends Entity {
|
||||
|
||||
public BugSpitEntity( EntityType<? extends BugSpitEntity> entityType, World world ) {
|
||||
super( entityType, world );
|
||||
}
|
||||
|
||||
//TODO
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package fathertoast.specialmobs.common.entity.projectile;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityType;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public abstract class SpecialFishHookEntity extends Entity {
|
||||
|
||||
public SpecialFishHookEntity( EntityType<? extends SpecialFishHookEntity> entityType, World world ) {
|
||||
super( entityType, world );
|
||||
}
|
||||
|
||||
//TODO
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package fathertoast.specialmobs.common.entity.silverfish;
|
||||
|
||||
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
|
||||
import fathertoast.specialmobs.common.bestiary.SpecialMob;
|
||||
import fathertoast.specialmobs.common.entity.ai.IAngler;
|
||||
import fathertoast.specialmobs.common.util.AttributeHelper;
|
||||
import fathertoast.specialmobs.common.util.References;
|
||||
import fathertoast.specialmobs.datagen.loot.LootEntryItemBuilder;
|
||||
import fathertoast.specialmobs.datagen.loot.LootPoolBuilder;
|
||||
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 FishingSilverfishEntity extends _SpecialSilverfishEntity implements IAngler {
|
||||
|
||||
//--------------- Static Special Mob Hooks ----------------
|
||||
|
||||
@SpecialMob.BestiaryInfoSupplier
|
||||
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> entityType ) {
|
||||
entityType.sized( 0.5F, 0.4F );
|
||||
return new BestiaryInfo( 0x2D41F4 );
|
||||
//TODO theme - fishing
|
||||
}
|
||||
|
||||
@SpecialMob.AttributeCreator
|
||||
public static AttributeModifierMap.MutableAttribute createAttributes() {
|
||||
return AttributeHelper.of( _SpecialSilverfishEntity.createAttributes() )
|
||||
.addAttribute( Attributes.MAX_HEALTH, 4.0 )
|
||||
.multAttribute( Attributes.MOVEMENT_SPEED, 0.9 )
|
||||
.build();
|
||||
}
|
||||
|
||||
@SpecialMob.LanguageProvider
|
||||
public static String[] getTranslations( String langKey ) {
|
||||
return References.translations( langKey, "Fishing Silverfish",
|
||||
"", "", "", "", "", "" );//TODO
|
||||
}
|
||||
|
||||
@SpecialMob.LootTableProvider
|
||||
public static void buildLootTable( LootTableBuilder loot ) {
|
||||
addBaseLoot( loot );
|
||||
loot.addPool( new LootPoolBuilder( "common" )
|
||||
.addEntry( new LootEntryItemBuilder( Items.COD ).setCount( 0, 2 ).addLootingBonus( 0, 1 ).smeltIfBurning().toLootEntry() )
|
||||
.toLootPool() );
|
||||
}
|
||||
|
||||
@SpecialMob.Constructor
|
||||
public FishingSilverfishEntity( EntityType<? extends _SpecialSilverfishEntity> entityType, World world ) {
|
||||
super( entityType, world );
|
||||
getSpecialData().setBaseScale( 1.2F );
|
||||
getSpecialData().setCanBreatheInWater( true );
|
||||
getSpecialData().setIgnoreWaterPush( true );
|
||||
xpReward += 2;
|
||||
}
|
||||
|
||||
|
||||
//--------------- Variant-Specific Implementations ----------------
|
||||
|
||||
/** Override to change this entity's AI goals. */
|
||||
@Override
|
||||
protected void registerVariantGoals() {
|
||||
getSpecialData().rangedAttackSpread = 10.0F;
|
||||
getSpecialData().rangedAttackCooldown = 32;
|
||||
getSpecialData().rangedAttackMaxCooldown = 48;
|
||||
getSpecialData().rangedAttackMaxRange = 10.0F;
|
||||
|
||||
//TODO add angler AI @ 4
|
||||
}
|
||||
|
||||
private static final ResourceLocation[] TEXTURES = {
|
||||
GET_TEXTURE_PATH( "fishing" )
|
||||
};
|
||||
|
||||
/** @return All default textures for this entity. */
|
||||
@Override
|
||||
public ResourceLocation[] getDefaultTextures() { return TEXTURES; }
|
||||
|
||||
|
||||
//--------------- IAngler Implementations ----------------
|
||||
|
||||
/** Sets this angler's line as out (or in). */
|
||||
@Override
|
||||
public void setLineOut( boolean value ) { }
|
||||
|
||||
/** @return Whether this angler's line is out. */
|
||||
@Override
|
||||
public boolean isLineOut() { return false; }
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
package fathertoast.specialmobs.common.entity.skeleton;
|
||||
|
||||
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
|
||||
import fathertoast.specialmobs.common.bestiary.SpecialMob;
|
||||
import fathertoast.specialmobs.common.entity.ai.INinja;
|
||||
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.block.Blocks;
|
||||
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.entity.player.PlayerEntity;
|
||||
import net.minecraft.network.datasync.DataParameter;
|
||||
import net.minecraft.network.datasync.DataSerializers;
|
||||
import net.minecraft.network.datasync.EntityDataManager;
|
||||
import net.minecraft.util.ActionResultType;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.Hand;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.util.Optional;
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
@SpecialMob
|
||||
public class NinjaSkeletonEntity extends _SpecialSkeletonEntity implements INinja {
|
||||
|
||||
//--------------- Static Special Mob Hooks ----------------
|
||||
|
||||
@SpecialMob.BestiaryInfoSupplier
|
||||
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> entityType ) {
|
||||
return new BestiaryInfo( 0x333366 );
|
||||
}
|
||||
|
||||
@SpecialMob.AttributeCreator
|
||||
public static AttributeModifierMap.MutableAttribute createAttributes() {
|
||||
return AttributeHelper.of( _SpecialSkeletonEntity.createAttributes() )
|
||||
.multAttribute( Attributes.MOVEMENT_SPEED, 1.2 )
|
||||
.build();
|
||||
}
|
||||
|
||||
@SpecialMob.LanguageProvider
|
||||
public static String[] getTranslations( String langKey ) {
|
||||
return References.translations( langKey, "Skeleton Ninja",
|
||||
"", "", "", "", "", "" );//TODO
|
||||
}
|
||||
|
||||
@SpecialMob.LootTableProvider
|
||||
public static void buildLootTable( LootTableBuilder loot ) {
|
||||
addBaseLoot( loot );
|
||||
loot.addUncommonDrop( "uncommon", Blocks.INFESTED_STONE, Blocks.INFESTED_COBBLESTONE, Blocks.INFESTED_STONE_BRICKS,
|
||||
Blocks.INFESTED_CRACKED_STONE_BRICKS, Blocks.INFESTED_MOSSY_STONE_BRICKS, Blocks.INFESTED_CHISELED_STONE_BRICKS );
|
||||
}
|
||||
|
||||
@SpecialMob.Constructor
|
||||
public NinjaSkeletonEntity( EntityType<? extends _SpecialSkeletonEntity> entityType, World world ) {
|
||||
super( entityType, world );
|
||||
xpReward += 2;
|
||||
}
|
||||
|
||||
|
||||
//--------------- Variant-Specific Implementations ----------------
|
||||
|
||||
/** Override to change this entity's AI goals. */
|
||||
@Override
|
||||
protected void registerVariantGoals() {
|
||||
setRangedAI( 1.0, 10, 9.0F );
|
||||
|
||||
//TODO AIHelper.insertGoalReverse( goalSelector, getVariantAttackPriority() - 1, null );
|
||||
}
|
||||
|
||||
/** Called during spawn finalization to set starting equipment. */
|
||||
@Override
|
||||
protected void populateDefaultEquipmentSlots( DifficultyInstance difficulty ) {
|
||||
super.populateDefaultEquipmentSlots( difficulty );
|
||||
setCanPickUpLoot( true );
|
||||
}
|
||||
|
||||
/** Override to change this entity's chance to spawn with a melee weapon. */
|
||||
@Override
|
||||
protected double getVariantMeleeChance() { return 0.5; }
|
||||
|
||||
/** Override to apply effects when this entity hits a target with a melee attack. */
|
||||
@Override
|
||||
protected void onVariantAttack( Entity target ) {
|
||||
revealTo( target );
|
||||
}
|
||||
|
||||
/** @return Attempts to damage this entity; returns true if the hit was successful. */
|
||||
@Override
|
||||
public boolean hurt( DamageSource source, float amount ) {
|
||||
if( super.hurt( source, amount ) ) {
|
||||
revealTo( source.getEntity() );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @return Interacts (right click) with this entity and returns the result. */
|
||||
@Override
|
||||
public ActionResultType mobInteract( PlayerEntity player, Hand hand ) {
|
||||
// Attack if the player tries to right click the "block"
|
||||
if( !level.isClientSide() && getDisguiseBlock() != null ) revealTo( player );
|
||||
return super.mobInteract( player, hand );
|
||||
}
|
||||
|
||||
/** Called each tick to update this entity. */
|
||||
@Override
|
||||
public void tick() {
|
||||
// TODO can this be moved to the ninja AI?
|
||||
if( !level.isClientSide() ) {
|
||||
if( canHide ) {
|
||||
//EntityAINinja.startHiding( this ); TODO
|
||||
}
|
||||
else if( onGround && getDisguiseBlock() == null &&
|
||||
(getTarget() == null || getTarget() instanceof PlayerEntity && ((PlayerEntity) getTarget()).isCreative()) ) {
|
||||
canHide = true;
|
||||
}
|
||||
}
|
||||
super.tick();
|
||||
}
|
||||
|
||||
// // Moves this entity.
|
||||
// @Override TODO
|
||||
// public void move( MoverType type, double x, double y, double z ) {
|
||||
// if( isImmobile() && type != MoverType.PISTON ) {
|
||||
// motionY = 0.0;
|
||||
// }
|
||||
// else {
|
||||
// super.move( type, x, y, z );
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Returns true if this entity should push and be pushed by other entities when colliding.
|
||||
// @Override
|
||||
// public boolean canBePushed() { return !isImmobile(); }
|
||||
|
||||
/** Sets this entity on fire for a specific duration. */
|
||||
@Override
|
||||
public void setRemainingFireTicks( int ticks ) {
|
||||
if( !isImmobile() ) super.setRemainingFireTicks( ticks );
|
||||
}
|
||||
|
||||
/** Reveals this ninja and sets its target so that it doesn't immediately re-disguise itself. */
|
||||
public void revealTo( @Nullable Entity target ) {
|
||||
setDisguiseBlock( null );
|
||||
if( target instanceof LivingEntity ) setTarget( (LivingEntity) target );
|
||||
}
|
||||
|
||||
private static final ResourceLocation[] TEXTURES = {
|
||||
new ResourceLocation( "textures/entity/skeleton/skeleton.png" ),
|
||||
null,
|
||||
GET_TEXTURE_PATH( "ninja_overlay" )
|
||||
};
|
||||
|
||||
/** @return All default textures for this entity. */
|
||||
@Override
|
||||
public ResourceLocation[] getDefaultTextures() { return TEXTURES; }
|
||||
|
||||
|
||||
//--------------- INinja Implementations ----------------
|
||||
|
||||
/** The parameter for the ninja immobile state. */
|
||||
private static final DataParameter<Boolean> IS_HIDING = EntityDataManager.defineId( NinjaSkeletonEntity.class, DataSerializers.BOOLEAN );
|
||||
/** The parameter for the ninja disguise block. */
|
||||
private static final DataParameter<Optional<BlockState>> HIDING_BLOCK = EntityDataManager.defineId( NinjaSkeletonEntity.class, DataSerializers.BLOCK_STATE );
|
||||
|
||||
private boolean canHide = true;
|
||||
|
||||
/** Called from the Entity.class constructor to define data watcher variables. */
|
||||
@Override
|
||||
protected void defineSynchedData() {
|
||||
super.defineSynchedData();
|
||||
entityData.define( IS_HIDING, false );
|
||||
entityData.define( HIDING_BLOCK, Optional.empty() );
|
||||
}
|
||||
|
||||
/** @return Whether this ninja is currently immobile. */
|
||||
@Override
|
||||
public boolean isImmobile() { return getEntityData().get( IS_HIDING ); }
|
||||
|
||||
/** Sets this ninja's immovable state. When activated, the entity is 'snapped' to the nearest block position. */
|
||||
@Override
|
||||
public void setImmobile( boolean value ) {
|
||||
if( value != isImmobile() ) {
|
||||
getEntityData().set( IS_HIDING, value );
|
||||
if( value ) {
|
||||
clearFire();
|
||||
moveTo( Math.floor( getX() ) + 0.5, Math.floor( getY() ), Math.floor( getZ() ) + 0.5 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @return The block being hidden (rendered) as, or null if not hiding. */
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockState getDisguiseBlock() {
|
||||
if( isAlive() ) return getEntityData().get( HIDING_BLOCK ).orElse( null );
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Sets the block being hidden (rendered) as, set to null to cancel hiding. */
|
||||
@Override
|
||||
public void setDisguiseBlock( @Nullable BlockState block ) {
|
||||
getEntityData().set( HIDING_BLOCK, Optional.ofNullable( block ) );
|
||||
canHide = false;
|
||||
|
||||
// Smoke puff when emerging from disguise
|
||||
if( block == null ) {
|
||||
//spawnExplosionParticle(); TODO
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
package fathertoast.specialmobs.common.entity.witherskeleton;
|
||||
|
||||
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
|
||||
import fathertoast.specialmobs.common.bestiary.SpecialMob;
|
||||
import fathertoast.specialmobs.common.entity.ai.INinja;
|
||||
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.block.Blocks;
|
||||
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.entity.player.PlayerEntity;
|
||||
import net.minecraft.network.datasync.DataParameter;
|
||||
import net.minecraft.network.datasync.DataSerializers;
|
||||
import net.minecraft.network.datasync.EntityDataManager;
|
||||
import net.minecraft.util.ActionResultType;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.Hand;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.util.Optional;
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
@SpecialMob
|
||||
public class NinjaWitherSkeletonEntity extends _SpecialWitherSkeletonEntity implements INinja {
|
||||
|
||||
//--------------- Static Special Mob Hooks ----------------
|
||||
|
||||
@SpecialMob.BestiaryInfoSupplier
|
||||
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> entityType ) {
|
||||
return new BestiaryInfo( 0x333366 );
|
||||
}
|
||||
|
||||
@SpecialMob.AttributeCreator
|
||||
public static AttributeModifierMap.MutableAttribute createAttributes() {
|
||||
return AttributeHelper.of( _SpecialWitherSkeletonEntity.createAttributes() )
|
||||
.multAttribute( Attributes.MOVEMENT_SPEED, 1.2 )
|
||||
.build();
|
||||
}
|
||||
|
||||
@SpecialMob.LanguageProvider
|
||||
public static String[] getTranslations( String langKey ) {
|
||||
return References.translations( langKey, "Wither Skeleton Ninja",
|
||||
"", "", "", "", "", "" );//TODO
|
||||
}
|
||||
|
||||
@SpecialMob.LootTableProvider
|
||||
public static void buildLootTable( LootTableBuilder loot ) {
|
||||
addBaseLoot( loot );
|
||||
loot.addUncommonDrop( "uncommon", Blocks.INFESTED_STONE, Blocks.INFESTED_COBBLESTONE, Blocks.INFESTED_STONE_BRICKS,
|
||||
Blocks.INFESTED_CRACKED_STONE_BRICKS, Blocks.INFESTED_MOSSY_STONE_BRICKS, Blocks.INFESTED_CHISELED_STONE_BRICKS );
|
||||
}
|
||||
|
||||
@SpecialMob.Constructor
|
||||
public NinjaWitherSkeletonEntity( EntityType<? extends _SpecialWitherSkeletonEntity> entityType, World world ) {
|
||||
super( entityType, world );
|
||||
xpReward += 2;
|
||||
}
|
||||
|
||||
|
||||
//--------------- Variant-Specific Implementations ----------------
|
||||
|
||||
/** Override to change this entity's AI goals. */
|
||||
@Override
|
||||
protected void registerVariantGoals() {
|
||||
setRangedAI( 1.0, 10, 9.0F );
|
||||
|
||||
//TODO AIHelper.insertGoalReverse( goalSelector, getVariantAttackPriority() - 1, null );
|
||||
}
|
||||
|
||||
/** Called during spawn finalization to set starting equipment. */
|
||||
@Override
|
||||
protected void populateDefaultEquipmentSlots( DifficultyInstance difficulty ) {
|
||||
super.populateDefaultEquipmentSlots( difficulty );
|
||||
setCanPickUpLoot( true );
|
||||
}
|
||||
|
||||
/** Override to change this entity's chance to spawn with a bow. */
|
||||
@Override
|
||||
protected double getVariantBowChance() { return 0.5; }
|
||||
|
||||
/** Override to apply effects when this entity hits a target with a melee attack. */
|
||||
@Override
|
||||
protected void onVariantAttack( Entity target ) {
|
||||
revealTo( target );
|
||||
}
|
||||
|
||||
/** @return Attempts to damage this entity; returns true if the hit was successful. */
|
||||
@Override
|
||||
public boolean hurt( DamageSource source, float amount ) {
|
||||
if( super.hurt( source, amount ) ) {
|
||||
revealTo( source.getEntity() );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @return Interacts (right click) with this entity and returns the result. */
|
||||
@Override
|
||||
public ActionResultType mobInteract( PlayerEntity player, Hand hand ) {
|
||||
// Attack if the player tries to right click the "block"
|
||||
if( !level.isClientSide() && getDisguiseBlock() != null ) revealTo( player );
|
||||
return super.mobInteract( player, hand );
|
||||
}
|
||||
|
||||
/** Called each tick to update this entity. */
|
||||
@Override
|
||||
public void tick() {
|
||||
// TODO can this be moved to the ninja AI?
|
||||
if( !level.isClientSide() ) {
|
||||
if( canHide ) {
|
||||
//EntityAINinja.startHiding( this ); TODO
|
||||
}
|
||||
else if( onGround && getDisguiseBlock() == null &&
|
||||
(getTarget() == null || getTarget() instanceof PlayerEntity && ((PlayerEntity) getTarget()).isCreative()) ) {
|
||||
canHide = true;
|
||||
}
|
||||
}
|
||||
super.tick();
|
||||
}
|
||||
|
||||
// // Moves this entity.
|
||||
// @Override TODO
|
||||
// public void move( MoverType type, double x, double y, double z ) {
|
||||
// if( isImmobile() && type != MoverType.PISTON ) {
|
||||
// motionY = 0.0;
|
||||
// }
|
||||
// else {
|
||||
// super.move( type, x, y, z );
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Returns true if this entity should push and be pushed by other entities when colliding.
|
||||
// @Override
|
||||
// public boolean canBePushed() { return !isImmobile(); }
|
||||
|
||||
/** Sets this entity on fire for a specific duration. */
|
||||
@Override
|
||||
public void setRemainingFireTicks( int ticks ) {
|
||||
if( !isImmobile() ) super.setRemainingFireTicks( ticks );
|
||||
}
|
||||
|
||||
/** Reveals this ninja and sets its target so that it doesn't immediately re-disguise itself. */
|
||||
public void revealTo( @Nullable Entity target ) {
|
||||
setDisguiseBlock( null );
|
||||
if( target instanceof LivingEntity ) setTarget( (LivingEntity) target );
|
||||
}
|
||||
|
||||
private static final ResourceLocation[] TEXTURES = {
|
||||
new ResourceLocation( "textures/entity/skeleton/skeleton.png" ),
|
||||
null,
|
||||
GET_TEXTURE_PATH( "ninja_overlay" )
|
||||
};
|
||||
|
||||
/** @return All default textures for this entity. */
|
||||
@Override
|
||||
public ResourceLocation[] getDefaultTextures() { return TEXTURES; }
|
||||
|
||||
|
||||
//--------------- INinja Implementations ----------------
|
||||
|
||||
/** The parameter for the ninja immobile state. */
|
||||
private static final DataParameter<Boolean> IS_HIDING = EntityDataManager.defineId( NinjaWitherSkeletonEntity.class, DataSerializers.BOOLEAN );
|
||||
/** The parameter for the ninja disguise block. */
|
||||
private static final DataParameter<Optional<BlockState>> HIDING_BLOCK = EntityDataManager.defineId( NinjaWitherSkeletonEntity.class, DataSerializers.BLOCK_STATE );
|
||||
|
||||
private boolean canHide = true;
|
||||
|
||||
/** Called from the Entity.class constructor to define data watcher variables. */
|
||||
@Override
|
||||
protected void defineSynchedData() {
|
||||
super.defineSynchedData();
|
||||
entityData.define( IS_HIDING, false );
|
||||
entityData.define( HIDING_BLOCK, Optional.empty() );
|
||||
}
|
||||
|
||||
/** @return Whether this ninja is currently immobile. */
|
||||
@Override
|
||||
public boolean isImmobile() { return getEntityData().get( IS_HIDING ); }
|
||||
|
||||
/** Sets this ninja's immovable state. When activated, the entity is 'snapped' to the nearest block position. */
|
||||
@Override
|
||||
public void setImmobile( boolean value ) {
|
||||
if( value != isImmobile() ) {
|
||||
getEntityData().set( IS_HIDING, value );
|
||||
if( value ) {
|
||||
clearFire();
|
||||
moveTo( Math.floor( getX() ) + 0.5, Math.floor( getY() ), Math.floor( getZ() ) + 0.5 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @return The block being hidden (rendered) as, or null if not hiding. */
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockState getDisguiseBlock() {
|
||||
if( isAlive() ) return getEntityData().get( HIDING_BLOCK ).orElse( null );
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Sets the block being hidden (rendered) as, set to null to cancel hiding. */
|
||||
@Override
|
||||
public void setDisguiseBlock( @Nullable BlockState block ) {
|
||||
getEntityData().set( HIDING_BLOCK, Optional.ofNullable( block ) );
|
||||
canHide = false;
|
||||
|
||||
// Smoke puff when emerging from disguise
|
||||
if( block == null ) {
|
||||
//spawnExplosionParticle(); TODO
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
package fathertoast.specialmobs.common.entity.zombie;
|
||||
|
||||
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
|
||||
import fathertoast.specialmobs.common.bestiary.SpecialMob;
|
||||
import fathertoast.specialmobs.common.entity.ai.IAngler;
|
||||
import fathertoast.specialmobs.common.util.AttributeHelper;
|
||||
import fathertoast.specialmobs.common.util.References;
|
||||
import fathertoast.specialmobs.datagen.loot.LootEntryItemBuilder;
|
||||
import fathertoast.specialmobs.datagen.loot.LootHelper;
|
||||
import fathertoast.specialmobs.datagen.loot.LootPoolBuilder;
|
||||
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.inventory.EquipmentSlotType;
|
||||
import net.minecraft.item.FishingRodItem;
|
||||
import net.minecraft.item.IDyeableArmorItem;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.Items;
|
||||
import net.minecraft.network.datasync.DataParameter;
|
||||
import net.minecraft.network.datasync.DataSerializers;
|
||||
import net.minecraft.network.datasync.EntityDataManager;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
@SpecialMob
|
||||
public class FishingZombieEntity extends _SpecialZombieEntity implements IAngler {
|
||||
|
||||
//--------------- Static Special Mob Hooks ----------------
|
||||
|
||||
@SpecialMob.BestiaryInfoSupplier
|
||||
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> entityType ) {
|
||||
return new BestiaryInfo( 0x2D41F4 );
|
||||
//TODO theme - fishing
|
||||
}
|
||||
|
||||
@SpecialMob.AttributeCreator
|
||||
public static AttributeModifierMap.MutableAttribute createAttributes() {
|
||||
return AttributeHelper.of( _SpecialZombieEntity.createAttributes() )
|
||||
.multAttribute( Attributes.MOVEMENT_SPEED, 0.8 )
|
||||
.build();
|
||||
}
|
||||
|
||||
@SpecialMob.LanguageProvider
|
||||
public static String[] getTranslations( String langKey ) {
|
||||
return References.translations( langKey, "Fishing Zombie",
|
||||
"", "", "", "", "", "" );//TODO
|
||||
}
|
||||
|
||||
@SpecialMob.LootTableProvider
|
||||
public static void buildLootTable( LootTableBuilder loot ) {
|
||||
addBaseLoot( loot );
|
||||
loot.addPool( new LootPoolBuilder( "common" )
|
||||
.addEntry( new LootEntryItemBuilder( Items.COD ).setCount( 0, 2 ).addLootingBonus( 0, 1 ).smeltIfBurning().toLootEntry() )
|
||||
.toLootPool() );
|
||||
loot.addPool( new LootPoolBuilder( "rare" ).addConditions( LootHelper.RARE_CONDITIONS )
|
||||
.addEntry( new LootEntryItemBuilder( Items.FISHING_ROD ).enchant( 30, true ).toLootEntry() )
|
||||
.toLootPool() );
|
||||
}
|
||||
|
||||
@SpecialMob.Constructor
|
||||
public FishingZombieEntity( EntityType<? extends _SpecialZombieEntity> entityType, World world ) {
|
||||
super( entityType, world );
|
||||
getSpecialData().setCanBreatheInWater( true );
|
||||
getSpecialData().setIgnoreWaterPush( true );
|
||||
xpReward += 2;
|
||||
}
|
||||
|
||||
|
||||
//--------------- Variant-Specific Implementations ----------------
|
||||
|
||||
/** Override to change this entity's AI goals. */
|
||||
@Override
|
||||
protected void registerVariantGoals() {
|
||||
getSpecialData().rangedAttackSpread = 10.0F;
|
||||
getSpecialData().rangedAttackCooldown = 32;
|
||||
getSpecialData().rangedAttackMaxCooldown = 48;
|
||||
getSpecialData().rangedAttackMaxRange = 10.0F;
|
||||
|
||||
//TODO add angler AI @ attack priority
|
||||
}
|
||||
|
||||
/** Called during spawn finalization to set starting equipment. */
|
||||
@Override
|
||||
protected void populateDefaultEquipmentSlots( DifficultyInstance difficulty ) {
|
||||
super.populateDefaultEquipmentSlots( difficulty );
|
||||
|
||||
setItemSlot( EquipmentSlotType.MAINHAND, new ItemStack( Items.FISHING_ROD ) );
|
||||
if( getItemBySlot( EquipmentSlotType.FEET ).isEmpty() ) {
|
||||
ItemStack booties = new ItemStack( Items.LEATHER_BOOTS );
|
||||
((IDyeableArmorItem) booties.getItem()).setColor( booties, 0xFFFF00 );
|
||||
setItemSlot( EquipmentSlotType.FEET, booties );
|
||||
}
|
||||
setCanPickUpLoot( false );
|
||||
}
|
||||
|
||||
/** Override to change this entity's chance to spawn with a bow. */
|
||||
@Override
|
||||
protected double getVariantBowChance() { return 0.0; }
|
||||
|
||||
|
||||
//--------------- IAngler Implementations ----------------
|
||||
|
||||
/** The parameter for baby status. */
|
||||
private static final DataParameter<Boolean> IS_LINE_OUT = EntityDataManager.defineId( FishingZombieEntity.class, DataSerializers.BOOLEAN );
|
||||
|
||||
/** Called from the Entity.class constructor to define data watcher variables. */
|
||||
@Override
|
||||
protected void defineSynchedData() {
|
||||
super.defineSynchedData();
|
||||
entityData.define( IS_LINE_OUT, false );
|
||||
}
|
||||
|
||||
/** Sets this angler's line as out (or in). */
|
||||
@Override
|
||||
public void setLineOut( boolean value ) { getEntityData().set( IS_LINE_OUT, value ); }
|
||||
|
||||
/** @return Whether this angler's line is out. */
|
||||
@Override
|
||||
public boolean isLineOut() { return getEntityData().get( IS_LINE_OUT ); }
|
||||
|
||||
/** @return The item equipped in a particular slot. */
|
||||
@Override
|
||||
public ItemStack getItemBySlot( EquipmentSlotType slot ) {
|
||||
// Display a stick in place of the "cast fishing rod" when the fancy render is disabled
|
||||
if( level.isClientSide() && /*!Config.get().GENERAL.FANCY_FISHING_MOBS &&*/ EquipmentSlotType.MAINHAND.equals( slot ) ) {
|
||||
final ItemStack held = super.getItemBySlot( slot );
|
||||
if( !held.isEmpty() && held.getItem() instanceof FishingRodItem && isLineOut() ) {
|
||||
return new ItemStack( Items.STICK );
|
||||
}
|
||||
return held;
|
||||
}
|
||||
return super.getItemBySlot( slot );
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue