mirror of
https://github.com/FatherToast/SpecialMobs.git
synced 2025-09-16 13:41:13 +00:00
le progress
This commit is contained in:
parent
81f12240e8
commit
3fac9d5013
22 changed files with 2692 additions and 35 deletions
|
@ -0,0 +1,37 @@
|
||||||
|
package fathertoast.specialmobs.client;
|
||||||
|
|
||||||
|
import fathertoast.specialmobs.client.renderer.entity.SpecialCreeperRenderer;
|
||||||
|
import fathertoast.specialmobs.common.bestiary.MobFamily;
|
||||||
|
import fathertoast.specialmobs.common.core.SpecialMobs;
|
||||||
|
import mcp.MethodsReturnNonnullByDefault;
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.client.registry.IRenderFactory;
|
||||||
|
import net.minecraftforge.fml.client.registry.RenderingRegistry;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
|
|
||||||
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
@MethodsReturnNonnullByDefault
|
||||||
|
@Mod.EventBusSubscriber( value = Dist.CLIENT, modid = SpecialMobs.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD )
|
||||||
|
public class ClientRegister {
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onClientSetup( FMLClientSetupEvent event ) {
|
||||||
|
registerEntityRenderers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void registerEntityRenderers() {
|
||||||
|
// Family-based renderers
|
||||||
|
registerFamilyRenderers( MobFamily.CREEPER, SpecialCreeperRenderer::new );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends LivingEntity> void registerFamilyRenderers( MobFamily<T> family, IRenderFactory<? super T> renderFactory ) {
|
||||||
|
RenderingRegistry.registerEntityRenderingHandler( family.vanillaReplacement.entityType.get(), renderFactory );
|
||||||
|
for( MobFamily.Species<? extends T> species : family.variants )
|
||||||
|
RenderingRegistry.registerEntityRenderingHandler( species.entityType.get(), renderFactory );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package fathertoast.specialmobs.client.renderer.entity;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||||
|
import fathertoast.specialmobs.common.entity.ISpecialMob;
|
||||||
|
import mcp.MethodsReturnNonnullByDefault;
|
||||||
|
import net.minecraft.client.renderer.entity.CreeperRenderer;
|
||||||
|
import net.minecraft.client.renderer.entity.EntityRendererManager;
|
||||||
|
import net.minecraft.client.renderer.entity.model.CreeperModel;
|
||||||
|
import net.minecraft.entity.monster.CreeperEntity;
|
||||||
|
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 SpecialCreeperRenderer extends CreeperRenderer {
|
||||||
|
|
||||||
|
private final float baseShadowRadius;
|
||||||
|
|
||||||
|
public SpecialCreeperRenderer( EntityRendererManager rendererManager ) {
|
||||||
|
super( rendererManager );
|
||||||
|
baseShadowRadius = shadowRadius;
|
||||||
|
// addLayer( new LayerSpecialMobEyes<>( this ) ); TODO render layer impl
|
||||||
|
// addLayer( new LayerSpecialMobOverlay<>( this, new CreeperModel<>( 0.25F ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResourceLocation getTextureLocation( CreeperEntity entity ) {
|
||||||
|
return super.getTextureLocation( entity );
|
||||||
|
//return ((ISpecialMob<?>) entity).getSpecialData().getTexture();TODO textures
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void scale( CreeperEntity 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 );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
package fathertoast.specialmobs.common.bestiary;
|
||||||
|
|
||||||
|
//import net.minecraft.init.Biomes;
|
||||||
|
|
||||||
|
import net.minecraft.world.DimensionType;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
public class BestiaryInfo {
|
||||||
|
|
||||||
|
public enum BaseWeight {
|
||||||
|
DEFAULT( 600 ),
|
||||||
|
HIGHEST( DEFAULT.value * 8 ),
|
||||||
|
HIGH( DEFAULT.value * 4 ),
|
||||||
|
LOW( DEFAULT.value / 4 ),
|
||||||
|
LOWEST( DEFAULT.value / 8 );
|
||||||
|
|
||||||
|
public final int value;
|
||||||
|
|
||||||
|
BaseWeight( int v ) { value = v; }
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO default themes
|
||||||
|
// public static final EnvironmentListConfig DEFAULT_THEME_FIRE = new EnvironmentListConfig(
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "desert", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "savanna", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "mesa", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetDimension( DimensionType.NETHER, BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
//
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "ice", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "frozen", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "cold", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "taiga_cold", BestiaryInfo.BASE_WEIGHT_RARE )
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// public static final EnvironmentListConfig DEFAULT_THEME_ICE = new EnvironmentListConfig(
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "ice", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "frozen", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "cold", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "taiga_cold", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
//
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "desert", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "savanna", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "mesa", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetDimension( DimensionType.NETHER, BestiaryInfo.BASE_WEIGHT_RARE )
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// public static final EnvironmentListConfig DEFAULT_THEME_FOREST = new EnvironmentListConfig(
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "swamp", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "forest", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "birch_forest", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "roofed_forest", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "jungle", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "taiga", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "redwood_taiga", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
//
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "desert", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "ice", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "stone", BestiaryInfo.BASE_WEIGHT_RARE )
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// public static final EnvironmentListConfig DEFAULT_THEME_MOUNTAIN = new EnvironmentListConfig(
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "extreme", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "smaller_extreme", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "mesa", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "ice_mountains", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "stone", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
//
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "plains", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "desert", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "swamp", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "beach", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "cold_beach", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "savanna", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "ice_flats", BestiaryInfo.BASE_WEIGHT_RARE )
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// public static final EnvironmentListConfig DEFAULT_THEME_WATER = new EnvironmentListConfig(
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "swamp", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiome( Biomes.BEACH, BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiome( Biomes.OCEAN, BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiome( Biomes.DEEP_OCEAN, BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiome( Biomes.FROZEN_OCEAN, BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
//
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "desert", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "savanna", BestiaryInfo.BASE_WEIGHT_RARE ),
|
||||||
|
// new TargetEnvironment.TargetDimension( DimensionType.NETHER, 0 )
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// public static final EnvironmentListConfig DEFAULT_THEME_FISHING = new EnvironmentListConfig(
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "swamp", BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiome( Biomes.BEACH, BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiome( Biomes.OCEAN, BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiome( Biomes.DEEP_OCEAN, BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiome( Biomes.FROZEN_OCEAN, BestiaryInfo.BASE_WEIGHT_COMMON ),
|
||||||
|
//
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "desert", BestiaryInfo.BASE_WEIGHT_UNCOMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "savanna", BestiaryInfo.BASE_WEIGHT_UNCOMMON ),
|
||||||
|
// new TargetEnvironment.TargetBiomeGroup( "mesa", BestiaryInfo.BASE_WEIGHT_UNCOMMON )
|
||||||
|
// );
|
||||||
|
|
||||||
|
// These can be set in the entity class to change these default values.
|
||||||
|
//public EnvironmentListConfig weightExceptions = new EnvironmentListConfig( );
|
||||||
|
|
||||||
|
public final int eggSpotsColor;
|
||||||
|
public final BaseWeight weight;
|
||||||
|
|
||||||
|
public BestiaryInfo( int eggColor ) { this( eggColor, BaseWeight.DEFAULT ); }
|
||||||
|
|
||||||
|
public BestiaryInfo( int eggColor, BaseWeight baseWeight ) {
|
||||||
|
eggSpotsColor = eggColor;
|
||||||
|
weight = baseWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BestiaryInfo setTheme() {
|
||||||
|
return this;//TODO theme builder
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,308 @@
|
||||||
|
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.*;
|
||||||
|
import net.minecraft.entity.monster.CreeperEntity;
|
||||||
|
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.lang.reflect.InvocationTargetException;
|
||||||
|
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<T extends LivingEntity> {
|
||||||
|
/** List of all families, generated to make iteration possible. */
|
||||||
|
private static final List<MobFamily<?>> FAMILY_LIST = new ArrayList<>();
|
||||||
|
/** List of all species, generated to make iteration more convenient. */
|
||||||
|
private static final List<Species<?>> SPECIES_LIST;
|
||||||
|
|
||||||
|
/** Maps each replaceable entity type to the family that replaces it. */
|
||||||
|
private static final Map<EntityType<?>, 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<CreeperEntity> CREEPER = new MobFamily<>(
|
||||||
|
"Creeper", "creepers", 0x0da70b, new EntityType[] { EntityType.CREEPER },
|
||||||
|
"Dark"//, "Death", "Dirt", "Doom", "Drowning", "Ender", "Fire", "Gravel", "Jumping", "Lightning", "Mini", "Splitting"
|
||||||
|
);
|
||||||
|
|
||||||
|
// public static final MobFamily<ZombieEntity> ZOMBIE = new MobFamily<>(
|
||||||
|
// "Zombie", "zombies", 0x00afaf, new EntityType[] { EntityType.ZOMBIE, EntityType.HUSK },
|
||||||
|
// "Brute", "Fire", "Fishing", "Giant", "Hungry", "Husk", "Plague"
|
||||||
|
// );
|
||||||
|
// public static final MobFamily<ZombieEntity> ZOMBIFIED_PIGLIN = new MobFamily<>(
|
||||||
|
// "ZombifiedPiglin", "zombie pigmen", 0xea9393, new EntityType[] { EntityType.ZOMBIFIED_PIGLIN },
|
||||||
|
// "Brute", "Fishing", "Giant", "Hungry", "Knight", "Plague", "Vampire"
|
||||||
|
// );
|
||||||
|
|
||||||
|
// public static final MobFamily<AbstractSkeletonEntity> 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<AbstractSkeletonEntity> WITHER_SKELETON = new MobFamily<>(
|
||||||
|
// "WitherSkeleton", "wither skeletons", 0x141414, new EntityType[] { EntityType.WITHER_SKELETON },
|
||||||
|
// "Brute", "Gatling", "Giant", "Knight", "Ninja", "Sniper", "Spitfire"
|
||||||
|
// );
|
||||||
|
|
||||||
|
// public static final MobFamily<SlimeEntity> SLIME = new MobFamily<>(
|
||||||
|
// "Slime", "slimes", 0x51a03e, new EntityType[] { EntityType.SLIME },
|
||||||
|
// "Blackberry", "Blueberry", "Caramel", "Grape", "Lemon", "Strawberry", "Watermelon"
|
||||||
|
// );
|
||||||
|
// public static final MobFamily<MagmaCubeEntity> MAGMA_CUBE = new MobFamily<>(
|
||||||
|
// "MagmaCube", "magma cubes", 0x340000, new EntityType[] { EntityType.MAGMA_CUBE },
|
||||||
|
// "Flying", "Hardened", "Sticky", "Volatile"
|
||||||
|
// );
|
||||||
|
|
||||||
|
// public static final MobFamily<SpiderEntity> SPIDER = new MobFamily<>(
|
||||||
|
// "Spider", "spiders", 0x342d27, new EntityType[] { EntityType.SPIDER },
|
||||||
|
// "Baby", "Desert", "Flying", "Giant", "Hungry", "Mother", "Pale", "Poison", "Web", "Witch"
|
||||||
|
// );
|
||||||
|
// public static final MobFamily<CaveSpiderEntity> CAVE_SPIDER = new MobFamily<>(
|
||||||
|
// "CaveSpider", "cave spiders", 0x0c424e, new EntityType[] { EntityType.CAVE_SPIDER },
|
||||||
|
// "Baby", "Flying", "Mother", "Web", "Witch"
|
||||||
|
// );
|
||||||
|
|
||||||
|
// public static final MobFamily<SilverfishEntity> SILVERFISH = new MobFamily<>(
|
||||||
|
// "Silverfish", "silverfish", 0x6e6e6e, new EntityType[] { EntityType.SILVERFISH },
|
||||||
|
// "Blinding", "Fishing", "Flying", "Poison", "Tough"
|
||||||
|
// );
|
||||||
|
|
||||||
|
// public static final MobFamily<EndermanEntity> ENDERMAN = new MobFamily<>(
|
||||||
|
// "Enderman", "endermen", 0x161616, new EntityType[] { EntityType.ENDERMAN },
|
||||||
|
// "Blinding", "Icy", "Lightning", "Mini", "Mirage", "Thief"
|
||||||
|
// );
|
||||||
|
|
||||||
|
// public static final MobFamily<WitchEntity> WITCH = new MobFamily<>(
|
||||||
|
// "Witch", "witches", 0x340000, new EntityType[] { EntityType.WITCH },
|
||||||
|
// "Domination", "Shadows", "Undead", "Wilds", "Wind"
|
||||||
|
// );
|
||||||
|
|
||||||
|
// public static final MobFamily<GhastEntity> GHAST = new MobFamily<>(
|
||||||
|
// "Ghast", "ghasts", 0xf9f9f9, new EntityType[] { EntityType.GHAST },
|
||||||
|
// "Baby", "Fighter", "King", "Queen", "Unholy"
|
||||||
|
// );
|
||||||
|
|
||||||
|
// public static final MobFamily<BlazeEntity> BLAZE = new MobFamily<>(
|
||||||
|
// "Blaze", "blazes", 0xf6b201, new EntityType[] { EntityType.BLAZE },
|
||||||
|
// "Cinder", "Conflagration", "Ember", "Hellfire", "Inferno", "Jolt", "Wildfire"
|
||||||
|
// );
|
||||||
|
|
||||||
|
static {
|
||||||
|
final HashMap<EntityType<?>, MobFamily<?>> classToFamilyMap = new HashMap<>();
|
||||||
|
final ArrayList<Species<?>> 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<MobFamily<?>> getAll() { return Collections.unmodifiableList( FAMILY_LIST ); }
|
||||||
|
|
||||||
|
/** @return A list of all species. */
|
||||||
|
public static List<Species<?>> 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<? extends T> vanillaReplacement;
|
||||||
|
/** Array of all special variant species in this family. In practice, these are the true "special mobs". */
|
||||||
|
public final Species<? extends T>[] 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<? extends T> 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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* Though typically special variant entity classes will extend the vanilla replacement, this cannot always be assumed.
|
||||||
|
*
|
||||||
|
* @see MobFamily
|
||||||
|
*/
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
@MethodsReturnNonnullByDefault
|
||||||
|
public static class Species<T extends LivingEntity> {
|
||||||
|
/** The special mob family this species belongs to. */
|
||||||
|
public final MobFamily<? super T> 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<T> 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<T>> entityType;
|
||||||
|
/** This species's spawn egg item, wrapped in its registry object. */
|
||||||
|
public final RegistryObject<ForgeSpawnEggItem> spawnEgg;
|
||||||
|
|
||||||
|
/** Constructs a new mob species. For vanilla replacements, the variant name is null. */
|
||||||
|
private Species( MobFamily<? super T> 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<T> entityTypeBuilder = makeEntityTypeBuilder( parentFamily.replaceableTypes[0] );
|
||||||
|
bestiaryInfo = makeBestiaryInfo( entityTypeBuilder );
|
||||||
|
|
||||||
|
// Initialize deferred registry objects
|
||||||
|
entityType = SMEntities.register( name.toLowerCase( Locale.ROOT ), entityTypeBuilder );
|
||||||
|
spawnEgg = SMItems.registerSpawnEgg( entityType, parentFamily.eggBaseColor, bestiaryInfo.eggSpotsColor );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Finds the entity class based on a standard format. */
|
||||||
|
private Class<T> findClass( String format, String packageRoot ) {
|
||||||
|
try {
|
||||||
|
//noinspection unchecked
|
||||||
|
return (Class<T>) Class.forName( String.format( format, packageRoot, name ) );
|
||||||
|
}
|
||||||
|
catch( ClassNotFoundException ex ) {
|
||||||
|
throw new RuntimeException( "Failed to find entity class for mob species " + name, ex );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calls on this species' entity class to generate its bestiary info. */
|
||||||
|
private BestiaryInfo makeBestiaryInfo( EntityType.Builder<T> entityTypeBuilder ) {
|
||||||
|
try {
|
||||||
|
return AnnotationHelper.getBestiaryInfo( this, entityTypeBuilder );
|
||||||
|
}
|
||||||
|
catch( IllegalAccessException | NoSuchMethodException | InvocationTargetException ex ) {
|
||||||
|
throw new RuntimeException( "Entity class for " + name + " has invalid bestiary info method", 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<T> makeEntityTypeBuilder( EntityType<?> original ) {
|
||||||
|
final EntityType.IFactory<T> factory;
|
||||||
|
try {
|
||||||
|
factory = AnnotationHelper.getEntityFactory( this );
|
||||||
|
}
|
||||||
|
catch( NoSuchMethodException ex ) {
|
||||||
|
throw new RuntimeException( "Entity class for " + name + " has no valid constructors", ex );
|
||||||
|
}
|
||||||
|
final EntityType.Builder<T> 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() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package fathertoast.specialmobs.common.bestiary;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to note classes that define special mobs entities. Think of this as a kind of static interface that defines
|
||||||
|
* a number of static methods that must be implemented in each special mob class.
|
||||||
|
* <p>
|
||||||
|
* Currently no runtime processing is done on special mob classes, however this could be modified in the future
|
||||||
|
* to fully automate variant registration, if desired.
|
||||||
|
*/
|
||||||
|
@Target( ElementType.TYPE )
|
||||||
|
public @interface SpecialMob {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REQUIRED. This is grabbed during registration to be used as a mob 'factory'; the needed constructor will probably
|
||||||
|
* already be needed by the entity's superclass, so it's just a matter of applying the annotation in that case.
|
||||||
|
* <p>
|
||||||
|
* The annotated constructor must have a signature that follows the pattern:
|
||||||
|
* <p>
|
||||||
|
* public CLASS_NAME( EntityType entityType, World world )
|
||||||
|
*/
|
||||||
|
@Retention( RetentionPolicy.RUNTIME )
|
||||||
|
@Target( ElementType.CONSTRUCTOR )
|
||||||
|
@interface Constructor { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REQUIRED. This is called during registration to collect static properties of the mob needed for the bestiary
|
||||||
|
* and for building the species's entity type.
|
||||||
|
* <p>
|
||||||
|
* The annotated method must have a signature that follows the pattern:
|
||||||
|
* <p>
|
||||||
|
* public static BestiaryInfo METHOD_NAME( EntityType.Builder<LivingEntity> entityType )
|
||||||
|
* <p>
|
||||||
|
* The returned bestiary info will be used to describe the mob species.
|
||||||
|
* The builder passed in is a copy of the vanilla 'base' entity type. Common uses of the entity type builder are
|
||||||
|
* modifying entity size and fire/hazard immunities.
|
||||||
|
*/
|
||||||
|
@Retention( RetentionPolicy.RUNTIME )
|
||||||
|
@Target( ElementType.METHOD )
|
||||||
|
@interface BestiaryInfoSupplier { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REQUIRED. This is called during registration to build the base attributes for the species.
|
||||||
|
* <p>
|
||||||
|
* The annotated method must have a signature that follows the pattern:
|
||||||
|
* <p>
|
||||||
|
* public static AttributeModifierMap.MutableAttribute METHOD_NAME()
|
||||||
|
* <p>
|
||||||
|
* The returned attribute modifier map builder will be 'built' immediately after the call and registered to be
|
||||||
|
* applied to all entity class instances of the mob species.
|
||||||
|
*/
|
||||||
|
@Retention( RetentionPolicy.RUNTIME )
|
||||||
|
@Target( ElementType.METHOD )
|
||||||
|
@interface AttributeCreator { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REQUIRED. This is called during data generation to build the mob's default loot table. Special variants will
|
||||||
|
* typically start this method by calling their vanilla replacement's implementation of this method.
|
||||||
|
* <p>
|
||||||
|
* The annotated method must have a signature that follows the pattern:
|
||||||
|
* <p>
|
||||||
|
* public static void METHOD_NAME( LootTableBuilder loot )
|
||||||
|
* <p>
|
||||||
|
* The builder passed in is a new empty instance and will be 'built' immediately after the call.
|
||||||
|
*/
|
||||||
|
@Retention( RetentionPolicy.RUNTIME )
|
||||||
|
@Target( ElementType.METHOD )
|
||||||
|
@interface LootTableProvider { }
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
package fathertoast.specialmobs.common.core;
|
package fathertoast.specialmobs.common.core;
|
||||||
|
|
||||||
|
import fathertoast.specialmobs.common.bestiary.MobFamily;
|
||||||
import fathertoast.specialmobs.common.config.Config;
|
import fathertoast.specialmobs.common.config.Config;
|
||||||
|
import fathertoast.specialmobs.common.core.register.SMEntities;
|
||||||
|
import fathertoast.specialmobs.common.core.register.SMItems;
|
||||||
import net.minecraft.util.ResourceLocation;
|
import net.minecraft.util.ResourceLocation;
|
||||||
import net.minecraftforge.common.MinecraftForge;
|
|
||||||
import net.minecraftforge.eventbus.api.IEventBus;
|
import net.minecraftforge.eventbus.api.IEventBus;
|
||||||
import net.minecraftforge.fml.ModList;
|
|
||||||
import net.minecraftforge.fml.common.Mod;
|
import net.minecraftforge.fml.common.Mod;
|
||||||
import net.minecraftforge.fml.event.lifecycle.InterModProcessEvent;
|
|
||||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||||
import net.minecraftforge.registries.ForgeRegistryEntry;
|
import net.minecraftforge.registries.ForgeRegistryEntry;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
@ -17,14 +17,77 @@ import javax.annotation.Nullable;
|
||||||
@Mod( SpecialMobs.MOD_ID )
|
@Mod( SpecialMobs.MOD_ID )
|
||||||
public class SpecialMobs {
|
public class SpecialMobs {
|
||||||
|
|
||||||
/* Feature List:
|
/* TODO List:
|
||||||
* TODO
|
* Reimplement all old features (see list below)
|
||||||
|
* Utility features:
|
||||||
|
* - Bestiary
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Feature List: //TODO; list may not be complete
|
||||||
|
* (KEY: - = complete in current version, o = incomplete feature from previous version,
|
||||||
|
* + = incomplete new feature, ? = feature to consider adding)
|
||||||
|
* o general
|
||||||
|
* o entity replacer
|
||||||
|
* o dimension-sensitive configs
|
||||||
|
* o environment-sensitive configs
|
||||||
|
* o entities
|
||||||
|
* o nbt-driven capabilities (special mob data)
|
||||||
|
* + bestiary
|
||||||
|
* ? configurable stats
|
||||||
|
* o monster families (see doc for specifics)
|
||||||
|
* o creepers
|
||||||
|
* o chance to spawn charged
|
||||||
|
* + scope
|
||||||
|
* o zombies
|
||||||
|
* o villager infection
|
||||||
|
* o ranged attack AI (using bow)
|
||||||
|
* o use shields
|
||||||
|
* + drowned
|
||||||
|
* o zombified piglins
|
||||||
|
* o ranged attack AI (using bow)
|
||||||
|
* o use shields
|
||||||
|
* o skeletons
|
||||||
|
* o use shields
|
||||||
|
* o babies
|
||||||
|
* o wither skeletons
|
||||||
|
* o use shields
|
||||||
|
* o babies
|
||||||
|
* o slimes
|
||||||
|
* o use attack damage attribute
|
||||||
|
* o magma cubes
|
||||||
|
* o use attack damage attribute
|
||||||
|
* o spiders
|
||||||
|
* o ranged attack AI
|
||||||
|
* o cave spiders
|
||||||
|
* o ranged attack AI
|
||||||
|
* o silverfish
|
||||||
|
* ? ranged attack AI
|
||||||
|
* o endermen
|
||||||
|
* o witches
|
||||||
|
* o ability to equip held items
|
||||||
|
* o ghasts
|
||||||
|
* o melee attack AI
|
||||||
|
* o blazes
|
||||||
|
* o melee attack AI
|
||||||
|
* ? piglins
|
||||||
|
* ? hoglins
|
||||||
|
* ? zoglins
|
||||||
|
* ? endermites
|
||||||
|
* ? guardians
|
||||||
|
* ? shulkers
|
||||||
|
* ? phantoms
|
||||||
|
* + the goat
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** Our mod ID. */
|
/** Our mod ID. */
|
||||||
@SuppressWarnings( "SpellCheckingInspection" )
|
@SuppressWarnings( "SpellCheckingInspection" )
|
||||||
public static final String MOD_ID = "specialmobs";
|
public static final String MOD_ID = "specialmobs";
|
||||||
|
|
||||||
|
/** The path to the textures folder. */
|
||||||
|
public static final String TEXTURE_PATH = MOD_ID + ":textures/entity/";
|
||||||
|
/** The path to the loot tables folder. */
|
||||||
|
public static final String LOOT_TABLE_PATH = MOD_ID + ":entities/";
|
||||||
|
|
||||||
/** Logger instance for the mod. */
|
/** Logger instance for the mod. */
|
||||||
public static final Logger LOG = LogManager.getLogger( MOD_ID );
|
public static final Logger LOG = LogManager.getLogger( MOD_ID );
|
||||||
|
|
||||||
|
@ -32,46 +95,23 @@ public class SpecialMobs {
|
||||||
//@SuppressWarnings( "FieldCanBeLocal" )
|
//@SuppressWarnings( "FieldCanBeLocal" )
|
||||||
//private final PacketHandler packetHandler = new PacketHandler();
|
//private final PacketHandler packetHandler = new PacketHandler();
|
||||||
|
|
||||||
//** Mod API instance **/
|
|
||||||
//private final INaturalAbsorption modApi = new NaturalAbsorptionAPI();
|
|
||||||
|
|
||||||
|
|
||||||
public SpecialMobs() {
|
public SpecialMobs() {
|
||||||
Config.initialize();
|
Config.initialize();
|
||||||
|
|
||||||
//packetHandler.registerMessages();
|
//packetHandler.registerMessages();
|
||||||
//CraftingUtil.registerConditions();
|
|
||||||
|
|
||||||
//MinecraftForge.EVENT_BUS.register( new NAEventListener() );
|
//MinecraftForge.EVENT_BUS.register( new NAEventListener() );
|
||||||
//MinecraftForge.EVENT_BUS.register( new HeartManager() );
|
|
||||||
|
|
||||||
IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus();
|
IEventBus eventBus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||||
|
|
||||||
//modBus.addListener( this::onInterModProcess );
|
eventBus.addListener( SMEntities::createAttributes );
|
||||||
//modBus.addListener( HeartManager::onEntityAttributeCreation );
|
|
||||||
|
|
||||||
//NAItems.ITEMS.register( modBus );
|
SMEntities.REGISTRY.register( eventBus );
|
||||||
//NAAttributes.ATTRIBUTES.register( modBus );
|
SMItems.REGISTRY.register( eventBus );
|
||||||
//NAEnchantments.ENCHANTMENTS.register( modBus );
|
|
||||||
//NALootModifiers.LOOT_MODIFIER_SERIALIZERS.register( modBus );
|
|
||||||
|
|
||||||
//if( ModList.get().isLoaded( "tconstruct" ) ) {
|
MobFamily.initBestiary();
|
||||||
// NaturalAbsorptionTC.init( modBus );
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Hands the mod API to mods that ask for it.
|
|
||||||
// */
|
|
||||||
// private void onInterModProcess( InterModProcessEvent event ) {
|
|
||||||
// event.getIMCStream().forEach( ( message ) -> {
|
|
||||||
// if( message.getMethod().equals( "getNaturalAbsorptionAPI" ) ) {
|
|
||||||
// Supplier<Function<INaturalAbsorption, Void>> supplier = message.getMessageSupplier();
|
|
||||||
// supplier.get().apply( modApi );
|
|
||||||
// }
|
|
||||||
// } );
|
|
||||||
// }
|
|
||||||
|
|
||||||
/** @return A ResourceLocation with the mod's namespace. */
|
/** @return A ResourceLocation with the mod's namespace. */
|
||||||
public static ResourceLocation resourceLoc( String path ) { return new ResourceLocation( MOD_ID, path ); }
|
public static ResourceLocation resourceLoc( String path ) { return new ResourceLocation( MOD_ID, path ); }
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package fathertoast.specialmobs.common.core.register;
|
||||||
|
|
||||||
|
import fathertoast.specialmobs.common.bestiary.MobFamily;
|
||||||
|
import fathertoast.specialmobs.common.core.SpecialMobs;
|
||||||
|
import fathertoast.specialmobs.common.util.AnnotationHelper;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.entity.EntityType;
|
||||||
|
import net.minecraftforge.event.entity.EntityAttributeCreationEvent;
|
||||||
|
import net.minecraftforge.fml.RegistryObject;
|
||||||
|
import net.minecraftforge.registries.DeferredRegister;
|
||||||
|
import net.minecraftforge.registries.ForgeRegistries;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
public class SMEntities {
|
||||||
|
|
||||||
|
public static final DeferredRegister<EntityType<?>> REGISTRY = DeferredRegister.create( ForgeRegistries.ENTITIES, SpecialMobs.MOD_ID );
|
||||||
|
|
||||||
|
/** Registers an entity type to the deferred register. */
|
||||||
|
public static <T extends Entity> RegistryObject<EntityType<T>> register( String name, EntityType.Builder<T> builder ) {
|
||||||
|
return REGISTRY.register( name, () -> builder.build( name ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the default attributes for entity types, such as max health, attack damage etc. */
|
||||||
|
public static void createAttributes( EntityAttributeCreationEvent event ) {
|
||||||
|
// Bestiary-generated entities
|
||||||
|
for( MobFamily.Species<?> variant : MobFamily.getAllSpecies() )
|
||||||
|
createSpeciesAttributes( event, variant );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Builds the attributes for a specific entity species. */
|
||||||
|
private static void createSpeciesAttributes( EntityAttributeCreationEvent event, MobFamily.Species<?> species ) {
|
||||||
|
try {
|
||||||
|
event.put( species.entityType.get(), AnnotationHelper.createAttributes( species ) );
|
||||||
|
}
|
||||||
|
catch( NoSuchMethodException | InvocationTargetException | IllegalAccessException ex ) {
|
||||||
|
throw new RuntimeException( "Entity class for " + species.name + " has invalid attribute creation method", ex );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the natural spawn placement rules for entity types. */
|
||||||
|
public static void registerSpawnPlacements() {
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package fathertoast.specialmobs.common.core.register;
|
||||||
|
|
||||||
|
import fathertoast.specialmobs.common.core.SpecialMobs;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.entity.EntityType;
|
||||||
|
import net.minecraft.item.Item;
|
||||||
|
import net.minecraft.item.ItemGroup;
|
||||||
|
import net.minecraftforge.common.ForgeSpawnEggItem;
|
||||||
|
import net.minecraftforge.fml.RegistryObject;
|
||||||
|
import net.minecraftforge.registries.DeferredRegister;
|
||||||
|
import net.minecraftforge.registries.ForgeRegistries;
|
||||||
|
|
||||||
|
public class SMItems {
|
||||||
|
|
||||||
|
public static final DeferredRegister<Item> REGISTRY = DeferredRegister.create( ForgeRegistries.ITEMS, SpecialMobs.MOD_ID );
|
||||||
|
|
||||||
|
/** Registers an entity type's spawn egg item to the deferred register. */
|
||||||
|
public static <T extends Entity> RegistryObject<ForgeSpawnEggItem> registerSpawnEgg(
|
||||||
|
RegistryObject<EntityType<T>> entityType, int eggBaseColor, int eggSpotsColor ) {
|
||||||
|
final String name = entityType.getId().getPath() + "_spawn_egg";
|
||||||
|
return REGISTRY.register( name, () ->
|
||||||
|
new ForgeSpawnEggItem( entityType, eggBaseColor, eggSpotsColor, new Item.Properties().tab( ItemGroup.TAB_MISC ) )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package fathertoast.specialmobs.common.entity;
|
||||||
|
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
|
import net.minecraft.pathfinding.PathNodeType;
|
||||||
|
|
||||||
|
public interface ISpecialMob<T extends LivingEntity & ISpecialMob<T>> {
|
||||||
|
/** @return This mob's special data. */
|
||||||
|
SpecialMobData<T> getSpecialData();
|
||||||
|
|
||||||
|
/** @return The experience that should be dropped by the entity. */
|
||||||
|
int getExperience();
|
||||||
|
|
||||||
|
/** Sets the experience that should be dropped by the entity. */
|
||||||
|
void setExperience( int xp );
|
||||||
|
|
||||||
|
/** Sets the entity's pathfinding malus for a particular node type; negative value is un-walkable. */
|
||||||
|
void setPathfindingMalus( PathNodeType nodeType, float malus );
|
||||||
|
}
|
|
@ -0,0 +1,591 @@
|
||||||
|
package fathertoast.specialmobs.common.entity;
|
||||||
|
|
||||||
|
import fathertoast.specialmobs.common.core.SpecialMobs;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.Blocks;
|
||||||
|
import net.minecraft.enchantment.EnchantmentHelper;
|
||||||
|
import net.minecraft.enchantment.Enchantments;
|
||||||
|
import net.minecraft.entity.CreatureAttribute;
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
|
import net.minecraft.entity.ai.attributes.Attribute;
|
||||||
|
import net.minecraft.entity.ai.attributes.ModifiableAttributeInstance;
|
||||||
|
import net.minecraft.entity.monster.SpiderEntity;
|
||||||
|
import net.minecraft.nbt.CompoundNBT;
|
||||||
|
import net.minecraft.nbt.ListNBT;
|
||||||
|
import net.minecraft.nbt.StringNBT;
|
||||||
|
import net.minecraft.network.datasync.DataParameter;
|
||||||
|
import net.minecraft.pathfinding.PathNodeType;
|
||||||
|
import net.minecraft.potion.Effect;
|
||||||
|
import net.minecraft.potion.EffectInstance;
|
||||||
|
import net.minecraft.potion.Effects;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
import net.minecraftforge.common.MinecraftForge;
|
||||||
|
import net.minecraftforge.event.entity.living.PotionEvent;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import static fathertoast.specialmobs.common.util.References.*;
|
||||||
|
|
||||||
|
public class SpecialMobData<T extends LivingEntity & ISpecialMob<T>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param tag The mob's base nbt tag.
|
||||||
|
* @return The nbt tag to save special mob data to.
|
||||||
|
*/
|
||||||
|
public static CompoundNBT getSaveLocation( CompoundNBT tag ) {
|
||||||
|
if( !tag.contains( TAG_FORGE_DATA, NBT_TYPE_COMPOUND ) ) {
|
||||||
|
tag.put( TAG_FORGE_DATA, new CompoundNBT() );
|
||||||
|
}
|
||||||
|
final CompoundNBT forgeTag = tag.getCompound( TAG_FORGE_DATA );
|
||||||
|
|
||||||
|
if( !forgeTag.contains( TAG_SPECIAL_MOB_DATA, NBT_TYPE_COMPOUND ) ) {
|
||||||
|
forgeTag.put( TAG_SPECIAL_MOB_DATA, new CompoundNBT() );
|
||||||
|
}
|
||||||
|
return forgeTag.getCompound( TAG_SPECIAL_MOB_DATA );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** The entity this data is for. */
|
||||||
|
private final T theEntity;
|
||||||
|
/** Data manager parameter for render scale. */
|
||||||
|
private final DataParameter<Float> renderScale;
|
||||||
|
|
||||||
|
/** The base collision box scale of this variant's family. */
|
||||||
|
private final float familyScale;
|
||||||
|
/** The base collision box scale of this variant. */
|
||||||
|
private float baseScale;
|
||||||
|
|
||||||
|
/** The base texture of the entity. */
|
||||||
|
private ResourceLocation texture;
|
||||||
|
/** The glowing eyes texture of the entity. */
|
||||||
|
private ResourceLocation textureEyes;
|
||||||
|
/** The overlay texture of the entity. */
|
||||||
|
private ResourceLocation textureOverlay;
|
||||||
|
/** True if the textures need to be sent to the client. */
|
||||||
|
private boolean updateTextures;
|
||||||
|
|
||||||
|
/** The damage the entity uses for its ranged attacks, when applicable. */
|
||||||
|
public float rangedAttackDamage;
|
||||||
|
/** The spread (inaccuracy) of the entity's ranged attacks. */
|
||||||
|
public float rangedAttackSpread;
|
||||||
|
/** The movement speed multiplier the entity uses during its ranged attack ai. Requires an ai reload to take effect. */
|
||||||
|
public float rangedWalkSpeed = 1.0F;
|
||||||
|
/** The delay (in ticks) before a new ranged attack can begin after firing. Requires an ai reload to take effect. */
|
||||||
|
public int rangedAttackCooldown;
|
||||||
|
/**
|
||||||
|
* The delay (in ticks) between each ranged attack at maximum delay. Requires an ai reload to take effect.
|
||||||
|
* Unused for bow attacks. For fireball attacks, this is the cooldown + charge time.
|
||||||
|
* For all other attacks, this is the cooldown at maximum range (scaled down to the minimum cooldown at point-blank).
|
||||||
|
*/
|
||||||
|
public int rangedAttackMaxCooldown;
|
||||||
|
/**
|
||||||
|
* The maximum distance (in blocks) the entity can fire ranged attacks from. Requires an ai reload to take effect.
|
||||||
|
* Ranged ai can only be used if this stat is greater than 0. Does not change aggro range.
|
||||||
|
*/
|
||||||
|
public float rangedAttackMaxRange;
|
||||||
|
|
||||||
|
/** The rate this mob regenerates health (ticks per 1 health). Off if 0 or less. */
|
||||||
|
private int healTimeMax;
|
||||||
|
/** Counter to the next heal, if healTimeMax is greater than 0. */
|
||||||
|
private int healTime;
|
||||||
|
|
||||||
|
/** Proportion of fall damage taken. */
|
||||||
|
private float fallDamageMultiplier = 1.0F;
|
||||||
|
|
||||||
|
/** Whether the entity is immune to fire damage. */
|
||||||
|
private boolean isImmuneToFire;
|
||||||
|
/** Whether the entity is immune to being set on fire. */
|
||||||
|
private boolean isImmuneToBurning;
|
||||||
|
/** Whether the entity can be leashed. */
|
||||||
|
private boolean allowLeashing;
|
||||||
|
/** Whether the entity does not trigger pressure plates. */
|
||||||
|
private boolean ignorePressurePlates;
|
||||||
|
|
||||||
|
/** Whether the entity can breathe under water. */
|
||||||
|
private boolean canBreatheInWater;
|
||||||
|
/** Whether the entity can ignore pushing from flowing water. */
|
||||||
|
private boolean ignoreWaterPush;
|
||||||
|
/** Whether the entity is damaged when wet. */
|
||||||
|
private boolean isDamagedByWater;
|
||||||
|
|
||||||
|
/** List of blocks that the entity cannot be stuck in. */
|
||||||
|
private final HashSet<String> immuneToStickyBlocks = new HashSet<>();
|
||||||
|
/** List of potions that cannot be applied to the entity. */
|
||||||
|
private final HashSet<String> immuneToPotions = new HashSet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a SpecialMobData to store generic data about a mob.
|
||||||
|
*
|
||||||
|
* @param entity The entity to store data for.
|
||||||
|
* @param scale Data parameter for storing the render scale.
|
||||||
|
*/
|
||||||
|
public SpecialMobData( T entity, DataParameter<Float> scale ) {
|
||||||
|
this( entity, scale, 1.0F );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a SpecialMobData to store generic data about a mob.
|
||||||
|
* <p>
|
||||||
|
* This constructor should be called during data watcher definitions, and defining the 'render scale' data watcher
|
||||||
|
* parameter is the only thing actually done while constructing.
|
||||||
|
* <p>
|
||||||
|
* The #initialize() method must be called later on to complete initialization (e.g. in the entity constructor).
|
||||||
|
*
|
||||||
|
* @param entity The entity to store data for.
|
||||||
|
* @param scale Data parameter for storing the render scale.
|
||||||
|
* @param familyBaseScale Base render scale. Typically 1.0F.
|
||||||
|
*/
|
||||||
|
public SpecialMobData( T entity, DataParameter<Float> scale, float familyBaseScale ) {
|
||||||
|
theEntity = entity;
|
||||||
|
renderScale = scale;
|
||||||
|
|
||||||
|
familyScale = baseScale = familyBaseScale;
|
||||||
|
|
||||||
|
//setTextures( entity.getDefaultTextures() ); TODO
|
||||||
|
|
||||||
|
entity.getEntityData().define( renderScale, nextScale() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called to finish initialization, since we can only define data watcher params in the constructor. */
|
||||||
|
public void initialize() {
|
||||||
|
setImmuneToFire( theEntity.getType().fireImmune() );
|
||||||
|
if( theEntity.getMobType() == CreatureAttribute.UNDEAD ) {
|
||||||
|
addPotionImmunity( Effects.REGENERATION, Effects.POISON );
|
||||||
|
}
|
||||||
|
if( theEntity instanceof SpiderEntity ) {
|
||||||
|
addStickyBlockImmunity( Blocks.COBWEB );
|
||||||
|
addPotionImmunity( Effects.POISON );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Copies all of the data from another mob, optionally copying texture(s). */
|
||||||
|
public void copyDataFrom( LivingEntity entity, boolean copyTextures ) {
|
||||||
|
if( entity instanceof ISpecialMob ) {
|
||||||
|
CompoundNBT tag = new CompoundNBT();
|
||||||
|
|
||||||
|
((ISpecialMob<?>) entity).getSpecialData().writeToNBT( tag );
|
||||||
|
if( !copyTextures ) {
|
||||||
|
tag.remove( TAG_TEXTURE );
|
||||||
|
tag.remove( TAG_TEXTURE_EYES );
|
||||||
|
tag.remove( TAG_TEXTURE_OVER );
|
||||||
|
}
|
||||||
|
readFromNBT( tag );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called each tick for every living special mob. */
|
||||||
|
public void tick() {
|
||||||
|
if( !theEntity.level.isClientSide && theEntity.isAlive() ) {
|
||||||
|
// Send texture to client
|
||||||
|
if( updateTextures && theEntity.tickCount > 1 ) {
|
||||||
|
updateTextures = false;
|
||||||
|
//SpecialMobs.network().sendToDimension( new MessageTexture( theEntity ), theEntity.dimension ); TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update natural regen
|
||||||
|
if( healTimeMax > 0 && ++healTime >= healTimeMax ) {
|
||||||
|
healTime = 0;
|
||||||
|
theEntity.heal( 1.0F );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alters the entity's base attribute by adding an amount to it.
|
||||||
|
* Do NOT use this for move speed, instead use {@link SpecialMobData#multAttribute(Attribute, double)}
|
||||||
|
*
|
||||||
|
* @param attribute the attribute to modify
|
||||||
|
* @param amount the amount to add to the attribute
|
||||||
|
*/
|
||||||
|
public void addAttribute( Attribute attribute, double amount ) {
|
||||||
|
final ModifiableAttributeInstance attributeInstance = theEntity.getAttribute( attribute );
|
||||||
|
if( attributeInstance == null )
|
||||||
|
throw new IllegalStateException( "Special mob '" + theEntity + "' does not have registered attribute " +
|
||||||
|
attribute.getDescriptionId() );
|
||||||
|
attributeInstance.setBaseValue( attributeInstance.getBaseValue() + amount );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alters the entity's base attribute by multiplying it by an amount.
|
||||||
|
* Only use this for move speed, for other attributes use {@link SpecialMobData#addAttribute(Attribute, double)}
|
||||||
|
*
|
||||||
|
* @param attribute the attribute to modify
|
||||||
|
* @param amount the amount to multiply the attribute by
|
||||||
|
*/
|
||||||
|
public void multAttribute( Attribute attribute, double amount ) {
|
||||||
|
final ModifiableAttributeInstance attributeInstance = theEntity.getAttribute( attribute );
|
||||||
|
if( attributeInstance == null )
|
||||||
|
throw new IllegalStateException( "Special mob '" + theEntity + "' does not have registered attribute " +
|
||||||
|
attribute.getDescriptionId() );
|
||||||
|
attributeInstance.setBaseValue( attributeInstance.getBaseValue() * amount );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Whether this entity has a glowing eyes texture.
|
||||||
|
*/
|
||||||
|
public boolean hasEyesTexture() { return textureEyes != null; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Whether this entity has an overlay texture.
|
||||||
|
*/
|
||||||
|
public boolean hasOverlayTexture() { return textureOverlay != null; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The base texture for the entity.
|
||||||
|
*/
|
||||||
|
public ResourceLocation getTexture() { return texture; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The glowing eyes texture for the entity.
|
||||||
|
*/
|
||||||
|
public ResourceLocation getTextureEyes() { return textureEyes; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The overlay texture for the entity.
|
||||||
|
*/
|
||||||
|
public ResourceLocation getTextureOverlay() { return textureOverlay; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param textures The new texture(s) to set for the entity.
|
||||||
|
*/
|
||||||
|
private void setTextures( ResourceLocation[] textures ) {
|
||||||
|
texture = textures[0];
|
||||||
|
if( textures.length > 1 ) {
|
||||||
|
textureEyes = textures[1];
|
||||||
|
}
|
||||||
|
if( textures.length > 2 ) {
|
||||||
|
textureOverlay = textures[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param textures The new texture(s) to load for the entity. Called when loaded from a packet.
|
||||||
|
*/
|
||||||
|
public void loadTextures( String[] textures ) {
|
||||||
|
try {
|
||||||
|
loadTexture( textures[0] );
|
||||||
|
loadTextureEyes( textures.length > 1 ? textures[1] : "" );
|
||||||
|
loadTextureOverlay( textures.length > 2 ? textures[2] : "" );
|
||||||
|
}
|
||||||
|
catch( Exception ex ) {
|
||||||
|
SpecialMobs.LOG.warn( "Failed to load textures for {}! ({})", theEntity, textures );
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadTexture( String tex ) {
|
||||||
|
if( tex.isEmpty() ) throw new IllegalArgumentException( "Entity must have a base texture" );
|
||||||
|
final ResourceLocation newTexture = new ResourceLocation( tex );
|
||||||
|
if( !newTexture.toString().equals( texture.toString() ) ) {
|
||||||
|
texture = newTexture;
|
||||||
|
updateTextures = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadTextureEyes( String tex ) {
|
||||||
|
if( tex.isEmpty() ) {
|
||||||
|
if( textureEyes != null ) {
|
||||||
|
textureEyes = null;
|
||||||
|
updateTextures = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if( textureEyes == null ) {
|
||||||
|
textureEyes = new ResourceLocation( tex );
|
||||||
|
updateTextures = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final ResourceLocation newTexture = new ResourceLocation( tex );
|
||||||
|
if( !newTexture.toString().equals( textureEyes.toString() ) ) {
|
||||||
|
texture = newTexture;
|
||||||
|
updateTextures = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadTextureOverlay( String tex ) {
|
||||||
|
if( tex.isEmpty() ) {
|
||||||
|
if( textureOverlay != null ) {
|
||||||
|
textureOverlay = null;
|
||||||
|
updateTextures = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if( textureOverlay == null ) {
|
||||||
|
textureOverlay = new ResourceLocation( tex );
|
||||||
|
updateTextures = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final ResourceLocation newTexture = new ResourceLocation( tex );
|
||||||
|
if( !newTexture.toString().equals( textureOverlay.toString() ) ) {
|
||||||
|
texture = newTexture;
|
||||||
|
updateTextures = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return The render scale for the entity. */
|
||||||
|
public float getRenderScale() { return theEntity.getEntityData().get( renderScale ); }
|
||||||
|
|
||||||
|
public void setRenderScale( float scale ) {
|
||||||
|
if( !theEntity.level.isClientSide ) {
|
||||||
|
theEntity.getEntityData().set( renderScale, scale );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getFamilyBaseScale() { return familyScale; }
|
||||||
|
|
||||||
|
public float getBaseScaleForPreScaledValues() { return getBaseScale() / getFamilyBaseScale(); }
|
||||||
|
|
||||||
|
public float getBaseScale() { return baseScale; }
|
||||||
|
|
||||||
|
public void setBaseScale( float newBaseScale ) {
|
||||||
|
baseScale = newBaseScale;
|
||||||
|
setRenderScale( nextScale() );
|
||||||
|
}
|
||||||
|
|
||||||
|
private float nextScale() {
|
||||||
|
// if( Config.get().GENERAL.RANDOM_SCALING > 0.0F ) { TODO configs
|
||||||
|
// return baseScale * (1.0F + (theEntity.getRNG().nextFloat() - 0.5F) * Config.get().GENERAL.RANDOM_SCALING);
|
||||||
|
// }
|
||||||
|
return baseScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getRangedDamage( float distanceFactor ) {
|
||||||
|
final int powerEnchant = EnchantmentHelper.getEnchantmentLevel( Enchantments.POWER_ARROWS, theEntity );
|
||||||
|
return rangedAttackDamage * (distanceFactor +
|
||||||
|
theEntity.getRandom().nextGaussian() * 0.125 +
|
||||||
|
theEntity.level.getDifficulty().getId() * 0.055F) +
|
||||||
|
(powerEnchant > 0 ? powerEnchant * 0.5 + 0.5 : 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegenerationTime( int ticks ) { healTimeMax = ticks; }
|
||||||
|
|
||||||
|
public float getFallDamageMultiplier() { return fallDamageMultiplier; }
|
||||||
|
|
||||||
|
public void setFallDamageMultiplier( float value ) { fallDamageMultiplier = value; }
|
||||||
|
|
||||||
|
public boolean isImmuneToFire() { return isImmuneToFire; }
|
||||||
|
|
||||||
|
public void setImmuneToFire( boolean value ) {
|
||||||
|
isImmuneToFire = value;
|
||||||
|
if( value ) {
|
||||||
|
theEntity.setPathfindingMalus( PathNodeType.LAVA, PathNodeType.WATER.getMalus() );
|
||||||
|
theEntity.setPathfindingMalus( PathNodeType.DANGER_FIRE, PathNodeType.OPEN.getMalus() );
|
||||||
|
theEntity.setPathfindingMalus( PathNodeType.DAMAGE_FIRE, PathNodeType.OPEN.getMalus() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
theEntity.setPathfindingMalus( PathNodeType.LAVA, PathNodeType.LAVA.getMalus() );
|
||||||
|
theEntity.setPathfindingMalus( PathNodeType.DANGER_FIRE, PathNodeType.DANGER_FIRE.getMalus() );
|
||||||
|
theEntity.setPathfindingMalus( PathNodeType.DAMAGE_FIRE, PathNodeType.DAMAGE_FIRE.getMalus() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isImmuneToBurning() { return isImmuneToBurning; }
|
||||||
|
|
||||||
|
public void setImmuneToBurning( boolean value ) {
|
||||||
|
theEntity.clearFire();
|
||||||
|
isImmuneToBurning = value;
|
||||||
|
if( value ) {
|
||||||
|
theEntity.setPathfindingMalus( PathNodeType.DANGER_FIRE, PathNodeType.OPEN.getMalus() );
|
||||||
|
theEntity.setPathfindingMalus( PathNodeType.DAMAGE_FIRE, PathNodeType.DANGER_FIRE.getMalus() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
theEntity.setPathfindingMalus( PathNodeType.DANGER_FIRE, PathNodeType.DANGER_FIRE.getMalus() );
|
||||||
|
theEntity.setPathfindingMalus( PathNodeType.DAMAGE_FIRE, PathNodeType.DAMAGE_FIRE.getMalus() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean allowLeashing() { return allowLeashing; }
|
||||||
|
|
||||||
|
public void setAllowLeashing( boolean value ) { allowLeashing = value; }
|
||||||
|
|
||||||
|
public boolean ignorePressurePlates() { return ignorePressurePlates; }
|
||||||
|
|
||||||
|
public void setIgnorePressurePlates( boolean value ) { ignorePressurePlates = value; }
|
||||||
|
|
||||||
|
public boolean canBreatheInWater() { return canBreatheInWater; }
|
||||||
|
|
||||||
|
public void setCanBreatheInWater( boolean value ) { canBreatheInWater = value; }
|
||||||
|
|
||||||
|
public boolean ignoreWaterPush() { return ignoreWaterPush; }
|
||||||
|
|
||||||
|
public void setIgnoreWaterPush( boolean value ) { ignoreWaterPush = value; }
|
||||||
|
|
||||||
|
public boolean isDamagedByWater() { return isDamagedByWater; }
|
||||||
|
|
||||||
|
public void setDamagedByWater( boolean value ) {
|
||||||
|
isDamagedByWater = value;
|
||||||
|
theEntity.setPathfindingMalus( PathNodeType.WATER, value ? PathNodeType.LAVA.getMalus() : PathNodeType.WATER.getMalus() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests a block state to see if the entity can be 'stuck' inside.
|
||||||
|
*
|
||||||
|
* @param block The block state to test.
|
||||||
|
* @return True if the block is allowed to apply its stuck speed multiplier.
|
||||||
|
*/
|
||||||
|
public boolean canBeStuckIn( BlockState block ) { return !immuneToStickyBlocks.contains( block.getBlock().getDescriptionId() ); }
|
||||||
|
|
||||||
|
/** @param blocks The sticky block(s) to grant immunity from. */
|
||||||
|
public void addStickyBlockImmunity( Block... blocks ) {
|
||||||
|
for( Block block : blocks ) immuneToStickyBlocks.add( block.getDescriptionId() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests a potion effect to see if it is applicable to the entity.
|
||||||
|
*
|
||||||
|
* @param effect The potion effect to test.
|
||||||
|
* @return True if the potion is allowed to be applied.
|
||||||
|
*/
|
||||||
|
public boolean isPotionApplicable( EffectInstance effect ) {
|
||||||
|
final PotionEvent.PotionApplicableEvent event = new PotionEvent.PotionApplicableEvent( theEntity, effect );
|
||||||
|
MinecraftForge.EVENT_BUS.post( event );
|
||||||
|
switch( event.getResult() ) {
|
||||||
|
case DENY: return false;
|
||||||
|
case ALLOW: return true;
|
||||||
|
default: return !immuneToPotions.contains( effect.getDescriptionId() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param effects The effect(s) to grant immunity from. */
|
||||||
|
public void addPotionImmunity( Effect... effects ) {
|
||||||
|
for( Effect effect : effects ) immuneToPotions.add( effect.getDescriptionId() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves this data to NBT.
|
||||||
|
*
|
||||||
|
* @param tag The tag to save to.
|
||||||
|
*/
|
||||||
|
public void writeToNBT( CompoundNBT tag ) {
|
||||||
|
tag.putFloat( TAG_RENDER_SCALE, getRenderScale() );
|
||||||
|
tag.putInt( TAG_EXPERIENCE, theEntity.getExperience() );
|
||||||
|
tag.putByte( TAG_REGENERATION, (byte) healTimeMax );
|
||||||
|
|
||||||
|
//tag.putString( TAG_TEXTURE, texture.toString() );TODO textures
|
||||||
|
tag.putString( TAG_TEXTURE_EYES, textureEyes == null ? "" : textureEyes.toString() );
|
||||||
|
tag.putString( TAG_TEXTURE_OVER, textureOverlay == null ? "" : textureOverlay.toString() );
|
||||||
|
|
||||||
|
// Arrow AI
|
||||||
|
tag.putFloat( TAG_ARROW_DAMAGE, rangedAttackDamage );
|
||||||
|
tag.putFloat( TAG_ARROW_SPREAD, rangedAttackSpread );
|
||||||
|
tag.putFloat( TAG_ARROW_WALK_SPEED, rangedWalkSpeed );
|
||||||
|
tag.putShort( TAG_ARROW_REFIRE_MIN, (short) rangedAttackCooldown );
|
||||||
|
tag.putShort( TAG_ARROW_REFIRE_MAX, (short) rangedAttackMaxCooldown );
|
||||||
|
tag.putFloat( TAG_ARROW_RANGE, rangedAttackMaxRange );
|
||||||
|
|
||||||
|
// Abilities
|
||||||
|
tag.putFloat( TAG_FALL_MULTI, getFallDamageMultiplier() );
|
||||||
|
tag.putBoolean( TAG_FIRE_IMMUNE, isImmuneToFire() );
|
||||||
|
tag.putBoolean( TAG_BURN_IMMUNE, isImmuneToBurning() );
|
||||||
|
tag.putBoolean( TAG_LEASHABLE, allowLeashing() );
|
||||||
|
tag.putBoolean( TAG_TRAP_IMMUNE, ignorePressurePlates() );
|
||||||
|
tag.putBoolean( TAG_DROWN_IMMUNE, canBreatheInWater() );
|
||||||
|
tag.putBoolean( TAG_WATER_PUSH_IMMUNE, ignoreWaterPush() );
|
||||||
|
tag.putBoolean( TAG_WATER_DAMAGE, isDamagedByWater() );
|
||||||
|
|
||||||
|
final ListNBT stickyBlocksTag = new ListNBT();
|
||||||
|
for( String blockName : immuneToStickyBlocks ) {
|
||||||
|
stickyBlocksTag.add( StringNBT.valueOf( blockName ) );
|
||||||
|
}
|
||||||
|
tag.put( TAG_STICKY_IMMUNE, stickyBlocksTag );
|
||||||
|
|
||||||
|
final ListNBT potionsTag = new ListNBT();
|
||||||
|
for( String potionName : immuneToPotions ) {
|
||||||
|
potionsTag.add( StringNBT.valueOf( potionName ) );
|
||||||
|
}
|
||||||
|
tag.put( TAG_POTION_IMMUNE, potionsTag );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads this data from NBT.
|
||||||
|
*
|
||||||
|
* @param tag The tag to load from.
|
||||||
|
*/
|
||||||
|
public void readFromNBT( CompoundNBT tag ) {
|
||||||
|
if( tag.contains( TAG_RENDER_SCALE, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
setRenderScale( tag.getFloat( TAG_RENDER_SCALE ) );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_EXPERIENCE, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
theEntity.setExperience( tag.getInt( TAG_EXPERIENCE ) );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_REGENERATION, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
healTimeMax = tag.getByte( TAG_REGENERATION );
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if( tag.contains( TAG_TEXTURE, NBT_TYPE_STRING ) ) {
|
||||||
|
loadTexture( tag.getString( TAG_TEXTURE ) );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_TEXTURE_EYES, NBT_TYPE_STRING ) ) {
|
||||||
|
loadTextureEyes( tag.getString( TAG_TEXTURE_EYES ) );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_TEXTURE_OVER, NBT_TYPE_STRING ) ) {
|
||||||
|
loadTextureOverlay( tag.getString( TAG_TEXTURE_OVER ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( Exception ex ) {
|
||||||
|
SpecialMobs.LOG.warn( "Failed to load textures from NBT! " + theEntity.toString() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arrow AI
|
||||||
|
if( tag.contains( TAG_ARROW_DAMAGE, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
rangedAttackDamage = tag.getFloat( TAG_ARROW_DAMAGE );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_ARROW_SPREAD, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
rangedAttackSpread = tag.getFloat( TAG_ARROW_SPREAD );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_ARROW_WALK_SPEED, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
rangedWalkSpeed = tag.getFloat( TAG_ARROW_WALK_SPEED );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_ARROW_REFIRE_MIN, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
rangedAttackCooldown = tag.getShort( TAG_ARROW_REFIRE_MIN );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_ARROW_REFIRE_MAX, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
rangedAttackMaxCooldown = tag.getShort( TAG_ARROW_REFIRE_MAX );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_ARROW_RANGE, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
rangedAttackMaxRange = tag.getFloat( TAG_ARROW_RANGE );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abilities
|
||||||
|
if( tag.contains( TAG_FALL_MULTI, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
setFallDamageMultiplier( tag.getFloat( TAG_FALL_MULTI ) );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_FIRE_IMMUNE, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
setImmuneToFire( tag.getBoolean( TAG_FIRE_IMMUNE ) );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_BURN_IMMUNE, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
setImmuneToBurning( tag.getBoolean( TAG_BURN_IMMUNE ) );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_LEASHABLE, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
setAllowLeashing( tag.getBoolean( TAG_LEASHABLE ) );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_TRAP_IMMUNE, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
setIgnorePressurePlates( tag.getBoolean( TAG_TRAP_IMMUNE ) );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_DROWN_IMMUNE, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
setCanBreatheInWater( tag.getBoolean( TAG_DROWN_IMMUNE ) );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_WATER_PUSH_IMMUNE, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
setIgnoreWaterPush( tag.getBoolean( TAG_WATER_PUSH_IMMUNE ) );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_WATER_DAMAGE, NBT_TYPE_NUMERICAL ) ) {
|
||||||
|
setDamagedByWater( tag.getBoolean( TAG_WATER_DAMAGE ) );
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_STICKY_IMMUNE, NBT_TYPE_LIST ) ) {
|
||||||
|
final ListNBT stickyBlocksTag = tag.getList( TAG_STICKY_IMMUNE, NBT_TYPE_STRING );
|
||||||
|
immuneToStickyBlocks.clear();
|
||||||
|
for( int i = 0; i < stickyBlocksTag.size(); i++ ) {
|
||||||
|
immuneToStickyBlocks.add( stickyBlocksTag.getString( i ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( tag.contains( TAG_POTION_IMMUNE, NBT_TYPE_LIST ) ) {
|
||||||
|
final ListNBT potionsTag = tag.getList( TAG_POTION_IMMUNE, NBT_TYPE_STRING );
|
||||||
|
immuneToPotions.clear();
|
||||||
|
for( int i = 0; i < potionsTag.size(); i++ ) {
|
||||||
|
immuneToPotions.add( potionsTag.getString( i ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package fathertoast.specialmobs.common.entity.creeper;
|
||||||
|
|
||||||
|
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
|
||||||
|
import fathertoast.specialmobs.common.bestiary.SpecialMob;
|
||||||
|
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
|
||||||
|
import mcp.MethodsReturnNonnullByDefault;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.Blocks;
|
||||||
|
import net.minecraft.entity.EntityType;
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
|
import net.minecraft.entity.ai.attributes.AttributeModifierMap;
|
||||||
|
import net.minecraft.entity.monster.CreeperEntity;
|
||||||
|
import net.minecraft.fluid.FluidState;
|
||||||
|
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.math.BlockPos;
|
||||||
|
import net.minecraft.world.EntityExplosionContext;
|
||||||
|
import net.minecraft.world.Explosion;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
import net.minecraft.world.server.ServerWorld;
|
||||||
|
|
||||||
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
@MethodsReturnNonnullByDefault
|
||||||
|
@SpecialMob
|
||||||
|
public class DarkCreeperEntity extends _SpecialCreeperEntity {
|
||||||
|
|
||||||
|
//--------------- Static Special Mob Hooks ----------------
|
||||||
|
|
||||||
|
@SpecialMob.BestiaryInfoSupplier
|
||||||
|
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> entityType ) {
|
||||||
|
return new BestiaryInfo( 0xf9ff3a );
|
||||||
|
}
|
||||||
|
|
||||||
|
@SpecialMob.AttributeCreator
|
||||||
|
public static AttributeModifierMap.MutableAttribute createAttributes() {
|
||||||
|
return _SpecialCreeperEntity.createAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SpecialMob.LootTableProvider
|
||||||
|
public static void buildLootTable( LootTableBuilder loot ) {
|
||||||
|
addBaseLoot( loot );
|
||||||
|
loot.addClusterDrop( "common", Blocks.TORCH );
|
||||||
|
loot.addRareDrop( "rare", PotionUtils.setPotion( new ItemStack( Items.POTION ), Potions.NIGHT_VISION ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@SpecialMob.Constructor
|
||||||
|
public DarkCreeperEntity( EntityType<? extends _SpecialCreeperEntity> entityType, World world ) {
|
||||||
|
super( entityType, world );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------- Variant-Specific Implementations ----------------
|
||||||
|
|
||||||
|
/** Override to change this creeper's explosion power multiplier. */
|
||||||
|
protected float getVariantExplosionPower() { return super.getVariantExplosionPower() / 2.0F; }
|
||||||
|
|
||||||
|
/** Override to change this creeper's explosion. */
|
||||||
|
@Override
|
||||||
|
protected void makeVariantExplosion( float explosionPower, Explosion.Mode explosionMode ) {
|
||||||
|
final EntityExplosionContext damageCalculator = new EntityExplosionContext( this );
|
||||||
|
final Explosion explosion = new Explosion( level, this, null,
|
||||||
|
damageCalculator, getX(), getY(), getZ(), explosionPower, false, explosionMode );
|
||||||
|
|
||||||
|
if( net.minecraftforge.event.ForgeEventFactory.onExplosionStart( level, explosion ) ) return;
|
||||||
|
explosion.explode();
|
||||||
|
|
||||||
|
// Add unaffected light sources to the explosion's affected area
|
||||||
|
// Note that this does NOT simulate another explosion, instead just directly searches for and targets lights
|
||||||
|
final BlockPos center = new BlockPos( getX(), getY(), getZ() );
|
||||||
|
final int radius = explosionRadius * 4 * (isPowered() ? 2 : 1);
|
||||||
|
final BlockPos.Mutable pos = new BlockPos.Mutable();
|
||||||
|
for( int y = -radius; y <= radius; y++ ) {
|
||||||
|
for( int x = -radius; x <= radius; x++ ) {
|
||||||
|
for( int z = -radius; z <= radius; z++ ) {
|
||||||
|
if( x * x + y * y + z * z <= radius * radius ) {
|
||||||
|
pos.set( center.getX() + x, center.getY() + y, center.getZ() + z );
|
||||||
|
final BlockState block = level.getBlockState( pos );
|
||||||
|
|
||||||
|
// Ignore the block if it is not a light or is already exploded
|
||||||
|
if( block.getLightValue( level, pos ) > 1 && !explosion.getToBlow().contains( pos ) ) {
|
||||||
|
float blockDamage = (float) radius * (0.7F + random.nextFloat() * 0.6F);
|
||||||
|
final FluidState fluid = level.getFluidState( pos );
|
||||||
|
final Optional<Float> optional = damageCalculator.getBlockExplosionResistance(
|
||||||
|
explosion, level, pos, block, fluid );
|
||||||
|
if( optional.isPresent() ) {
|
||||||
|
blockDamage -= (optional.get() + 0.3F) * 0.3F;
|
||||||
|
}
|
||||||
|
if( blockDamage > 0.0F && damageCalculator.shouldBlockExplode( explosion, level, pos, block, blockDamage ) ) {
|
||||||
|
explosion.getToBlow().add( pos );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
explosion.finalizeExplosion( true );
|
||||||
|
|
||||||
|
// Move the time forward to next night if powered
|
||||||
|
if( isPowered() && level instanceof ServerWorld ) {
|
||||||
|
// Days are 24k ticks long; find how far along we are in the current day (0-23,999)
|
||||||
|
long time = level.getDayTime();
|
||||||
|
final int dayTime = (int) (time % 24_000L);
|
||||||
|
|
||||||
|
// We decide that night starts at 13k ticks
|
||||||
|
time += 13_000L - dayTime;
|
||||||
|
// If we were already past the 13k point today, advance to the next night entirely
|
||||||
|
if( dayTime > 13_000 ) time += 24_000L;
|
||||||
|
|
||||||
|
((ServerWorld) level).setDayTime( time );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to change effects applied by the lingering cloud left by this creeper's explosion.
|
||||||
|
* If this list is empty, the lingering cloud is not created.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void modifyVariantLingeringCloudEffects( List<EffectInstance> potions ) {
|
||||||
|
potions.add( new EffectInstance( Effects.BLINDNESS, 100 ) );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,380 @@
|
||||||
|
package fathertoast.specialmobs.common.entity.creeper;
|
||||||
|
|
||||||
|
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.util.References;
|
||||||
|
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
|
||||||
|
import mcp.MethodsReturnNonnullByDefault;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.entity.AreaEffectCloudEntity;
|
||||||
|
import net.minecraft.entity.EntityType;
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
|
import net.minecraft.entity.ai.attributes.AttributeModifierMap;
|
||||||
|
import net.minecraft.entity.effect.LightningBoltEntity;
|
||||||
|
import net.minecraft.entity.monster.CreeperEntity;
|
||||||
|
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.pathfinding.PathNodeType;
|
||||||
|
import net.minecraft.potion.EffectInstance;
|
||||||
|
import net.minecraft.util.DamageSource;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
import net.minecraft.util.math.vector.Vector3d;
|
||||||
|
import net.minecraft.world.Explosion;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
import net.minecraft.world.server.ServerWorld;
|
||||||
|
|
||||||
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
@MethodsReturnNonnullByDefault
|
||||||
|
@SpecialMob
|
||||||
|
public class _SpecialCreeperEntity extends CreeperEntity implements ISpecialMob<_SpecialCreeperEntity> {
|
||||||
|
|
||||||
|
//--------------- Static Special Mob Hooks ----------------
|
||||||
|
|
||||||
|
@SpecialMob.BestiaryInfoSupplier
|
||||||
|
public static BestiaryInfo bestiaryInfo( EntityType.Builder<LivingEntity> entityType ) {
|
||||||
|
return new BestiaryInfo( 0x000000 );
|
||||||
|
}
|
||||||
|
|
||||||
|
@SpecialMob.AttributeCreator
|
||||||
|
public static AttributeModifierMap.MutableAttribute createAttributes() {
|
||||||
|
return CreeperEntity.createAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SpecialMob.LootTableProvider
|
||||||
|
public static void addBaseLoot( LootTableBuilder loot ) {
|
||||||
|
loot.addLootTable( "main", EntityType.CREEPER.getDefaultLootTable() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@SpecialMob.Constructor
|
||||||
|
public _SpecialCreeperEntity( EntityType<? extends _SpecialCreeperEntity> 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 perform this creeper's explosion 'attack'. */
|
||||||
|
@Override
|
||||||
|
protected void explodeCreeper() {
|
||||||
|
if( !level.isClientSide ) {
|
||||||
|
final Explosion.Mode explosionMode = net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent( level, this ) ?
|
||||||
|
Explosion.Mode.DESTROY : Explosion.Mode.NONE;
|
||||||
|
final float explosionPower = getVariantExplosionPower();
|
||||||
|
dead = true;
|
||||||
|
makeVariantExplosion( explosionPower, explosionMode );
|
||||||
|
remove();
|
||||||
|
spawnLingeringCloud();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Override to change this creeper's explosion power multiplier. */
|
||||||
|
protected float getVariantExplosionPower() { return isPowered() ? 2.0F : 1.0F; }
|
||||||
|
|
||||||
|
/** Override to change this creeper's explosion. */
|
||||||
|
protected void makeVariantExplosion( float explosionPower, Explosion.Mode explosionMode ) {
|
||||||
|
level.explode( this, getX(), getY(), getZ(), (float) explosionRadius * explosionPower, explosionMode );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called to create a lingering effect cloud as part of this creeper's explosion 'attack'. */
|
||||||
|
@Override
|
||||||
|
protected void spawnLingeringCloud() {
|
||||||
|
final List<EffectInstance> effects = new ArrayList<>( getActiveEffects() );
|
||||||
|
modifyVariantLingeringCloudEffects( effects );
|
||||||
|
|
||||||
|
if( !effects.isEmpty() ) {
|
||||||
|
final AreaEffectCloudEntity potionCloud = new AreaEffectCloudEntity( level, getX(), getY(), getZ() );
|
||||||
|
potionCloud.setRadius( (explosionRadius - 0.5F) * getVariantExplosionPower() );
|
||||||
|
potionCloud.setRadiusOnUse( -0.5F );
|
||||||
|
potionCloud.setWaitTime( 10 );
|
||||||
|
potionCloud.setDuration( potionCloud.getDuration() / 2 );
|
||||||
|
potionCloud.setRadiusPerTick( -potionCloud.getRadius() / (float) potionCloud.getDuration() );
|
||||||
|
for( EffectInstance effect : effects ) {
|
||||||
|
potionCloud.addEffect( new EffectInstance( effect ) );
|
||||||
|
}
|
||||||
|
modifyVariantLingeringCloud( potionCloud );
|
||||||
|
level.addFreshEntity( potionCloud );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to change effects applied by the lingering cloud left by this creeper's explosion.
|
||||||
|
* If this list is empty, the lingering cloud is not created.
|
||||||
|
*/
|
||||||
|
protected void modifyVariantLingeringCloudEffects( List<EffectInstance> potions ) { }
|
||||||
|
|
||||||
|
/** Override to change stats of the lingering cloud left by this creeper's explosion. */
|
||||||
|
protected void modifyVariantLingeringCloud( AreaEffectCloudEntity potionCloud ) { }
|
||||||
|
|
||||||
|
|
||||||
|
//--------------- Family-Specific Implementations ----------------
|
||||||
|
|
||||||
|
/** The parameter for special mob render scale. */
|
||||||
|
private static final DataParameter<Float> SCALE = EntityDataManager.defineId( _SpecialCreeperEntity.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 );
|
||||||
|
entityData.define( EXPLODE_FLAGS, (byte) 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called each tick to update this entity. */
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
if( isAlive() ) {
|
||||||
|
// Implement "cannot explode while wet" property
|
||||||
|
if( isInWaterRainOrBubble() && cannotExplodeWhileWet() ) {
|
||||||
|
if( isIgnited() ) entityData.set( DATA_IS_IGNITED, false );
|
||||||
|
setSwellDir( -1 );
|
||||||
|
}
|
||||||
|
// Implement "explodes while burning" property
|
||||||
|
else if( isOnFire() && explodesWhileBurning() ) {
|
||||||
|
setSwellDir( 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return Attempts to damage this entity; returns true if the hit was successful. */
|
||||||
|
@Override
|
||||||
|
public boolean hurt( DamageSource damageSource, float damage ) {
|
||||||
|
if( super.hurt( damageSource, damage ) ) {
|
||||||
|
// Implement "explodes when shot" property
|
||||||
|
if( damageSource.getDirectEntity() != damageSource.getEntity() && explodesWhenShot() ) ignite();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when this entity is struck by lightning. */
|
||||||
|
@Override
|
||||||
|
public void thunderHit( ServerWorld world, LightningBoltEntity lightningBolt ) {
|
||||||
|
super.thunderHit( world, lightningBolt );
|
||||||
|
|
||||||
|
// Make it less likely for charged "explode while burning" creepers to immediately explode
|
||||||
|
if( explodesWhileBurning() ) clearFire();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when this entity is first spawned to initialize it. TODO chance to be charged on spawn
|
||||||
|
// @Override
|
||||||
|
// @Nullable
|
||||||
|
// public IEntityLivingData onInitialSpawn( DifficultyInstance difficulty, @Nullable IEntityLivingData data ) {
|
||||||
|
// data = super.onInitialSpawn( difficulty, data );
|
||||||
|
//
|
||||||
|
// if( Entity_SpecialCreeper.POWERED != null && world.isThundering() && rand.nextDouble() < Config.get().CREEPERS.CHARGED_CHANCE ) {
|
||||||
|
// dataManager.set( Entity_SpecialCreeper.POWERED, true );
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return data;
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
//--------------- Creeper Explosion Property Setters/Getters ----------------
|
||||||
|
|
||||||
|
/** The parameter for creeper explosion properties. This is a combination of boolean flags. */
|
||||||
|
private static final DataParameter<Byte> EXPLODE_FLAGS = EntityDataManager.defineId( _SpecialCreeperEntity.class, DataSerializers.BYTE );
|
||||||
|
|
||||||
|
/** The bit for "cannot explode while wet". */
|
||||||
|
private static final byte EXPLODE_FLAG_DEFUSE_IN_WATER = 0b0001;
|
||||||
|
/** The bit for "explodes while burning". */
|
||||||
|
private static final byte EXPLODE_FLAG_ON_FIRE = 0b0010;
|
||||||
|
/** The bit for "explodes when shot". */
|
||||||
|
private static final byte EXPLODE_FLAG_WHEN_SHOT = 0b0100;
|
||||||
|
|
||||||
|
/** @return True if this creeper is unable to explode while wet. */
|
||||||
|
public boolean cannotExplodeWhileWet() { return getExplodeFlag( EXPLODE_FLAG_DEFUSE_IN_WATER ); }
|
||||||
|
|
||||||
|
/** Sets this creeper's capability to explode while wet. */
|
||||||
|
public void setCannotExplodeWhileWet( boolean value ) {
|
||||||
|
setExplodeFlag( EXPLODE_FLAG_DEFUSE_IN_WATER, value );
|
||||||
|
setPathfindingMalus( PathNodeType.WATER, value ? PathNodeType.LAVA.getMalus() : PathNodeType.WATER.getMalus() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return True if this creeper explodes while burning. */
|
||||||
|
public boolean explodesWhileBurning() { return getExplodeFlag( EXPLODE_FLAG_ON_FIRE ); }
|
||||||
|
|
||||||
|
/** Sets this creeper's property to explode while burning. */
|
||||||
|
public void setExplodesWhileBurning( boolean value ) {
|
||||||
|
setExplodeFlag( EXPLODE_FLAG_ON_FIRE, value );
|
||||||
|
if( value ) {
|
||||||
|
setPathfindingMalus( PathNodeType.DANGER_FIRE, PathNodeType.DAMAGE_FIRE.getMalus() );
|
||||||
|
setPathfindingMalus( PathNodeType.DAMAGE_FIRE, PathNodeType.BLOCKED.getMalus() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setPathfindingMalus( PathNodeType.DANGER_FIRE, PathNodeType.DANGER_FIRE.getMalus() );
|
||||||
|
setPathfindingMalus( PathNodeType.DAMAGE_FIRE, PathNodeType.DAMAGE_FIRE.getMalus() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return True if this creeper explodes when shot. */
|
||||||
|
public boolean explodesWhenShot() { return getExplodeFlag( EXPLODE_FLAG_WHEN_SHOT ); }
|
||||||
|
|
||||||
|
/** Sets this creeper's property to explode when shot. */
|
||||||
|
public void setExplodesWhenShot( boolean value ) { setExplodeFlag( EXPLODE_FLAG_WHEN_SHOT, value ); }
|
||||||
|
|
||||||
|
/** @return The value for a specific explode flag. */
|
||||||
|
private boolean getExplodeFlag( byte flag ) { return (entityData.get( EXPLODE_FLAGS ) & flag) != 0; }
|
||||||
|
|
||||||
|
/** Sets the value for a specific explode flag. */
|
||||||
|
private void setExplodeFlag( byte flag, boolean value ) {
|
||||||
|
final byte allFlags = entityData.get( EXPLODE_FLAGS );
|
||||||
|
if( value == ((allFlags & flag) == 0) ) {
|
||||||
|
entityData.set( EXPLODE_FLAGS, (byte) (allFlags ^ flag) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//TODO--------------- ISpecialMob Implementation ----------------
|
||||||
|
|
||||||
|
private SpecialMobData<_SpecialCreeperEntity> specialData;
|
||||||
|
|
||||||
|
/** @return This mob's special data. */
|
||||||
|
@Override
|
||||||
|
public SpecialMobData<_SpecialCreeperEntity> getSpecialData() { return specialData; }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Applies changes to this entity's attributes, applied on creation and after copying replacement data.
|
||||||
|
// */
|
||||||
|
// @Override
|
||||||
|
// public final void applyAttributeAdjustments() {
|
||||||
|
// float prevMax = getMaxHealth();
|
||||||
|
// adjustTypeAttributes();
|
||||||
|
// setHealth( getMaxHealth() + getHealth() - prevMax );
|
||||||
|
// }
|
||||||
|
|
||||||
|
/** @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; }
|
||||||
|
|
||||||
|
private static final ResourceLocation[] TEXTURES = { new ResourceLocation( "textures/entity/creeper/creeper.png" ) };
|
||||||
|
|
||||||
|
static String GET_TEXTURE_PATH( String type ) {
|
||||||
|
return SpecialMobs.TEXTURE_PATH + "creeper/" + type + ".png";
|
||||||
|
}
|
||||||
|
|
||||||
|
// /** @return This entity's default textures. */
|
||||||
|
// @Override
|
||||||
|
// public ResourceLocation[] getDefaultTextures() { return TEXTURES; }
|
||||||
|
|
||||||
|
|
||||||
|
//TODO--------------- SpecialMobData Hooks ----------------
|
||||||
|
|
||||||
|
/** Called each tick to update this entity's movement. */
|
||||||
|
@Override
|
||||||
|
public void aiStep() {
|
||||||
|
super.aiStep();
|
||||||
|
getSpecialData().tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Called to attack the target.
|
||||||
|
// @Override
|
||||||
|
// public boolean attackEntityAsMob( Entity target ) {
|
||||||
|
// if( super.attackEntityAsMob( target ) ) {
|
||||||
|
// onTypeAttack( target );
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
// public float getEyeHeight() { return super.getEyeHeight(); } // Uses boundingbox-scaled eye height
|
||||||
|
|
||||||
|
/** @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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
// public boolean canBeLeashedTo( EntityPlayer player ) { return !getLeashed() && 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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Called when this mob falls. Calculates and applies fall damage.
|
||||||
|
// @Override
|
||||||
|
// public void fall( float distance, float damageMultiplier ) { super.fall( distance, damageMultiplier * getSpecialData().getFallDamageMultiplier() ); }
|
||||||
|
|
||||||
|
// // Return whether this entity should NOT trigger a pressure plate or a tripwire.
|
||||||
|
// @Override
|
||||||
|
// public boolean doesEntityNotTriggerPressurePlate() { 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 );
|
||||||
|
|
||||||
|
saveTag.putBoolean( References.TAG_DRY_EXPLODE, cannotExplodeWhileWet() );
|
||||||
|
saveTag.putBoolean( References.TAG_WHEN_BURNING_EXPLODE, explodesWhileBurning() );
|
||||||
|
saveTag.putBoolean( References.TAG_WHEN_SHOT_EXPLODE, explodesWhenShot() );
|
||||||
|
|
||||||
|
getSpecialData().writeToNBT( 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 );
|
||||||
|
|
||||||
|
if( saveTag.contains( References.TAG_DRY_EXPLODE, References.NBT_TYPE_NUMERICAL ) )
|
||||||
|
setCannotExplodeWhileWet( saveTag.getBoolean( References.TAG_DRY_EXPLODE ) );
|
||||||
|
if( saveTag.contains( References.TAG_WHEN_BURNING_EXPLODE, References.NBT_TYPE_NUMERICAL ) )
|
||||||
|
setExplodesWhileBurning( saveTag.getBoolean( References.TAG_WHEN_BURNING_EXPLODE ) );
|
||||||
|
if( saveTag.contains( References.TAG_WHEN_SHOT_EXPLODE, References.NBT_TYPE_NUMERICAL ) )
|
||||||
|
setExplodesWhenShot( saveTag.getBoolean( References.TAG_WHEN_SHOT_EXPLODE ) );
|
||||||
|
|
||||||
|
getSpecialData().readFromNBT( saveTag );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package fathertoast.specialmobs.common.util;
|
||||||
|
|
||||||
|
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
|
||||||
|
import fathertoast.specialmobs.common.bestiary.MobFamily;
|
||||||
|
import fathertoast.specialmobs.common.bestiary.SpecialMob;
|
||||||
|
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
|
||||||
|
import net.minecraft.entity.EntityType;
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
|
import net.minecraft.entity.ai.attributes.AttributeModifierMap;
|
||||||
|
|
||||||
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
public final class AnnotationHelper {
|
||||||
|
|
||||||
|
//--------------- PRETTY HELPER METHODS ----------------
|
||||||
|
|
||||||
|
/** Creates an entity factory from a special mob species. Throws an exception if anything goes wrong. */
|
||||||
|
public static <T extends LivingEntity> EntityType.IFactory<T> getEntityFactory( MobFamily.Species<T> species )
|
||||||
|
throws NoSuchMethodException {
|
||||||
|
final Constructor<T> constructor = getConstructor( species.entityClass, SpecialMob.Constructor.class );
|
||||||
|
return ( entityType, world ) -> {
|
||||||
|
try {
|
||||||
|
return constructor.newInstance( entityType, world );
|
||||||
|
}
|
||||||
|
catch( InstantiationException | IllegalAccessException | InvocationTargetException ex ) {
|
||||||
|
throw new RuntimeException( "Class for " + constructor.getDeclaringClass().getName() + " has invalid constructor", ex );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets bestiary info from a special mob species. Throws an exception if anything goes wrong. */
|
||||||
|
public static <T extends LivingEntity> BestiaryInfo getBestiaryInfo( MobFamily.Species<T> species, EntityType.Builder<T> entityType )
|
||||||
|
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||||
|
return (BestiaryInfo) getMethod( species.entityClass, SpecialMob.BestiaryInfoSupplier.class ).invoke( null, entityType );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates an attribute modifier map from a special mob species. Throws an exception if anything goes wrong. */
|
||||||
|
public static AttributeModifierMap createAttributes( MobFamily.Species<?> species )
|
||||||
|
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||||
|
return ((AttributeModifierMap.MutableAttribute) getMethod( species.entityClass, SpecialMob.AttributeCreator.class ).invoke( null )).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Builds a loot table from a special mob species. Throws an exception if anything goes wrong. */
|
||||||
|
public static LootTableBuilder buildLootTable( MobFamily.Species<?> species )
|
||||||
|
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||||
|
final LootTableBuilder builder = new LootTableBuilder();
|
||||||
|
getMethod( species.entityClass, SpecialMob.LootTableProvider.class ).invoke( null, builder );
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------- RAW ANNOTATION METHODS ----------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Pulls a static method with a specific annotation from a class.
|
||||||
|
* @throws NoSuchMethodException if the method does not exist.
|
||||||
|
*/
|
||||||
|
private static Method getMethod( Class<?> type, Class<? extends Annotation> annotation ) throws NoSuchMethodException {
|
||||||
|
final Method method = getMethodOptional( type, annotation );
|
||||||
|
if( method == null ) {
|
||||||
|
throw new NoSuchMethodException( String.format( "Could not find static @%s annotated method in %s",
|
||||||
|
annotation.getSimpleName(), type.getName() ) );
|
||||||
|
}
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Pulls a static method with a specific annotation from a class, or null if the method does not exist.
|
||||||
|
*/
|
||||||
|
private static Method getMethodOptional( Class<?> type, Class<? extends Annotation> annotation ) {
|
||||||
|
for( Method method : type.getMethods() ) {
|
||||||
|
if( Modifier.isStatic( method.getModifiers() ) && method.isAnnotationPresent( annotation ) )
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Pulls a constructor with a specific annotation from a class.
|
||||||
|
* @throws NoSuchMethodException if the constructor does not exist.
|
||||||
|
*/
|
||||||
|
private static <T> Constructor<T> getConstructor( Class<T> type, Class<? extends Annotation> annotation ) throws NoSuchMethodException {
|
||||||
|
for( Constructor<?> constructor : type.getConstructors() ) {
|
||||||
|
if( constructor.isAnnotationPresent( annotation ) )
|
||||||
|
//noinspection unchecked
|
||||||
|
return (Constructor<T>) constructor;
|
||||||
|
}
|
||||||
|
throw new NoSuchMethodException( String.format( "Could not find @%s annotated constructor in %s",
|
||||||
|
annotation.getSimpleName(), type.getName() ) );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package fathertoast.specialmobs.common.util;
|
||||||
|
|
||||||
|
import fathertoast.specialmobs.common.core.SpecialMobs;
|
||||||
|
import net.minecraft.nbt.CompoundNBT;
|
||||||
|
import net.minecraft.nbt.ListNBT;
|
||||||
|
import net.minecraft.nbt.StringNBT;
|
||||||
|
|
||||||
|
public final class References {
|
||||||
|
|
||||||
|
//--------------- BESTIARY REFLECTION ----------------
|
||||||
|
|
||||||
|
public static final String ENTITY_PACKAGE = "fathertoast." + SpecialMobs.MOD_ID + ".common.entity.";
|
||||||
|
public static final String VANILLA_REPLACEMENT_FORMAT = "%s_Special%sEntity";
|
||||||
|
public static final String SPECIAL_VARIANT_FORMAT = "%s%sEntity";
|
||||||
|
|
||||||
|
|
||||||
|
//--------------- AI STUFF ----------------
|
||||||
|
|
||||||
|
public static final int AI_BIT_NONE = 0b00000000;
|
||||||
|
public static final int AI_BIT_MOVE = 0b00000001;
|
||||||
|
public static final int AI_BIT_FACE = 0b00000010;
|
||||||
|
public static final int AI_BIT_SWIM = 0b00000100;
|
||||||
|
|
||||||
|
|
||||||
|
//--------------- NBT STUFF ----------------
|
||||||
|
|
||||||
|
public static final int NBT_TYPE_NUMERICAL = 99;
|
||||||
|
public static final int NBT_TYPE_STRING = StringNBT.valueOf( "" ).getId(); // 8
|
||||||
|
public static final int NBT_TYPE_LIST = new ListNBT().getId(); // 9
|
||||||
|
public static final int NBT_TYPE_COMPOUND = new CompoundNBT().getId(); // 10
|
||||||
|
|
||||||
|
public static final String TAG_FORGE_DATA = "ForgeData";
|
||||||
|
|
||||||
|
// Special mob data
|
||||||
|
public static final String TAG_SPECIAL_MOB_DATA = "SpecialMobsData";
|
||||||
|
public static final String TAG_RENDER_SCALE = "RenderScale";
|
||||||
|
public static final String TAG_EXPERIENCE = "Experience";
|
||||||
|
public static final String TAG_REGENERATION = "Regeneration";
|
||||||
|
public static final String TAG_TEXTURE = "Texture";
|
||||||
|
public static final String TAG_TEXTURE_EYES = "TextureEyes";
|
||||||
|
public static final String TAG_TEXTURE_OVER = "TextureOverlay";
|
||||||
|
public static final String TAG_ARROW_DAMAGE = "ArrowDamage";
|
||||||
|
public static final String TAG_ARROW_SPREAD = "ArrowSpread";
|
||||||
|
public static final String TAG_ARROW_WALK_SPEED = "ArrowWalkSpeed";
|
||||||
|
public static final String TAG_ARROW_REFIRE_MIN = "ArrowRefireMin";
|
||||||
|
public static final String TAG_ARROW_REFIRE_MAX = "ArrowRefireMax";
|
||||||
|
public static final String TAG_ARROW_RANGE = "ArrowRange";
|
||||||
|
public static final String TAG_FALL_MULTI = "FallMulti";
|
||||||
|
public static final String TAG_FIRE_IMMUNE = "FireImmune";
|
||||||
|
public static final String TAG_BURN_IMMUNE = "BurningImmune";
|
||||||
|
public static final String TAG_LEASHABLE = "Leashable";
|
||||||
|
public static final String TAG_TRAP_IMMUNE = "UnderPressure";
|
||||||
|
public static final String TAG_DROWN_IMMUNE = "DrownImmune";
|
||||||
|
public static final String TAG_WATER_PUSH_IMMUNE = "WaterPushImmune";
|
||||||
|
public static final String TAG_WATER_DAMAGE = "WaterDamage";
|
||||||
|
public static final String TAG_STICKY_IMMUNE = "StickyImmune";
|
||||||
|
public static final String TAG_POTION_IMMUNE = "PotionImmune";
|
||||||
|
|
||||||
|
// Creepers
|
||||||
|
public static final String TAG_DRY_EXPLODE = "CannotExplodeWhileWet";
|
||||||
|
public static final String TAG_WHEN_BURNING_EXPLODE = "ExplodesWhileBurning";
|
||||||
|
public static final String TAG_WHEN_SHOT_EXPLODE = "ExplodesWhenShot";
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package fathertoast.specialmobs.datagen;
|
||||||
|
|
||||||
|
import fathertoast.specialmobs.common.core.SpecialMobs;
|
||||||
|
import net.minecraft.data.DataGenerator;
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import net.minecraftforge.fml.event.lifecycle.GatherDataEvent;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Mod.EventBusSubscriber( modid = SpecialMobs.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD )
|
||||||
|
public class DataGatherListener {
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onGatherData( GatherDataEvent event ) {
|
||||||
|
DataGenerator generator = event.getGenerator();
|
||||||
|
|
||||||
|
if( event.includeClient() ) {
|
||||||
|
for( Map.Entry<String, SMLanguageProvider.TranslationKey> entry : SMLanguageProvider.LANG_CODE_MAP.entrySet() ) {
|
||||||
|
generator.addProvider( new SMLanguageProvider( generator, entry.getKey(), entry.getValue() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( event.includeServer() ) {
|
||||||
|
generator.addProvider( new SMLootTableProvider( generator ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
package fathertoast.specialmobs.datagen;
|
||||||
|
|
||||||
|
import fathertoast.specialmobs.common.core.SpecialMobs;
|
||||||
|
import net.minecraft.data.DataGenerator;
|
||||||
|
import net.minecraftforge.common.data.LanguageProvider;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public class SMLanguageProvider extends LanguageProvider {
|
||||||
|
|
||||||
|
/** All supported translations. */
|
||||||
|
public enum TranslationKey {
|
||||||
|
ENGLISH( "en" ), SPANISH( "es" ), PORTUGUESE( "pt" ), FRENCH( "fr" ), ITALIAN( "it" ),
|
||||||
|
GERMAN( "de" ), PIRATE( "en" );
|
||||||
|
|
||||||
|
public final String code;
|
||||||
|
|
||||||
|
TranslationKey( String id ) { code = id; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This method provides helper tags to make linking translations up easier, and also enforces the correct array length. */
|
||||||
|
private static String[] translations( String key, String en, String es, String pt, String fr, String it, String de, String pir ) {
|
||||||
|
// Note that this must match up EXACTLY to the TranslationKey enum above
|
||||||
|
String[] translation = { key, en, es, pt, fr, it, de, pir };
|
||||||
|
|
||||||
|
// Fix the encoding to allow us to use accented characters in the translation string literals
|
||||||
|
// Note: If a translation uses any non-ASCII characters, make sure they are all in this matrix! (case-sensitive)
|
||||||
|
final String[][] utf8ToUnicode = {
|
||||||
|
{ "à", "\u00E0" }, { "á", "\u00E1" }, { "ã", "\u00E3" }, { "ä", "\u00E4" },
|
||||||
|
{ "ç", "\u00E7" },
|
||||||
|
{ "è", "\u00E8" }, { "é", "\u00E9" }, { "ê", "\u00EA" },
|
||||||
|
{ "í", "\u00ED" },
|
||||||
|
{ "ó", "\u00F3" }, { "õ", "\u00F5" }, { "ö", "\u00F6" },
|
||||||
|
{ "ù", "\u00F9" }, { "û", "\u00FB" }, { "ü", "\u00FC" },
|
||||||
|
{ "œ", "\u0153" }
|
||||||
|
};
|
||||||
|
for( int i = 1; i < translation.length; i++ ) {
|
||||||
|
for( String[] fix : utf8ToUnicode )
|
||||||
|
translation[i] = translation[i].replace( fix[0], fix[1] ); // Note: This is kinda dumb, but it works so idc
|
||||||
|
}
|
||||||
|
return translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matrix linking the actual translations to their lang key.
|
||||||
|
* <p>
|
||||||
|
* Each row of the matrix is one translation array.
|
||||||
|
* In each translation array, the lang key is at index 0 and the translation for a particular
|
||||||
|
* translation key is at index (translationKey.ordinal() + 1).
|
||||||
|
*
|
||||||
|
* @see #addTranslations()
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "SpellCheckingInspection" )
|
||||||
|
private static final String[][] TRANSLATIONS = {
|
||||||
|
// NYI
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Maps which translation key each lang code uses, allowing multiple lang codes to use the same translations. */
|
||||||
|
public static final HashMap<String, TranslationKey> LANG_CODE_MAP = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Assign all specific locales to the translation we want to use
|
||||||
|
mapAll( TranslationKey.ENGLISH, "us" ); // We can ignore other English locales, en_us is the fallback for all languages
|
||||||
|
mapAll( TranslationKey.SPANISH, "es", "ar", "cl", "ec", "mx", "uy", "ve" );
|
||||||
|
mapAll( TranslationKey.PORTUGUESE, "pt", "br" );
|
||||||
|
mapAll( TranslationKey.FRENCH, "fr", "ca" );
|
||||||
|
mapAll( TranslationKey.ITALIAN, "it" );
|
||||||
|
mapAll( TranslationKey.GERMAN, "de", "at", "ch" );
|
||||||
|
mapAll( TranslationKey.PIRATE, "pt" );
|
||||||
|
|
||||||
|
// Make sure all supported languages are completely implemented
|
||||||
|
SpecialMobs.LOG.info( "Starting translation key verification..." );
|
||||||
|
for( TranslationKey key : TranslationKey.values() ) {
|
||||||
|
if( !LANG_CODE_MAP.containsValue( key ) ) {
|
||||||
|
SpecialMobs.LOG.error( "Translation key {} has no lang codes assigned!", key.name() );
|
||||||
|
}
|
||||||
|
final int k = key.ordinal() + 1;
|
||||||
|
for( String[] translationArray : TRANSLATIONS ) {
|
||||||
|
if( translationArray[k] == null || translationArray[k].equals( "" ) ) {
|
||||||
|
SpecialMobs.LOG.error( "Translation key {} is missing a translation for lang key \"{}\"!",
|
||||||
|
key.name(), translationArray[0] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SpecialMobs.LOG.info( "Translation key verification complete!" );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Maps any number of locale codes to a single translation. */
|
||||||
|
private static void mapAll( TranslationKey translation, String... locales ) {
|
||||||
|
for( String locale : locales ) {
|
||||||
|
LANG_CODE_MAP.put( translation.code + "_" + locale, translation );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The translation key to use for this locale. */
|
||||||
|
private final TranslationKey translationKey;
|
||||||
|
|
||||||
|
/** Creates a language provider for a specific locale. This correlates to exactly one .json file. */
|
||||||
|
public SMLanguageProvider( DataGenerator gen, String locale, TranslationKey translateKey ) {
|
||||||
|
super( gen, SpecialMobs.MOD_ID, locale );
|
||||||
|
translationKey = translateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the .json file for this locale (based solely on its translation key).
|
||||||
|
*
|
||||||
|
* @see SMLanguageProvider#TRANSLATIONS
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void addTranslations() {
|
||||||
|
final int k = translationKey.ordinal() + 1;
|
||||||
|
for( String[] translationArray : TRANSLATIONS ) {
|
||||||
|
add( translationArray[0], translationArray[k] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package fathertoast.specialmobs.datagen;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import fathertoast.specialmobs.common.bestiary.MobFamily;
|
||||||
|
import fathertoast.specialmobs.common.core.register.SMEntities;
|
||||||
|
import fathertoast.specialmobs.common.util.AnnotationHelper;
|
||||||
|
import mcp.MethodsReturnNonnullByDefault;
|
||||||
|
import net.minecraft.advancements.criterion.EntityPredicate;
|
||||||
|
import net.minecraft.data.DataGenerator;
|
||||||
|
import net.minecraft.data.LootTableProvider;
|
||||||
|
import net.minecraft.data.loot.EntityLootTables;
|
||||||
|
import net.minecraft.entity.EntityType;
|
||||||
|
import net.minecraft.loot.*;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
import net.minecraftforge.fml.RegistryObject;
|
||||||
|
|
||||||
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
@MethodsReturnNonnullByDefault
|
||||||
|
public class SMLootTableProvider extends LootTableProvider {
|
||||||
|
|
||||||
|
public SMLootTableProvider( DataGenerator gen ) { super( gen ); }
|
||||||
|
|
||||||
|
/** Provides all loot table sub-providers for this mod, paired with their parameter sets (context for use). */
|
||||||
|
@Override
|
||||||
|
protected List<Pair<Supplier<Consumer<BiConsumer<ResourceLocation, LootTable.Builder>>>, LootParameterSet>> getTables() {
|
||||||
|
return ImmutableList.of(
|
||||||
|
Pair.of( EntitySubProvider::new, LootParameterSets.ENTITY )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Validates this mod's loot tables. */
|
||||||
|
@Override
|
||||||
|
protected void validate( Map<ResourceLocation, LootTable> tables, ValidationTracker ctx ) {
|
||||||
|
tables.forEach( ( name, table ) -> LootTableManager.validate( ctx, name, table ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Provides all entity loot tables for this mod. */
|
||||||
|
public static class EntitySubProvider extends EntityLootTables {
|
||||||
|
// Pull this protected field out into the Court of Public Opinion.
|
||||||
|
public static final EntityPredicate.Builder ENTITY_ON_FIRE = EntityLootTables.ENTITY_ON_FIRE;
|
||||||
|
|
||||||
|
/** Builds all loot tables for this provider. */
|
||||||
|
@Override
|
||||||
|
protected void addTables() {
|
||||||
|
// Bestiary-generated tables
|
||||||
|
for( MobFamily.Species<?> variant : MobFamily.getAllSpecies() ) addTable( variant );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Builds the loot table for a specific entity species. */
|
||||||
|
private void addTable( MobFamily.Species<?> species ) {
|
||||||
|
try {
|
||||||
|
add( species.entityType.get(), AnnotationHelper.buildLootTable( species ).toLootTable() );
|
||||||
|
}
|
||||||
|
catch( NoSuchMethodException | InvocationTargetException | IllegalAccessException ex ) {
|
||||||
|
throw new RuntimeException( "Entity class for " + species.name + " has invalid loot table builder method", ex );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Supplies the entity types this loot table provider will be used for. */
|
||||||
|
@Override
|
||||||
|
protected Iterable<EntityType<?>> getKnownEntities() {
|
||||||
|
// This is basically pulled straight from the forge docs on data gen for block/entity loot tables
|
||||||
|
return SMEntities.REGISTRY.getEntries().stream().flatMap( RegistryObject::stream )::iterator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
package fathertoast.specialmobs.datagen.loot;
|
||||||
|
|
||||||
|
import fathertoast.specialmobs.datagen.SMLootTableProvider;
|
||||||
|
import net.minecraft.enchantment.Enchantment;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.loot.ItemLootEntry;
|
||||||
|
import net.minecraft.loot.LootContext;
|
||||||
|
import net.minecraft.loot.LootEntry;
|
||||||
|
import net.minecraft.loot.RandomValueRange;
|
||||||
|
import net.minecraft.loot.conditions.EntityHasProperty;
|
||||||
|
import net.minecraft.loot.conditions.ILootCondition;
|
||||||
|
import net.minecraft.loot.functions.*;
|
||||||
|
import net.minecraft.nbt.CompoundNBT;
|
||||||
|
import net.minecraft.util.IItemProvider;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@SuppressWarnings( { "unused", "WeakerAccess", "UnusedReturnValue" } )
|
||||||
|
public
|
||||||
|
class LootEntryItemBuilder {
|
||||||
|
|
||||||
|
private final IItemProvider item;
|
||||||
|
|
||||||
|
private int weight = 1;
|
||||||
|
private int quality = 0;
|
||||||
|
|
||||||
|
private final List<ILootFunction.IBuilder> itemFunctions = new ArrayList<>();
|
||||||
|
private final List<ILootCondition.IBuilder> entryConditions = new ArrayList<>();
|
||||||
|
|
||||||
|
public LootEntryItemBuilder( IItemProvider baseItem ) {
|
||||||
|
item = baseItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LootEntryItemBuilder( ItemStack itemStack ) {
|
||||||
|
this( itemStack.getItem() );
|
||||||
|
if( itemStack.getTag() != null ) {
|
||||||
|
setNBTTag( itemStack.getTag().copy() );
|
||||||
|
}
|
||||||
|
// if( !itemStack.isDamageableItem() && itemStack.getMetadata() != 0 ) {
|
||||||
|
// setMetadata( itemStack.getMetadata() );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return A new loot entry object reflecting the current state of this builder. */
|
||||||
|
public LootEntry.Builder<?> toLootEntry() {
|
||||||
|
return LootHelper.build( ItemLootEntry.lootTableItem( item ), entryConditions, itemFunctions )
|
||||||
|
.setWeight( weight ).setQuality( quality );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param value A new weight for the loot entry. */
|
||||||
|
public LootEntryItemBuilder setWeight( int value ) {
|
||||||
|
weight = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param value A new quality for the loot entry. Quality alters the weight of this entry based on luck level. */
|
||||||
|
public LootEntryItemBuilder setQuality( int value ) {
|
||||||
|
quality = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param condition A condition to add to this builder. */
|
||||||
|
public LootEntryItemBuilder addCondition( ILootCondition.IBuilder condition ) {
|
||||||
|
entryConditions.add( condition );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a stack size function. */
|
||||||
|
public LootEntryItemBuilder setCount( int value ) {
|
||||||
|
return addFunction( SetCount.setCount( new RandomValueRange( value ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a stack size function. */
|
||||||
|
public LootEntryItemBuilder setCount( int min, int max ) {
|
||||||
|
return addFunction( SetCount.setCount( new RandomValueRange( min, max ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a looting enchant (luck) bonus function. Gross. */
|
||||||
|
public LootEntryItemBuilder addLootingBonus( float value ) {
|
||||||
|
return addFunction( LootingEnchantBonus.lootingMultiplier( new RandomValueRange( value ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a looting enchant (luck) bonus function. Gross. */
|
||||||
|
public LootEntryItemBuilder addLootingBonus( float min, float max ) {
|
||||||
|
return addFunction( LootingEnchantBonus.lootingMultiplier( new RandomValueRange( min, max ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a looting enchant (luck) bonus function. Gross. */
|
||||||
|
public LootEntryItemBuilder addLootingBonus( float min, float max, int limit ) {
|
||||||
|
return addFunction( LootingEnchantBonus.lootingMultiplier( new RandomValueRange( min, max ) ).setLimit( limit ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a set damage function. */
|
||||||
|
public LootEntryItemBuilder setDamage( int value ) {
|
||||||
|
return addFunction( SetDamage.setDamage( new RandomValueRange( value ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a set damage function. */
|
||||||
|
public LootEntryItemBuilder setDamage( int min, int max ) {
|
||||||
|
return addFunction( SetDamage.setDamage( new RandomValueRange( min, max ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// /** Adds a set metadata function. */
|
||||||
|
// public LootEntryItemBuilder setMetadata( int min, int max ) {
|
||||||
|
// return addFunction( new SetMetadata( NO_CONDITIONS, new RandomValueRange( min, max ) ) );
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /** Adds a set metadata function. */
|
||||||
|
// public LootEntryItemBuilder setMetadata( int value ) {
|
||||||
|
// return addFunction( new SetMetadata( NO_CONDITIONS, new RandomValueRange( value ) ) );
|
||||||
|
// }
|
||||||
|
|
||||||
|
/** Adds an nbt tag compound function. */
|
||||||
|
public LootEntryItemBuilder setNBTTag( CompoundNBT tag ) { return addFunction( SetNBT.setTag( tag ) ); }
|
||||||
|
|
||||||
|
/** Adds a smelt function with the EntityOnFire condition. */
|
||||||
|
public LootEntryItemBuilder smeltIfBurning() {
|
||||||
|
return addFunction( Smelt.smelted().when( EntityHasProperty.hasProperties( LootContext.EntityTarget.THIS,
|
||||||
|
SMLootTableProvider.EntitySubProvider.ENTITY_ON_FIRE ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a random enchantment function. */
|
||||||
|
public LootEntryItemBuilder applyOneRandomApplicableEnchant() {
|
||||||
|
return addFunction( EnchantRandomly.randomApplicableEnchantment() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a random enchantment function. */
|
||||||
|
public LootEntryItemBuilder applyOneRandomEnchant( Enchantment... enchantments ) {
|
||||||
|
final EnchantRandomly.Builder builder = new EnchantRandomly.Builder();
|
||||||
|
for( Enchantment enchant : enchantments ) builder.withEnchantment( enchant );
|
||||||
|
return addFunction( builder );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds an enchanting function. */
|
||||||
|
public LootEntryItemBuilder enchant( int level, boolean treasure ) {
|
||||||
|
final EnchantWithLevels.Builder builder = EnchantWithLevels.enchantWithLevels( new RandomValueRange( level ) );
|
||||||
|
if( treasure ) builder.allowTreasure();
|
||||||
|
return addFunction( builder );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds an enchanting function. */
|
||||||
|
public LootEntryItemBuilder enchant( int levelMin, int levelMax, boolean treasure ) {
|
||||||
|
final EnchantWithLevels.Builder builder = EnchantWithLevels.enchantWithLevels( new RandomValueRange( levelMin, levelMax ) );
|
||||||
|
if( treasure ) builder.allowTreasure();
|
||||||
|
return addFunction( builder );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds an item function to this builder. */
|
||||||
|
public LootEntryItemBuilder addFunction( ILootFunction.IBuilder function ) {
|
||||||
|
itemFunctions.add( function );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package fathertoast.specialmobs.datagen.loot;
|
||||||
|
|
||||||
|
import net.minecraft.loot.*;
|
||||||
|
import net.minecraft.loot.conditions.ILootCondition;
|
||||||
|
import net.minecraft.loot.conditions.KilledByPlayer;
|
||||||
|
import net.minecraft.loot.conditions.RandomChanceWithLooting;
|
||||||
|
import net.minecraft.loot.functions.ILootFunction;
|
||||||
|
|
||||||
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
public class LootHelper {
|
||||||
|
|
||||||
|
public static final ILootCondition.IBuilder KILLED_BY_PLAYER_CONDITION = KilledByPlayer.killedByPlayer();
|
||||||
|
|
||||||
|
public static final ILootCondition.IBuilder[] UNCOMMON_CONDITIONS = {
|
||||||
|
RandomChanceWithLooting.randomChanceAndLootingBoost( 0.25F, 0.05F ),
|
||||||
|
KILLED_BY_PLAYER_CONDITION
|
||||||
|
};
|
||||||
|
public static final ILootCondition.IBuilder[] RARE_CONDITIONS = {
|
||||||
|
RandomChanceWithLooting.randomChanceAndLootingBoost( 0.025F, 0.0F ),
|
||||||
|
KILLED_BY_PLAYER_CONDITION
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final RandomValueRange ONE_ROLL = new RandomValueRange( 1 );
|
||||||
|
public static final RandomValueRange NO_ROLL = new RandomValueRange( 0 );
|
||||||
|
|
||||||
|
/** Convenience method to put all loot entries, conditions, and functions into a loot pool. Returns the loot pool. */
|
||||||
|
public static LootPool.Builder build( LootPool.Builder builder, List<LootEntry.Builder<?>> entries,
|
||||||
|
List<ILootCondition.IBuilder> conditions, List<ILootFunction.IBuilder> functions ) {
|
||||||
|
return build( build( builder, entries ), conditions, functions );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convenience method to put all loot conditions and functions into a loot builder. Returns the loot builder. */
|
||||||
|
public static <T extends ILootFunctionConsumer<?>> // Can't figure out how to require both, but function consumer is more stringent
|
||||||
|
T build( T builder, List<ILootCondition.IBuilder> conditions, List<ILootFunction.IBuilder> functions ) {
|
||||||
|
build( (ILootConditionConsumer<?>) builder, conditions );
|
||||||
|
return build( builder, functions );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convenience method to put all loot pools into a loot table. Returns the loot table. */
|
||||||
|
public static LootTable.Builder build( LootTable.Builder builder, List<LootPool.Builder> pools ) {
|
||||||
|
for( LootPool.Builder pool : pools ) builder.withPool( pool );
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convenience method to put all loot entries into a loot pool. Returns the loot pool. */
|
||||||
|
public static LootPool.Builder build( LootPool.Builder builder, List<LootEntry.Builder<?>> entries ) {
|
||||||
|
for( LootEntry.Builder<?> entry : entries ) builder.add( entry );
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convenience method to put all loot conditions into a loot builder. Returns the loot builder. */
|
||||||
|
public static <T extends ILootConditionConsumer<?>> T build( T builder, List<ILootCondition.IBuilder> conditions ) {
|
||||||
|
for( ILootCondition.IBuilder condition : conditions ) builder.when( condition );
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convenience method to put all loot functions into a loot builder. Returns the loot builder. */
|
||||||
|
public static <T extends ILootFunctionConsumer<?>> T build( T builder, List<ILootFunction.IBuilder> functions ) {
|
||||||
|
for( ILootFunction.IBuilder function : functions ) builder.apply( function );
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package fathertoast.specialmobs.datagen.loot;
|
||||||
|
|
||||||
|
import net.minecraft.loot.*;
|
||||||
|
import net.minecraft.loot.conditions.ILootCondition;
|
||||||
|
import net.minecraft.loot.functions.ILootFunction;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@SuppressWarnings( { "unused", "WeakerAccess", "UnusedReturnValue" } )
|
||||||
|
public
|
||||||
|
class LootPoolBuilder {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private RandomValueRange rolls = LootHelper.ONE_ROLL;
|
||||||
|
private RandomValueRange bonusRolls = LootHelper.NO_ROLL;
|
||||||
|
|
||||||
|
private final List<LootEntry.Builder<?>> lootEntries = new ArrayList<>();
|
||||||
|
private final List<ILootCondition.IBuilder> poolConditions = new ArrayList<>();
|
||||||
|
private final List<ILootFunction.IBuilder> poolFunctions = new ArrayList<>();
|
||||||
|
|
||||||
|
public LootPoolBuilder( String id ) { name = id; }
|
||||||
|
|
||||||
|
/** @return A new loot pool object reflecting the current state of this builder. */
|
||||||
|
public LootPool.Builder toLootPool() {
|
||||||
|
return LootHelper.build( LootPool.lootPool(), lootEntries, poolConditions, poolFunctions )
|
||||||
|
.setRolls( rolls ).bonusRolls( bonusRolls.getMin(), bonusRolls.getMax() ).name( name );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param value The number of rolls for this pool. */
|
||||||
|
public LootPoolBuilder setRolls( float value ) {
|
||||||
|
rolls = new RandomValueRange( value );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param min Minimum rolls for this pool (inclusive).
|
||||||
|
* @param max Maximum rolls for this pool (inclusive).
|
||||||
|
*/
|
||||||
|
public LootPoolBuilder setRolls( float min, float max ) {
|
||||||
|
rolls = new RandomValueRange( min, max );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param value The additional rolls for each level of looting. */
|
||||||
|
public LootPoolBuilder setBonusRolls( float value ) {
|
||||||
|
bonusRolls = new RandomValueRange( value );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param min Minimum additional rolls for this pool for each level of looting (inclusive).
|
||||||
|
* @param max Maximum additional rolls for this pool for each level of looting (inclusive).
|
||||||
|
*/
|
||||||
|
public LootPoolBuilder setBonusRolls( float min, float max ) {
|
||||||
|
bonusRolls = new RandomValueRange( min, max );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param entry A loot entry to add to this builder. */
|
||||||
|
public LootPoolBuilder addEntry( LootEntry.Builder<?> entry ) {
|
||||||
|
lootEntries.add( entry );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param conditions Any number of conditions to add to this builder. */
|
||||||
|
public LootPoolBuilder addConditions( ILootCondition.IBuilder... conditions ) {
|
||||||
|
poolConditions.addAll( Arrays.asList( conditions ) );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param functions Any number of functions to add to this builder. */
|
||||||
|
public LootPoolBuilder addFunctions( ILootFunction.IBuilder... functions ) {
|
||||||
|
poolFunctions.addAll( Arrays.asList( functions ) );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a loot table as a drop. */
|
||||||
|
public LootPoolBuilder addEntryTable( ResourceLocation lootTable ) {
|
||||||
|
return addEntryTable( lootTable, 1, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a loot table as a drop. */
|
||||||
|
public LootPoolBuilder addEntryTable( ResourceLocation lootTable, int weight, int quality, ILootCondition.IBuilder... conditions ) {
|
||||||
|
final StandaloneLootEntry.Builder<?> builder = TableLootEntry.lootTableReference( lootTable );
|
||||||
|
// Note: we can also extend this to allow functions, if needed
|
||||||
|
for( ILootCondition.IBuilder condition : conditions ) builder.when( condition );
|
||||||
|
return addEntry( builder.setWeight( weight ).setQuality( quality ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds an empty entry. */
|
||||||
|
public LootPoolBuilder addEntryEmpty( int weight, int quality, ILootCondition.IBuilder... conditions ) {
|
||||||
|
StandaloneLootEntry.Builder<?> builder = EmptyLootEntry.emptyItem();
|
||||||
|
for( ILootCondition.IBuilder condition : conditions ) builder.when( condition );
|
||||||
|
return addEntry( builder.setWeight( weight ).setQuality( quality ) );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
package fathertoast.specialmobs.datagen.loot;
|
||||||
|
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.loot.LootPool;
|
||||||
|
import net.minecraft.loot.LootTable;
|
||||||
|
import net.minecraft.util.IItemProvider;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for the vanilla loot table builder, largely a holdover from before the vanilla loot table builders existed,
|
||||||
|
* but still offers many convenience methods for simple and standardized loot table construction.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( { "unused", "WeakerAccess", "UnusedReturnValue" } )
|
||||||
|
public class LootTableBuilder {
|
||||||
|
|
||||||
|
private final List<LootPool.Builder> pools = new ArrayList<>();
|
||||||
|
//private final List<ILootFunction.IBuilder> tableFunctions = new ArrayList<>(); - Can be implemented if needed
|
||||||
|
|
||||||
|
/** @return A new loot table object reflecting the current state of this builder. */
|
||||||
|
public LootTable.Builder toLootTable() { return LootHelper.build( LootTable.lootTable(), pools ); }
|
||||||
|
|
||||||
|
/** @param pool A loot pool to add to this builder. */
|
||||||
|
public LootTableBuilder addPool( LootPool.Builder pool ) {
|
||||||
|
pools.add( pool );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a pool referencing a loot table. */
|
||||||
|
public LootTableBuilder addLootTable( String id, ResourceLocation lootTable ) {
|
||||||
|
return addPool( new LootPoolBuilder( id ).addEntryTable( lootTable ).toLootPool() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a pool with a single item drop. */
|
||||||
|
public LootTableBuilder addGuaranteedDrop( String id, IItemProvider item, int count ) {
|
||||||
|
return addPool( new LootPoolBuilder( id )
|
||||||
|
.addEntry( new LootEntryItemBuilder( item ).setCount( count ).toLootEntry() )
|
||||||
|
.toLootPool() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a pool with an item drop of 0-2 + (0-1 * luck). */
|
||||||
|
public LootTableBuilder addCommonDrop( String id, IItemProvider item ) { return addCommonDrop( id, item, 2 ); }
|
||||||
|
|
||||||
|
/** Adds a pool with an item drop of 0-2 + (0-1 * luck). */
|
||||||
|
public LootTableBuilder addCommonDrop( String id, ItemStack item ) { return addCommonDrop( id, item, 2 ); }
|
||||||
|
|
||||||
|
/** Adds a pool with an item drop of 0-max + (0-1 * luck). */
|
||||||
|
public LootTableBuilder addCommonDrop( String id, IItemProvider item, int max ) {
|
||||||
|
return addPool( new LootPoolBuilder( id )
|
||||||
|
.addEntry( new LootEntryItemBuilder( item ).setCount( 0, max ).addLootingBonus( 0, 1 ).toLootEntry() )
|
||||||
|
.toLootPool() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a pool with an item drop of 0-max + (0-1 * luck). */
|
||||||
|
public LootTableBuilder addCommonDrop( String id, ItemStack item, int max ) {
|
||||||
|
return addPool( new LootPoolBuilder( id )
|
||||||
|
.addEntry( new LootEntryItemBuilder( item ).setCount( 0, max ).addLootingBonus( 0, 1 ).toLootEntry() )
|
||||||
|
.toLootPool() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a pool with an item drop of (-1)-1 + (0-1 * luck). */
|
||||||
|
public LootTableBuilder addSemicommonDrop( String id, IItemProvider item ) {
|
||||||
|
return addPool( new LootPoolBuilder( id )
|
||||||
|
.addConditions( LootHelper.KILLED_BY_PLAYER_CONDITION )
|
||||||
|
.addEntry( new LootEntryItemBuilder( item ).setCount( -1, 1 ).addLootingBonus( 0, 1 ).toLootEntry() )
|
||||||
|
.toLootPool() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a pool with an item drop of (-1)-1 + (0-1 * luck). */
|
||||||
|
public LootTableBuilder addSemicommonDrop( String id, ItemStack item ) {
|
||||||
|
return addPool( new LootPoolBuilder( id )
|
||||||
|
.addConditions( LootHelper.KILLED_BY_PLAYER_CONDITION )
|
||||||
|
.addEntry( new LootEntryItemBuilder( item ).setCount( -1, 1 ).addLootingBonus( 0, 1 ).toLootEntry() )
|
||||||
|
.toLootPool() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a pool with an item drop of 1-8 and chance of 25% + (5% * luck). */
|
||||||
|
public LootTableBuilder addClusterDrop( String id, IItemProvider item ) { return addClusterDrop( id, item, 8 ); }
|
||||||
|
|
||||||
|
/** Adds a pool with an item drop of 1-8 and chance of 25% + (5% * luck). */
|
||||||
|
public LootTableBuilder addClusterDrop( String id, ItemStack item ) { return addClusterDrop( id, item, 8 ); }
|
||||||
|
|
||||||
|
/** Adds a pool with an item drop of 1-max and chance of 25% + (5% * luck). */
|
||||||
|
public LootTableBuilder addClusterDrop( String id, IItemProvider item, int max ) {
|
||||||
|
return addPool( new LootPoolBuilder( id )
|
||||||
|
.addConditions( LootHelper.UNCOMMON_CONDITIONS )
|
||||||
|
.addEntry( new LootEntryItemBuilder( item ).setCount( 1, max ).toLootEntry() )
|
||||||
|
.toLootPool() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a pool with an item drop of 1-max and chance of 25% + (5% * luck). */
|
||||||
|
public LootTableBuilder addClusterDrop( String id, ItemStack item, int max ) {
|
||||||
|
return addPool( new LootPoolBuilder( id )
|
||||||
|
.addConditions( LootHelper.UNCOMMON_CONDITIONS )
|
||||||
|
.addEntry( new LootEntryItemBuilder( item ).setCount( 1, max ).toLootEntry() )
|
||||||
|
.toLootPool() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a pool with a single item drop (from a list) and chance of 25% + (5% * luck). */
|
||||||
|
public LootTableBuilder addUncommonDrop( String id, IItemProvider... items ) {
|
||||||
|
LootPoolBuilder pool = new LootPoolBuilder( id ).addConditions( LootHelper.UNCOMMON_CONDITIONS );
|
||||||
|
for( IItemProvider item : items ) {
|
||||||
|
pool.addEntry( new LootEntryItemBuilder( item ).toLootEntry() );
|
||||||
|
}
|
||||||
|
return addPool( pool.toLootPool() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a pool with a single item drop (from a list) and chance of 25% + (5% * luck).
|
||||||
|
* Item stack size is used as the weight of each item.
|
||||||
|
*/
|
||||||
|
public LootTableBuilder addUncommonDrop( String id, ItemStack... items ) {
|
||||||
|
LootPoolBuilder pool = new LootPoolBuilder( id ).addConditions( LootHelper.UNCOMMON_CONDITIONS );
|
||||||
|
for( ItemStack item : items ) {
|
||||||
|
pool.addEntry( new LootEntryItemBuilder( item ).setWeight( item.getCount() ).toLootEntry() );
|
||||||
|
}
|
||||||
|
return addPool( pool.toLootPool() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a pool with a single item drop (from a list) and chance of 2.5% + (0% * luck). */
|
||||||
|
public LootTableBuilder addRareDrop( String id, IItemProvider... items ) {
|
||||||
|
LootPoolBuilder pool = new LootPoolBuilder( id ).addConditions( LootHelper.RARE_CONDITIONS );
|
||||||
|
for( IItemProvider item : items ) {
|
||||||
|
pool.addEntry( new LootEntryItemBuilder( item ).toLootEntry() );
|
||||||
|
}
|
||||||
|
return addPool( pool.toLootPool() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a pool with a single item drop (from a list) and chance of 2.5% + (0% * luck).
|
||||||
|
* Item stack size is used as the weight of each item.
|
||||||
|
*/
|
||||||
|
public LootTableBuilder addRareDrop( String id, ItemStack... items ) {
|
||||||
|
LootPoolBuilder pool = new LootPoolBuilder( id ).addConditions( LootHelper.RARE_CONDITIONS );
|
||||||
|
for( ItemStack item : items ) {
|
||||||
|
pool.addEntry( new LootEntryItemBuilder( item ).setWeight( item.getCount() ).toLootEntry() );
|
||||||
|
}
|
||||||
|
return addPool( pool.toLootPool() );
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,12 @@
|
||||||
# Note: Things that can be safely transformed are things that cannot be overridden - final methods & classes,
|
# Note: Things that can be safely transformed are things that cannot be overridden - final methods & classes,
|
||||||
# anything inside a final class, and private and/or static fields & methods.
|
# anything inside a final class, and private and/or static fields & methods.
|
||||||
|
|
||||||
|
# Entity registration
|
||||||
|
public net.minecraft.entity.EntityType field_233593_bg_ # immuneTo
|
||||||
|
|
||||||
|
# Creepers
|
||||||
|
protected 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 field_82226_g # explosionRadius
|
||||||
protected net.minecraft.entity.monster.CreeperEntity func_146077_cc()V # explodeCreeper
|
protected net.minecraft.entity.monster.CreeperEntity func_146077_cc()V # explodeCreeper()
|
||||||
protected net.minecraft.entity.monster.CreeperEntity func_190741_do()V # spawnLingeringCloud
|
protected net.minecraft.entity.monster.CreeperEntity func_190741_do()V # spawnLingeringCloud()
|
Loading…
Add table
Reference in a new issue