MADMAN (also some other stuff)

This commit is contained in:
FatherToast 2022-07-13 10:31:37 -05:00
parent 3b1544e8d1
commit 9eccaed1a9
13 changed files with 213 additions and 45 deletions

View file

@ -7,6 +7,7 @@ import fathertoast.specialmobs.common.core.register.SMEntities;
import fathertoast.specialmobs.common.entity.ghast.CorporealShiftGhastEntity;
import fathertoast.specialmobs.common.entity.skeleton.NinjaSkeletonEntity;
import fathertoast.specialmobs.common.entity.witherskeleton.NinjaWitherSkeletonEntity;
import fathertoast.specialmobs.common.entity.zombie.MadScientistZombieEntity;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ItemRenderer;
@ -54,6 +55,7 @@ public class ClientRegister {
registerFamilyRenderers( MobFamily.BLAZE, SpecialBlazeRenderer::new );
// Species overrides
registerSpeciesRenderer( MadScientistZombieEntity.SPECIES, SpecialZombieVillagerRenderer::new );
registerSpeciesRenderer( NinjaSkeletonEntity.SPECIES, NinjaSkeletonRenderer::new );
registerSpeciesRenderer( NinjaWitherSkeletonEntity.SPECIES, NinjaSkeletonRenderer::new );
registerSpeciesRenderer( CorporealShiftGhastEntity.SPECIES, CorporealShiftGhastRenderer::new );

View file

@ -7,46 +7,54 @@ import fathertoast.specialmobs.common.core.SpecialMobs;
import fathertoast.specialmobs.common.entity.ISpecialMob;
import fathertoast.specialmobs.common.entity.SpecialMobData;
import fathertoast.specialmobs.common.entity.ghast.CorporealShiftGhastEntity;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.MobRenderer;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.function.Function;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@OnlyIn( Dist.CLIENT )
public class CorporealShiftGhastRenderer extends MobRenderer<CorporealShiftGhastEntity, CorporealShiftGhastModel<CorporealShiftGhastEntity>> {
private static final Function<ResourceLocation, RenderType> INCORPOREAL = (resourceLocation) -> SMRenderTypes.entityCutoutNoCullBlend(resourceLocation, SMRenderTypes.INCORPOREAL_ALPHA);
private static final ResourceLocation EYES = SpecialMobs.resourceLoc("textures/entity/ghast/corporeal_shift_eyes.png");
private static final ResourceLocation SHOOT_EYES = SpecialMobs.resourceLoc("textures/entity/ghast/corporeal_shift_shoot_eyes.png");
private static final Function<ResourceLocation, RenderType> INCORPOREAL = ( resourceLocation ) -> SMRenderTypes.entityCutoutNoCullBlend( resourceLocation, SMRenderTypes.INCORPOREAL_ALPHA );
private static final ResourceLocation EYES = SpecialMobs.resourceLoc( SpecialMobs.TEXTURE_PATH + "ghast/corporeal_shift_eyes.png" );
private static final ResourceLocation SHOOT_EYES = SpecialMobs.resourceLoc( SpecialMobs.TEXTURE_PATH + "ghast/corporeal_shift_shooting_eyes.png" );
private final float baseShadowRadius;
public CorporealShiftGhastRenderer(EntityRendererManager rendererManager) {
super(rendererManager, new CorporealShiftGhastModel<>(), 1.5F);
addLayer(new SpecialGhastEyesLayer<>(this, EYES, SHOOT_EYES ));
public CorporealShiftGhastRenderer( EntityRendererManager rendererManager ) {
super( rendererManager, new CorporealShiftGhastModel<>(), 1.5F );
addLayer( new SpecialGhastEyesLayer<>( this, EYES, SHOOT_EYES ) );
baseShadowRadius = shadowRadius;
}
@Override
public void render(CorporealShiftGhastEntity ghast, float rotation, float partialTicks, MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight) {
public void render( CorporealShiftGhastEntity ghast, float rotation, float partialTicks, MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight ) {
model.renderType = ghast.isCorporeal() ? RenderType::entityCutoutNoCull : INCORPOREAL;
super.render(ghast, rotation, partialTicks, matrixStack, buffer, packedLight);
super.render( ghast, rotation, partialTicks, matrixStack, buffer, packedLight );
}
@Override
public ResourceLocation getTextureLocation( CorporealShiftGhastEntity entity ) {
final SpecialMobData<?> data = ((ISpecialMob<?>) entity).getSpecialData();
return entity.isCharging() && data.hasOverlayTexture() ? data.getTextureOverlay() : data.getTexture();
}
@Override
protected void scale( CorporealShiftGhastEntity entity, MatrixStack matrixStack, float partialTick ) {
final float scale = 4.5F + ((ISpecialMob<?>) entity).getSpecialData().getRenderScale();
// The base scale of 4.5 is taken from GhastRenderer
final float scale = 4.5F * ((ISpecialMob<?>) entity).getSpecialData().getRenderScale();
shadowRadius = baseShadowRadius * scale;
matrixStack.scale( scale, scale, scale );
}
}
}

View file

@ -0,0 +1,48 @@
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.BipedRenderer;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.layers.BipedArmorLayer;
import net.minecraft.client.renderer.entity.model.ZombieVillagerModel;
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 SpecialZombieVillagerRenderer extends BipedRenderer<ZombieEntity, ZombieVillagerModel<ZombieEntity>> {
private final float baseShadowRadius;
public SpecialZombieVillagerRenderer( EntityRendererManager rendererManager ) {
super( rendererManager, new ZombieVillagerModel<>( 0.0F, false ), 0.5F );
addLayer( new BipedArmorLayer<>( this, new ZombieVillagerModel<>( 0.5F, true ), new ZombieVillagerModel<>( 1.0F, true ) ) );
baseShadowRadius = shadowRadius;
addLayer( new SpecialMobEyesLayer<>( this ) );
addLayer( new SpecialMobOverlayLayer<>( this, new ZombieVillagerModel<>( 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 );
}
}

View file

@ -47,7 +47,7 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
"Mini", /*"Scope",*/ "Skeleton", "Splitting"
);
public static final MobFamily<ZombieEntity, FamilyConfig> ZOMBIE = new MobFamily<>( FamilyConfig::new,
public static final MobFamily<ZombieEntity, FamilyConfig> ZOMBIE = new MobFamily<>( FamilyConfig::newLessSpecial,
"Zombie", "zombies", 0x00AFAF, new EntityType[] { EntityType.ZOMBIE, EntityType.HUSK },
"Brute", "Fire", /*"Fishing",*/ "Giant", "Hungry", "Husk", "MadScientist", "Plague"
);
@ -70,16 +70,16 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
"Slime", "slimes", 0x51A03E, new EntityType[] { EntityType.SLIME },
"Blackberry", "Blueberry", "Caramel", "Grape", "Lemon", "Strawberry", "Watermelon"
);
public static final MobFamily<MagmaCubeEntity, FamilyConfig> MAGMA_CUBE = new MobFamily<>( FamilyConfig::new,
public static final MobFamily<MagmaCubeEntity, FamilyConfig> MAGMA_CUBE = new MobFamily<>( FamilyConfig::newMoreSpecial,
"MagmaCube", "magma cubes", 0x340000, new EntityType[] { EntityType.MAGMA_CUBE },
"Bouncing", "Hardened", "Sticky", "Volatile"
);
public static final MobFamily<SpiderEntity, FamilyConfig> SPIDER = new MobFamily<>( FamilyConfig.withVariantChance( 0.33 ),
public static final MobFamily<SpiderEntity, FamilyConfig> SPIDER = new MobFamily<>( FamilyConfig::newMoreSpecial,
"Spider", "spiders", 0x342D27, new EntityType[] { EntityType.SPIDER },
"Baby", "Desert", "Flying", "Giant", "Hungry", "Mother", "Pale", "Poison", /*"Water",*/ "Web", "Witch"
);
public static final MobFamily<CaveSpiderEntity, FamilyConfig> CAVE_SPIDER = new MobFamily<>( FamilyConfig.withVariantChance( 0.33 ),
public static final MobFamily<CaveSpiderEntity, FamilyConfig> CAVE_SPIDER = new MobFamily<>( FamilyConfig::newMoreSpecial,
"CaveSpider", "cave spiders", 0x0C424E, new EntityType[] { EntityType.CAVE_SPIDER },
"Baby", "Flying", "Mother", /*"Water",*/ "Web", "Witch"
);

View file

@ -15,7 +15,7 @@ public class CreeperFamilyConfig extends FamilyConfig {
/** Builds the config spec that should be used for this config. */
public CreeperFamilyConfig( MobFamily<?, ?> family ) {
super( family, 0.33 );
super( family, VARIANT_CHANCE_HIGH );
CREEPERS = new Creepers( SPEC, family );
}

View file

@ -11,21 +11,24 @@ import fathertoast.specialmobs.common.config.util.ConfigUtil;
import java.io.File;
import java.util.List;
import java.util.function.Function;
/**
* This is the base config for mob families. This may be extended to add categories specific to the family, but all
* options that are used by all families should be defined in this class.
*/
public class FamilyConfig extends Config.AbstractConfig {
protected static final double VARIANT_CHANCE_LOW = 0.2;
protected static final double VARIANT_CHANCE_HIGH = 0.33;
public static File dir( MobFamily<?, ?> family ) { return new File( Config.CONFIG_DIR, ConfigUtil.noSpaces( family.configName ) ); }
protected static String fileName( MobFamily<?, ?> family ) { return "_family_of_" + ConfigUtil.noSpaces( family.configName ); }
/** @return A basic config supplier with custom default variant chance. */
public static Function<MobFamily<?, ?>, FamilyConfig> withVariantChance( double chance ) {
return ( family ) -> new FamilyConfig( family, chance );
}
/** @return A basic config supplier with a lower default variant chance. */
public static FamilyConfig newLessSpecial( MobFamily<?, ?> family ) { return new FamilyConfig( family, VARIANT_CHANCE_LOW ); }
/** @return A basic config supplier with a higher default variant chance. */
public static FamilyConfig newMoreSpecial( MobFamily<?, ?> family ) { return new FamilyConfig( family, VARIANT_CHANCE_HIGH ); }
/** Category containing all options applicable to mob families as a whole; i.e. not specific to any particular family. */
public final General GENERAL;

View file

@ -15,7 +15,7 @@ public class SilverfishFamilyConfig extends FamilyConfig {
/** Builds the config spec that should be used for this config. */
public SilverfishFamilyConfig( MobFamily<?, ?> family ) {
super( family );
super( family, VARIANT_CHANCE_LOW );
SILVERFISH = new Silverfish( SPEC, family );
}

View file

@ -15,7 +15,7 @@ public class SlimeFamilyConfig extends FamilyConfig {
/** Builds the config spec that should be used for this config. */
public SlimeFamilyConfig( MobFamily<?, ?> family ) {
super( family );
super( family, VARIANT_CHANCE_HIGH );
SLIMES = new Slimes( SPEC, family );
}

View file

@ -0,0 +1,35 @@
package fathertoast.specialmobs.common.config.species;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.config.Config;
import fathertoast.specialmobs.common.config.field.IntField;
import fathertoast.specialmobs.common.config.file.ToastConfigSpec;
import fathertoast.specialmobs.common.config.util.ConfigUtil;
public class MadScientistZombieSpeciesConfig extends ZombieSpeciesConfig {
public final MadScientist MAD_SCIENTIST;
/** Builds the config spec that should be used for this config. */
public MadScientistZombieSpeciesConfig( MobFamily.Species<?> species, double bowChance, double shieldChance, int minCharges, int maxCharges ) {
super( species, bowChance, shieldChance );
MAD_SCIENTIST = new MadScientist( SPEC, species, speciesName, minCharges, maxCharges );
}
public static class MadScientist extends Config.AbstractCategory {
public final IntField.RandomRange chargeCount;
MadScientist( ToastConfigSpec parent, MobFamily.Species<?> species, String speciesName, int minCharges, int maxCharges ) {
super( parent, ConfigUtil.camelCaseToLowerUnderscore( species.specialVariantName ),
"Options specific to " + speciesName + "." );
chargeCount = new IntField.RandomRange(
SPEC.define( new IntField( "charges.min", minCharges, IntField.Range.NON_NEGATIVE,
"The minimum and maximum (inclusive) number of creepers a " + speciesName + " can charge." ) ),
SPEC.define( new IntField( "charges.max", maxCharges, IntField.Range.NON_NEGATIVE ) )
);
}
}
}

View file

@ -0,0 +1,13 @@
package fathertoast.specialmobs.common.entity.ai;
/**
* Monsters must implement this interface to use ammo-based AI goals.
* The default implementation is "unlimited ammo".
*/
public interface IAmmoUser {
/** @return True if this entity has ammo to use. */
default boolean hasAmmo() { return true; }
/** Consumes ammo for a single use. */
default void consumeAmmo() { }
}

View file

@ -1,6 +1,8 @@
package fathertoast.specialmobs.common.entity.ai.goal;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.ai.IAmmoUser;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.entity.MobEntity;
import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.entity.monster.CreeperEntity;
@ -9,11 +11,14 @@ import net.minecraft.util.SoundEvents;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.World;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.EnumSet;
import java.util.List;
import java.util.function.BiPredicate;
public class ChargeCreeperGoal<T extends MobEntity> extends Goal {
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
public class ChargeCreeperGoal<T extends MobEntity & IAmmoUser> extends Goal {
private final BiPredicate<T, ? super CreeperEntity> targetPredicate;
@ -21,7 +26,7 @@ public class ChargeCreeperGoal<T extends MobEntity> extends Goal {
private final double movementSpeed;
private final double targetRange;
/** The creeper to target for power-up injection **/
/** The creeper to target for power-up injection */
private CreeperEntity creeper;
private int pathUpdateCooldown;
@ -45,7 +50,7 @@ public class ChargeCreeperGoal<T extends MobEntity> extends Goal {
/** @return Returns true if this AI can be activated. */
@Override
public boolean canUse() {
if( madman.isPassenger() || !canUseWhileMounted && madman.isVehicle() ) return false;
if( !madman.hasAmmo() || madman.isPassenger() || !canUseWhileMounted && madman.isVehicle() ) return false;
findCreeper();
if( creeper == null ) return false;
@ -95,9 +100,12 @@ public class ChargeCreeperGoal<T extends MobEntity> extends Goal {
madman.getLookControl().setLookAt( creeper, 30.0F, 30.0F );
if( distanceSq < 2.5 ) {
MobHelper.charge( creeper );
madman.level.playSound( null, creeper.getX(), creeper.getY(), creeper.getZ(),
SoundEvents.BEE_STING, SoundCategory.HOSTILE, 0.9F, 1.0F );
if( madman.hasAmmo() ) {
madman.consumeAmmo();
MobHelper.charge( creeper );
madman.level.playSound( null, creeper.getX(), creeper.getY(), creeper.getZ(),
SoundEvents.BEE_STING, SoundCategory.HOSTILE, 0.9F, 1.0F );
}
creeper = null;
}

View file

@ -3,18 +3,21 @@ package fathertoast.specialmobs.common.entity.zombie;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.config.species.MadScientistZombieSpeciesConfig;
import fathertoast.specialmobs.common.config.species.SpeciesConfig;
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.IAmmoUser;
import fathertoast.specialmobs.common.entity.ai.goal.ChargeCreeperGoal;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.entity.*;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.entity.monster.CreeperEntity;
import net.minecraft.inventory.EquipmentSlotType;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.potion.EffectInstance;
import net.minecraft.potion.Effects;
import net.minecraft.world.DifficultyInstance;
@ -28,7 +31,7 @@ import java.util.function.BiPredicate;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@SpecialMob
public class MadScientistZombieEntity extends _SpecialZombieEntity {
public class MadScientistZombieEntity extends _SpecialZombieEntity implements IAmmoUser {
//--------------- Static Special Mob Hooks ----------------
@ -38,10 +41,19 @@ public class MadScientistZombieEntity extends _SpecialZombieEntity {
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0xDED4C6 )
.uniqueTextureWithOverlay()
.uniqueTextureBaseOnly()
.addExperience( 2 ).disableRangedAttack();
}
@SpecialMob.ConfigSupplier
public static SpeciesConfig createConfig( MobFamily.Species<?> species ) {
return new MadScientistZombieSpeciesConfig( species, 0.0, 0.0, 1, 3 );
}
/** @return This entity's species config. */
@Override
public MadScientistZombieSpeciesConfig getConfig() { return (MadScientistZombieSpeciesConfig) getSpecies().config; }
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Mad Scientist Zombie",
@ -65,10 +77,16 @@ public class MadScientistZombieEntity extends _SpecialZombieEntity {
//--------------- Variant-Specific Implementations ----------------
private final BiPredicate<MadScientistZombieEntity, ? super CreeperEntity> CHARGE_CREEPER_TARGET = ( madman, creeper ) ->
private static final BiPredicate<MadScientistZombieEntity, ? super CreeperEntity> CHARGE_CREEPER_TARGET = ( madman, creeper ) ->
creeper.isAlive() && !creeper.isPowered() && madman.getSensing().canSee( creeper );
public MadScientistZombieEntity( EntityType<? extends _SpecialZombieEntity> entityType, World world ) { super( entityType, world ); }
/** The number of creepers this madman can charge. */
private int chargeCount;
public MadScientistZombieEntity( EntityType<? extends _SpecialZombieEntity> entityType, World world ) {
super( entityType, world );
chargeCount = getConfig().MAD_SCIENTIST.chargeCount.next( random );
}
/** Override to change this entity's AI goals. */
@Override
@ -91,11 +109,44 @@ public class MadScientistZombieEntity extends _SpecialZombieEntity {
/** 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 ) {
if( target instanceof LivingEntity && hasAmmo() ) {
final LivingEntity livingTarget = (LivingEntity) target;
final int duration = MobHelper.getDebuffDuration( level.getDifficulty() );
livingTarget.addEffect( new EffectInstance( Effects.POISON, duration, 1 ) );
livingTarget.addEffect( new EffectInstance( Effects.POISON, duration ) );
}
}
/** Called each tick to update this entity's movement. */
@Override
public void aiStep() {
super.aiStep();
// We can't do this when the last charge is used, since it would change the AI during the AI loop
if( chargeCount <= 0 && getItemBySlot( EquipmentSlotType.MAINHAND ).getItem() == SMItems.SYRINGE.get() ) {
broadcastBreakEvent( EquipmentSlotType.MAINHAND );
setItemSlot( EquipmentSlotType.MAINHAND, ItemStack.EMPTY );
}
}
/** @return True if this entity has ammo to use. */
@Override
public boolean hasAmmo() { return chargeCount > 0; }
/** Consumes ammo for a single use. */
@Override
public void consumeAmmo() { chargeCount--; }
/** Override to save data to this entity's NBT data. */
@Override
public void addVariantSaveData( CompoundNBT saveTag ) {
saveTag.putByte( References.TAG_AMMO, (byte) chargeCount );
}
/** Override to load data from this entity's NBT data. */
@Override
public void readVariantSaveData( CompoundNBT saveTag ) {
if( saveTag.contains( References.TAG_AMMO, References.NBT_TYPE_NUMERICAL ) )
chargeCount = saveTag.getByte( References.TAG_AMMO );
}
}

View file

@ -26,7 +26,7 @@ public final class References {
public static final String TEXTURE_EYES_SUFFIX = "_eyes";
public static final String TEXTURE_OVERLAY_SUFFIX = "_overlay";
public static final String TEXTURE_SHOOTING_SUFFIX = "_shooting";
public static final String TEXTURE_SHOOTING_EYES_SUFFIX = "_shooting_eyes";
//public static final String TEXTURE_SHOOTING_EYES_SUFFIX = "_shooting_eyes";
//--------------- BIT FLAGS ----------------
@ -113,7 +113,7 @@ public final class References {
// Misc.
public static final String TAG_FUSE_TIME = "FuseTime"; // Blackberry Slime, Volatile Magma Cube
public static final String TAG_AMMO = "Ammo"; // Web (Cave) Spider
public static final String TAG_AMMO = "Ammo"; // Web (Cave) Spider, Mad Scientist Zombie
public static final String TAG_IS_FAKE = "IsFake"; // Mirage Enderman
public static final String TAG_EXPLOSION_POWER = "ExplosionPower"; // Hellfire Blaze