diff --git a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java index 383f64d..f70f33d 100644 --- a/src/main/java/fathertoast/specialmobs/client/ClientRegister.java +++ b/src/main/java/fathertoast/specialmobs/client/ClientRegister.java @@ -3,6 +3,7 @@ package fathertoast.specialmobs.client; import fathertoast.specialmobs.client.renderer.entity.SpecialCreeperRenderer; import fathertoast.specialmobs.client.renderer.entity.SpecialSkeletonRenderer; import fathertoast.specialmobs.client.renderer.entity.SpecialSpiderRenderer; +import fathertoast.specialmobs.client.renderer.entity.SpecialZombieRenderer; import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.core.SpecialMobs; import mcp.MethodsReturnNonnullByDefault; @@ -29,6 +30,7 @@ public class ClientRegister { private static void registerEntityRenderers() { // Family-based renderers registerFamilyRenderers( MobFamily.CREEPER, SpecialCreeperRenderer::new ); + registerFamilyRenderers( MobFamily.ZOMBIE, SpecialZombieRenderer::new ); //registerFamilyRenderers( MobFamily.SKELETON, SpecialSkeletonRenderer::new ); registerFamilyRenderers( MobFamily.SPIDER, SpecialSpiderRenderer::new ); registerFamilyRenderers( MobFamily.CAVE_SPIDER, SpecialSpiderRenderer::new ); diff --git a/src/main/java/fathertoast/specialmobs/client/renderer/entity/SpecialZombieRenderer.java b/src/main/java/fathertoast/specialmobs/client/renderer/entity/SpecialZombieRenderer.java new file mode 100644 index 0000000..63ce9c0 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/client/renderer/entity/SpecialZombieRenderer.java @@ -0,0 +1,45 @@ +package fathertoast.specialmobs.client.renderer.entity; + +import com.mojang.blaze3d.matrix.MatrixStack; +import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobEyesLayer; +import fathertoast.specialmobs.client.renderer.entity.layers.SpecialMobOverlayLayer; +import fathertoast.specialmobs.common.entity.ISpecialMob; +import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.client.renderer.entity.EntityRendererManager; +import net.minecraft.client.renderer.entity.ZombieRenderer; +import net.minecraft.client.renderer.entity.model.ZombieModel; +import net.minecraft.entity.monster.ZombieEntity; +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 SpecialZombieRenderer extends ZombieRenderer { + + private final float baseShadowRadius; + + public SpecialZombieRenderer(EntityRendererManager rendererManager) { + super(rendererManager); + baseShadowRadius = shadowRadius; + addLayer( new SpecialMobEyesLayer<>( this ) ); + addLayer( new SpecialMobOverlayLayer<>( this, new ZombieModel<>( 0.25F, true ) ) ); + } + + @Override + public ResourceLocation getTextureLocation(ZombieEntity entity ) { + return ((ISpecialMob) entity).getSpecialData().getTexture(); + } + + @Override + protected void scale(ZombieEntity 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 ); + } +} diff --git a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java index 8870568..3c73553 100644 --- a/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java +++ b/src/main/java/fathertoast/specialmobs/common/bestiary/MobFamily.java @@ -8,10 +8,7 @@ import mcp.MethodsReturnNonnullByDefault; import net.minecraft.block.Block; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.monster.AbstractSkeletonEntity; -import net.minecraft.entity.monster.CaveSpiderEntity; -import net.minecraft.entity.monster.CreeperEntity; -import net.minecraft.entity.monster.SpiderEntity; +import net.minecraft.entity.monster.*; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.common.ForgeSpawnEggItem; @@ -46,10 +43,10 @@ public class MobFamily { "Mini", /*"Scope",*/ "Splitting" ); - // public static final MobFamily ZOMBIE = new MobFamily<>( - // "Zombie", "zombies", 0x00AFAF, new EntityType[] { EntityType.ZOMBIE, EntityType.HUSK }, - // "Brute", "Fire", "Fishing", "Giant", "Hungry", "Husk", "Plague" - // ); + public static final MobFamily ZOMBIE = new MobFamily<>( + "Zombie", "zombies", 0x00AFAF, new EntityType[] { EntityType.ZOMBIE, EntityType.HUSK }, + /*"Brute", "Fire", "Fishing", "Giant", "Hungry", "Husk", "Plague",*/ "Mad Scientist" + ); // public static final MobFamily ZOMBIFIED_PIGLIN = new MobFamily<>( // "ZombifiedPiglin", "zombie pigmen", 0xEA9393, new EntityType[] { EntityType.ZOMBIFIED_PIGLIN }, // "Brute", "Fishing", "Giant", "Hungry", "Knight", "Plague", "Vampire" diff --git a/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialInjectCreeperGoal.java b/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialInjectCreeperGoal.java new file mode 100644 index 0000000..59b0178 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/ai/SpecialInjectCreeperGoal.java @@ -0,0 +1,80 @@ +package fathertoast.specialmobs.common.entity.ai; + +import fathertoast.specialmobs.common.entity.zombie.MadScientistZombieEntity; +import net.minecraft.entity.ai.goal.Goal; +import net.minecraft.entity.monster.CreeperEntity; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.world.World; + +import java.util.EnumSet; +import java.util.function.BiPredicate; + +public class SpecialInjectCreeperGoal extends Goal { + + private final BiPredicate targetPredicate; + + private final T madman; + private final double movementSpeed; + private final AxisAlignedBB targetBox; + + /** 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) { + this.madman = madman; + this.movementSpeed = movementSpeed; + this.targetBox = madman.getBoundingBox().inflate(targetRange); + this.targetPredicate = targetPredicate; + this.setFlags(EnumSet.of(Flag.MOVE)); + } + + /** Builder that enables the entity to leap while mounted. */ + public SpecialInjectCreeperGoal canUseWhileMounted() { + canUseWhileMounted = true; + return this; + } + + /** @return Returns true if this AI can be activated. */ + @Override + public boolean canUse() { + if( !madman.isOnGround() || madman.isPassenger() || !canUseWhileMounted && madman.isVehicle() ) return false; + findCreeper(); + return creeper != null; + } + + private void findCreeper() { + World world = madman.level; + world.getLoadedEntitiesOfClass(CreeperEntity.class, targetBox, (creeper) -> targetPredicate.test(madman, creeper)); + } + + /** Called when this AI is activated. */ + @Override + public void start() { + 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) { + findCreeper(); + } + else { + madman.getNavigation().moveTo(creeper, movementSpeed); + + if (madman.distanceTo(creeper) < 1.0D) { + creeper.getEntityData().set(CreeperEntity.DATA_IS_POWERED, true); + creeper = null; + } + } + } +} diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/MadScientistZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/MadScientistZombieEntity.java new file mode 100644 index 0000000..003bafe --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/MadScientistZombieEntity.java @@ -0,0 +1,116 @@ +package fathertoast.specialmobs.common.entity.zombie; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +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.SpecialInjectCreeperGoal; +import fathertoast.specialmobs.common.entity.ai.SpecialLeapAtTargetGoal; +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 net.minecraft.entity.*; +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.nbt.CompoundNBT; +import net.minecraft.potion.EffectInstance; +import net.minecraft.potion.Effects; +import net.minecraft.util.Hand; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.Difficulty; +import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.IServerWorld; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class MadScientistZombieEntity extends _SpecialZombieEntity { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo(EntityType.Builder entityType ) { + return new BestiaryInfo( 0xDED4C6 ); // TODO - Temp color + //TODO theme - madness + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return AttributeHelper.of( _SpecialSpiderEntity.createAttributes() ) + .addAttribute( Attributes.MOVEMENT_SPEED, 1.1 ) + .build(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Mad Scientist Zombie", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void buildLootTable( LootTableBuilder loot ) { + addBaseLoot( loot ); + loot.addRareDrop( "rare", SMItems.SYRINGE.get() ); + } + + @SpecialMob.Constructor + public MadScientistZombieEntity(EntityType entityType, World world) { + super(entityType, world); + xpReward += 2; + } + + + //--------------- Variant-Specific Implementations ---------------- + + /** Override to change this entity's AI goals. */ + @Override + protected void registerVariantGoals() { + goalSelector.addGoal( 3, new SpecialInjectCreeperGoal<>( + this, 1.0D, 20.0D, (madman, creeper) -> creeper.isAlive() && !creeper.isPowered() && madman.getSensing().canSee(creeper)) ); + } + + /** Override to apply effects when this entity hits a target with a melee attack. */ + @Override + protected void onVariantAttack( Entity target ) { + if( target instanceof LivingEntity && random.nextFloat() < 0.3F ) { + final LivingEntity livingTarget = (LivingEntity) target; + final int duration = MobHelper.getDebuffDuration( level.getDifficulty() ); + + livingTarget.addEffect( new EffectInstance( Effects.POISON, duration, 1 ) ); + } + } + + /** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */ + @Nullable + @Override + public ILivingEntityData finalizeSpawn(IServerWorld world, DifficultyInstance difficulty, SpawnReason spawnReason, + @Nullable ILivingEntityData groupData, @Nullable CompoundNBT eggTag ) { + this.populateDefaultEquipmentSlots(difficulty); + return super.finalizeSpawn(world, difficulty, spawnReason, groupData, eggTag); + } + + /** Only drop armor. The syringe item should be dropped from the loot table, and not from the hand item. **/ + @Override + protected float getEquipmentDropChance(EquipmentSlotType slotType) { + return slotType.getType() == EquipmentSlotType.Group.ARMOR ? this.armorDropChances[slotType.getIndex()] : 0.0F; + } + + @Override + protected void populateDefaultEquipmentSlots(DifficultyInstance difficultyInstance) { + super.populateDefaultEquipmentSlots(difficultyInstance); + this.setItemSlot(EquipmentSlotType.MAINHAND, new ItemStack(SMItems.SYRINGE.get())); + } + + private static final ResourceLocation[] TEXTURES = { + GET_TEXTURE_PATH( "mad_scientist" ), + GET_TEXTURE_PATH( "mad_scientist_clothing" ) + }; + + /** @return All default textures for this entity. */ + @Override + public ResourceLocation[] getDefaultTextures() { return TEXTURES; } +} diff --git a/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java b/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java new file mode 100644 index 0000000..459e669 --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/entity/zombie/_SpecialZombieEntity.java @@ -0,0 +1,226 @@ +package fathertoast.specialmobs.common.entity.zombie; + +import fathertoast.specialmobs.common.bestiary.BestiaryInfo; +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.spider._SpecialSpiderEntity; +import fathertoast.specialmobs.common.util.References; +import fathertoast.specialmobs.datagen.loot.LootTableBuilder; +import net.minecraft.block.BlockState; +import net.minecraft.entity.*; +import net.minecraft.entity.ai.attributes.AttributeModifierMap; +import net.minecraft.entity.monster.ZombieEntity; +import net.minecraft.entity.player.PlayerEntity; +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.ResourceLocation; +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; + +public class _SpecialZombieEntity extends ZombieEntity implements ISpecialMob<_SpecialZombieEntity> { + + //--------------- Static Special Mob Hooks ---------------- + + @SpecialMob.BestiaryInfoSupplier + public static BestiaryInfo bestiaryInfo(EntityType.Builder entityType ) { + return new BestiaryInfo( 0xA80E0E ); + } + + @SpecialMob.AttributeCreator + public static AttributeModifierMap.MutableAttribute createAttributes() { + return ZombieEntity.createAttributes(); + } + + @SpecialMob.LanguageProvider + public static String[] getTranslations( String langKey ) { + return References.translations( langKey, "Zombie", + "", "", "", "", "", "" );//TODO + } + + @SpecialMob.LootTableProvider + public static void addBaseLoot( LootTableBuilder loot ) { + loot.addLootTable( "main", EntityType.ZOMBIE.getDefaultLootTable() ); + } + + @SpecialMob.Constructor + public _SpecialZombieEntity(EntityType entityType, World world ) { + super( entityType, world ); + specialData.initialize(); + } + + + //--------------- Variant-Specific Breakouts ---------------- + + /** Called in the MobEntity.class constructor to initialize AI goals. */ + @Override + protected void registerGoals() { + super.registerGoals(); + registerVariantGoals(); + } + + /** Override to change this entity's AI goals. */ + protected void registerVariantGoals() { } + + /** Called to melee attack the target. */ + @Override + public boolean doHurtTarget( Entity target ) { + if( super.doHurtTarget( target ) ) { + onVariantAttack( target ); + return true; + } + return false; + } + + /** 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( _SpecialSpiderEntity.class, DataSerializers.FLOAT ); + + /** Called from the Entity.class constructor to define data watcher variables. */ + @Override + protected void defineSynchedData() { + super.defineSynchedData(); + specialData = new SpecialMobData<>( this, SCALE, 1.0F ); + } + + /** Called on spawn to initialize properties based on the world, difficulty, and the group it spawns with. */ + @Nullable + public ILivingEntityData finalizeSpawn(IServerWorld world, DifficultyInstance difficulty, SpawnReason spawnReason, + @Nullable ILivingEntityData groupData, @Nullable CompoundNBT eggTag ) { + groupData = super.finalizeSpawn( world, difficulty, spawnReason, groupData, eggTag ); + // TODO ranged attack + return groupData; + } + + + //--------------- ISpecialMob Implementation ---------------- + + private SpecialMobData<_SpecialZombieEntity> specialData; + + /** @return This mob's special data. */ + @Override + public SpecialMobData<_SpecialZombieEntity> 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 + "zombie/" + type + ".png" ); + } + + private static final ResourceLocation[] TEXTURES = { + new ResourceLocation( "textures/entity/zombie/zombie.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 specialData.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( specialData.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 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 ); + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 1eb5140..6ed9b20 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -10,7 +10,7 @@ public net.minecraft.entity.ai.goal.GoalSelector field_220892_d #availableGoals public-f net.minecraft.entity.ai.goal.PrioritizedGoal field_220775_b #priority # Creepers -protected net.minecraft.entity.monster.CreeperEntity field_184714_b # DATA_IS_POWERED +public net.minecraft.entity.monster.CreeperEntity field_184714_b # DATA_IS_POWERED protected net.minecraft.entity.monster.CreeperEntity field_184715_c # DATA_IS_IGNITED protected net.minecraft.entity.monster.CreeperEntity field_82226_g # explosionRadius protected net.minecraft.entity.monster.CreeperEntity func_146077_cc()V # explodeCreeper()