From 30df52a0e27a7a81614c092a7f4d2bbdc17def1b Mon Sep 17 00:00:00 2001 From: FatherToast Date: Thu, 30 Jun 2022 12:06:42 -0500 Subject: [PATCH 01/10] Nevermind on this --- .../common/entity/blaze/HellfireBlazeEntity.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java index eafac4e..b0ae9c2 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java @@ -11,11 +11,9 @@ import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.ai.attributes.AttributeModifierMap; import net.minecraft.entity.ai.attributes.Attributes; -import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.projectile.FireballEntity; import net.minecraft.item.Items; import net.minecraft.nbt.CompoundNBT; -import net.minecraft.util.DamageSource; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; @@ -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 ) { From 214bd23b922ae499103f3d9fc87b305769f1ce2f Mon Sep 17 00:00:00 2001 From: FatherToast Date: Thu, 30 Jun 2022 12:40:43 -0500 Subject: [PATCH 02/10] Spitfire mobs --- .../common/bestiary/MobFamily.java | 4 +-- .../skeleton/SpitfireSkeletonEntity.java | 29 +++++++++++++++-- .../SpitfireWitherSkeletonEntity.java | 31 +++++++++++++++++-- .../entity/zombie/FireZombieEntity.java | 4 +++ 4 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java index 1a6e869..14e09b4 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java @@ -55,11 +55,11 @@ public class MobFamily { public static final MobFamily SKELETON = new MobFamily<>( "Skeleton", "skeletons", 0xC1C1C1, new EntityType[] { EntityType.SKELETON, EntityType.STRAY }, - "Brute", "Fire", "Gatling", "Giant", "Knight", "Ninja", "Poison", "Sniper", /*"Spitfire",*/ "Stray" + "Brute", "Fire", "Gatling", "Giant", "Knight", "Ninja", "Poison", "Sniper", "Spitfire", "Stray" ); public static final MobFamily WITHER_SKELETON = new MobFamily<>( "WitherSkeleton", "wither skeletons", 0x141414, new EntityType[] { EntityType.WITHER_SKELETON }, - "Brute", "Gatling", "Giant", "Knight", "Ninja", "Sniper"//, "Spitfire" + "Brute", "Gatling", "Giant", "Knight", "Ninja", "Sniper", "Spitfire" ); public static final MobFamily SLIME = new MobFamily<>( diff --git a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java index 7bd6a74..76b689f 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java @@ -12,8 +12,10 @@ import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.ai.attributes.AttributeModifierMap; import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.entity.projectile.SmallFireballEntity; import net.minecraft.item.Items; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; import javax.annotation.ParametersAreNonnullByDefault; @@ -73,9 +75,12 @@ public class SpitfireSkeletonEntity extends _SpecialSkeletonEntity { @Override protected void registerVariantGoals() { getSpecialData().rangedAttackDamage += 2.0F; - getSpecialData().rangedAttackSpread *= 0.5F; } + /** Override to change this entity's chance to spawn with a melee weapon. */ + @Override + protected double getVariantMeleeChance() { return 0.0; } + /** Override to apply effects when this entity hits a target with a melee attack. */ @Override protected void onVariantAttack( Entity target ) { @@ -85,9 +90,29 @@ 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, 1018, 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" ) }; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java index dbd0bb7..5230d0b 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java @@ -12,8 +12,10 @@ import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.ai.attributes.AttributeModifierMap; import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.entity.projectile.SmallFireballEntity; import net.minecraft.item.Items; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; import javax.annotation.ParametersAreNonnullByDefault; @@ -62,7 +64,7 @@ public class SpitfireWitherSkeletonEntity extends _SpecialWitherSkeletonEntity { public SpitfireWitherSkeletonEntity( EntityType entityType, World world ) { super( entityType, world ); - getSpecialData().setBaseScale( 1.5F ); + getSpecialData().setBaseScale( 1.8F ); getSpecialData().setDamagedByWater( true ); maxUpStep = 1.0F; xpReward += 2; @@ -72,9 +74,12 @@ public class SpitfireWitherSkeletonEntity extends _SpecialWitherSkeletonEntity { @Override protected void registerVariantGoals() { getSpecialData().rangedAttackDamage += 2.0F; - getSpecialData().rangedAttackSpread *= 0.5F; } + /** Override to change this entity's chance to spawn with a bow. */ + @Override + protected double getVariantBowChance() { return 1.0; } + /** Override to apply effects when this entity hits a target with a melee attack. */ @Override protected void onVariantAttack( Entity target ) { @@ -84,9 +89,29 @@ 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, 1018, 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" ) }; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/FireZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/FireZombieEntity.java index 4ee921b..33e6060 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/FireZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/FireZombieEntity.java @@ -75,6 +75,10 @@ public class FireZombieEntity extends _SpecialZombieEntity { return arrow; } + /** @return True if this entity should appear to be on fire. */ + @Override + public boolean isOnFire() { return isAlive() && !isInWaterRainOrBubble(); } + /** @return The sound this entity makes idly. */ @Override protected SoundEvent getAmbientSound() { return SoundEvents.HUSK_AMBIENT; } From 51d7c73ffe920d93327b01853798f42fffe358f8 Mon Sep 17 00:00:00 2001 From: FatherToast Date: Fri, 1 Jul 2022 10:20:52 -0500 Subject: [PATCH 03/10] Removed the patriarchy --- .../specialmobs/client/ClientRegister.java | 2 +- .../common/bestiary/MobFamily.java | 8 +- .../specialmobs/common/core/SpecialMobs.java | 1 + .../common/entity/ai/AIHelper.java | 24 ++ .../entity/blaze/WildfireBlazeEntity.java | 19 +- .../entity/witch/DominationWitchEntity.java | 121 ++++++++ .../entity/witch/ShadowsWitchEntity.java | 96 +++++++ .../entity/witch/UndeadWitchEntity.java | 138 +++++++++ .../common/entity/witch/WildsWitchEntity.java | 226 +++++++++++++++ .../common/entity/witch/WindWitchEntity.java | 256 +++++++++++++++++ .../entity/witch/_SpecialWitchEntity.java | 266 +++++++++++++++++- .../specialmobs/common/util/References.java | 9 +- .../resources/META-INF/accesstransformer.cfg | 3 + 13 files changed, 1149 insertions(+), 20 deletions(-) create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/witch/DominationWitchEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/witch/ShadowsWitchEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/witch/UndeadWitchEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/witch/WildsWitchEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/witch/WindWitchEntity.java diff --git a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java index 58e8008..034b4d1 100644 --- a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java +++ b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java @@ -39,7 +39,7 @@ 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.BLAZE, SpecialBlazeRenderer::new ); // Species overrides diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java index 14e09b4..b0576de 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java @@ -90,10 +90,10 @@ public class MobFamily { "Blinding", "Icy", "Lightning", "Mini", "Mirage", "Thief" ); - // public static final MobFamily WITCH = new MobFamily<>( - // "Witch", "witches", 0x340000, new EntityType[] { EntityType.WITCH }, - // "Domination"//, "Shadows", "Undead", "Wilds", "Wind"//Note - should wind be able to walk on water? - // ); + public static final MobFamily WITCH = new MobFamily<>( + "Witch", "witches", 0x340000, new EntityType[] { EntityType.WITCH }, + "Domination", "Shadows", "Undead", "Wilds", "Wind" + ); // public static final MobFamily GHAST = new MobFamily<>( // "Ghast", "ghasts", 0xF9F9F9, new EntityType[] { EntityType.GHAST }, diff --git a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java index 9b96e1b..a562b69 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java +++ b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java @@ -81,6 +81,7 @@ public class SpecialMobs { * - endermen * o witches * o ability to equip held items + * - uses splash speed instead of regular * o ghasts * o melee attack AI * - blazes diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/AIHelper.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/AIHelper.java index d4aa3a5..a684afe 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/AIHelper.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/AIHelper.java @@ -1,8 +1,10 @@ package fathertoast.specialmobs.common.entity.ai; +import fathertoast.specialmobs.common.core.SpecialMobs; import net.minecraft.entity.CreatureEntity; import net.minecraft.entity.ai.goal.*; +import javax.annotation.Nullable; import java.util.ArrayList; /** @@ -40,6 +42,26 @@ public final class AIHelper { } } + /** @return A goal with the specified priority; null if none are found. */ + @Nullable + public static Goal getGoal( GoalSelector ai, int priority ) { + for( PrioritizedGoal task : new ArrayList<>( ai.availableGoals ) ) { + if( task.getPriority() == priority ) return task.getGoal(); + } + SpecialMobs.LOG.warn( "Attempted to get '{}'-priority goal, but none exists!", priority ); + return null; + } + + /** @return A goal of the specified type; null if none are found. */ + @Nullable + public static Goal getGoal( GoalSelector ai, Class goalType ) { + for( PrioritizedGoal task : new ArrayList<>( ai.availableGoals ) ) { + if( task.getGoal().getClass().equals( goalType ) ) return task.getGoal(); + } + SpecialMobs.LOG.warn( "Attempted to get '{}' goal, but none exists!", goalType.getSimpleName() ); + return null; + } + /** Replaces the entity's water avoiding random walking goal with an equivalent non-water-avoiding goal. */ public static void replaceWaterAvoidingRandomWalking( CreatureEntity entity, double speedModifier ) { for( PrioritizedGoal task : new ArrayList<>( entity.goalSelector.availableGoals ) ) { @@ -50,6 +72,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 +85,6 @@ public final class AIHelper { return; } } + SpecialMobs.LOG.warn( "Attempted to replace hurt by target goal for {}, but none exists!", entity.getClass().getSimpleName() ); } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java index 60a7cf3..91f6d38 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java @@ -64,7 +64,7 @@ public class WildfireBlazeEntity extends _SpecialBlazeEntity { /** The number of babies spawned on death. */ private int babies; /** The number of extra babies that can be spawned by attacks. */ - private int extraBabies; + private int summons; public WildfireBlazeEntity( EntityType entityType, World world ) { super( entityType, world ); @@ -72,8 +72,8 @@ public class WildfireBlazeEntity extends _SpecialBlazeEntity { getSpecialData().setRegenerationTime( 40 ); xpReward += 2; - babies = 2 + random.nextInt( 3 ); - extraBabies = 3 + random.nextInt( 4 ); + babies = 3 + random.nextInt( 4 ); + summons = 4 + random.nextInt( 7 ); } /** Override to change this entity's AI goals. */ @@ -91,8 +91,8 @@ 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(); @@ -112,9 +112,8 @@ 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(); @@ -147,7 +146,7 @@ public class WildfireBlazeEntity extends _SpecialBlazeEntity { @Override public void addVariantSaveData( CompoundNBT saveTag ) { saveTag.putByte( References.TAG_BABIES, (byte) babies ); - saveTag.putByte( References.TAG_EXTRA_BABIES, (byte) extraBabies ); + saveTag.putByte( References.TAG_SUMMONS, (byte) summons ); } /** Override to load data from this entity's NBT data. */ @@ -155,8 +154,8 @@ public class WildfireBlazeEntity extends _SpecialBlazeEntity { public void readVariantSaveData( CompoundNBT saveTag ) { if( saveTag.contains( References.TAG_BABIES, References.NBT_TYPE_NUMERICAL ) ) babies = saveTag.getByte( References.TAG_BABIES ); - if( saveTag.contains( References.TAG_EXTRA_BABIES, References.NBT_TYPE_NUMERICAL ) ) - extraBabies = saveTag.getByte( References.TAG_EXTRA_BABIES ); + if( saveTag.contains( References.TAG_SUMMONS, References.NBT_TYPE_NUMERICAL ) ) + summons = saveTag.getByte( References.TAG_SUMMONS ); } private static final ResourceLocation[] TEXTURES = { diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/DominationWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/DominationWitchEntity.java new file mode 100644 index 0000000..7925103 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/DominationWitchEntity.java @@ -0,0 +1,121 @@ +package fathertoast.specialmobs.common.entity.witch; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.util.AttributeHelper; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.potion.EffectInstance; +import net.minecraft.potion.Effects; +import net.minecraft.potion.Potions; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.world.World; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.Collection; +import java.util.Collections; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class DominationWitchEntity extends _SpecialWitchEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + return new BestiaryInfo( 0xFFF87E, BestiaryInfo.BaseWeight.LOW ); + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialWitchEntity.createAttributes() ) + .multAttribute( Attributes.MOVEMENT_SPEED, 0.8 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Witch of Domination", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addCommonDrop( "common", Items.EXPERIENCE_BOTTLE ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return DominationWitchEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + private static final Collection LEVITATION_EFFECTS = Collections.singletonList( + new EffectInstance( Effects.LEVITATION, 140, 0 ) ); + + /** Ticks before this witch can use its pull ability. */ + private int pullDelay; + + public DominationWitchEntity( EntityType entityType, World world ) { + super( entityType, world ); + xpReward += 2; + } + + /** Override to modify potion attacks. Return an empty item stack to cancel the potion throw. */ + @Override + protected ItemStack pickVariantThrownPotion( ItemStack originalPotion, LivingEntity target, float damageMulti, float distance ) { + if( !target.hasEffect( Effects.WEAKNESS ) ) { + return makeSplashPotion( Potions.WEAKNESS ); + } + else if( distance > 5.0F && !target.hasEffect( Effects.LEVITATION ) && random.nextFloat() < 0.5F ) { + return makeSplashPotion( LEVITATION_EFFECTS ); + } + return originalPotion; + } + + /** Called each tick to update this entity's movement. */ + @Override + public void aiStep() { + final LivingEntity target = getTarget(); + if( !level.isClientSide() && isAlive() && pullDelay-- <= 0 && target != null && random.nextInt( 20 ) == 0 ) { + + // Pull the player toward this entity if they are vulnerable + final double distanceSq = target.distanceToSqr( this ); + if( distanceSq > 100.0 && distanceSq < 196.0 && + (target.hasEffect( Effects.WEAKNESS ) || target.hasEffect( Effects.LEVITATION )) && canSee( target ) ) { + pullDelay = 100; + + target.setDeltaMovement( new Vector3d( + getX() - target.getX(), + getY() - target.getY(), + getZ() - target.getZ() ) + .scale( 0.32 ) + .add( 0.0, Math.sqrt( Math.sqrt( distanceSq ) ) * 0.1, 0.0 ) ); + target.hurtMarked = true; + } + } + super.aiStep(); + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "domination" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/ShadowsWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/ShadowsWitchEntity.java new file mode 100644 index 0000000..80887a3 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/ShadowsWitchEntity.java @@ -0,0 +1,96 @@ +package fathertoast.specialmobs.common.entity.witch; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.potion.EffectInstance; +import net.minecraft.potion.Effects; +import net.minecraft.potion.PotionUtils; +import net.minecraft.potion.Potions; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.Arrays; +import java.util.Collection; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class ShadowsWitchEntity extends _SpecialWitchEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + return new BestiaryInfo( 0x000000 ); + //TODO theme - forest + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Witch of Shadows", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addCommonDrop( "common", Items.INK_SAC ); + loot.addRareDrop( "rare", PotionUtils.setPotion( new ItemStack( Items.POTION ), Potions.NIGHT_VISION ) ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return ShadowsWitchEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + private static final Collection POTION_SHADOWS = Arrays.asList( + new EffectInstance( Effects.BLINDNESS, 300, 0 ), + new EffectInstance( Effects.WITHER, 200, 0 ) + ); + + public ShadowsWitchEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().addPotionImmunity( Effects.BLINDNESS, Effects.WITHER ); + xpReward += 2; + } + + /** Override to modify potion attacks. Return an empty item stack to cancel the potion throw. */ + @Override + protected ItemStack pickVariantThrownPotion( ItemStack originalPotion, LivingEntity target, float damageMulti, float distance ) { + if( target.getHealth() >= 4.0F && (!target.hasEffect( Effects.BLINDNESS ) || !target.hasEffect( Effects.WITHER )) ) { + return makeSplashPotion( POTION_SHADOWS ); + } + return originalPotion; + } + + /** Called each tick to update this entity's movement. */ + @Override + public void aiStep() { + final LivingEntity target = getTarget(); + if( !level.isClientSide() && isAlive() && target != null && target.hasEffect( Effects.BLINDNESS ) && random.nextInt( 10 ) == 0 ) { + target.removeEffect( Effects.NIGHT_VISION ); // Prevent blind + night vision combo (black screen) + } + super.aiStep(); + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "shadows" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/UndeadWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/UndeadWitchEntity.java new file mode 100644 index 0000000..30a999a --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/UndeadWitchEntity.java @@ -0,0 +1,138 @@ +package fathertoast.specialmobs.common.entity.witch; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.entity.skeleton._SpecialSkeletonEntity; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.CreatureAttribute; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.SpawnReason; +import net.minecraft.entity.monster.AbstractSkeletonEntity; +import net.minecraft.inventory.EquipmentSlotType; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.potion.Potions; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundEvents; +import net.minecraft.world.IServerWorld; +import net.minecraft.world.World; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class UndeadWitchEntity extends _SpecialWitchEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + return new BestiaryInfo( 0x799C65 ); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Lich", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addLootTable( "common", EntityType.ZOMBIE.getDefaultLootTable() ); + loot.addUncommonDrop( "uncommon", Items.SKELETON_SPAWN_EGG ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return UndeadWitchEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + /** The number of skeletons this witch can spawn. */ + private int summons; + + public UndeadWitchEntity( EntityType entityType, World world ) { + super( entityType, world ); + xpReward += 2; + + summons = 3 + random.nextInt( 4 ); + } + + /** Override to modify potion attacks. Return an empty item stack to cancel the potion throw. */ + @Override + protected ItemStack pickVariantThrownPotion( ItemStack originalPotion, LivingEntity target, float damageMulti, float distance ) { + if( summons > 0 && random.nextFloat() < (isNearSkeletons() ? 0.25F : 0.75F) ) { + summons--; + + final _SpecialSkeletonEntity skeleton = _SpecialSkeletonEntity.SPECIES.entityType.get().create( level ); + if( skeleton != null ) { + skeleton.copyPosition( this ); + skeleton.yHeadRot = yRot; + skeleton.yBodyRot = yRot; + skeleton.finalizeSpawn( (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ), + SpawnReason.MOB_SUMMONED, null, null ); + skeleton.setItemSlot( EquipmentSlotType.MAINHAND, new ItemStack( Items.IRON_SWORD ) ); + skeleton.setItemSlot( EquipmentSlotType.HEAD, new ItemStack( Items.CHAINMAIL_HELMET ) ); + skeleton.setTarget( getTarget() ); + + final double vX = target.getX() - getX(); + final double vZ = target.getZ() - getZ(); + final double vH = Math.sqrt( vX * vX + vZ * vZ ); + skeleton.setDeltaMovement( + vX / vH * 0.7 + getDeltaMovement().x * 0.2, + 0.4, // Used to cause floor clip bug; remove if it happens again + vZ / vH * 0.7 + getDeltaMovement().z * 0.2 ); + skeleton.setOnGround( false ); + + level.addFreshEntity( skeleton ); + playSound( SoundEvents.BLAZE_SHOOT, 1.0F, 2.0F / (random.nextFloat() * 0.4F + 0.8F) ); + skeleton.spawnAnim(); + + return ItemStack.EMPTY; + } + } + + // Only throw harming potions - heals self and minions, while probably damaging the target + return random.nextFloat() < 0.2F ? makeLingeringPotion( Potions.HARMING ) : makeSplashPotion( Potions.HARMING ); + } + + /** @return True if there are any skeletons near this entity. */ + private boolean isNearSkeletons() { + return level.getEntitiesOfClass( AbstractSkeletonEntity.class, getBoundingBox().inflate( 11.0 ) ).size() > 0; + } + + /** @return This entity's creature type. */ + @Override + public CreatureAttribute getMobType() { return CreatureAttribute.UNDEAD; } + + /** Override to save data to this entity's NBT data. */ + @Override + public void addVariantSaveData( CompoundNBT saveTag ) { + saveTag.putByte( References.TAG_SUMMONS, (byte) summons ); + } + + /** Override to load data from this entity's NBT data. */ + @Override + public void readVariantSaveData( CompoundNBT saveTag ) { + if( saveTag.contains( References.TAG_SUMMONS, References.NBT_TYPE_NUMERICAL ) ) + summons = saveTag.getByte( References.TAG_SUMMONS ); + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "undead" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/WildsWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/WildsWitchEntity.java new file mode 100644 index 0000000..087adcb --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/WildsWitchEntity.java @@ -0,0 +1,226 @@ +package fathertoast.specialmobs.common.entity.witch; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.entity.spider.BabySpiderEntity; +import fathertoast.specialmobs.common.entity.spider._SpecialSpiderEntity; +import fathertoast.specialmobs.common.util.AttributeHelper; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.block.Blocks; +import net.minecraft.entity.*; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.potion.Effects; +import net.minecraft.potion.Potion; +import net.minecraft.potion.PotionUtils; +import net.minecraft.potion.Potions; +import net.minecraft.tags.FluidTags; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundEvents; +import net.minecraft.world.IServerWorld; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class WildsWitchEntity extends _SpecialWitchEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + return new BestiaryInfo( 0xA80E0E ); + //TODO theme - forest + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialWitchEntity.createAttributes() ) + .multAttribute( Attributes.MOVEMENT_SPEED, 0.7 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Witch of the Wilds", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addUncommonDrop( "uncommon", Items.SPIDER_SPAWN_EGG ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return WildsWitchEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + /** The number of spider mounts this witch can spawn. */ + private int spiderMounts; + /** The number of spider swarm attacks this witch can cast. */ + private int spiderSwarms; + /** The number of baby spiders to spawn in each spider swarm attack. */ + private int spiderSwarmSize; + + public WildsWitchEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().addStickyBlockImmunity( Blocks.COBWEB ); + getSpecialData().addPotionImmunity( Effects.POISON ); + xpReward += 1; + + spiderMounts = 1 + random.nextInt( 3 ); + spiderSwarms = 3 + random.nextInt( 4 ); + spiderSwarmSize = 3; + } + + /** Override to modify potion attacks. Return an empty item stack to cancel the potion throw. */ + @Override + protected ItemStack pickVariantThrownPotion( ItemStack originalPotion, LivingEntity target, float damageMulti, float distance ) { + if( spiderSwarms > 0 && random.nextFloat() < 0.33F ) { + spiderSwarms--; + + ILivingEntityData groupData = null; + for( int i = 0; i < spiderSwarmSize; i++ ) { + groupData = spawnBaby( groupData ); + } + spawnAnim(); + playSound( SoundEvents.EGG_THROW, 1.0F, 2.0F / (random.nextFloat() * 0.4F + 0.8F) ); + + return ItemStack.EMPTY; + } + if( !target.hasEffect( Effects.POISON ) ) { + return makeSplashPotion( Potions.STRONG_POISON ); + } + // Save the spiders + final Potion originalType = PotionUtils.getPotion( originalPotion ); + if( originalType == Potions.HARMING || originalType == Potions.STRONG_HARMING ) { + return makeSplashPotion( Potions.STRONG_POISON ); + } + return originalPotion; + } + + /** Helper method to simplify spawning babies. */ + @Nullable + private ILivingEntityData spawnBaby( @Nullable ILivingEntityData groupData ) { + final BabySpiderEntity baby = BabySpiderEntity.SPECIES.entityType.get().create( level ); + if( baby == null ) return groupData; + + baby.copyPosition( this ); + baby.yHeadRot = yRot; + baby.yBodyRot = yRot; + groupData = baby.finalizeSpawn( (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ), + SpawnReason.MOB_SUMMONED, groupData, null ); + baby.setTarget( getTarget() ); + + baby.setDeltaMovement( + (random.nextDouble() - 0.5) * 0.33, + random.nextDouble() * 0.5, // Used to cause floor clip bug; remove if it happens again + (random.nextDouble() - 0.5) * 0.33 ); + baby.setOnGround( false ); + + level.addFreshEntity( baby ); + return groupData; + } + + /** Override to add additional potions this witch can drink if none of the base potions are chosen. */ + @Override + protected void tryVariantUsingPotion() { + final LivingEntity mount = getVehicle() instanceof LivingEntity ? (LivingEntity) getVehicle() : null; + + if( mount != null && random.nextFloat() < 0.15F && mount.isEyeInFluid( FluidTags.WATER ) && + !mount.hasEffect( Effects.WATER_BREATHING ) ) { + usePotion( makeSplashPotion( Potions.WATER_BREATHING ) ); + } + else if( mount != null && random.nextFloat() < 0.15F && (mount.isOnFire() || mount.getLastDamageSource() != null && + mount.getLastDamageSource().isFire()) && !hasEffect( Effects.FIRE_RESISTANCE ) ) { + usePotion( makeSplashPotion( Potions.FIRE_RESISTANCE ) ); + } + else if( mount != null && random.nextFloat() < 0.05F && mount.getMobType() != CreatureAttribute.UNDEAD && + mount.getHealth() < mount.getMaxHealth() ) { + usePotion( makeSplashPotion( Potions.HEALING ) ); + } + // else if( mount != null && random.nextFloat() < 0.5F && getTarget() != null && !mount.hasEffect( Effects.MOVEMENT_SPEED ) && + // getTarget().distanceToSqr( this ) > 121.0 ) { + // usePotion( makeSplashPotion( Potions.SWIFTNESS ) ); // TODO config + // } + else if( spiderMounts > 0 && random.nextFloat() < 0.15F && getVehicle() == null && getTarget() != null && + getTarget().distanceToSqr( this ) > 100.0 ) { + final _SpecialSpiderEntity spider = _SpecialSpiderEntity.SPECIES.entityType.get().create( level ); + if( spider != null ) { + spider.copyPosition( this ); + spider.yHeadRot = yRot; + spider.yBodyRot = yRot; + + if( level.noCollision( spider.getBoundingBox() ) ) { + spiderMounts--; + potionUseCooldownTimer = 40; + + spider.setTarget( getTarget() ); + spider.finalizeSpawn( (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ), + SpawnReason.MOB_SUMMONED, null, null ); + level.addFreshEntity( spider ); + spider.spawnAnim(); + playSound( SoundEvents.BLAZE_SHOOT, 1.0F, 2.0F / (random.nextFloat() * 0.4F + 0.8F) ); + + startRiding( spider, true ); + } + else { + // Cancel spawn; spider is in too small of a space + spider.remove(); + } + } + } + } + + /** Called each tick to update this entity's movement. */ + @Override + public void aiStep() { + // With changes to mount/rider mechanics, is this still needed? + // if( getVehicle() instanceof MobEntity && getTarget() != null && random.nextInt( 10 ) == 0 ) { + // ((MobEntity) getVehicle()).setTarget( getTarget() ); + // } + super.aiStep(); + } + + /** Override to save data to this entity's NBT data. */ + @Override + public void addVariantSaveData( CompoundNBT saveTag ) { + saveTag.putByte( References.TAG_SUMMONS, (byte) spiderMounts ); + saveTag.putByte( References.TAG_BABIES, (byte) spiderSwarms ); + saveTag.putByte( References.TAG_EXTRA_BABIES, (byte) spiderSwarmSize ); + } + + /** Override to load data from this entity's NBT data. */ + @Override + public void readVariantSaveData( CompoundNBT saveTag ) { + if( saveTag.contains( References.TAG_SUMMONS, References.NBT_TYPE_NUMERICAL ) ) + spiderMounts = saveTag.getByte( References.TAG_SUMMONS ); + if( saveTag.contains( References.TAG_BABIES, References.NBT_TYPE_NUMERICAL ) ) + spiderSwarms = saveTag.getByte( References.TAG_BABIES ); + if( saveTag.contains( References.TAG_EXTRA_BABIES, References.NBT_TYPE_NUMERICAL ) ) + spiderSwarmSize = saveTag.getByte( References.TAG_EXTRA_BABIES ); + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "wilds" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/WindWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/WindWitchEntity.java new file mode 100644 index 0000000..60dcf8a --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/WindWitchEntity.java @@ -0,0 +1,256 @@ +package fathertoast.specialmobs.common.entity.witch; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.entity.MobHelper; +import fathertoast.specialmobs.common.entity.ai.AIHelper; +import fathertoast.specialmobs.common.entity.ai.FluidPathNavigator; +import fathertoast.specialmobs.common.util.AttributeHelper; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.block.BlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.fluid.Fluid; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.pathfinding.PathNavigator; +import net.minecraft.pathfinding.PathNodeType; +import net.minecraft.potion.EffectInstance; +import net.minecraft.potion.Effects; +import net.minecraft.tags.FluidTags; +import net.minecraft.util.*; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.world.World; +import net.minecraftforge.event.ForgeEventFactory; +import net.minecraftforge.event.entity.living.EntityTeleportEvent; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class WindWitchEntity extends _SpecialWitchEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + return new BestiaryInfo( 0x6388B2 ); + //TODO theme - mountain + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialWitchEntity.createAttributes() ) + .addAttribute( Attributes.ATTACK_DAMAGE, 2.0 ) + .multAttribute( Attributes.MOVEMENT_SPEED, 1.2 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Witch of the Wind", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addCommonDrop( "common", Items.FEATHER ); + loot.addSemicommonDrop( "semicommon", Items.ENDER_PEARL ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return WindWitchEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + /** Ticks before this witch can teleport. */ + private int teleportDelay; + + public WindWitchEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().setFallDamageMultiplier( 0.0F ); + xpReward += 2; + + setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() ); + } + + /** Override to change this entity's AI goals. */ + protected void registerVariantGoals() { + AIHelper.replaceWaterAvoidingRandomWalking( this, 1.0 ); + } + + /** @return A new path navigator for this entity to use. */ + @Override + protected PathNavigator createNavigation( World world ) { + return new FluidPathNavigator( this, world, true, false ); + } + + /** @return Whether this entity can stand on a particular type of fluid. */ + @Override + public boolean canStandOnFluid( Fluid fluid ) { return fluid.is( FluidTags.WATER ); } + + /** Called each tick to update this entity. */ + @Override + public void tick() { + super.tick(); + MobHelper.floatInFluid( this, 0.06, FluidTags.WATER ); + } + + /** Called each tick to update this entity's movement. */ + @Override + public void aiStep() { + if( !level.isClientSide() && isAlive() && teleportDelay-- <= 0 && getTarget() != null && random.nextInt( 20 ) == 0 ) { + if( getTarget().distanceToSqr( this ) > 64.0 ) { + for( int i = 0; i < 16; i++ ) { + if( teleportTowards( getTarget() ) ) { + teleportDelay = 60; + removeEffect( Effects.INVISIBILITY ); + break; + } + } + } + else { + addEffect( new EffectInstance( Effects.INVISIBILITY, 30 ) ); + for( int i = 0; i < 16; i++ ) { + if( teleport() ) { + teleportDelay = 30; + break; + } + } + } + } + super.aiStep(); + } + + /** @return Attempts to damage this entity; returns true if the hit was successful. */ + @Override + public boolean hurt( DamageSource source, float amount ) { + if( isInvulnerableTo( source ) || fireImmune() && source.isFire() ) return false; + + if( source instanceof IndirectEntityDamageSource ) { + for( int i = 0; i < 64; i++ ) { + if( teleport() ) return true; + } + return false; + } + + final boolean success = super.hurt( source, amount ); + if( !level.isClientSide() && getHealth() > 0.0F ) { + if( source.getEntity() instanceof LivingEntity ) { + teleportDelay -= 15; + if( teleportDelay <= 0 && random.nextFloat() < 0.5F ) { + for( int i = 0; i < 16; i++ ) { + if( teleport() ) break; + } + } + else { + removeEffect( Effects.INVISIBILITY ); + } + } + else if( random.nextInt( 10 ) != 0 ) { + teleport(); + } + } + return success; + } + + /** @return Teleports this "enderman" to a random nearby position; returns true if successful. */ + protected boolean teleport() { + if( level.isClientSide() || !isAlive() ) return false; + + final double x = getX() + (random.nextDouble() - 0.5) * 20.0; + final double y = getY() + (double) (random.nextInt( 12 ) - 4); + final double z = getZ() + (random.nextDouble() - 0.5) * 20.0; + return teleport( x, y, z ); + } + + /** @return Teleports this "enderman" towards another entity; returns true if successful. */ + protected boolean teleportTowards( Entity target ) { + final Vector3d directionFromTarget = new Vector3d( + getX() - target.getX(), + getY( 0.5 ) - target.getEyeY(), + getZ() - target.getZ() ) + .normalize(); + + final double x = getX() + (random.nextDouble() - 0.5) * 8.0 - directionFromTarget.x * 10.0; + final double y = getY() + (double) (random.nextInt( 8 ) - 2) - directionFromTarget.y * 10.0; + final double z = getZ() + (random.nextDouble() - 0.5) * 8.0 - directionFromTarget.z * 10.0; + return teleport( x, y, z ); + } + + /** @return Teleports this "enderman" to a new position; returns true if successful. */ + protected boolean teleport( double x, double y, double z ) { + final BlockPos.Mutable pos = new BlockPos.Mutable( x, y, z ); + + while( pos.getY() > 0 ) { + // Allow wind witch to teleport on top of water + final BlockState block = level.getBlockState( pos ); + if( block.getMaterial().blocksMotion() || block.getFluidState().is( FluidTags.WATER ) ) { + + final EntityTeleportEvent.EnderEntity event = ForgeEventFactory.onEnderTeleport( this, x, y + 1, z ); + if( event.isCanceled() ) return false; + + final boolean success = uncheckedTeleport( event.getTargetX(), event.getTargetY(), event.getTargetZ(), false ); + if( success && !isSilent() ) { + level.playSound( null, xo, yo, zo, SoundEvents.GHAST_SHOOT, getSoundSource(), + 1.0F, 1.0F ); + playSound( SoundEvents.GHAST_SHOOT, 1.0F, 1.0F ); + } + return success; + } + else { + pos.move( Direction.DOWN ); + y--; + } + } + return false; + } + + /** This is #randomTeleport, but uses a pre-determined y-coord. */ + @SuppressWarnings( "SameParameterValue" ) // Don't care; maintain vanilla's method signature + private boolean uncheckedTeleport( double x, double y, double z, boolean spawnParticles ) { + final double xI = getX(); + final double yI = getY(); + final double zI = getZ(); + + //noinspection deprecation + if( level.hasChunkAt( new BlockPos( x, y, z ) ) ) { + teleportTo( x, y, z ); + + if( level.noCollision( this ) && !level.containsAnyLiquid( getBoundingBox() ) ) { + if( spawnParticles ) level.broadcastEntityEvent( this, References.EVENT_TELEPORT_TRAIL_PARTICLES ); + getNavigation().stop(); + return true; + } + } + teleportTo( xI, yI, zI ); + return false; + } + + /** Override to load data from this entity's NBT data. */ + @Override + public void readVariantSaveData( CompoundNBT saveTag ) { + setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() ); + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "wind" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java index 0dbf63c..a4354e2 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/_SpecialWitchEntity.java @@ -11,21 +11,36 @@ import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; import net.minecraft.block.BlockState; import net.minecraft.entity.*; +import net.minecraft.entity.ai.attributes.AttributeModifier; import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.entity.ai.attributes.ModifiableAttributeInstance; +import net.minecraft.entity.monster.AbstractRaiderEntity; import net.minecraft.entity.monster.WitchEntity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.projectile.PotionEntity; import net.minecraft.entity.projectile.SnowballEntity; +import net.minecraft.inventory.EquipmentSlotType; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; import net.minecraft.nbt.CompoundNBT; import net.minecraft.network.datasync.DataParameter; import net.minecraft.network.datasync.DataSerializers; import net.minecraft.network.datasync.EntityDataManager; -import net.minecraft.potion.EffectInstance; +import net.minecraft.potion.*; +import net.minecraft.tags.FluidTags; import net.minecraft.util.DamageSource; +import net.minecraft.util.IItemProvider; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundEvents; +import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.World; import javax.annotation.ParametersAreNonnullByDefault; +import java.util.Collection; +import java.util.List; +import java.util.UUID; @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault @@ -66,6 +81,7 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe public _SpecialWitchEntity( EntityType entityType, World world ) { super( entityType, world ); + usingTime = Integer.MAX_VALUE; // Effectively disable vanilla witch potion drinking logic in combo with "fake drinking" getSpecialData().initialize(); } @@ -89,7 +105,106 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe /** Override to apply effects when this entity hits a target with a melee attack. */ protected void onVariantAttack( Entity target ) { } - //TODO + /** Called to attack the target with a ranged attack. */ + @Override + public void performRangedAttack( LivingEntity target, float damageMulti ) { + if( isDrinkingPotion() ) return; + + final Vector3d vTarget = target.getDeltaMovement(); + final double dX = target.getX() + vTarget.x - getX(); + final double dY = target.getEyeY() - 1.1 - getY(); + final double dZ = target.getZ() + vTarget.z - getZ(); + final float dH = MathHelper.sqrt( dX * dX + dZ * dZ ); + + final ItemStack potion = pickThrownPotion( target, damageMulti, dH ); + if( potion.isEmpty() ) return; + + final PotionEntity thrownPotion = new PotionEntity( level, this ); + thrownPotion.setItem( potion ); + thrownPotion.xRot += 20.0F; + thrownPotion.shoot( dX, dY + (double) (dH * 0.2F), dZ, 0.75F, 8.0F ); + if( !isSilent() ) { + level.playSound( null, getX(), getY(), getZ(), SoundEvents.WITCH_THROW, getSoundSource(), + 1.0F, 0.8F + random.nextFloat() * 0.4F ); + } + level.addFreshEntity( thrownPotion ); + } + + /** @return A throwable potion item depending on the situation. */ + protected ItemStack pickThrownPotion( LivingEntity target, float damageMulti, float distance ) { + final ItemStack potion; + + // Healing an ally + if( target instanceof AbstractRaiderEntity ) { + if( target.getMobType() == CreatureAttribute.UNDEAD ) { + potion = makeSplashPotion( Potions.HARMING ); + } + else if( target.getHealth() <= 4.0F ) { + potion = makeSplashPotion( Potions.HEALING ); + } + else { + potion = makeSplashPotion( Potions.REGENERATION ); + } + setTarget( null ); + + // Let the variant change the choice or cancel potion throwing + return pickVariantSupportPotion( potion, (AbstractRaiderEntity) target, distance ); + } + + // Attack potions + if( distance >= 8.0F && !target.hasEffect( Effects.MOVEMENT_SLOWDOWN ) ) { + potion = makeSplashPotion( Potions.SLOWNESS ); + } + else if( target.getHealth() >= 8.0F && !target.hasEffect( Effects.POISON ) ) { + potion = makeSplashPotion( Potions.POISON ); + } + else if( distance <= 3.0F && !target.hasEffect( Effects.WEAKNESS ) && random.nextFloat() < 0.25F ) { + potion = makeSplashPotion( Potions.WEAKNESS ); + } + else if( target.getMobType() == CreatureAttribute.UNDEAD ) { + potion = makeSplashPotion( Potions.HEALING ); + } + else { + potion = makeSplashPotion( Potions.HARMING ); + } + // Let the variant change the choice or cancel potion throwing + return pickVariantThrownPotion( potion, target, damageMulti, distance ); + } + + /** Override to modify potion support. Return an empty item stack to cancel the potion throw. */ + protected ItemStack pickVariantSupportPotion( ItemStack originalPotion, AbstractRaiderEntity target, float distance ) { + return originalPotion; + } + + /** Override to modify potion attacks. Return an empty item stack to cancel the potion throw. */ + protected ItemStack pickVariantThrownPotion( ItemStack originalPotion, LivingEntity target, float damageMulti, float distance ) { + return originalPotion; + } + + /** Called each tick while this witch is capable of using a potion on itself. */ + protected void tryUsingPotion() { + if( random.nextFloat() < 0.15F && isEyeInFluid( FluidTags.WATER ) && !hasEffect( Effects.WATER_BREATHING ) ) { + usePotion( makePotion( Potions.WATER_BREATHING ) ); + } + else if( random.nextFloat() < 0.15F && (isOnFire() || getLastDamageSource() != null && getLastDamageSource().isFire()) && + !hasEffect( Effects.FIRE_RESISTANCE ) ) { + usePotion( makePotion( Potions.FIRE_RESISTANCE ) ); + } + else if( random.nextFloat() < 0.05F && getHealth() < getMaxHealth() ) { + usePotion( makePotion( getMobType() == CreatureAttribute.UNDEAD ? Potions.HARMING : Potions.HEALING ) ); + } + else if( random.nextFloat() < 0.5F && getTarget() != null && !hasEffect( Effects.MOVEMENT_SPEED ) && + getTarget().distanceToSqr( this ) > 121.0 ) { + //usePotion( ____ ? makeSplashPotion( Potions.SWIFTNESS ) : makePotion( Potions.SWIFTNESS ) ); + usePotion( makeSplashPotion( Potions.SWIFTNESS ) ); // TODO config + } + else { + tryVariantUsingPotion(); + } + } + + /** Override to add additional potions this witch can drink if none of the base potions are chosen. */ + protected void tryVariantUsingPotion() { } /** Override to save data to this entity's NBT data. */ public void addVariantSaveData( CompoundNBT saveTag ) { } @@ -100,9 +215,24 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe //--------------- Family-Specific Implementations ---------------- + /** The speed penalty to apply while drinking. */ + private static final AttributeModifier DRINKING_SPEED_PENALTY = new AttributeModifier( UUID.fromString( "5CD17E52-A79A-43D3-A529-90FDE04B181E" ), + "Drinking speed penalty", -0.25, AttributeModifier.Operation.ADDITION ); + /** The parameter for special mob render scale. */ private static final DataParameter SCALE = EntityDataManager.defineId( _SpecialWitchEntity.class, DataSerializers.FLOAT ); + /** Used to prevent vanilla code from handling potion-drinking. */ + private boolean fakeDrinkingPotion; + + /** Ticks until this witch finishes drinking. */ + protected int potionDrinkTimer; + /** Ticks until this witch can use another potion on itself. */ + protected int potionUseCooldownTimer; + + /** While the witch is drinking a potion, it stores its 'actual' held item here. */ + public ItemStack sheathedItem = ItemStack.EMPTY; + /** Called from the Entity.class constructor to define data watcher variables. */ @Override protected void defineSynchedData() { @@ -110,7 +240,112 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe specialData = new SpecialMobData<>( this, SCALE, 1.0F ); } - //TODO + /** Called each AI tick to update potion-drinking behavior. */ + public void drinkPotionUpdate() { + potionUseCooldownTimer--; + + if( isDrinkingPotion() ) { + // Complete potion drinking + if( potionDrinkTimer-- <= 0 ) { + final ItemStack drinkingItem = getMainHandItem(); + usePotion( ItemStack.EMPTY ); + + if( drinkingItem.getItem() == Items.POTION ) { + final List effects = PotionUtils.getMobEffects( drinkingItem ); + for( EffectInstance effect : effects ) { + addEffect( new EffectInstance( effect ) ); + } + } + } + } + else if( potionUseCooldownTimer <= 0 ) { + tryUsingPotion(); + } + } + + /** Have this witch use the potion item on itself, or stop drinking if the given 'potion' is an empty stack. */ + public void usePotion( ItemStack potion ) { + // Cancel any current drinking before using a new potion so we don't accidentally delete the sheathed item + if( isDrinkingPotion() && !potion.isEmpty() ) usePotion( ItemStack.EMPTY ); + + if( potion.isEmpty() ) { + // Cancel drinking the current potion and re-equip the sheathed item + if( isDrinkingPotion() ) { + setUsingItem( false ); + potionDrinkTimer = 0; + + final ModifiableAttributeInstance attribute = getAttribute( Attributes.MOVEMENT_SPEED ); + if( attribute != null ) attribute.removeModifier( DRINKING_SPEED_PENALTY ); + + setItemSlot( EquipmentSlotType.MAINHAND, sheathedItem ); + sheathedItem = ItemStack.EMPTY; + } + } + else if( potion.getItem() == Items.POTION ) { + // It is a normal potion, start drinking and sheathe the held item + sheathedItem = getMainHandItem(); + + setItemSlot( EquipmentSlotType.MAINHAND, potion ); + setUsingItem( true ); + potionDrinkTimer = getMainHandItem().getUseDuration(); + + if( !isSilent() ) { + level.playSound( null, getX(), getY(), getZ(), SoundEvents.WITCH_DRINK, getSoundSource(), + 1.0F, 0.8F + random.nextFloat() * 0.4F ); + } + + final ModifiableAttributeInstance attribute = getAttribute( Attributes.MOVEMENT_SPEED ); + if( attribute != null ) { + attribute.removeModifier( DRINKING_SPEED_PENALTY ); + attribute.addTransientModifier( DRINKING_SPEED_PENALTY ); + } + } + else if( potion.getItem() == Items.SPLASH_POTION || potion.getItem() == Items.LINGERING_POTION ) { + // It is a splash or lingering potion, throw it straight down to apply to self + potionUseCooldownTimer = 40; + + final PotionEntity thrownPotion = new PotionEntity( level, this ); + thrownPotion.setItem( potion ); + thrownPotion.xRot += 20.0F; + thrownPotion.shoot( 0.0, -1.0, 0.0, 0.2F, 0.0F ); + if( !isSilent() ) { + level.playSound( null, getX(), getY(), getZ(), SoundEvents.WITCH_THROW, getSoundSource(), + 1.0F, 0.8F + random.nextFloat() * 0.4F ); + } + level.addFreshEntity( thrownPotion ); + } + else { + SpecialMobs.LOG.warn( "Witch {} attempted to use '{}' as a potion! Gross!", getClass().getSimpleName(), potion ); + } + } + + /** @return A new regular potion with standard effects. */ + public ItemStack makePotion( Potion type ) { return newPotion( Items.POTION, type ); } + + /** @return A new regular potion with custom effects. */ + public ItemStack makePotion( Collection effects ) { return newPotion( Items.POTION, effects ); } + + /** @return A new splash potion with standard effects. */ + public ItemStack makeSplashPotion( Potion type ) { return newPotion( Items.SPLASH_POTION, type ); } + + /** @return A new splash potion on self with custom effects. */ + public ItemStack makeSplashPotion( Collection effects ) { return newPotion( Items.SPLASH_POTION, effects ); } + + /** @return A new lingering splash potion with standard effects. */ + public ItemStack makeLingeringPotion( Potion type ) { return newPotion( Items.LINGERING_POTION, type ); } + + /** @return A new lingering splash potion with custom effects. */ + public ItemStack makeLingeringPotion( Collection effects ) { return newPotion( Items.LINGERING_POTION, effects ); } + + /** @return A new potion with standard effects. */ + private ItemStack newPotion( IItemProvider item, Potion type ) { + return PotionUtils.setPotion( new ItemStack( item ), type ); + } + + /** @return A new potion with custom effects. */ + private ItemStack newPotion( IItemProvider item, Collection effects ) { + return PotionUtils.setCustomEffects( new ItemStack( item ), effects ); + } //--------------- ISpecialMob Implementation ---------------- @@ -147,10 +382,25 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe /** Called each tick to update this entity's movement. */ @Override public void aiStep() { + if( !level.isClientSide() && isAlive() ) { + drinkPotionUpdate(); + fakeDrinkingPotion = true; + } super.aiStep(); getSpecialData().tick(); } + /** @return True if this witch is currently drinking a potion. */ + @Override + public boolean isDrinkingPotion() { + // Effectively disable vanilla witch potion drinking logic in combo with "infinite using time" + if( fakeDrinkingPotion ) { + fakeDrinkingPotion = false; + return true; + } + return super.isDrinkingPotion(); + } + /** @return The eye height of this entity when standing. */ @Override protected float getStandingEyeHeight( Pose pose, EntitySize size ) { @@ -219,6 +469,11 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe final CompoundNBT saveTag = SpecialMobData.getSaveLocation( tag ); + final CompoundNBT itemTag = new CompoundNBT(); + if( !sheathedItem.isEmpty() ) sheathedItem.save( itemTag ); + saveTag.put( References.TAG_SHEATHED_ITEM, itemTag ); + saveTag.putShort( References.TAG_POTION_USE_TIME, (short) potionDrinkTimer ); + getSpecialData().writeToNBT( saveTag ); addVariantSaveData( saveTag ); } @@ -230,6 +485,11 @@ public class _SpecialWitchEntity extends WitchEntity implements ISpecialMob<_Spe final CompoundNBT saveTag = SpecialMobData.getSaveLocation( tag ); + if( saveTag.contains( References.TAG_SHEATHED_ITEM, References.NBT_TYPE_COMPOUND ) ) + sheathedItem = ItemStack.of( saveTag.getCompound( References.TAG_SHEATHED_ITEM ) ); + if( saveTag.contains( References.TAG_POTION_USE_TIME, References.NBT_TYPE_NUMERICAL ) ) + potionDrinkTimer = saveTag.getShort( References.TAG_POTION_USE_TIME ); + getSpecialData().readFromNBT( saveTag ); readVariantSaveData( saveTag ); } diff --git a/src/main/java/fathertoast/specialmobs/common/util/References.java b/src/main/java/fathertoast/specialmobs/common/util/References.java index d979f81..caedea2 100644 --- a/src/main/java/fathertoast/specialmobs/common/util/References.java +++ b/src/main/java/fathertoast/specialmobs/common/util/References.java @@ -65,6 +65,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 +77,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, 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, Wildfire Blaze // Growing mobs public static final String TAG_GROWTH_LEVEL = "GrowthLevel"; // Hungry Spider, Conflagration Blaze diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index a985722..0120535 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -23,5 +23,8 @@ protected net.minecraft.entity.monster.CreeperEntity func_190741_do()V # spawnLi protected net.minecraft.entity.monster.EndermanEntity func_70816_c(Lnet/minecraft/entity/Entity;)Z # teleportTowards(Entity) protected net.minecraft.entity.monster.EndermanEntity func_70825_j(DDD)Z # teleport(x,y,z) +# Witches +protected net.minecraft.entity.monster.WitchEntity field_82200_e # usingTime + # Blazes public net.minecraft.entity.monster.BlazeEntity func_70844_e(Z)V # setCharged(value) \ No newline at end of file From 4aae78e2787e97ea70f024345eeaf30658f48c41 Mon Sep 17 00:00:00 2001 From: FatherToast Date: Fri, 1 Jul 2022 13:52:26 -0500 Subject: [PATCH 04/10] Experimental config changes --- .../common/config/file/ToastConfigSpec.java | 63 ++++++++++++++++--- .../common/config/file/ToastTomlParser.java | 2 +- .../common/config/file/ToastTomlWriter.java | 2 + 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java b/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java index a5aece4..bc80ecb 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java +++ b/src/main/java/fathertoast/specialmobs/common/config/file/ToastConfigSpec.java @@ -2,12 +2,16 @@ package fathertoast.specialmobs.common.config.file; import com.electronwill.nightconfig.core.file.FileConfig; import com.electronwill.nightconfig.core.file.FileConfigBuilder; +import com.electronwill.nightconfig.core.file.FileWatcher; import com.electronwill.nightconfig.core.io.CharacterOutput; +import com.electronwill.nightconfig.core.io.ParsingException; +import com.electronwill.nightconfig.core.io.WritingException; import fathertoast.specialmobs.common.config.field.AbstractConfigField; import fathertoast.specialmobs.common.config.field.GenericField; import fathertoast.specialmobs.common.core.SpecialMobs; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -34,6 +38,8 @@ public class ToastConfigSpec { /** Used to make sure the file is always rewritten when the config is initialized. */ private boolean firstLoad; + /** True while this config spec is currently writing. */ + boolean writing; /** Creates a new config spec at a specified location with only the basic 'start of file' action. */ public ToastConfigSpec( File dir, String fileName ) { @@ -49,7 +55,7 @@ public class ToastConfigSpec { // Create the config file format final FileConfigBuilder builder = FileConfig.builder( new File( dir, fileName + ToastConfigFormat.FILE_EXT ), new ToastConfigFormat( this ) ); - builder.sync().autoreload(); + builder.sync();//.autoreload(); TODO test alternative reloading CONFIG_FILE = builder.build(); } @@ -57,7 +63,39 @@ public class ToastConfigSpec { public void initialize() { SpecialMobs.LOG.info( "First-time loading config file {}", CONFIG_FILE.getFile() ); firstLoad = true; - CONFIG_FILE.load(); + try { + CONFIG_FILE.load(); + } + catch( ParsingException ex ) { + SpecialMobs.LOG.error( "Failed first-time loading of config file {} - this is bad!", CONFIG_FILE.getFile() ); + } + + //TODO test alternative reloading + try { + FileWatcher.defaultInstance().addWatch( CONFIG_FILE.getFile(), this::onFileChanged ); + SpecialMobs.LOG.info( "Started watching config file {} for updates", CONFIG_FILE.getFile() ); + } + catch( IOException ex ) { + SpecialMobs.LOG.error( "Failed to watch config file {} - this file will NOT update in-game until restarted!", + CONFIG_FILE.getFile() ); + } + } + + /** Called when a change to the config file is detected. */ + public void onFileChanged() { + if( writing ) { + SpecialMobs.LOG.info( "Attempted to reload config file {} while it was still saving - this is probably okay", + CONFIG_FILE.getFile() ); + } + else { + try { + SpecialMobs.LOG.info( "Reloading config file {}", CONFIG_FILE.getFile() ); + CONFIG_FILE.load(); + } + catch( ParsingException ex ) { + SpecialMobs.LOG.error( "Failed to reload config file {}", CONFIG_FILE.getFile() ); + } + } } /** Called after the config is loaded to update cached values. */ @@ -70,7 +108,12 @@ public class ToastConfigSpec { // Only rewrite on first load or if one of the load actions requests it if( rewrite || firstLoad ) { firstLoad = false; - CONFIG_FILE.save(); + try { + CONFIG_FILE.save(); + } + catch( WritingException ex ) { + SpecialMobs.LOG.error( "Failed to save config file {}", CONFIG_FILE.getFile() ); + } } } @@ -209,7 +252,7 @@ public class ToastConfigSpec { /** Called when the config is saved. */ @Override - public final void write( ToastTomlWriter writer, CharacterOutput output ) {} // Read callback actions do not affect file writing + public final void write( ToastTomlWriter writer, CharacterOutput output ) { } // Read callback actions do not affect file writing } /** Represents a spec action that reads and writes to a field. */ @@ -294,15 +337,15 @@ public class ToastConfigSpec { /** @param comment The file comment to insert. */ public void header( List comment ) { ACTIONS.add( new Header( this, comment ) ); } - + /** Inserts a detailed description of how to use the given field. */ - public void verboseFieldDesc(GenericField field) { + public void verboseFieldDesc( GenericField field ) { final List description = field.verboseDescription(); - - if (description != null && !description.isEmpty()) - ACTIONS.add(new Comment(field.verboseDescription())); + + if( description != null && !description.isEmpty() ) + ACTIONS.add( new Comment( field.verboseDescription() ) ); } - + /** * @param name The category name. * @param comment The category comment to insert. diff --git a/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlParser.java b/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlParser.java index 9abc77e..8c396df 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlParser.java +++ b/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlParser.java @@ -18,7 +18,7 @@ public class ToastTomlParser implements ConfigParser { /** The actual parser. */ private final TomlParser WRAPPED_PARSER = new TomlParser(); - /** The config spec that drives this writer. */ + /** The config spec that drives this parser. */ private final ToastConfigSpec CONFIG_SPEC; ToastTomlParser( ToastConfigSpec spec ) { CONFIG_SPEC = spec; } diff --git a/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlWriter.java b/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlWriter.java index 378760b..be9c68d 100644 --- a/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlWriter.java +++ b/src/main/java/fathertoast/specialmobs/common/config/file/ToastTomlWriter.java @@ -42,10 +42,12 @@ public class ToastTomlWriter implements ConfigWriter { */ @Override public void write( UnmodifiableConfig config, Writer writer ) { + CONFIG_SPEC.writing = true; SpecialMobs.LOG.debug( "Writing config file! ({}{})", CONFIG_SPEC.NAME, ToastConfigFormat.FILE_EXT ); CharacterOutput output = new WriterOutput( writer ); currentIndentLevel = 0; CONFIG_SPEC.write( this, output ); + CONFIG_SPEC.writing = false; } /** Increases the indent level by 1. */ From ff3fbf5cae8262a41b5795e6dad702a6c4de2785 Mon Sep 17 00:00:00 2001 From: FatherToast Date: Fri, 1 Jul 2022 15:04:41 -0500 Subject: [PATCH 05/10] uhh fish --- .../projectile/SpecialFishHookEntity.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/fathertoast/specialmobs/common/entity/projectile/SpecialFishHookEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/projectile/SpecialFishHookEntity.java index df85c43..c7511e6 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/projectile/SpecialFishHookEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/projectile/SpecialFishHookEntity.java @@ -1,14 +1,23 @@ package fathertoast.specialmobs.common.entity.projectile; -import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.projectile.DamagingProjectileEntity; import net.minecraft.world.World; -public abstract class SpecialFishHookEntity extends Entity { +public class SpecialFishHookEntity extends DamagingProjectileEntity { - public SpecialFishHookEntity( EntityType entityType, World world ) { + protected SpecialFishHookEntity( EntityType entityType, World world ) { super( entityType, world ); } - //TODO + public SpecialFishHookEntity( EntityType entityType, double x, double y, double z, + double dX, double dY, double dZ, World world ) { + super( entityType, x, y, z, dX, dY, dZ, world ); + } + + public SpecialFishHookEntity( EntityType entityType, LivingEntity shooter, + double dX, double dY, double dZ, World world ) { + super( entityType, shooter, dX, dY, dZ, world ); + } } \ No newline at end of file From ddc213a69a16273b4ce7478493cfd45f27e6b93b Mon Sep 17 00:00:00 2001 From: FatherToast Date: Fri, 1 Jul 2022 16:27:54 -0500 Subject: [PATCH 06/10] maybe? --- .../specialmobs/common/entity/projectile/BugSpitEntity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/fathertoast/specialmobs/common/entity/projectile/BugSpitEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/projectile/BugSpitEntity.java index 7ca4d83..df46c2a 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/projectile/BugSpitEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/projectile/BugSpitEntity.java @@ -2,9 +2,10 @@ package fathertoast.specialmobs.common.entity.projectile; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; +import net.minecraft.entity.projectile.ThrowableEntity; import net.minecraft.world.World; -public abstract class BugSpitEntity extends Entity { +public abstract class BugSpitEntity extends ThrowableEntity { public BugSpitEntity( EntityType entityType, World world ) { super( entityType, world ); From c8ad1e8c4b63a65ec4d88b01760f8cebd9853448 Mon Sep 17 00:00:00 2001 From: FatherToast Date: Fri, 1 Jul 2022 18:14:48 -0500 Subject: [PATCH 07/10] minor texture pass --- .../silverfish/ToughSilverfishEntity.java | 3 ++- .../entity/skeleton/FireSkeletonEntity.java | 3 ++- .../skeleton/SpitfireSkeletonEntity.java | 3 ++- .../entity/witch/DominationWitchEntity.java | 3 ++- .../entity/witch/ShadowsWitchEntity.java | 3 ++- .../common/entity/witch/WindWitchEntity.java | 3 ++- .../SpitfireWitherSkeletonEntity.java | 3 ++- .../textures/entity/creeper/ender_eyes.png | Bin 2921 -> 2860 bytes .../entity/creeper/splitting_eyes.png | Bin 11274 -> 2803 bytes .../entity/enderman/blinding_eyes.png | Bin 2864 -> 2807 bytes .../textures/entity/enderman/icy_eyes.png | Bin 2869 -> 2807 bytes .../entity/enderman/lightning_eyes.png | Bin 2889 -> 2821 bytes .../textures/entity/enderman/mirage_eyes.png | Bin 2872 -> 2811 bytes .../textures/entity/enderman/thief_eyes.png | Bin 2869 -> 2807 bytes .../textures/entity/silverfish/tough_eyes.png | Bin 0 -> 2796 bytes .../textures/entity/skeleton/fire_eyes.png | Bin 0 -> 292 bytes .../textures/entity/witch/domination_eyes.png | Bin 0 -> 2828 bytes .../textures/entity/witch/shadows_eyes.png | Bin 0 -> 2819 bytes .../textures/entity/witch/wind.png | Bin 5000 -> 3995 bytes .../textures/entity/witch/wind_eyes.png | Bin 0 -> 2822 bytes .../entity/witherskeleton/fire_eyes.png | Bin 0 -> 290 bytes 21 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 src/main/resources/assets/specialmobs/textures/entity/silverfish/tough_eyes.png create mode 100644 src/main/resources/assets/specialmobs/textures/entity/skeleton/fire_eyes.png create mode 100644 src/main/resources/assets/specialmobs/textures/entity/witch/domination_eyes.png create mode 100644 src/main/resources/assets/specialmobs/textures/entity/witch/shadows_eyes.png create mode 100644 src/main/resources/assets/specialmobs/textures/entity/witch/wind_eyes.png create mode 100644 src/main/resources/assets/specialmobs/textures/entity/witherskeleton/fire_eyes.png diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/ToughSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/ToughSilverfishEntity.java index 7c1a12b..9b6ac10 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/ToughSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/ToughSilverfishEntity.java @@ -75,7 +75,8 @@ public class ToughSilverfishEntity extends _SpecialSilverfishEntity { } private static final ResourceLocation[] TEXTURES = { - GET_TEXTURE_PATH( "tough" ) + GET_TEXTURE_PATH( "tough" ), + GET_TEXTURE_PATH( "tough_eyes" ) }; /** @return All default textures for this entity. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/FireSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/FireSkeletonEntity.java index e006a4d..3c965b6 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/FireSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/FireSkeletonEntity.java @@ -73,7 +73,8 @@ public class FireSkeletonEntity extends _SpecialSkeletonEntity { } private static final ResourceLocation[] TEXTURES = { - GET_TEXTURE_PATH( "fire" ) + GET_TEXTURE_PATH( "fire" ), + GET_TEXTURE_PATH( "fire_eyes" ) }; /** @return All default textures for this entity. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java index 76b689f..11f1977 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java @@ -114,7 +114,8 @@ public class SpitfireSkeletonEntity extends _SpecialSkeletonEntity { public boolean isBaby() { return false; } private static final ResourceLocation[] TEXTURES = { - GET_TEXTURE_PATH( "fire" ) + GET_TEXTURE_PATH( "fire" ), + GET_TEXTURE_PATH( "fire_eyes" ) }; /** @return All default textures for this entity. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/DominationWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/DominationWitchEntity.java index 7925103..b10b9cc 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witch/DominationWitchEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/DominationWitchEntity.java @@ -112,7 +112,8 @@ public class DominationWitchEntity extends _SpecialWitchEntity { } private static final ResourceLocation[] TEXTURES = { - GET_TEXTURE_PATH( "domination" ) + GET_TEXTURE_PATH( "domination" ), + GET_TEXTURE_PATH( "domination_eyes" ) }; /** @return All default textures for this entity. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/ShadowsWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/ShadowsWitchEntity.java index 80887a3..dc42e5a 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witch/ShadowsWitchEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/ShadowsWitchEntity.java @@ -87,7 +87,8 @@ public class ShadowsWitchEntity extends _SpecialWitchEntity { } private static final ResourceLocation[] TEXTURES = { - GET_TEXTURE_PATH( "shadows" ) + GET_TEXTURE_PATH( "shadows" ), + GET_TEXTURE_PATH( "shadows_eyes" ) }; /** @return All default textures for this entity. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witch/WindWitchEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witch/WindWitchEntity.java index 60dcf8a..ff17bba 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witch/WindWitchEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witch/WindWitchEntity.java @@ -247,7 +247,8 @@ public class WindWitchEntity extends _SpecialWitchEntity { } private static final ResourceLocation[] TEXTURES = { - GET_TEXTURE_PATH( "wind" ) + GET_TEXTURE_PATH( "wind" ), + GET_TEXTURE_PATH( "wind_eyes" ) }; /** @return All default textures for this entity. */ diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java index 5230d0b..46120d0 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java @@ -113,7 +113,8 @@ public class SpitfireWitherSkeletonEntity extends _SpecialWitherSkeletonEntity { public boolean isBaby() { return false; } private static final ResourceLocation[] TEXTURES = { - GET_TEXTURE_PATH( "fire" ) + GET_TEXTURE_PATH( "fire" ), + GET_TEXTURE_PATH( "fire_eyes" ) }; /** @return All default textures for this entity. */ diff --git a/src/main/resources/assets/specialmobs/textures/entity/creeper/ender_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/creeper/ender_eyes.png index ae525d4863ff0a728f6dafde1efe3b8f0d8127d7..a4623119c2101ae51dc0130a32a249ae398c1f24 100644 GIT binary patch delta 2833 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 delta 2914 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 M07*qoM6N<$g0KW=uK)l5 diff --git a/src/main/resources/assets/specialmobs/textures/entity/creeper/splitting_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/creeper/splitting_eyes.png index c20ae3dd6fef1990b83d387837618e1b6cc28d2a..156890c702dd4aa45361f8823e857e60aad1e6ca 100644 GIT binary patch delta 2796 zcmV004&%004{+008|`004nN z004b?008NW002DY000@xb3BE2000UvX+uL$Nkc;*P;zf(X>4Tx07%E3mUmQC*A|D* zy?1({%`gH|hTglt0MdJtUPWP;8DJ;_4l^{dA)*2iMMRn+NPkeofYDGSRz$H76jZPW zBnT)m7)4YpZ${ROuB^QG^78xRtZ(hJ_qqG*z0X<~0FtLKmzM@h0g%cQ@Pj;@=@F4p zbnF0t0Ult0DF94XM!H|Hdk7H8%gu$JA#ws=1Of1RSiAyo)6g@3P6z*=q*>{Ff#{e3 z5u39)87vVOh<`XGOOP(M&x)8Dmn>!&C1$=TK@rP|Ibo3vKKm@PqnO#LJhq6%Ij6Hz z*<$V$@wQAMN5qJ)hzm2hoGcOF60t^#FqJFfH{#e-4l@G)6iI9sa9D{VHW4w29}?su z;^hF~NC{tY+*d5%WDCTXa!E_i;d2ub1#}&jF5T4Hntws}$Xa&bXCpZPp zgG=BVxB+g1`(PAIfM;M9yao#p1YsdEM1kZX6-X1(hs+=bWDmJPzEBVp3B^JQ5DyYU zd4JGms2D1Ps-b$Q8TuYN37v_t9A*$R zj+w(^vGQ1btR2=L%ft$>h1e?WQS4dl5OxCl21mrH;LLFDxF{SCmyfH!9l@Q!4S(S# zaSM1Uye8fT?~iBWbMU43MtnE^I(`DbK#(SA6YK~fge1ZyLM5SA?cA^NYNxAX$R>L=^W`U=_Q#=)*?HSqsRjC4stX30{Id7 zjRZx)NWx2kEwMqOMxsMvNaDF9UVl}%DW=qVsT!%1 zQX^7x(iCY^X@BWd=@RK9(pRLXWUw+?GHx<#nF5)EG8bgVDF{WK;zD6jHd7iY7bz1| z3{{)zMNOg>Q@^7QP-kUjWS7Z?$!5#e$exxRr6DveninmFR!Tcg8>YQmqJOl+X$fb^ z_9d-LhL+6BEtPYW%g621?3i}-cBA%m`&jz}_M;A}4orsz zhsTcUj%>#!$4Mt$rzEGNPS2f9ocYe}&hsudF6&&*xgxF}u0^f`ZZdA6Zq;s&+|}F@ z+*{mdJuE!ddYtn_d-{0p@*MF}@?v>4d(C=Vd9U;C^&$BL`+rpXJoeS|<@UTb~$^R zW5+4uOvc;Am&H#d*d^>vm`-#^tVo>Ux^SzxFOocy>XPP@{gV$Re@Y2YX-mbW#-^U+ z$?%eSy=ls6*=d96`ssz~qibx|>{&C*_u)5XKpCqtx_<;z(a%)BP)E2$IF@OjS(EuD zD=h0owsbZxyFW)SXM4_Mu6ypcYf)=iYkTrk^ETy;t#evezaCm2x4vhC`i6oH6B|7? z9^ORQl)UMue3SgL{8yX9H+L5(6>KaR-{P^QrBI@fUpTVWc5B@>)Hd$6f$iqotG0hE zVi#R4Hh(FuD1KkUD!KZN={J?%eA*GeW1!TsbpKAwPTtPpT@Jermr0l9mW}WB+uc!4 zFE1*8wI^oJm5OB*b$bbWg?mRUeJi`FG^%!1y{}HJzFp&7(^jikyS;XPAA8@>e#iZ- zbxL)`b?**v58SEusPAadYN$AfIhc9yNn==J-+#A^Z=0JGnu?n~9O4}sJsfnnx7n(> z`H13?(iXTy*fM=I`sj`CT)*pTHEgYKqqP+u1IL8No_-(u{qS+0<2@%BCt82d{Gqm; z(q7a7b>wu+b|!X?c13m#p7cK1({0<`{-e>4hfb-UsyQuty7Ua;Ou?B?XLHZaol8GA zbALYJ{CH1H&;1J#7jE?i_6}V1xp?`K`=twgPJL%D+g(0&#pcS%tBk9i*DSAf^jq|| z|77t~`+(&@$91dgT{o<6bPp~cJTv4lbpEF6%}ckuZe6>*;`ZQh=^oC; zQ|`XFmw9jD{>BIB2SpF19#%Y3eAMu>?tjm1qn4wm$6Uwy9|u3aKh7F|_DjYu?mT-%DP~ zzdZD6*{hzpfVoGnQ(rI47rl{xbNDUeZQr}_casZQ@3HSIKj?nw{^;}Z!Kc(upJo09 z1DNytZzkLi00009a7bBm000XU000XU0RWnu7ytkOE=fc|RA@u($-xN#Fbo4R@4s{k y3570@S0(OYZ~y?{bJdM^U)w(b0002MzT^NdOb_w%E~5GX0000! literal 11274 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 A=l}o! diff --git a/src/main/resources/assets/specialmobs/textures/entity/enderman/blinding_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/enderman/blinding_eyes.png index 8258ff2e84b13c5127b356f429500fd54a3165d4..398f29052065a3af9faa8a52458ede3db5be38fb 100644 GIT binary patch delta 2780 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 Hu0mjf)eB0# diff --git a/src/main/resources/assets/specialmobs/textures/entity/enderman/icy_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/enderman/icy_eyes.png index c44f354cb0f0b4df74b76b3c0f8f30225733fc6a..8fc0cdc40aaaa7c6ba8920637e0081cdb16b27b7 100644 GIT binary patch delta 2780 zcmV<23M2Kk7WWm9B!2{FK}|sb0I`mI`%#ks001CkNK#Dz0EZ6%0E`a+0R2(`0D(XN z0DXA?0O^YW06gUY02$14JcV}v017W@LqkwWLqi}?a&Km7Y-IodNXMO)cT`l@7KhKh zcY2}CFalDB-n+m6(tDF$MPZm3U?>9)Gc>Uwq5=^`M4BQ;QlP9$S?PR%=$HTz zo3l9?ED;xoIDaKekS?~*ikKRgEM^!bX1*vv5zC1=VUZ0!`z*4fnAxd3wur?!r?XSp zV(u03woD;M#E7qm3p2T#ED_%lu||q8l`G;m;@DIUGXnq=No*HzScxJw5iyA$667M{ zc0%E1Ah>(_PY1 z)0w;+02c53Su*0<(nUqKG_|(0G&D0Z{i;y^b@OjZ+}lNZ8Th$p5Uu}MTtq^NHl*T1?CO*}7&0ztZsv2j*bmJyf3G7=Z`5B*Pv zzoDYB~8euXQVS(9J=A3hxi`{{&gM(L7aFFpTiSH zgo&n%%S#Zoo5$t~xM@5(m-nBV_z%PWq{X=wiPHEHP-BdM)O9LAe(eV+3K1aD`^8=V zqi??WFd%+;;VP4hbN}x*{b#|Y;w6Kd@Hx&UD1U^`67n+__r%W3O|GA5P%R78ls9AA`HX@@kgSNc!ZCvM~aXNqycF~ zx{*HQCNhf5Aa79^6a_^`8KP`ao~Te13xBl+wH{T1szx0~b)b4tH&J7#S=2`~8Lf!c zN86yi&=KeabQZc0U4d>wx1%qjZ=)yBuQ3=54Wo^*!gyjLF-e%Um=erBOdIALW)L%u znZshS@>qSW9o8Sq#0s#5*edK%>{;v(b^`kbN5rY%%y90wC>#%$kE_5P!JWYk;eRG^ z3wSBKCf)|`k7wg^@TK@hd^i3&egeNhkS1so>_C83pYk??@5*JW(Ig>h2k8*$9O*9UC7DdtB0G|!$O7^Xax?h?`4Rbz z1VzF~!b^fJu|c9nqC;Xx;<+SVQh!s@NpiJhu4IMe3CZh{Gg5ddEh!f%rqp_=8mW^~ zBT{qH6lqgwf9X`|66qt-SEQ$8urgXQZZd3{0-1v{7i7jM2t}RZLSa!hQyM83DHBu- zRh#NXO`;Z4zoQONXJut%m&u07X3N&do|YY@Av7(T7cGTWN;^&)roCIDw132D31`Xn zC9O+_mdwj7m2;Hi$Q8-8$=#NFCr_7mlTVi4CEqFkPywZ&rx2)+rLbS&qQcBl>QdXK ztffUuk1xHa2rKF-1}UypJgC^OIH#nnVlfKTBusSTASKKb%HuWJzl+By+?gk zLq)?+BTu76*gy zjC_sqjXI5<8*3Ox8SgUgGyZ5|VUl9fXma0F#?;$1-?ZEGcQZXRmRXJ2EpxKDyZHw5 zF7p@5^p|m#?O%4sf@0xkvDKo-;)A7?CEv2ua@tD6D%PsjYJ@>$1Tab%m#xv(&ej{O zPg%dUv9uA`9Jl$+)_>48+4hL-)N<|RoaK$n$L-YYn0EDcqxN+BSo;I^qYkPLOos-C z$BycbY{w?YNhe*WB&VZJ&z()2`OfXm^DZ_n>s-#cBCZ~;MXm#GGH#)6)ozd6)!Y-@ zTij=@0z9{CE354A2f(6YygoCNLndCh$p+X;5BJUoa&&CiqD3>k#LV(vbV1 zI-$bQo-oO<=&3mrl`4tAA5gpN^4?VaA+@MaPE69*KR=^k+6O=i=< z@RE7GY07EYX@lwd>4oW|Yi!o+Su@M`;WuSK8LKk71bTWll-#$SDV8(cNZuXY%Cbx;<2TrP@<4uII`7tYuz@~Htx28?dIF7wtp;Q z7hNqjDSxggeqX{Wx%!RiH@;dd*9H0$NjB! zN_E9`?+$Pe+^P4d?`Y6!s5po@n0fF?V_0L~w||Upo0}Axikm(h;vE`29CWz1*{Zqu zh~kmb7Pv*&GJQ1q=#B4Ozw2r>Y^`sjwG|%&$Arh8ejoe&@Nu8xJtr6^T7S^|p|+jU zUep0~_`T4=x`(GvqLI{-*2AOSimkUAw*F_TX^n@STz9kCYBs3V)UUwf4Er^B;b5{H=dBVs_#M|HY@@OJ2&q zJoIYWtDd=lxks;4UoXrTy^()&_$}jY-@EX4lM7kzvF|HC=zi$_==1Txr_@iM{s{#G znDhK^CfpB^As0vhGD$>1RA@u($gv3kAP@w>D_aY*xla_q@EZ_jl7$Tb06?eK+%>Xq i-)bKK0002+f#d)hz!86f^Ot`B0000 delta 2862 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 M07*qoM6N<$f`x5Qq5uE@ diff --git a/src/main/resources/assets/specialmobs/textures/entity/enderman/lightning_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/enderman/lightning_eyes.png index 6b66d66c242ce49709fe616e93063c10d3d659e5..0955b062b923e47162b317ca8ae5eb267dc04137 100644 GIT binary patch delta 2794 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 delta 2882 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 P00000NkvXXu0mjfs54KF diff --git a/src/main/resources/assets/specialmobs/textures/entity/enderman/thief_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/enderman/thief_eyes.png index bc545f5c163be91aa1218db383412e49cedcd77f..fb6096d6ee88c60ee86c5134c655d1a8b9be578b 100644 GIT binary patch delta 2780 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 literal 5000 zcmaKObx_oQ(EsP?4(Sx6ySophJA|VV;Rs1V8V_kuLb|~KBo1jQIl4i*`v7U>=zRFj zZ|3*>_x!Q@n)lAmzF)gLv-678*Ht6Lqs0RNfKWqS#qbdo9}R|s_2^r|JX{_D*Hazx z1^|e<{xeXYN9ij7z|(V9R@T>d^6>F^sLim@MxWSwCpf9d7try!P_w}>#I~AzB+A(Yd4nexQh=CZ4;5(R+bS=EEyLTPQ zuNa2M>IsnG)V^YVbH@S#E<Nqigtp_79Uq>p|e-UDY@f?yFKgio@h z77mOYPz+1}7Xyl_AXr`|yAe=`0oV@NzuW}$`2bsvciWQyEblTa2n1N9GE;yGlL00& zhZq&WNd~AGdlB~>FcSpG9ksfpfH^)uP{Y(w9r)1*AVx{>YXBSqK+qs2k_*5H0&M$P zSp0#AEP!0~#8mbl51eF&`*BiPHL^{tQqRLIaQM8iO-=b3IY%|9c*w==(Co4m`8xx$ zXoSN=iPuoW08p4r@z^cu&VQ5$J~}F%&_HCvx7UvK@a*Nw&HJsfa?i)bt^0(I-}4I8 z(!yl0U~c!H*^V%rtnmwdFXA0*h!yLB!rcW^l;?l5d0v#%G(W$EM9yh;gDva^O+)UX zoi+&5yVqBtvbPs!Yri&F!-TEE)G#jBI)_iSztN9g%P{$@TjOPu z88`W+!4t0pmFy2F;cw4k!JoK#*d`ucZLIR%pv$j{0Y6;iK|#rUrB?Rn=UN;jWhWt5I>wWDR;Ga%a)W1D$ztk4 zhH=JBCh80)a^7fC(Uz*s$uj+b@cY}SRyCp2uo?roDS~?Crcw#o2-VN{zhC(XWhYV= z=KS`qqm}z0UbOSO+=+N0QC_)E5b=i^TcrE7Ab)QcxLT4IH^qLa`dI}*c`xmc7ZvC& zv1j&U)qWUH!k!~=#WDQAOm>V)>So$zx^wEKO!TaxqzkwbxabiEogB|nO10|Pn@O*_ zsb2~UMVqPfvdlb5BoiPBic#*CV0{v+EX>iAgr-sb)p+8`gzAL5AdA6 z;c{t_F{xOhIk>W@!LZ68x|B@RH(P2XoKi#UTjl4pNm?f=rwiw*KR(T*%V~_d8NW>@ zhIZBdWc)!t^&=pRq6qJcfpXI1P-9a=Y5b_GGPR3oCgW-Pj0Gix_A_^~h_gIQ%(xr4 zgDH{+@zONY*wg5^1&qI!6_>4);hUgLhKv)xTN{PIRZT>UHoxOerI(wO<>~Jm(HTL% z%alf!K}$W16~2Z-?qabZUD{oG%3lo%t8F%0dztXnOzCXqkO}}y4kBo=7ZmI zw`ATiw8YDmeJ_$y7SR+jtu;iYIx1jqW_n%b`sm{BaF%4CLgQA*kGVFu=!!+Pn}i%J zUCYul(#^`%%MHra_S?kahb4K%ZO>aEH(^9deVQDW9BRrwY|d(6BnO*kqhK341f5Pya|e-2&ma(^3A)91{~YCUk`EH9@v zr}MCMvFb?t$oVLGsbE;NfGZy&^5%C}zw(;F*0=(GL7JfJYDK7?vR)XpeuqWCULe`D za`s2#H@~tAyJIT(b9wKAP5Dh(i51}$h|RDBqOremNUzHy-zIWZ`5nu{tB1gcai9do z27;o;hAD+vpxs;s1~a!OC`d0loY%}BDt#zyQld~wd|&x~tD} znJ7{}^v}$1Y`bl{_^wa_j>zap(aiI(`zwssIH>)bwD(NaKIZz=zsVY8A zF>d)XeizvViDuDegtCz4hp6a{ybb)#AgWA(ek;#_ zqS4ON$P&u${7-nL(-pO2bR|>&Dv@xPQhs1NA@gHPzzgVP=!8BK7#AOpnNzmpX4E}V z=hg}QbdcOC7Qosg@{M<^DT3pBQmZzh_HvGNK4tb>CO2U@={BM8PsvGM;4)^Nlm_S? zbldUmP=!Ne&3%u1e4_wk8uox|ZBhj$gb%L)T>Qt_oO6~Ny9VlD>exOKJ1CW2p3gIK zI9xlpm?V*VT<~!Cr1Q!8r-pA)TBdvqX4`EQE>bnJUIB}En-rKlQ7^D6WPipraJxdBz(d>E19e=(tUCtZchrMi_wh~ewjx9Jmu3&G^vnbxtMzu*QF(Oo6tQt|@U{qSL0LTYh&@FiRF5`*-Q8FBf6m0bJ2t8MT^#p zJNbcMizXlwooUBuhZ~KX+6x7EY}xK*`i+}arLLRTVITSg`W-e4QNwK0)HK6{oUviz z49~tAQQy;F_j$*De1zhJCLeQqH&{GX0+72GxT_b8gwhLdKLk~*V^2PP%K&8%Z~YV7MbZ!)9fl~8@D~5J=yz|Nxi<7 z+?H&3lcj6ZQD9qrkmJQJUB1NV@@ZSZKgx%h>3k`<X{?E)(@eC$5?ERk3lGchZ1D2gy@ zKVMQ_N@_#F`)>EPM0xDh7<)d|{pb53I2ENp=;ieO?C5yrcu~elMoPO|@VC1?#6C^c zVsB?J7^^n0#?>HZvuBy+BewW;&(4?e1V4!EBZtX?* z79aH{lfAm34gdtQJ?hYK0JwU1#9aXJ6$F4iTL6&G1OO_JPc}WO0Dx1gp`v8$zi?o# z@qwX-=DsX2z+;6jv5#Pg7mJmHQt&m_I_G!{>?J9V6q7D1w)hqnJy1=rPed;X%(F&# z^uK*iUZYMLt>X0~g)>T-3C|3RjEfV`@pZ3c?`GDngva6F`p7CA4!2&BmV0|=xPKP( z&ZPai{as~yJP2`AazqCF?N1EtDg5nmerv`QRc4LZ$868$~+-d+Ih8?KT z0mg?bB`hHK4eL0ANF$!j<=VlL;`x7d>RrfmJ35b`zc?5Jb_vtjouC+Af^D{g?894%{AuS)l>>!FK{# zVrCvTvh~`%iP%8s^C2v(RjVyE3H-a5Y%cfh zL6nW)FnuT?62;xt`r!FFvBUxNq;we4twtzt1KoW)EsSJ zZ0ut2-CYm5^2h51>OvR2Y4PRBU1=TBfcD90lC z%B44^mR-iRDCKK)-IYeLxge%=j)ft@)##f+OjD&e&bwz4ihLp{!|@h}0t0Podk5jk$MFi28Q7q-frDk znPmrnr|m(s0tYu-p)D62Sv)9vVTjnAFZjf=h7)pgs^C3b zU27buV)M|;XJrxxDOml!yI)~T8S@j(0VD37wbfP1c0UgUU-V8ZtV^dnt(h2&WdJu| zNf{-r#ZNo<>v-IO`fF(Rut(fi_e>GDOY0o&vM4{Du~-8rIq+k`40nPa zQ?cH)0`wq2b9=HXk7 z`yRd@WZ~!izS<{cfm(j+Bz;vxN2_4HM9%##!^Qg9L*NUyn03Qe?_73k)pDlhX*3`Xy=U_JiCPF{Uce5@+(5m+ zrG9y%FrRTB4|zq5%m7+4U8y}X;A3$C4Y}2a$4^%@?eN_9(0p4Z@ss+*vqSqIHuL$y zr<|_;hLHZ$`Lg?E&3u76DgETrwDM8_s%ntzo*>=brB*X#76tDH$rPmcMa9a{bncjA%I&;*X5i#C*~lc8)9Mu{GnoN zimxzeB8htyA*SSTNE~NoiM^H2b4>0L+Fal}xbN2mwpx9>eOQjwW>!Sg*V2Z4rFUVL zt+e|Q8`kNDsTnpu^sm2c$MEC-{fMP#saq1-e(kQmMcl?LtWNSrCkZdE;z~PGKVReU zUkL=`>?YPxm4jt0#U6ZQrC515J^3ttdTVWkDs<0(UO0HYYelM>#n>vhQlS2+r3~Fu zh%2;c6n_u5ggWqP38SIDjPP=%0kHtVsacr z`To(n6G=x?SilF0!4SFMr8DOm@&)3*CQ3ge$(1(`>y8HDyHfv3#_oH8wZ)8gM*YR} z8m6l9slRd0TFw(Ol+FN96M%^~D#?GdH}Y9hIL?)IeJWtDiBaleA|O}xQ>9F5YAP6M z`#C3LDwMVxvT7Y$4xU?zqvtZXIw^6_C(2aHMIGV;8l8?}77Wy^qRw)o<}=HfQ(gkQxIL2n(Q;uA`Tv>%GkRTciK3jb4WzLkcGw@t}$^G>*=x?_5TER((e?c4V zW%ZIp#$EQ3UF%OSW0x{#p$q;bN^;-6`6;QqgI;e;SjBi<&yf8zjsTUyskd+yQ>HXx z3RFy_K6^i)w;)r~0{>}csqc#@NQ@2wy^}pR#R68dv&b}jfsvN|xW&j@s}(w+bUM^VD?SI`ImEC3w_LI9p1 zvYgyp@&N0#&Q3HDN#HouZFqGpgI@0mqGZV>r1en~t5clJV(*x0iZ%1-FbJg}&XttrD z^1i1a)dgQXY1AGAOk&NQgeHh`V*^iji00EFUi9~+o{FvSo$r;3o z<9=G`Xjy24Z004&%004{+008|`004nN004b?008NW002DY000@xb3BE2000Uv zX+uL$Nkc;*P;zf(X>4Tx07%E3mUmQC*A|D*y?1({%`gH|hTglt0MdJtUPWP;8DJ;_ z4l^{dA)*2iMMRn+NKnLp(NH8-M6nPQRImpm2q-ZaMN}+rM%Ih2ti1Q~^84egZ|$@9 zx%=$B&srA%lBX}1mj+7#kjfMAgFKw+5s^`J>;QlP9$S?PR%=$HTzo3l9?ED;xoI3-JvF1F8#m>QQXW*8-A zz9>Nv%ZWK*kqtikEV84R*{M9Xh{ZXlvs2k(?iKO2Od&_ah_8qXGr62B5#JKAMv5?% zE8;ie*i;TP0{|3BY!`4?i6S-;F^L}%f`(o2L0Dz>ZZynda zx(`h}FNp#{x{a}MR#uh~m%}m=7xWMPPlvyuufAs_KJJh5&|Nw4Oks+EF0LCZEhSCJ zr)Q)ySsc3IpNIG#2mW;)20@&74xhslMTCi_jLS<9wVTK03b<)JI+ypKn)naH{-njZ z7KzgM5l~}{fYfy=Kz{89C<+lE(fh?+|D$id_%I-TdEqLPi*x_)H~nY9rQ#)noA5c# zB`Ac>67n+__r%Wu$9dISw03U@r;Pdb`_%=KWKZEBGfDjQH zqKX(I48#TTN1~8;gpaI8ijWGV0cl0Lkv`-mGK$O~Z&4T&1w}_0qHIx~s8AFOwFb2w zRf4KU9Y%GadQmq~W2jlwM>H9&h}K8jpuNx$=mc~Yx)5D~ZbG-CFQRXwC(y4k7z_=g zjj_UbVj?j~n6;P^%sxyT<{V}aGme?VVzKgAeXJeUAIroFu!Yzv>{0Al>=1SW`vynE zso>0T?zku%50{Utz#YMz!42UiaSM1Uye8fT?~iBWbMU43MtnE^I(`DbK#(SA6YK~f zge1ZyLM5SA?cA^NYNxAX$R>L=^W`U z=_Q#=)*?HSqsRjC4stX30{Id7jRZx)NWx2kEwMqOMxsMvNaDF9UQ$!iNpiJhu4IMe z3CZh{Gg5ddEh!f%rqp_=8mW^~BT{qH6lqgwf9X`|66qt-SEQ$8urgXQZZd3{0-1v{ z7i7jM2t}RZLSa!hQyM83DHBu-Rh#NXO`;Z4zoQONXJut%m&u07X3N&do|YY@Av7(T z7cGTWN;^&)roCIDw8Uu%XUX;@txJZM%*!p6bCl!A70I>9-IjYNPnUO-PnO>$-zoo4 z0i~d)5U7x)uwUV#!pu_YQro4hrA14RFTJM-E9xl*DXvvKsMxPKr=+app_HyvrF21Q zMwzDUsGOu+u6#y$T7{xwufkO+S2?TllrBqmqNmU+>Amz>RYg@#RiSFV>VWEknzmY~ zTE1GF+Cz1MIzv5Pys-#cBCZ~; zMXm#GGH#)6)ozd6)!Y-@Tijj2>R4y()XvmDLKXQ&yjjk&I!+oQOrohQ}U>eb4k~HZbSnyy9x( zW?3$*y{uH6t~>7#3G*6dj`%lF|oWk4CLGP(p*(a%)B zP)E2$IF@OjS(EuDD=h0owsbZxyFW)SXM4_Mu6ypcYf)=iYkTrk^ETy;t#evezaCm2 zx4vhC`i6oH6B|7?9^ORQl)UMue3SgL{8yX9H+L5(6>KaR-{P^QrBI@fUpTVWc5B@> z)Hd$6f$iqotG0hEVi#R4HYu(seqX{Wx%!RiH@;dd*9H0$NjB!N_E9`?+$Pe+^P4d?`Y6!s5po@n0fF?V_0L~w~TL_n-rRgn?4-k z9U46xbhx+Ks=4`y;*ru8xJB49eKh*$jqhB)>uNP@t#6~X6(0k~gvXwKAN&3Aai8No zCm1JMf6)A)ww=;m)B$zmbj)@pc8+#Mb`75NKH1Z4+ui=7(T|5tsh+AiEql834Bs>djZ*&hXA3QVUFm(Q=>&;8Iyl!2)z2f%ZaOm)z zk?4`pJM24CcT?`ZxR-fv;r_-4=m$j)r5;v1Qhe0#v+mDrqn4wm$6Uwy9|u3aKh7F| z_DjYu?mT-%DP~zdZD6*{hzpfVoGnQ(rI47rl{xbNDUeZQr}_casZQ@3HSIKj?nw{^;}Z z!Kc(upZ)~{nDhK^CfpAI000SaNLh0L01m+b01m+cxRGn^0000%Nkl4L^iAn$f000000000000000000000000^da2$500000000000KoT> Y0l_^2GGC`L4FCWD07*qoM6N<$f`1!5q5uE@ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/specialmobs/textures/entity/witherskeleton/fire_eyes.png b/src/main/resources/assets/specialmobs/textures/entity/witherskeleton/fire_eyes.png new file mode 100644 index 0000000000000000000000000000000000000000..1e05e3d923d1804e1df908354bfe9663200994b4 GIT binary patch literal 290 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3-p)I`?e@QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1``2&1HT&Mf$p3GsozexK1*)X7(pmAP4kYX(f@(cbC1Ps5o@dX0~I14-? ziy0WWg+Z8+Vb&Z8pkTMBi(`m~_uEOYxeh3BxLvLJpFZ!I9+&dIU#4#CVyso)N)^vu z5p~H;{Ou9JV*b=?n%ySOJ1rAgF0Q|JcFVK}LNgSXaacO){9&m`<+gj>?x8cMxp(E% ziu-35ux{4J`-g3Xtt_eVAF?hQAxvX Date: Sat, 2 Jul 2022 10:50:49 -0500 Subject: [PATCH 08/10] ghast progress, minor cleanup --- .../specialmobs/client/ClientRegister.java | 1 + .../renderer/entity/SpecialGhastRenderer.java | 46 +++ .../common/bestiary/MobFamily.java | 8 +- .../specialmobs/common/core/SpecialMobs.java | 14 +- .../entity/blaze/HellfireBlazeEntity.java | 2 +- .../entity/blaze/WildfireBlazeEntity.java | 4 +- .../entity/blaze/_SpecialBlazeEntity.java | 10 +- .../common/entity/ghast/BabyGhastEntity.java | 78 +++++ .../entity/ghast/_SpecialGhastEntity.java | 317 ++++++++++++++++++ .../entity/skeleton/NinjaSkeletonEntity.java | 11 +- .../skeleton/SpitfireSkeletonEntity.java | 2 +- .../NinjaWitherSkeletonEntity.java | 18 +- .../SpitfireWitherSkeletonEntity.java | 2 +- .../specialmobs/common/util/References.java | 8 +- src/main/resources/mcmod.info | 16 - 15 files changed, 481 insertions(+), 56 deletions(-) create mode 100644 src/main/java/fathertoast/specialmobs/client/renderer/entity/SpecialGhastRenderer.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ghast/BabyGhastEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java delete mode 100644 src/main/resources/mcmod.info diff --git a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java index 034b4d1..8dc5556 100644 --- a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java +++ b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java @@ -40,6 +40,7 @@ public class ClientRegister { registerFamilyRenderers( MobFamily.SILVERFISH, SpecialSilverfishRenderer::new ); registerFamilyRenderers( MobFamily.ENDERMAN, SpecialEndermanRenderer::new ); registerFamilyRenderers( MobFamily.WITCH, SpecialWitchRenderer::new ); + registerFamilyRenderers( MobFamily.GHAST, SpecialGhastRenderer::new ); registerFamilyRenderers( MobFamily.BLAZE, SpecialBlazeRenderer::new ); // Species overrides diff --git a/src/main/java/fathertoast/specialmobs/client/renderer/entity/SpecialGhastRenderer.java b/src/main/java/fathertoast/specialmobs/client/renderer/entity/SpecialGhastRenderer.java new file mode 100644 index 0000000..ea724e9 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/client/renderer/entity/SpecialGhastRenderer.java @@ -0,0 +1,46 @@ +package fathertoast.specialmobs.client.renderer.entity; + +import com.mojang.blaze3d.matrix.MatrixStack; +import fathertoast.specialmobs.common.entity.ISpecialMob; +import fathertoast.specialmobs.common.entity.SpecialMobData; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.client.renderer.entity.EntityRendererManager; +import net.minecraft.client.renderer.entity.GhastRenderer; +import net.minecraft.entity.monster.GhastEntity; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@OnlyIn( Dist.CLIENT ) +public class SpecialGhastRenderer extends GhastRenderer { + + private final float baseShadowRadius; + + public SpecialGhastRenderer( EntityRendererManager rendererManager ) { + super( rendererManager ); + baseShadowRadius = shadowRadius; + // Would require some big changes to make glowing eyes animate with the base texture + //addLayer( new SpecialMobEyesLayer<>( this ) ); + // Model doesn't support size parameter - overlay texture is applied to the animation instead + //addLayer( new SpecialMobOverlayLayer<>( this, new GhastModel<>( 0.25F ) ) ); + } + + @Override + public ResourceLocation getTextureLocation( GhastEntity entity ) { + final SpecialMobData data = ((ISpecialMob) entity).getSpecialData(); + return entity.isCharging() && data.hasOverlayTexture() ? data.getTextureOverlay() : data.getTexture(); + } + + @Override + protected void scale( GhastEntity entity, MatrixStack matrixStack, float partialTick ) { + super.scale( entity, matrixStack, partialTick ); + + final float scale = ((ISpecialMob) entity).getSpecialData().getRenderScale(); + shadowRadius = baseShadowRadius * scale; + matrixStack.scale( scale, scale, scale ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java index b0576de..737b774 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java @@ -95,10 +95,10 @@ public class MobFamily { "Domination", "Shadows", "Undead", "Wilds", "Wind" ); - // public static final MobFamily GHAST = new MobFamily<>( - // "Ghast", "ghasts", 0xF9F9F9, new EntityType[] { EntityType.GHAST }, - // "Baby", "Fighter", "King", "Queen", "Unholy" - // ); + public static final MobFamily GHAST = new MobFamily<>( + "Ghast", "ghasts", 0xF9F9F9, new EntityType[] { EntityType.GHAST }, + "Baby"//, "Fighter", "King", "Queen", "Unholy" + ); public static final MobFamily BLAZE = new MobFamily<>( "Blaze", "blazes", 0xF6B201, new EntityType[] { EntityType.BLAZE }, diff --git a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java index a562b69..6ebb6df 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java +++ b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java @@ -10,15 +10,11 @@ import fathertoast.specialmobs.common.network.PacketHandler; import net.minecraft.util.ResourceLocation; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.IEventBus; -import net.minecraftforge.fml.DeferredWorkQueue; import net.minecraftforge.fml.InterModComms; import net.minecraftforge.fml.ModList; -import net.minecraftforge.fml.ModLoader; import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.lifecycle.FMLConstructModEvent; import net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent; -import net.minecraftforge.fml.event.lifecycle.ParallelDispatchEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.registries.ForgeRegistryEntry; import org.apache.logging.log4j.LogManager; @@ -79,8 +75,8 @@ public class SpecialMobs { * ? ranged attack AI * + puffer * - endermen - * o witches - * o ability to equip held items + * - witches + * - ability to equip held items * - uses splash speed instead of regular * o ghasts * o melee attack AI @@ -128,12 +124,12 @@ public class SpecialMobs { MobFamily.initBestiary(); } - + // TODO - This could very well help out the config malformation issue // Only problem here is that this event is never fired apparently. // Perhaps DeferredWorkQueue.runLater() could work (ignore deprecation, simply marked for removal) - public void onParallelDispatch(FMLConstructModEvent event) { - event.enqueueWork(Config::initialize); + public void onParallelDispatch( FMLConstructModEvent event ) { + event.enqueueWork( Config::initialize ); } public void sendIMCMessages( InterModEnqueueEvent event ) { diff --git a/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java index b0ae9c2..e83878d 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/blaze/HellfireBlazeEntity.java @@ -80,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; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java index 91f6d38..1c0fe85 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/blaze/WildfireBlazeEntity.java @@ -99,7 +99,7 @@ public class WildfireBlazeEntity extends _SpecialBlazeEntity { 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 ); @@ -117,7 +117,7 @@ public class WildfireBlazeEntity extends _SpecialBlazeEntity { 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 ); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java index f587129..323aa69 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java @@ -47,7 +47,7 @@ public class _SpecialBlazeEntity extends BlazeEntity implements IRangedAttackMob @SpecialMob.BestiaryInfoSupplier public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { - return new BestiaryInfo( 0xFFF87E ); //TODO - TEMP: base size = 0.6F, 1.8F + return new BestiaryInfo( 0xFFF87E ); } @SpecialMob.AttributeCreator @@ -131,9 +131,9 @@ public class _SpecialBlazeEntity extends BlazeEntity implements IRangedAttackMob /** The parameter for special mob render scale. */ private static final DataParameter SCALE = EntityDataManager.defineId( _SpecialBlazeEntity.class, DataSerializers.FLOAT ); - // The amount of fireballs in each burst. + /** The amount of fireballs in each burst. */ public int fireballBurstCount; - // The ticks between each shot in a burst. + /** The ticks between each shot in a burst. */ public int fireballBurstDelay; /** Called from the Entity.class constructor to define data watcher variables. */ @@ -146,7 +146,7 @@ public class _SpecialBlazeEntity extends BlazeEntity implements IRangedAttackMob /** Called to attack the target with a ranged attack. */ @Override public void performRangedAttack( LivingEntity target, float damageMulti ) { - if( !isSilent() ) level.levelEvent( null, 1018, blockPosition(), 0 ); + if( !isSilent() ) level.levelEvent( null, References.EVENT_BLAZE_SHOOT, blockPosition(), 0 ); final float accelVariance = MathHelper.sqrt( distanceTo( target ) ) * 0.5F * getSpecialData().rangedAttackSpread; final double dX = target.getX() - getX() + getRandom().nextGaussian() * accelVariance; @@ -154,7 +154,7 @@ public class _SpecialBlazeEntity extends BlazeEntity implements IRangedAttackMob final double dZ = target.getZ() - getZ() + getRandom().nextGaussian() * accelVariance; final SmallFireballEntity fireball = new SmallFireballEntity( level, this, dX, dY, dZ ); - fireball.setPos( fireball.getX(), getY( 0.5 ) + 0.5, fireball.getZ() ); + fireball.setPos( getX(), getY( 0.5 ) + 0.5, getZ() ); level.addFreshEntity( fireball ); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/BabyGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/BabyGhastEntity.java new file mode 100644 index 0000000..10a67ee --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/BabyGhastEntity.java @@ -0,0 +1,78 @@ +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.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.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.item.Items; +import net.minecraft.util.SoundEvent; +import net.minecraft.world.World; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class BabyGhastEntity extends _SpecialGhastEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + entityType.sized( 1.0F, 1.0F ); + return new BestiaryInfo( 0xFFC0CB, BestiaryInfo.BaseWeight.DISABLED ); + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialSpiderEntity.createAttributes() ) + .addAttribute( Attributes.ATTACK_DAMAGE, -1.0 ) + .multAttribute( Attributes.MOVEMENT_SPEED, 1.3 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Baby Ghast", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + loot.addCommonDrop( "common", Items.GUNPOWDER, 1 ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return BabyGhastEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + public BabyGhastEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().setBaseScale( 0.25F ); + xpReward = 1; + } + + /** Override to change this entity's AI goals. */ + @Override + protected void registerVariantGoals() { + getSpecialData().rangedAttackDamage -= 1.0F; + disableRangedAI(); + } + + /** @return The sound this entity makes idly. */ + @Override + protected SoundEvent getAmbientSound() { return null; } // There could be a lot of these, need to be less annoying +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java new file mode 100644 index 0000000..54e99f3 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java @@ -0,0 +1,317 @@ +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.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.monster.GhastEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.projectile.FireballEntity; +import net.minecraft.entity.projectile.SnowballEntity; +import net.minecraft.inventory.EquipmentSlotType; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.network.datasync.DataParameter; +import net.minecraft.network.datasync.DataSerializers; +import net.minecraft.network.datasync.EntityDataManager; +import net.minecraft.potion.EffectInstance; +import net.minecraft.util.DamageSource; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.IServerWorld; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class _SpecialGhastEntity extends GhastEntity implements IRangedAttackMob, ISpecialMob<_SpecialGhastEntity> { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species<_SpecialGhastEntity> SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + return new BestiaryInfo( 0xBCBCBC ); + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return GhastEntity.createAttributes().add( Attributes.ATTACK_DAMAGE, 4.0 ); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Ghast", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void addBaseLoot( LootTableBuilder loot ) { + loot.addLootTable( "main", EntityType.GHAST.getDefaultLootTable() ); + } + + @SpecialMob.Factory + public static EntityType.IFactory<_SpecialGhastEntity> getFactory() { return _SpecialGhastEntity::new; } + + + //--------------- Variant-Specific Breakouts ---------------- + + public _SpecialGhastEntity( EntityType entityType, World world ) { + super( entityType, world ); + reassessWeaponGoal(); + getSpecialData().initialize(); + } + + /** Called in the MobEntity.class constructor to initialize AI goals. */ + @Override + protected void registerGoals() { + super.registerGoals(); + //TODO replace vanilla ai with our own + + 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 7; } + + /** Called when this entity successfully damages a target to apply on-hit effects. */ + @Override + public void doEnchantDamageEffects( LivingEntity attacker, Entity target ) { + onVariantAttack( target ); + super.doEnchantDamageEffects( attacker, target ); + } + + /** Override to apply effects when this entity hits a target with a melee attack. */ + protected void onVariantAttack( Entity target ) { } + + /** Override to save data to this entity's NBT data. */ + public void addVariantSaveData( CompoundNBT saveTag ) { } + + /** Override to load data from this entity's NBT data. */ + public void readVariantSaveData( CompoundNBT saveTag ) { } + + + //--------------- Family-Specific Implementations ---------------- + + /** The parameter for special mob render scale. */ + private static final DataParameter SCALE = EntityDataManager.defineId( _SpecialGhastEntity.class, DataSerializers.FLOAT ); + + /** This entity's attack AI. */ + private Goal currentAttackAI; + + /** Called from the Entity.class constructor to define data watcher variables. */ + @Override + protected void defineSynchedData() { + super.defineSynchedData(); + specialData = new SpecialMobData<>( this, SCALE, 1.0F ); + } + + /** Called 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 = getExplosionPower(); + fireball.setPos( + getX() + lookVec.x, + getY( 0.5 ) + 0.5, + getZ() + lookVec.z ); + level.addFreshEntity( fireball ); + } + + /** 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 ); + reassessWeaponGoal(); + 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 ) reassessWeaponGoal(); + } + + /** Called to set this entity's attack AI based on current equipment. */ + public void reassessWeaponGoal() { + // if( level != null && !level.isClientSide ) { TODO + // if( currentAttackAI != null ) goalSelector.removeGoal( currentAttackAI ); + // + // final SpecialMobData<_SpecialGhastEntity> data = getSpecialData(); + // if( data.rangedAttackMaxRange > 0.0F ) { + // currentAttackAI = new RangedBowAttackGoal<>( this, data.rangedWalkSpeed, + // data.rangedAttackCooldown, data.rangedAttackMaxRange ); + // } + // else { + // currentAttackAI = new ZombieAttackGoal( this, 1.0, false ); + // } + // 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 ); + + reassessWeaponGoal(); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/NinjaSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/NinjaSkeletonEntity.java index d42a37b..8cb59c4 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/NinjaSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/NinjaSkeletonEntity.java @@ -23,7 +23,6 @@ import net.minecraft.network.datasync.DataSerializers; import net.minecraft.network.datasync.EntityDataManager; import net.minecraft.potion.EffectInstance; import net.minecraft.potion.Effects; -import net.minecraft.tileentity.TileEntity; import net.minecraft.util.*; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.vector.Vector3d; @@ -142,16 +141,13 @@ public class NinjaSkeletonEntity extends _SpecialSkeletonEntity implements INinj super.tick(); } + /** Plays an appropriate step sound for this entity based on the floor block. */ @Override - protected void playStepSound( BlockPos pos, BlockState state ) { - // Nope - } + protected void playStepSound( BlockPos pos, BlockState state ) { } // Disable /** @return The sound this entity makes idly. */ @Override - protected SoundEvent getAmbientSound() { - return getHiddenDragon() == null ? null : SoundEvents.SKELETON_AMBIENT; - } + protected SoundEvent getAmbientSound() { return isCrouchingTiger() ? null : super.getAmbientSound(); } /** Moves this entity to a new position and rotation. */ @Override @@ -216,7 +212,6 @@ public class NinjaSkeletonEntity extends _SpecialSkeletonEntity implements INinj private static final DataParameter> HIDING_BLOCK = EntityDataManager.defineId( NinjaSkeletonEntity.class, DataSerializers.BLOCK_STATE ); private boolean canHide = true; - private TileEntity cachedTileEntity = null; /** Called from the Entity.class constructor to define data watcher variables. */ @Override diff --git a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java index 11f1977..208c296 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/SpitfireSkeletonEntity.java @@ -90,7 +90,7 @@ public class SpitfireSkeletonEntity extends _SpecialSkeletonEntity { /** 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; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/NinjaWitherSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/NinjaWitherSkeletonEntity.java index 3e8cac6..5203362 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/NinjaWitherSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/NinjaWitherSkeletonEntity.java @@ -23,11 +23,8 @@ import net.minecraft.network.datasync.DataSerializers; import net.minecraft.network.datasync.EntityDataManager; import net.minecraft.potion.EffectInstance; import net.minecraft.potion.Effects; -import net.minecraft.tileentity.TileEntity; -import net.minecraft.util.ActionResultType; -import net.minecraft.util.DamageSource; -import net.minecraft.util.Hand; -import net.minecraft.util.ResourceLocation; +import net.minecraft.util.*; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.World; @@ -144,6 +141,14 @@ public class NinjaWitherSkeletonEntity extends _SpecialWitherSkeletonEntity impl super.tick(); } + /** Plays an appropriate step sound for this entity based on the floor block. */ + @Override + protected void playStepSound( BlockPos pos, BlockState state ) { } // Disable + + /** @return The sound this entity makes idly. */ + @Override + protected SoundEvent getAmbientSound() { return isCrouchingTiger() ? null : super.getAmbientSound(); } + /** Moves this entity to a new position and rotation. */ @Override public void moveTo( double x, double y, double z, float yaw, float pitch ) { @@ -207,7 +212,6 @@ public class NinjaWitherSkeletonEntity extends _SpecialWitherSkeletonEntity impl private static final DataParameter> HIDING_BLOCK = EntityDataManager.defineId( NinjaWitherSkeletonEntity.class, DataSerializers.BLOCK_STATE ); private boolean canHide = true; - private TileEntity cachedTileEntity = null; /** Called from the Entity.class constructor to define data watcher variables. */ @Override @@ -242,7 +246,7 @@ public class NinjaWitherSkeletonEntity extends _SpecialWitherSkeletonEntity impl public void setHiddenDragon( @Nullable BlockState block ) { getEntityData().set( HIDING_BLOCK, Optional.ofNullable( block ) ); canHide = false; - + // Smoke puff when emerging from disguise if( block == null ) { spawnAnim(); diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java index 46120d0..9402f8a 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/SpitfireWitherSkeletonEntity.java @@ -89,7 +89,7 @@ public class SpitfireWitherSkeletonEntity extends _SpecialWitherSkeletonEntity { /** 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; diff --git a/src/main/java/fathertoast/specialmobs/common/util/References.java b/src/main/java/fathertoast/specialmobs/common/util/References.java index caedea2..2376da6 100644 --- a/src/main/java/fathertoast/specialmobs/common/util/References.java +++ b/src/main/java/fathertoast/specialmobs/common/util/References.java @@ -19,11 +19,15 @@ 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_SHOOT = 1016; + public static final int EVENT_BLAZE_SHOOT = 1018; + //--------------- NBT STUFF ---------------- diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info deleted file mode 100644 index b8966d7..0000000 --- a/src/main/resources/mcmod.info +++ /dev/null @@ -1,16 +0,0 @@ -[ -{ - "modid": "specialmobs", - "name": "Special Mobs", - "description": "Adds variety to vanilla monsters to make enemies more interesting, without breaking the 'Minecraft' feel.", - "version": "${version}", - "mcversion": "${mcversion}", - "url": "", - "updateUrl": "", - "authorList": ["Father Toast"], - "credits": "Mother Toast, for dealing with my shit", - "logoFile": "", - "screenshots": [], - "dependencies": [] -} -] From 2c3ee5d6018d0e2c6bd77e224af94b4887e98db8 Mon Sep 17 00:00:00 2001 From: FatherToast Date: Sat, 2 Jul 2022 12:55:39 -0500 Subject: [PATCH 09/10] config button! --- .../client/ClientEventHandler.java | 47 +++++++++++++++++++ .../specialmobs/client/ClientRegister.java | 1 + 2 files changed, 48 insertions(+) create mode 100644 src/main/java/fathertoast/specialmobs/client/ClientEventHandler.java diff --git a/src/main/java/fathertoast/specialmobs/client/ClientEventHandler.java b/src/main/java/fathertoast/specialmobs/client/ClientEventHandler.java new file mode 100644 index 0000000..d63e104 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/client/ClientEventHandler.java @@ -0,0 +1,47 @@ +package fathertoast.specialmobs.client; + +import fathertoast.specialmobs.common.config.Config; +import fathertoast.specialmobs.common.core.SpecialMobs; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.util.Util; +import net.minecraft.util.text.StringTextComponent; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.GuiOpenEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.ExtensionPoint; +import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.common.Mod; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@Mod.EventBusSubscriber( value = Dist.CLIENT, modid = SpecialMobs.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE ) +public class ClientEventHandler { + + static void registerConfigGUIFactory() { + ModLoadingContext.get().registerExtensionPoint( ExtensionPoint.CONFIGGUIFACTORY, + () -> ClientEventHandler.OpenConfigFolderScreen::new ); + } + + @SubscribeEvent + public static void onGuiOpen( GuiOpenEvent event ) { + if( event.getGui() instanceof OpenConfigFolderScreen ) { + event.setCanceled( true ); + Util.getPlatform().openFile( Config.CONFIG_DIR ); + } + } + + /** + * This screen is effectively a redirect. It is opened when the "mod config" button is pressed with the goal of behaving + * like the "mods folder" button; i.e. just opens the appropriate folder. + */ + private static class OpenConfigFolderScreen extends Screen { + private OpenConfigFolderScreen( Minecraft game, Screen parent ) { + // We don't need to localize the name or do anything since the opening of this screen is always canceled + super( new StringTextComponent( "Opening mod config folder" ) ); + } + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java index 8dc5556..4e3df63 100644 --- a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java +++ b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java @@ -23,6 +23,7 @@ public class ClientRegister { @SubscribeEvent public static void onClientSetup( FMLClientSetupEvent event ) { + ClientEventHandler.registerConfigGUIFactory(); registerEntityRenderers(); } From ce2562de876514e2c37138130bbb07ee5a7d0772 Mon Sep 17 00:00:00 2001 From: FatherToast Date: Sat, 2 Jul 2022 20:11:20 -0500 Subject: [PATCH 10/10] Ghasts + some AI cleanup --- .../common/bestiary/MobFamily.java | 2 +- .../specialmobs/common/core/SpecialMobs.java | 4 +- .../common/entity/ai/AIHelper.java | 1 + .../ai/SimpleFlyingMovementController.java | 88 +++++++++ .../ChargeCreeperGoal.java} | 70 ++++--- .../entity/ai/{ => goal}/NinjaGoal.java | 11 +- .../ai/{ => goal}/SpecialBlazeAttackGoal.java | 2 +- .../goal/SpecialGhastFireballAttackGoal.java | 56 ++++++ .../ai/goal/SpecialGhastLookAroundGoal.java | 47 +++++ .../ai/goal/SpecialGhastMeleeAttackGoal.java | 100 ++++++++++ .../{ => goal}/SpecialHurtByTargetGoal.java | 2 +- .../{ => goal}/SpecialLeapAtTargetGoal.java | 2 +- .../entity/blaze/_SpecialBlazeEntity.java | 4 +- .../cavespider/FlyingCaveSpiderEntity.java | 2 +- .../entity/creeper/JumpingCreeperEntity.java | 2 +- .../entity/creeper/_SpecialCreeperEntity.java | 3 +- .../common/entity/ghast/BabyGhastEntity.java | 4 +- .../entity/ghast/FighterGhastEntity.java | 95 ++++++++++ .../common/entity/ghast/KingGhastEntity.java | 91 +++++++++ .../common/entity/ghast/QueenGhastEntity.java | 173 ++++++++++++++++++ .../entity/ghast/UnholyGhastEntity.java | 133 ++++++++++++++ .../entity/ghast/_SpecialGhastEntity.java | 96 +++++----- .../magmacube/BouncingMagmaCubeEntity.java | 2 +- .../magmacube/HardenedMagmaCubeEntity.java | 2 +- .../silverfish/FlyingSilverfishEntity.java | 2 +- .../silverfish/_SpecialSilverfishEntity.java | 2 +- .../entity/skeleton/NinjaSkeletonEntity.java | 2 +- .../common/entity/slime/GrapeSlimeEntity.java | 2 +- .../entity/slime/WatermelonSlimeEntity.java | 2 +- .../entity/spider/FlyingSpiderEntity.java | 2 +- .../NinjaWitherSkeletonEntity.java | 2 +- .../zombie/MadScientistZombieEntity.java | 6 +- .../entity/zombie/_SpecialZombieEntity.java | 2 +- .../_SpecialZombifiedPiglinEntity.java | 2 +- .../specialmobs/common/util/References.java | 5 +- 35 files changed, 906 insertions(+), 115 deletions(-) create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ai/SimpleFlyingMovementController.java rename src/main/java/fathertoast/specialmobs/common/entity/ai/{SpecialInjectCreeperGoal.java => goal/ChargeCreeperGoal.java} (58%) rename src/main/java/fathertoast/specialmobs/common/entity/ai/{ => goal}/NinjaGoal.java (98%) rename src/main/java/fathertoast/specialmobs/common/entity/ai/{ => goal}/SpecialBlazeAttackGoal.java (98%) create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastFireballAttackGoal.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastLookAroundGoal.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastMeleeAttackGoal.java rename src/main/java/fathertoast/specialmobs/common/entity/ai/{ => goal}/SpecialHurtByTargetGoal.java (97%) rename src/main/java/fathertoast/specialmobs/common/entity/ai/{ => goal}/SpecialLeapAtTargetGoal.java (98%) create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ghast/FighterGhastEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ghast/KingGhastEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ghast/QueenGhastEntity.java create mode 100644 src/main/java/fathertoast/specialmobs/common/entity/ghast/UnholyGhastEntity.java diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java index 737b774..97ff7c1 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java @@ -97,7 +97,7 @@ public class MobFamily { public static final MobFamily GHAST = new MobFamily<>( "Ghast", "ghasts", 0xF9F9F9, new EntityType[] { EntityType.GHAST }, - "Baby"//, "Fighter", "King", "Queen", "Unholy" + "Baby", "Fighter", "King", "Queen", "Unholy" ); public static final MobFamily BLAZE = new MobFamily<>( diff --git a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java index 6ebb6df..908d07a 100644 --- a/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java +++ b/src/main/java/fathertoast/specialmobs/common/core/SpecialMobs.java @@ -78,8 +78,8 @@ public class SpecialMobs { * - witches * - ability to equip held items * - uses splash speed instead of regular - * o ghasts - * o melee attack AI + * - ghasts + * - melee attack AI * - blazes * - melee attack AI * ? piglins diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/AIHelper.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/AIHelper.java index a684afe..13e0255 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/AIHelper.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/AIHelper.java @@ -1,6 +1,7 @@ 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.*; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/SimpleFlyingMovementController.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/SimpleFlyingMovementController.java new file mode 100644 index 0000000..288f806 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/SimpleFlyingMovementController.java @@ -0,0 +1,88 @@ +package fathertoast.specialmobs.common.entity.ai; + +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.MobEntity; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.entity.ai.controller.MovementController; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.vector.Vector3d; + +import javax.annotation.ParametersAreNonnullByDefault; + +/** + * Simple movement controller that can be used by flying entities, which takes their movement speed attribute into account. + */ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public class SimpleFlyingMovementController extends MovementController { + + private int floatDuration; + + public SimpleFlyingMovementController( MobEntity entity ) { super( entity ); } + + /** Called each tick while this move controller is active. */ + @Override + public void tick() { + if( operation == MovementController.Action.MOVE_TO ) { + if( floatDuration-- <= 0 ) { + floatDuration = mob.getRandom().nextInt( 5 ) + 2; + + Vector3d moveVec = new Vector3d( + wantedX - mob.getX(), + wantedY - mob.getY(), + wantedZ - mob.getZ() ); + final int distance = MathHelper.ceil( moveVec.length() ); + moveVec = moveVec.normalize(); + + if( mob.getRandom().nextBoolean() || canReach( moveVec, distance ) ) { // Skip the "boxcast" sometimes + mob.setDeltaMovement( mob.getDeltaMovement().add( moveVec.scale( getScaledMoveSpeed() ) ) ); + } + else { + operation = MovementController.Action.WAIT; + } + } + } + } + + public double getScaledMoveSpeed() { + return 0.1 * speedModifier * mob.getAttributeValue( Attributes.MOVEMENT_SPEED ) / Attributes.MOVEMENT_SPEED.getDefaultValue(); + } + + public double getDistanceSqToWantedPosition() { + return mob.distanceToSqr( getWantedX(), getWantedY(), getWantedZ() ); + } + + public boolean isWantedPositionStale( LivingEntity target ) { + return !hasWanted() || + target.distanceToSqr( + getWantedX(), + getWantedY() - target.getBbHeight() / 2.0F, + getWantedZ() + ) > 1.0; + } + + public boolean canReachWantedPosition() { + return hasWanted() && canReachPosition( getWantedX(), getWantedY(), getWantedZ() ); + } + + public boolean canReachPosition( LivingEntity target ) { + return canReachPosition( target.getX(), target.getY( 0.5 ), target.getZ() ); + } + + public boolean canReachPosition( double x, double y, double z ) { + final Vector3d targetVec = new Vector3d( x - mob.getX(), y - mob.getY(), z - mob.getZ() ); + final int distance = MathHelper.ceil( targetVec.length() ); + return canReach( targetVec.normalize(), distance ); + } + + private boolean canReach( Vector3d direction, int distance ) { + AxisAlignedBB boundingBox = mob.getBoundingBox(); + for( int i = 1; i < distance; i++ ) { + boundingBox = boundingBox.move( direction ); + if( !mob.level.noCollision( mob, boundingBox ) ) return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialInjectCreeperGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/ChargeCreeperGoal.java similarity index 58% rename from src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialInjectCreeperGoal.java rename to src/main/java/fathertoast/specialmobs/common/entity/ai/goal/ChargeCreeperGoal.java index 64018e8..e04e289 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialInjectCreeperGoal.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/ChargeCreeperGoal.java @@ -1,47 +1,45 @@ -package fathertoast.specialmobs.common.entity.ai; +package fathertoast.specialmobs.common.entity.ai.goal; import fathertoast.specialmobs.common.entity.creeper._SpecialCreeperEntity; import fathertoast.specialmobs.common.entity.zombie.MadScientistZombieEntity; import net.minecraft.entity.ai.goal.Goal; -import net.minecraft.entity.ai.goal.LookAtGoal; import net.minecraft.entity.monster.CreeperEntity; import net.minecraft.util.SoundCategory; import net.minecraft.util.SoundEvents; -import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.world.World; import java.util.EnumSet; import java.util.List; import java.util.function.BiPredicate; -public class SpecialInjectCreeperGoal extends Goal { - +public class ChargeCreeperGoal extends Goal { + private final BiPredicate targetPredicate; - + private final T madman; private final double movementSpeed; private final double targetRange; - + /** The creeper to target for power-up injection **/ private CreeperEntity creeper; - + private boolean canUseWhileMounted = false; - - - public SpecialInjectCreeperGoal(T madman, double movementSpeed, double targetRange, BiPredicate targetPredicate) { + + + public ChargeCreeperGoal( T madman, double movementSpeed, double targetRange, BiPredicate targetPredicate ) { this.madman = madman; this.movementSpeed = movementSpeed; this.targetRange = targetRange; this.targetPredicate = targetPredicate; - this.setFlags(EnumSet.of(Flag.MOVE)); + this.setFlags( EnumSet.of( Flag.MOVE ) ); } - + /** Builder that enables the entity to leap while mounted. */ - public SpecialInjectCreeperGoal canUseWhileMounted() { + public ChargeCreeperGoal canUseWhileMounted() { canUseWhileMounted = true; return this; } - + /** @return Returns true if this AI can be activated. */ @Override public boolean canUse() { @@ -49,53 +47,53 @@ public class SpecialInjectCreeperGoal extend findCreeper(); return creeper != null; } - + private void findCreeper() { World world = madman.level; - List nearbyCreepers = world.getLoadedEntitiesOfClass(CreeperEntity.class, madman.getBoundingBox().inflate(targetRange), null); - - if (!nearbyCreepers.isEmpty()) { - for (CreeperEntity creeper : nearbyCreepers) { - if (targetPredicate.test(madman, creeper)) { + List nearbyCreepers = world.getLoadedEntitiesOfClass( CreeperEntity.class, madman.getBoundingBox().inflate( targetRange ), null ); + + if( !nearbyCreepers.isEmpty() ) { + for( CreeperEntity creeper : nearbyCreepers ) { + if( targetPredicate.test( madman, creeper ) ) { this.creeper = creeper; break; } } } } - + /** Called when this AI is activated. */ @Override public void start() { - madman.getNavigation().moveTo(creeper, movementSpeed); + madman.getNavigation().moveTo( creeper, movementSpeed ); } - + /** @return Called each update while active and returns true if this AI can remain active. */ @Override public boolean canContinueToUse() { return !madman.isOnGround() && !madman.isPassenger() && !madman.isInWaterOrBubble() && !madman.isInLava(); } - + /** Called each tick while this AI is active. */ @Override public void tick() { - if (creeper == null || !targetPredicate.test(madman, creeper)) { + if( creeper == null || !targetPredicate.test( madman, creeper ) ) { findCreeper(); } else { - madman.getNavigation().moveTo(creeper, movementSpeed); - madman.getLookControl().setLookAt(this.creeper.getX(), this.creeper.getEyeY(), this.creeper.getZ()); - - if (madman.distanceTo(creeper) < 1.5D) { - creeper.getEntityData().set(CreeperEntity.DATA_IS_POWERED, true); - + madman.getNavigation().moveTo( creeper, movementSpeed ); + madman.getLookControl().setLookAt( this.creeper.getX(), this.creeper.getEyeY(), this.creeper.getZ() ); + + if( madman.distanceTo( creeper ) < 1.5D ) { + creeper.getEntityData().set( CreeperEntity.DATA_IS_POWERED, true ); + // HEE HEE HEE HAW - if (creeper instanceof _SpecialCreeperEntity && creeper.level.random.nextDouble() < 0.1F) { - ((_SpecialCreeperEntity)creeper).setSupercharged( true ); + if( creeper instanceof _SpecialCreeperEntity && creeper.level.random.nextDouble() < 0.1 ) { // TODO config + ((_SpecialCreeperEntity) creeper).setSupercharged( true ); } - madman.level.playSound(null, creeper.getX() + 0.5D, creeper.getY(), creeper.getZ() + 0.5D, SoundEvents.BEE_STING, SoundCategory.HOSTILE, 0.9F, 1.0F); + madman.level.playSound( null, creeper.getX() + 0.5D, creeper.getY(), creeper.getZ() + 0.5D, SoundEvents.BEE_STING, SoundCategory.HOSTILE, 0.9F, 1.0F ); creeper = null; } } } -} +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/NinjaGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/NinjaGoal.java similarity index 98% rename from src/main/java/fathertoast/specialmobs/common/entity/ai/NinjaGoal.java rename to src/main/java/fathertoast/specialmobs/common/entity/ai/goal/NinjaGoal.java index 3e51526..ad70626 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/NinjaGoal.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/NinjaGoal.java @@ -1,5 +1,6 @@ -package fathertoast.specialmobs.common.entity.ai; +package fathertoast.specialmobs.common.entity.ai.goal; +import fathertoast.specialmobs.common.entity.ai.INinja; import net.minecraft.block.BlockRenderType; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; @@ -80,7 +81,7 @@ public class NinjaGoal extends Goal { /** Finds a nearby block for the entity to hide as and flags it to start hiding. */ public static BlockState pickDisguise( T entity ) { final Random random = entity.getRandom(); - + // Random blocks to be picked regardless of position switch( random.nextInt( 200 ) ) { case 0: return Blocks.TNT.defaultBlockState(); @@ -99,7 +100,7 @@ public class NinjaGoal extends Goal { case 12: return randomPottedFlower( random ); case 13: return Blocks.SWEET_BERRY_BUSH.defaultBlockState(); } - + final BlockPos posUnderFeet = entity.blockPosition().below(); final BlockState blockUnderFeet = entity.level.getBlockState( posUnderFeet ); if( !blockUnderFeet.getBlock().isAir( blockUnderFeet, entity.level, posUnderFeet ) ) { @@ -202,12 +203,12 @@ public class NinjaGoal extends Goal { /** @return A random flower. */ private static BlockState randomFlower( Random random ) { - return BlockTags.SMALL_FLOWERS.getRandomElement(random).defaultBlockState(); + return BlockTags.SMALL_FLOWERS.getRandomElement( random ).defaultBlockState(); } /** @return A random potted flower. */ private static BlockState randomPottedFlower( Random random ) { - + switch( random.nextInt( 12 ) ) { case 0: return Blocks.POTTED_DANDELION.defaultBlockState(); case 1: return Blocks.POTTED_POPPY.defaultBlockState(); diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialBlazeAttackGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialBlazeAttackGoal.java similarity index 98% rename from src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialBlazeAttackGoal.java rename to src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialBlazeAttackGoal.java index 2c3b2ef..c171cf3 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialBlazeAttackGoal.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialBlazeAttackGoal.java @@ -1,4 +1,4 @@ -package fathertoast.specialmobs.common.entity.ai; +package fathertoast.specialmobs.common.entity.ai.goal; import fathertoast.specialmobs.common.entity.SpecialMobData; import fathertoast.specialmobs.common.entity.blaze._SpecialBlazeEntity; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastFireballAttackGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastFireballAttackGoal.java new file mode 100644 index 0000000..354f1dc --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastFireballAttackGoal.java @@ -0,0 +1,56 @@ +package fathertoast.specialmobs.common.entity.ai.goal; + +import fathertoast.specialmobs.common.entity.SpecialMobData; +import fathertoast.specialmobs.common.entity.ghast._SpecialGhastEntity; +import fathertoast.specialmobs.common.util.References; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.goal.Goal; + +/** + * Implementation of GhastEntity.FireballAttackGoal that is visible to special ghasts and allows variants to + * decide how to execute the actual attack. + */ +public class SpecialGhastFireballAttackGoal extends Goal { + + private final _SpecialGhastEntity ghast; + + private int chargeTime; + + public SpecialGhastFireballAttackGoal( _SpecialGhastEntity entity ) { ghast = entity; } + + /** @return Returns true if this AI can be activated. */ + @Override + public boolean canUse() { return ghast.getTarget() != null; } + + /** Called when this AI is activated. */ + @Override + public void start() { chargeTime = 0; } + + /** Called when this AI is deactivated. */ + @Override + public void stop() { ghast.setCharging( false ); } + + /** Called each tick while this AI is active. */ + @Override + public void tick() { + final LivingEntity target = ghast.getTarget(); + if( target == null ) return; + + final SpecialMobData<_SpecialGhastEntity> data = ghast.getSpecialData(); + + if( target.distanceToSqr( ghast ) < data.rangedAttackMaxRange * data.rangedAttackMaxRange && ghast.canSee( target ) ) { + chargeTime++; + if( chargeTime == (data.rangedAttackCooldown >> 1) && !ghast.isSilent() ) { + ghast.level.levelEvent( null, References.EVENT_GHAST_WARN, ghast.blockPosition(), 0 ); + } + if( chargeTime >= data.rangedAttackCooldown ) { + ghast.performRangedAttack( target, 1.0F ); + chargeTime = data.rangedAttackCooldown - data.rangedAttackMaxCooldown; + } + } + else if( chargeTime > 0 ) { + chargeTime--; + } + ghast.setCharging( chargeTime > (data.rangedAttackCooldown >> 1) ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastLookAroundGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastLookAroundGoal.java new file mode 100644 index 0000000..61673a0 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastLookAroundGoal.java @@ -0,0 +1,47 @@ +package fathertoast.specialmobs.common.entity.ai.goal; + +import fathertoast.specialmobs.common.entity.ghast._SpecialGhastEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.goal.Goal; +import net.minecraft.util.math.MathHelper; + +import java.util.EnumSet; + +/** + * Implementation of GhastEntity.LookAroundGoal that is visible to special ghasts and sensitive to special mob data. + */ +public class SpecialGhastLookAroundGoal extends Goal { + + private final _SpecialGhastEntity ghast; + + public SpecialGhastLookAroundGoal( _SpecialGhastEntity entity ) { + ghast = entity; + setFlags( EnumSet.of( Goal.Flag.LOOK ) ); + } + + /** @return Returns true if this AI can be activated. */ + @Override + public boolean canUse() { return true; } + + /** Called each tick while this AI is active. */ + @Override + public void tick() { + final LivingEntity target = ghast.getTarget(); + if( target != null ) { + final float range = ghast.getSpecialData().rangedAttackMaxRange > 0.0F ? + ghast.getSpecialData().rangedAttackMaxRange : + 16.0F; // Range for melee ghast to face target + + if( target.distanceToSqr( ghast ) < range * range ) { + setFacing( target.getX() - ghast.getX(), target.getZ() - ghast.getZ() ); + return; + } + } + // Allow move direction facing even if target exists out of attack range + setFacing( ghast.getDeltaMovement().x, ghast.getDeltaMovement().z ); + } + + private void setFacing( double x, double z ) { + ghast.yBodyRot = ghast.yRot = (float) MathHelper.atan2( x, z ) * -180.0F / (float) Math.PI; + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastMeleeAttackGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastMeleeAttackGoal.java new file mode 100644 index 0000000..48e0aa7 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialGhastMeleeAttackGoal.java @@ -0,0 +1,100 @@ +package fathertoast.specialmobs.common.entity.ai.goal; + +import fathertoast.specialmobs.common.entity.ai.SimpleFlyingMovementController; +import fathertoast.specialmobs.common.entity.ghast._SpecialGhastEntity; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.controller.MovementController; +import net.minecraft.entity.ai.goal.Goal; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.EnumSet; + +/** + * Melee attack goal modified to function for ghasts. + */ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public class SpecialGhastMeleeAttackGoal extends Goal { + + private final _SpecialGhastEntity ghast; + + private int attackTimer; + + private int pathUpdateCooldown; + private boolean wasPathingToTarget; + + public SpecialGhastMeleeAttackGoal( _SpecialGhastEntity entity ) { + ghast = entity; + setFlags( EnumSet.of( Goal.Flag.MOVE ) ); + } + + /** @return Returns true if this AI can be activated. */ + @Override + public boolean canUse() { return ghast.getTarget() != null; } + + /** Called when this AI is activated. */ + @Override + public void start() { + attackTimer = 0; + + final LivingEntity target = ghast.getTarget(); + if( target != null ) { + setWantedPosition( target ); + wasPathingToTarget = !(ghast.getMoveControl() instanceof SimpleFlyingMovementController) || + ((SimpleFlyingMovementController) ghast.getMoveControl()).canReachWantedPosition(); + pathUpdateCooldown = 5; + } + } + + /** Called each tick while this AI is active. */ + @Override + public void tick() { + final LivingEntity target = ghast.getTarget(); + if( target == null ) return; + + // Move towards the target + final MovementController moveControl = ghast.getMoveControl(); + if( pathUpdateCooldown-- <= 0 || !moveControl.hasWanted() ) { + pathUpdateCooldown = ghast.getRandom().nextInt( 5 ) + 3; + + final SimpleFlyingMovementController flyingControl = moveControl instanceof SimpleFlyingMovementController ? + (SimpleFlyingMovementController) moveControl : null; + if( flyingControl == null || flyingControl.isWantedPositionStale( target ) ) { + if( flyingControl == null || flyingControl.canReachPosition( target ) ) { + setWantedPosition( target ); + wasPathingToTarget = true; + } + else if( !wasPathingToTarget || !moveControl.hasWanted() || flyingControl.getDistanceSqToWantedPosition() < 2.0 ) { + setWantedPosition(); + wasPathingToTarget = false; + } + } + } + + // Attack the target when able + if( attackTimer > 0 ) { + attackTimer--; + } + else { + final double reachSq = (ghast.getBbWidth() * ghast.getBbWidth() + target.getBbWidth() * target.getBbWidth()) / 4.0 + 5.0; + if( target.distanceToSqr( ghast ) < reachSq ) { + attackTimer = 20; + ghast.doHurtTarget( target ); + } + } + } + + private void setWantedPosition( LivingEntity target ) { + ghast.getMoveControl().setWantedPosition( target.getX(), target.getY( 0.5 ), target.getZ(), 1.2 ); + } + + private void setWantedPosition() { + final float diameter = 8.0F; + ghast.getMoveControl().setWantedPosition( + ghast.getX() + (ghast.getRandom().nextFloat() - 0.5F) * diameter, + ghast.getY() + (ghast.getRandom().nextFloat() - 0.5F) * diameter, + ghast.getZ() + (ghast.getRandom().nextFloat() - 0.5F) * diameter, + 1.0 ); + } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialHurtByTargetGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialHurtByTargetGoal.java similarity index 97% rename from src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialHurtByTargetGoal.java rename to src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialHurtByTargetGoal.java index 1bb6da6..fa8f05a 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialHurtByTargetGoal.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialHurtByTargetGoal.java @@ -1,4 +1,4 @@ -package fathertoast.specialmobs.common.entity.ai; +package fathertoast.specialmobs.common.entity.ai.goal; import mcp.MethodsReturnNonnullByDefault; import net.minecraft.entity.CreatureEntity; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialLeapAtTargetGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialLeapAtTargetGoal.java similarity index 98% rename from src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialLeapAtTargetGoal.java rename to src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialLeapAtTargetGoal.java index c1dc276..735715e 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialLeapAtTargetGoal.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/goal/SpecialLeapAtTargetGoal.java @@ -1,4 +1,4 @@ -package fathertoast.specialmobs.common.entity.ai; +package fathertoast.specialmobs.common.entity.ai.goal; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.MobEntity; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java index 323aa69..d73addc 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/blaze/_SpecialBlazeEntity.java @@ -7,8 +7,8 @@ import fathertoast.specialmobs.common.core.SpecialMobs; import fathertoast.specialmobs.common.entity.ISpecialMob; import fathertoast.specialmobs.common.entity.SpecialMobData; import fathertoast.specialmobs.common.entity.ai.AIHelper; -import fathertoast.specialmobs.common.entity.ai.SpecialBlazeAttackGoal; -import fathertoast.specialmobs.common.entity.ai.SpecialHurtByTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialBlazeAttackGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialHurtByTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/FlyingCaveSpiderEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/FlyingCaveSpiderEntity.java index ae866d9..b95b155 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/cavespider/FlyingCaveSpiderEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/cavespider/FlyingCaveSpiderEntity.java @@ -3,7 +3,7 @@ package fathertoast.specialmobs.common.entity.cavespider; import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; -import fathertoast.specialmobs.common.entity.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/creeper/JumpingCreeperEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/creeper/JumpingCreeperEntity.java index 26506bf..b34536e 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/creeper/JumpingCreeperEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/creeper/JumpingCreeperEntity.java @@ -4,7 +4,7 @@ import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; import fathertoast.specialmobs.common.entity.ai.AIHelper; -import fathertoast.specialmobs.common.entity.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java index d10b0f2..60ff144 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/creeper/_SpecialCreeperEntity.java @@ -30,7 +30,6 @@ import net.minecraft.world.DifficultyInstance; import net.minecraft.world.IServerWorld; import net.minecraft.world.World; import net.minecraft.world.server.ServerWorld; -import net.minecraftforge.common.util.Constants; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; @@ -189,7 +188,7 @@ public class _SpecialCreeperEntity extends CreeperEntity implements ISpecialMob< /** Called when this entity is struck by lightning. */ @Override public void thunderHit( ServerWorld world, LightningBoltEntity lightningBolt ) { - if( !isPowered() && random.nextDouble() < 0.1D ) + if( !isPowered() && random.nextDouble() < 0.1 ) // TODO config setSupercharged( true ); super.thunderHit( world, lightningBolt ); diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/BabyGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/BabyGhastEntity.java index 10a67ee..5b87bed 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ghast/BabyGhastEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/BabyGhastEntity.java @@ -3,7 +3,6 @@ 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.spider._SpecialSpiderEntity; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; @@ -36,9 +35,8 @@ public class BabyGhastEntity extends _SpecialGhastEntity { @SpecialMob.AttributeCreator public static AttributeModifierMap.MutableAttribute createAttributes() { - return AttributeHelper.of( _SpecialSpiderEntity.createAttributes() ) + return AttributeHelper.of( _SpecialGhastEntity.createAttributes() ) .addAttribute( Attributes.ATTACK_DAMAGE, -1.0 ) - .multAttribute( Attributes.MOVEMENT_SPEED, 1.3 ) .build(); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/FighterGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/FighterGhastEntity.java new file mode 100644 index 0000000..bc04729 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/FighterGhastEntity.java @@ -0,0 +1,95 @@ +package fathertoast.specialmobs.common.entity.ghast; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.entity.MobHelper; +import fathertoast.specialmobs.common.util.AttributeHelper; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.item.Items; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class FighterGhastEntity extends _SpecialGhastEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + entityType.sized( 2.0F, 2.0F ); + return new BestiaryInfo( 0x7A1300 ); + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialGhastEntity.createAttributes() ) + .addAttribute( Attributes.MAX_HEALTH, 20.0 ) + .addAttribute( Attributes.ARMOR, 10.0 ) + .addAttribute( Attributes.ATTACK_DAMAGE, 2.0 ) + .multAttribute( Attributes.MOVEMENT_SPEED, 0.8 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Fighter Ghast", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addUncommonDrop( "uncommon", Items.IRON_INGOT ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return FighterGhastEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + public FighterGhastEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().setBaseScale( 0.5F ); + xpReward += 1; + } + + /** Override to change this entity's AI goals. */ + @Override + protected void registerVariantGoals() { + getSpecialData().rangedAttackDamage += 2.0F; + disableRangedAI(); + } + + /** Override to apply effects when this entity hits a target with a melee attack. */ + protected void onVariantAttack( Entity target ) { + if( target instanceof LivingEntity ) { + MobHelper.causeLifeLoss( (LivingEntity) target, 2.0F ); + } + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "fighter" ), + null, + GET_TEXTURE_PATH( "fighter_shooting" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/KingGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/KingGhastEntity.java new file mode 100644 index 0000000..88c2911 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/KingGhastEntity.java @@ -0,0 +1,91 @@ +package fathertoast.specialmobs.common.entity.ghast; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.util.AttributeHelper; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.item.Items; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class KingGhastEntity extends _SpecialGhastEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + entityType.sized( 6.0F, 6.0F ); + return new BestiaryInfo( 0xE8C51A, BestiaryInfo.BaseWeight.LOW ); + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialGhastEntity.createAttributes() ) + .addAttribute( Attributes.MAX_HEALTH, 20.0 ) + .addAttribute( Attributes.ARMOR, 10.0 ) + .addAttribute( Attributes.ATTACK_DAMAGE, 4.0 ) + .multAttribute( Attributes.MOVEMENT_SPEED, 0.6 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "King Ghast", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addSemicommonDrop( "semicommon", Items.GOLD_INGOT ); + loot.addUncommonDrop( "uncommon", Items.EMERALD ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return KingGhastEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + public KingGhastEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().setBaseScale( 1.5F ); + getSpecialData().setRegenerationTime( 30 ); + xpReward += 4; + } + + /** Override to change this entity's AI goals. */ + @Override + protected void registerVariantGoals() { + getSpecialData().rangedAttackDamage += 4.0F; + } + + /** Override to change this ghast's explosion power multiplier. */ + @Override + protected int getVariantExplosionPower( int radius ) { return Math.round( radius * 2.5F ); } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "king" ), + null, + GET_TEXTURE_PATH( "king_shooting" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/QueenGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/QueenGhastEntity.java new file mode 100644 index 0000000..d6dbfdb --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/QueenGhastEntity.java @@ -0,0 +1,173 @@ +package fathertoast.specialmobs.common.entity.ghast; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.util.AttributeHelper; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.ILivingEntityData; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.SpawnReason; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.IServerWorld; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class QueenGhastEntity extends _SpecialGhastEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + entityType.sized( 5.0F, 5.0F ); + return new BestiaryInfo( 0xCE0Aff ); + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialGhastEntity.createAttributes() ) + .addAttribute( Attributes.MAX_HEALTH, 20.0 ) + .addAttribute( Attributes.ATTACK_DAMAGE, 2.0 ) + .multAttribute( Attributes.MOVEMENT_SPEED, 0.6 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Queen Ghast", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addSemicommonDrop( "semicommon", Items.GOLD_INGOT ); + loot.addUncommonDrop( "uncommon", Items.GHAST_SPAWN_EGG ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return QueenGhastEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + /** The number of babies spawned on death. */ + private int babies; + /** The number of extra babies that can be spawned by attacks. */ + private int summons; + + public QueenGhastEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().setBaseScale( 1.25F ); + getSpecialData().setRegenerationTime( 20 ); + xpReward += 2; + + babies = 3 + random.nextInt( 4 ); + summons = 4 + random.nextInt( 7 ); + } + + /** Override to change this entity's AI goals. */ + @Override + protected void registerVariantGoals() { + getSpecialData().rangedAttackDamage += 2.0F; + } + + /** Called to attack the target with a ranged attack. */ + @Override + public void performRangedAttack( LivingEntity target, float damageMulti ) { + if( !level.isClientSide() && summons > 0 && random.nextInt( 2 ) == 0 ) { + summons--; + + final double vX = target.getX() - getX(); + final double vZ = target.getZ() - getZ(); + final double vH = Math.sqrt( vX * vX + vZ * vZ ); + spawnBaby( vX / vH + getDeltaMovement().x * 0.2, vZ / vH + getDeltaMovement().z * 0.2, null ); + spawnAnim(); + if( !isSilent() ) level.levelEvent( null, References.EVENT_GHAST_SHOOT, blockPosition(), 0 ); + } + else { + super.performRangedAttack( target, damageMulti ); + } + } + + /** Override to change this ghast's explosion power multiplier. */ + @Override + protected int getVariantExplosionPower( int radius ) { return Math.round( radius * 1.5F ); } + + /** Called to remove this entity from the world. Includes death, unloading, interdimensional travel, etc. */ + @Override + public void remove( boolean keepData ) { + //noinspection deprecation + if( isDeadOrDying() && !removed && level instanceof IServerWorld ) { // Same conditions as slime splitting + // Spawn babies on death + ILivingEntityData groupData = null; + for( int i = 0; i < babies; i++ ) { + groupData = spawnBaby( (random.nextDouble() - 0.5) * 0.3, (random.nextDouble() - 0.5) * 0.3, groupData ); + } + spawnAnim(); + if( !isSilent() ) level.levelEvent( null, References.EVENT_BLAZE_SHOOT, blockPosition(), 0 ); + } + super.remove( keepData ); + } + + /** Helper method to simplify spawning babies. */ + @Nullable + private ILivingEntityData spawnBaby( double vX, double vZ, @Nullable ILivingEntityData groupData ) { + final BabyGhastEntity baby = BabyGhastEntity.SPECIES.entityType.get().create( level ); + if( baby == null ) return groupData; + + baby.copyPosition( this ); + baby.yHeadRot = yRot; + baby.yBodyRot = yRot; + groupData = baby.finalizeSpawn( (IServerWorld) level, level.getCurrentDifficultyAt( blockPosition() ), + SpawnReason.MOB_SUMMONED, groupData, null ); + baby.setTarget( getTarget() ); + + baby.setDeltaMovement( vX, 0.0, vZ ); + baby.setOnGround( false ); + + level.addFreshEntity( baby ); + return groupData; + } + + /** Override to save data to this entity's NBT data. */ + @Override + public void addVariantSaveData( CompoundNBT saveTag ) { + saveTag.putByte( References.TAG_BABIES, (byte) babies ); + saveTag.putByte( References.TAG_SUMMONS, (byte) summons ); + } + + /** Override to load data from this entity's NBT data. */ + @Override + public void readVariantSaveData( CompoundNBT saveTag ) { + if( saveTag.contains( References.TAG_BABIES, References.NBT_TYPE_NUMERICAL ) ) + babies = saveTag.getByte( References.TAG_BABIES ); + if( saveTag.contains( References.TAG_SUMMONS, References.NBT_TYPE_NUMERICAL ) ) + summons = saveTag.getByte( References.TAG_SUMMONS ); + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "queen" ), + null, + GET_TEXTURE_PATH( "queen_shooting" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/UnholyGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/UnholyGhastEntity.java new file mode 100644 index 0000000..b8caa89 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/UnholyGhastEntity.java @@ -0,0 +1,133 @@ +package fathertoast.specialmobs.common.entity.ghast; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +import fathertoast.specialmobs.common.bestiary.MobFamily; +import fathertoast.specialmobs.common.bestiary.SpecialMob; +import fathertoast.specialmobs.common.entity.MobHelper; +import fathertoast.specialmobs.common.util.AttributeHelper; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.entity.CreatureAttribute; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.ai.attributes.Attributes; +import net.minecraft.inventory.EquipmentSlotType; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.DamageSource; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@SpecialMob +public class UnholyGhastEntity extends _SpecialGhastEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.SpeciesReference + public static MobFamily.Species SPECIES; + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo( EntityType.Builder entityType ) { + entityType.sized( 2.0F, 2.0F ); + return new BestiaryInfo( 0x7AC754, BestiaryInfo.BaseWeight.LOW ); + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialGhastEntity.createAttributes() ) + .addAttribute( Attributes.MAX_HEALTH, 10.0 ) + .addAttribute( Attributes.ATTACK_DAMAGE, 2.0 ) + .multAttribute( Attributes.MOVEMENT_SPEED, 0.7 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Unholy Ghast", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addCommonDrop( "common", Items.BONE ); + loot.addSemicommonDrop( "semicommon", Items.QUARTZ ); + } + + @SpecialMob.Factory + public static EntityType.IFactory getVariantFactory() { return UnholyGhastEntity::new; } + + + //--------------- Variant-Specific Implementations ---------------- + + public UnholyGhastEntity( EntityType entityType, World world ) { + super( entityType, world ); + getSpecialData().setBaseScale( 0.5F ); + xpReward += 4; + } + + /** Override to change this entity's AI goals. */ + @Override + protected void registerVariantGoals() { + getSpecialData().rangedAttackDamage += 2.0F; + disableRangedAI(); + } + + /** Override to apply effects when this entity hits a target with a melee attack. */ + protected void onVariantAttack( Entity target ) { + if( target instanceof LivingEntity ) { + MobHelper.stealLife( this, (LivingEntity) target, 2.0F ); + } + } + + /** @return Attempts to damage this entity; returns true if the hit was successful. */ + @Override + public boolean hurt( DamageSource source, float amount ) { + if( MobHelper.isDamageSourceIneffectiveAgainstVampires( source ) ) { + amount = Math.min( 2.0F, amount ); + } + return super.hurt( source, amount ); + } + + /** @return This entity's creature type. */ + @Override + public CreatureAttribute getMobType() { return CreatureAttribute.UNDEAD; } + + /** Called each tick to update this entity's movement. */ + @Override + public void aiStep() { + if( isSunBurnTick() ) { + final ItemStack hat = getItemBySlot( EquipmentSlotType.HEAD ); + if( !hat.isEmpty() ) { + if( hat.isDamageableItem() ) { + hat.setDamageValue( hat.getDamageValue() + random.nextInt( 2 ) ); + if( hat.getDamageValue() >= hat.getMaxDamage() ) { + broadcastBreakEvent( EquipmentSlotType.HEAD ); + setItemSlot( EquipmentSlotType.HEAD, ItemStack.EMPTY ); + } + } + } + else { + setSecondsOnFire( 8 ); + } + } + super.aiStep(); + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "unholy" ), + null, + GET_TEXTURE_PATH( "unholy_shooting" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java index 54e99f3..69cbb3b 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/ghast/_SpecialGhastEntity.java @@ -6,6 +6,11 @@ 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; @@ -14,6 +19,7 @@ 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; @@ -75,7 +81,8 @@ public class _SpecialGhastEntity extends GhastEntity implements IRangedAttackMob public _SpecialGhastEntity( EntityType entityType, World world ) { super( entityType, world ); - reassessWeaponGoal(); + moveControl = new SimpleFlyingMovementController( this ); + reassessAttackGoal(); getSpecialData().initialize(); } @@ -83,7 +90,13 @@ public class _SpecialGhastEntity extends GhastEntity implements IRangedAttackMob @Override protected void registerGoals() { super.registerGoals(); - //TODO replace vanilla ai with our own + // 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; @@ -97,12 +110,33 @@ public class _SpecialGhastEntity extends GhastEntity implements IRangedAttackMob protected void registerVariantGoals() { } /** Helper method to set the ranged attack AI more easily. */ - protected void disableRangedAI() { - getSpecialData().rangedAttackMaxRange = 0.0F; - } + protected void disableRangedAI() { getSpecialData().rangedAttackMaxRange = 0.0F; } /** Override to change this entity's attack goal priority. */ - protected int getVariantAttackPriority() { return 7; } + 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 @@ -136,32 +170,12 @@ public class _SpecialGhastEntity extends GhastEntity implements IRangedAttackMob specialData = new SpecialMobData<>( this, SCALE, 1.0F ); } - /** 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 = getExplosionPower(); - fireball.setPos( - getX() + lookVec.x, - getY( 0.5 ) + 0.5, - getZ() + lookVec.z ); - level.addFreshEntity( fireball ); - } - /** 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 ); - reassessWeaponGoal(); + reassessAttackGoal(); return groupData; } @@ -169,24 +183,20 @@ public class _SpecialGhastEntity extends GhastEntity implements IRangedAttackMob @Override public void setItemSlot( EquipmentSlotType slot, ItemStack item ) { super.setItemSlot( slot, item ); - if( !level.isClientSide ) reassessWeaponGoal(); + if( !level.isClientSide ) reassessAttackGoal(); } /** Called to set this entity's attack AI based on current equipment. */ - public void reassessWeaponGoal() { - // if( level != null && !level.isClientSide ) { TODO - // if( currentAttackAI != null ) goalSelector.removeGoal( currentAttackAI ); - // - // final SpecialMobData<_SpecialGhastEntity> data = getSpecialData(); - // if( data.rangedAttackMaxRange > 0.0F ) { - // currentAttackAI = new RangedBowAttackGoal<>( this, data.rangedWalkSpeed, - // data.rangedAttackCooldown, data.rangedAttackMaxRange ); - // } - // else { - // currentAttackAI = new ZombieAttackGoal( this, 1.0, false ); - // } - // goalSelector.addGoal( getVariantAttackPriority(), currentAttackAI ); - // } + 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 ); + } } @@ -312,6 +322,6 @@ public class _SpecialGhastEntity extends GhastEntity implements IRangedAttackMob getSpecialData().readFromNBT( saveTag ); readVariantSaveData( saveTag ); - reassessWeaponGoal(); + reassessAttackGoal(); } } \ No newline at end of file diff --git a/src/main/java/fathertoast/specialmobs/common/entity/magmacube/BouncingMagmaCubeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/magmacube/BouncingMagmaCubeEntity.java index c033fe5..ab9a059 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/magmacube/BouncingMagmaCubeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/magmacube/BouncingMagmaCubeEntity.java @@ -6,7 +6,7 @@ import fathertoast.specialmobs.common.bestiary.SpecialMob; import fathertoast.specialmobs.common.entity.MobHelper; import fathertoast.specialmobs.common.entity.ai.AIHelper; import fathertoast.specialmobs.common.entity.ai.FluidPathNavigator; -import fathertoast.specialmobs.common.entity.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/magmacube/HardenedMagmaCubeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/magmacube/HardenedMagmaCubeEntity.java index ee8eadb..072a323 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/magmacube/HardenedMagmaCubeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/magmacube/HardenedMagmaCubeEntity.java @@ -3,7 +3,7 @@ package fathertoast.specialmobs.common.entity.magmacube; import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; -import fathertoast.specialmobs.common.entity.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/FlyingSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/FlyingSilverfishEntity.java index d37010b..dfaf187 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/FlyingSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/FlyingSilverfishEntity.java @@ -3,7 +3,7 @@ package fathertoast.specialmobs.common.entity.silverfish; import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; -import fathertoast.specialmobs.common.entity.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java index 3c49aa3..4d40a27 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/silverfish/_SpecialSilverfishEntity.java @@ -7,7 +7,7 @@ import fathertoast.specialmobs.common.core.SpecialMobs; import fathertoast.specialmobs.common.entity.ISpecialMob; import fathertoast.specialmobs.common.entity.SpecialMobData; import fathertoast.specialmobs.common.entity.ai.AIHelper; -import fathertoast.specialmobs.common.entity.ai.SpecialHurtByTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialHurtByTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/NinjaSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/NinjaSkeletonEntity.java index 8cb59c4..0998c8c 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/skeleton/NinjaSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/skeleton/NinjaSkeletonEntity.java @@ -5,7 +5,7 @@ import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; import fathertoast.specialmobs.common.entity.MobHelper; import fathertoast.specialmobs.common.entity.ai.INinja; -import fathertoast.specialmobs.common.entity.ai.NinjaGoal; +import fathertoast.specialmobs.common.entity.ai.goal.NinjaGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/slime/GrapeSlimeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/slime/GrapeSlimeEntity.java index 95a91ad..11c79b7 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/slime/GrapeSlimeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/slime/GrapeSlimeEntity.java @@ -3,7 +3,7 @@ package fathertoast.specialmobs.common.entity.slime; import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; -import fathertoast.specialmobs.common.entity.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/slime/WatermelonSlimeEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/slime/WatermelonSlimeEntity.java index 64401c7..f3a89b4 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/slime/WatermelonSlimeEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/slime/WatermelonSlimeEntity.java @@ -3,7 +3,7 @@ package fathertoast.specialmobs.common.entity.slime; import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; -import fathertoast.specialmobs.common.entity.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/spider/FlyingSpiderEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/spider/FlyingSpiderEntity.java index cf20878..d659754 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/spider/FlyingSpiderEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/spider/FlyingSpiderEntity.java @@ -3,7 +3,7 @@ package fathertoast.specialmobs.common.entity.spider; import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; -import fathertoast.specialmobs.common.entity.ai.SpecialLeapAtTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialLeapAtTargetGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/NinjaWitherSkeletonEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/NinjaWitherSkeletonEntity.java index 5203362..2824176 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/NinjaWitherSkeletonEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/witherskeleton/NinjaWitherSkeletonEntity.java @@ -5,7 +5,7 @@ import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.SpecialMob; import fathertoast.specialmobs.common.entity.MobHelper; import fathertoast.specialmobs.common.entity.ai.INinja; -import fathertoast.specialmobs.common.entity.ai.NinjaGoal; +import fathertoast.specialmobs.common.entity.ai.goal.NinjaGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/MadScientistZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/MadScientistZombieEntity.java index 25c9fe9..d07045e 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/MadScientistZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/MadScientistZombieEntity.java @@ -6,7 +6,7 @@ import fathertoast.specialmobs.common.bestiary.SpecialMob; import fathertoast.specialmobs.common.core.register.SMItems; import fathertoast.specialmobs.common.entity.MobHelper; import fathertoast.specialmobs.common.entity.ai.AIHelper; -import fathertoast.specialmobs.common.entity.ai.SpecialInjectCreeperGoal; +import fathertoast.specialmobs.common.entity.ai.goal.ChargeCreeperGoal; import fathertoast.specialmobs.common.util.AttributeHelper; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; @@ -76,8 +76,8 @@ public class MadScientistZombieEntity extends _SpecialZombieEntity { protected void registerVariantGoals() { disableRangedAI(); - AIHelper.insertGoal( goalSelector, 2, new SpecialInjectCreeperGoal<>( - this, getAttributeValue(Attributes.MOVEMENT_SPEED) * 1.25D, 20.0D, + AIHelper.insertGoal( goalSelector, 2, new ChargeCreeperGoal<>( + this, getAttributeValue( Attributes.MOVEMENT_SPEED ) * 1.25D, 20.0D, ( madman, creeper ) -> creeper.isAlive() && !creeper.isPowered() && madman.getSensing().canSee( creeper ) ) ); } diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java index d699396..dde2b9d 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java @@ -8,7 +8,7 @@ import fathertoast.specialmobs.common.entity.ISpecialMob; import fathertoast.specialmobs.common.entity.MobHelper; import fathertoast.specialmobs.common.entity.SpecialMobData; import fathertoast.specialmobs.common.entity.ai.AIHelper; -import fathertoast.specialmobs.common.entity.ai.SpecialHurtByTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialHurtByTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java index d71a7d4..e34541f 100644 --- a/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombifiedpiglin/_SpecialZombifiedPiglinEntity.java @@ -8,7 +8,7 @@ import fathertoast.specialmobs.common.entity.ISpecialMob; import fathertoast.specialmobs.common.entity.MobHelper; import fathertoast.specialmobs.common.entity.SpecialMobData; import fathertoast.specialmobs.common.entity.ai.AIHelper; -import fathertoast.specialmobs.common.entity.ai.SpecialHurtByTargetGoal; +import fathertoast.specialmobs.common.entity.ai.goal.SpecialHurtByTargetGoal; import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import mcp.MethodsReturnNonnullByDefault; diff --git a/src/main/java/fathertoast/specialmobs/common/util/References.java b/src/main/java/fathertoast/specialmobs/common/util/References.java index 2376da6..d194192 100644 --- a/src/main/java/fathertoast/specialmobs/common/util/References.java +++ b/src/main/java/fathertoast/specialmobs/common/util/References.java @@ -25,6 +25,7 @@ public final class References { 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; @@ -81,9 +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, Wilds Witch, Wildfire Blaze + 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, Wildfire Blaze + 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