package fathertoast.specialmobs.common.bestiary; import fathertoast.specialmobs.common.core.register.SMEntities; import fathertoast.specialmobs.common.core.register.SMItems; import fathertoast.specialmobs.common.util.AnnotationHelper; import fathertoast.specialmobs.common.util.References; import mcp.MethodsReturnNonnullByDefault; import net.minecraft.block.Block; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.monster.*; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.common.ForgeSpawnEggItem; import net.minecraftforge.fml.RegistryObject; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import java.util.*; /** * Special mobs are broken up into distinct 'families', each of which correspond to a type of vanilla mob that can be * replaced. During mob replacement, any member of the family (species) may be chosen, depending on location and config. * * @see MobFamily.Species */ @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault public class MobFamily { /** List of all families, generated to make iteration possible. */ private static final List> FAMILY_LIST = new ArrayList<>(); /** List of all species, generated to make iteration more convenient. */ private static final List> SPECIES_LIST; /** Maps each replaceable entity type to the family that replaces it. */ private static final Map, MobFamily> TYPE_TO_FAMILY_MAP; // NOTE: When adding a new mob family, do not forget to also register its renderer in the client register! public static final MobFamily CREEPER = new MobFamily<>( "Creeper", "creepers", 0x0DA70B, new EntityType[] { EntityType.CREEPER }, "Dark", "Death", "Dirt", "Doom", "Drowning", /*"Ender",*/ "Fire", "Gravel", "Jumping", "Lightning", "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",*/ "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" // ); // 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" // ); // public static final MobFamily WITHER_SKELETON = new MobFamily<>( // "WitherSkeleton", "wither skeletons", 0x141414, new EntityType[] { EntityType.WITHER_SKELETON }, // "Brute", "Gatling", "Giant", "Knight", "Ninja", "Sniper", "Spitfire" // ); // public static final MobFamily SLIME = new MobFamily<>( // "Slime", "slimes", 0x51A03E, new EntityType[] { EntityType.SLIME }, // "Blackberry", "Blueberry", "Caramel", "Grape", "Lemon", "Strawberry", "Watermelon" // ); // public static final MobFamily MAGMA_CUBE = new MobFamily<>( // "MagmaCube", "magma cubes", 0x340000, new EntityType[] { EntityType.MAGMA_CUBE }, // "Flying", "Hardened", "Sticky", "Volatile" // ); public static final MobFamily SPIDER = new MobFamily<>( "Spider", "spiders", 0x342D27, new EntityType[] { EntityType.SPIDER }, "Baby", "Desert", "Flying", "Giant", "Hungry", "Mother", "Pale", "Poison", "Web", "Witch" ); public static final MobFamily CAVE_SPIDER = new MobFamily<>( "CaveSpider", "cave spiders", 0x0C424E, new EntityType[] { EntityType.CAVE_SPIDER }, "Baby", "Flying", "Mother", "Web", "Witch" ); // public static final MobFamily SILVERFISH = new MobFamily<>( // "Silverfish", "silverfish", 0x6E6E6E, new EntityType[] { EntityType.SILVERFISH }, // "Blinding", "Fishing", "Flying", "Poison", "Tough" // ); // public static final MobFamily ENDERMAN = new MobFamily<>( // "Enderman", "endermen", 0x161616, new EntityType[] { EntityType.ENDERMAN }, // "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" // ); // 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 }, // "Cinder", "Conflagration", "Ember", "Hellfire", "Inferno", "Jolt", "Wildfire" // ); static { final HashMap, MobFamily> classToFamilyMap = new HashMap<>(); final ArrayList> allSpecies = new ArrayList<>(); for( MobFamily family : FAMILY_LIST ) { for( EntityType replaceable : family.replaceableTypes ) classToFamilyMap.put( replaceable, family ); allSpecies.add( family.vanillaReplacement ); allSpecies.addAll( Arrays.asList( family.variants ) ); } allSpecies.trimToSize(); TYPE_TO_FAMILY_MAP = Collections.unmodifiableMap( classToFamilyMap ); SPECIES_LIST = Collections.unmodifiableList( allSpecies ); } /** Called during mod construction to initialize the bestiary. */ public static void initBestiary() { } /** @return A list of all families. */ public static List> getAll() { return Collections.unmodifiableList( FAMILY_LIST ); } /** @return A list of all species. */ public static List> getAllSpecies() { return SPECIES_LIST; } /** @return The family of mobs that can replace the passed entity; returns null if the entity is not replaceable. */ @Nullable public static MobFamily getReplacementFamily( LivingEntity entity ) { return TYPE_TO_FAMILY_MAP.get( entity.getType() ); } /** The technical name that refers to this family. Note that this is UpperCamelCase, but is often used in lowercase. */ public final String name; /** The name used to refer to this family in unlocalized situations; e.g. config comments. */ public final String configName; /** The base egg color for species in this family. Species' eggs differ visually only by spot color. */ public final int eggBaseColor; /** List of replaceable entity types. The vanilla replacement's entity type is based on the first entry in this array. */ public final EntityType[] replaceableTypes; /** * The vanilla replacement species for this family. These will subtly replace the vanilla mob and provide extra * capabilities (unless disabled by config) - both for the mob and for map/pack creators. */ public final Species vanillaReplacement; /** Array of all special variant species in this family. In practice, these are the true "special mobs". */ public final Species[] variants; //public Config.FamilyConfig config; private MobFamily( String familyName, String familyKey, int eggColor, EntityType[] replaceable, String... variantNames ) { name = familyName; configName = familyKey; eggBaseColor = eggColor; replaceableTypes = replaceable; if( replaceable.length < 1 ) throw new IllegalArgumentException( familyName + " family must have at least one replaceable type!" ); final String packageRoot = References.ENTITY_PACKAGE + name.toLowerCase() + "."; vanillaReplacement = new Species<>( this, packageRoot, null ); //noinspection unchecked variants = new Species[variantNames.length]; for( int i = 0; i < variants.length; i++ ) { variants[i] = new Species<>( this, packageRoot, variantNames[i] ); } // We register here because otherwise there's no way to find all families FAMILY_LIST.add( this ); } /** Pick a new species from this family, based on the location. */ public Species nextVariant( World world, BlockPos pos ) { // TODO mob replacer // Build weights for the current location int totalWeight = 0; int[] variantWeights = new int[variants.length]; for( int i = 0; i < variants.length; i++ ) { int weight = 0;//config.getVariantWeight( world, pos, variants[i] ); TODO configs if( weight > 0 ) { totalWeight += weight; variantWeights[i] = weight; } } // Pick one item at random if( totalWeight > 0 ) { int weight = world.random.nextInt( totalWeight ); for( int i = 0; i < variants.length; i++ ) { if( variantWeights[i] > 0 ) { weight -= variantWeights[i]; if( weight < 0 ) { return variants[i]; } } } } return vanillaReplacement; } /** * Each special mob family is effectively a collection of special mob species, and each species corresponds to one * entity type. *

* There are two types of species; vanilla replacements that closely resemble their vanilla counterparts, and * special variants that differ both visually and mechanically. Each family has exactly one vanilla replacement * species and may have any number of special variants. *

* Though typically special variant entity classes will extend the vanilla replacement, this cannot always be assumed. * * @see MobFamily */ @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault public static class Species { /** The special mob family this species belongs to. */ public final MobFamily family; /** The name of this special variant; or null for vanilla replacement species. */ public final String specialVariantName; /** The technical name that refers to this species. Note that this is UpperCamelCase, but is often used in lowercase. */ public final String name; /** The entity class for this species. */ public final Class entityClass; /** The bestiary info describing this species */ public final BestiaryInfo bestiaryInfo; /** This species's entity type, wrapped in its registry object. */ public final RegistryObject> entityType; /** This species's spawn egg item, wrapped in its registry object. */ public final RegistryObject spawnEgg; /** Constructs a new mob species. For vanilla replacements, the variant name is null. */ private Species( MobFamily parentFamily, String packageRoot, @Nullable String variantName ) { final boolean vanillaReplacement = variantName == null; family = parentFamily; specialVariantName = variantName; name = vanillaReplacement ? parentFamily.name : variantName + parentFamily.name; // Below require unlocalized name to be defined entityClass = findClass( vanillaReplacement ? References.VANILLA_REPLACEMENT_FORMAT : References.SPECIAL_VARIANT_FORMAT, packageRoot ); // Below require variant class to be defined final EntityType.Builder entityTypeBuilder = makeEntityTypeBuilder( parentFamily.replaceableTypes[0] ); bestiaryInfo = AnnotationHelper.getBestiaryInfo( this, entityTypeBuilder ); // Initialize deferred registry objects entityType = SMEntities.register( name.toLowerCase( Locale.ROOT ), entityTypeBuilder ); spawnEgg = SMItems.registerSpawnEgg( entityType, parentFamily.eggBaseColor, bestiaryInfo.eggSpotsColor ); AnnotationHelper.injectEntityTypeHolder( this ); } /** Finds the entity class based on a standard format. */ private Class findClass( String format, String packageRoot ) { try { //noinspection unchecked return (Class) Class.forName( String.format( format, packageRoot, name ) ); } catch( ClassNotFoundException ex ) { throw new RuntimeException( "Failed to find entity class for mob species " + name, ex ); } } /** * Builds a deep copy of an entity type with a different entity constructor. * Leaves the new entity type "un-built" so it can be further modified, if needed. */ private EntityType.Builder makeEntityTypeBuilder( EntityType original ) { final EntityType.IFactory factory = AnnotationHelper.getEntityFactory( this ); final EntityType.Builder clone = EntityType.Builder.of( factory, original.getCategory() ); if( !original.canSummon() ) clone.noSummon(); if( !original.canSerialize() ) clone.noSave(); if( original.fireImmune() ) clone.fireImmune(); if( original.canSpawnFarFromPlayer() ) clone.canSpawnFarFromPlayer(); return clone.sized( original.getWidth(), original.getHeight() ).immuneTo( original.immuneTo.toArray( new Block[0] ) ) // Note: the below are (or also have) suppliers and they cannot be built with the vanilla builder // - this is okay for us because we only replace vanilla mobs .clientTrackingRange( original.clientTrackingRange() ).updateInterval( original.updateInterval() ) .setShouldReceiveVelocityUpdates( original.trackDeltas() ); } } }