Merge branch '1.16.5' of https://github.com/FatherToast/SpecialMobs into 1.16.5

 Conflicts:
	src/main/java/fathertoast/specialmobs/common/entity/projectile/IncorporealFireballEntity.java
This commit is contained in:
Sarinsa 2022-08-27 03:05:47 +02:00
commit 041cfa3bb7
239 changed files with 1326 additions and 255 deletions

View file

@ -8,6 +8,7 @@ import fathertoast.specialmobs.client.renderer.entity.species.*;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.config.Config;
import fathertoast.specialmobs.common.core.SpecialMobs;
import fathertoast.specialmobs.common.core.register.SMBlocks;
import fathertoast.specialmobs.common.core.register.SMEntities;
import fathertoast.specialmobs.common.entity.creeper.EnderCreeperEntity;
import fathertoast.specialmobs.common.entity.enderman.RunicEndermanEntity;
@ -20,6 +21,8 @@ import fathertoast.specialmobs.common.entity.zombie.MadScientistZombieEntity;
import fathertoast.specialmobs.common.entity.zombifiedpiglin.VampireZombifiedPiglinEntity;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ItemRenderer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.RenderTypeLookup;
import net.minecraft.client.renderer.entity.SpriteRenderer;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
@ -43,6 +46,8 @@ public class ClientRegister {
@SubscribeEvent
public static void onClientSetup( FMLClientSetupEvent event ) {
RenderTypeLookup.setRenderLayer( SMBlocks.MELTING_ICE.get(), RenderType.translucent() );
if( Config.MAIN.GENERAL.fancyFishingMobs.get() ) {
ItemModelsProperties.register( Items.FISHING_ROD, new ResourceLocation( "cast" ), new FishingRodItemPropertyGetter() );
}

View file

@ -43,16 +43,31 @@ public class BestiaryInfo {
NONE( new EnvironmentList() ),
FIRE( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inUltraWarmDimension().build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).isRaining().canSeeSky().notInDryBiome().build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).isHot().build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).isWarm().build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).isFreezing().build()
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).isFreezing().build(),
// Regular frozen ocean is actually freezing, so already covered
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inBiome( Biomes.WARM_OCEAN ).build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inBiome( Biomes.DEEP_WARM_OCEAN ).build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).inBiome( Biomes.LUKEWARM_OCEAN ).build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).inBiome( Biomes.DEEP_LUKEWARM_OCEAN ).build(),
EnvironmentEntry.builder( DefaultWeight.LOW.value ).inBiome( Biomes.COLD_OCEAN ).build(),
EnvironmentEntry.builder( DefaultWeight.LOW.value ).inBiome( Biomes.DEEP_COLD_OCEAN ).build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inBiome( Biomes.DEEP_FROZEN_OCEAN ).build()
) ),
ICE( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inUltraWarmDimension().build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).isFreezing().build(),
EnvironmentEntry.builder( DefaultWeight.LOW.value ).isWarm().build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).isHot().build()
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).isHot().build(),
// Regular frozen ocean is actually freezing, so already covered
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inBiome( Biomes.DEEP_FROZEN_OCEAN ).build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).inBiome( Biomes.COLD_OCEAN ).build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).inBiome( Biomes.DEEP_COLD_OCEAN ).build(),
EnvironmentEntry.builder( DefaultWeight.LOW.value ).inBiome( Biomes.LUKEWARM_OCEAN ).build(),
EnvironmentEntry.builder( DefaultWeight.LOW.value ).inBiome( Biomes.DEEP_LUKEWARM_OCEAN ).build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inBiome( Biomes.WARM_OCEAN ).build(),
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inBiome( Biomes.DEEP_WARM_OCEAN ).build()
) ),
DESERT( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inUltraWarmDimension().build(),
@ -89,6 +104,13 @@ public class BestiaryInfo {
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).isRaining().build(),
EnvironmentEntry.builder( DefaultWeight.LOW.value ).cannotSeeSky().build()
) ),
TROPICAL( new EnvironmentList(
// All ocean biomes (except regular frozen ocean) have the same temp of 0.5, so we must call out specific biomes
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inBiome( Biomes.WARM_OCEAN ).build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inBiome( Biomes.DEEP_WARM_OCEAN ).build(),
EnvironmentEntry.builder( DefaultWeight.DISABLED.value ).isFreezing().build(),
EnvironmentEntry.builder( DefaultWeight.DISABLED.value ).inBiome( Biomes.DEEP_FROZEN_OCEAN ).build()
) ),
FISHING( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inWaterBiome().build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).atMaxMoonLight().build(),

View file

@ -13,6 +13,7 @@ import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.monster.*;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeSpawnEggItem;
import net.minecraftforge.fml.RegistryObject;
@ -20,6 +21,7 @@ import net.minecraftforge.fml.RegistryObject;
import javax.annotation.Nullable;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* Special mobs are broken up into distinct 'families', each of which correspond to a type of vanilla mob that can be
@ -42,7 +44,7 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
"Creeper", "creepers", 0x0DA70B, new EntityType[] { EntityType.CREEPER },
"Dark", "Death", "Dirt", "Doom", "Drowning", "Ender", "Fire", "Gravel", "Jumping", "Lightning",
"Mini", "Sand", /*"Scope",*/ "Snow", "Skeleton", "Splitting"
);//TODO scope
);//TODO scope - maybe in 1.18 when spyglasses exist
public static final MobFamily<ZombieEntity, FamilyConfig> ZOMBIE = new MobFamily<>( FamilyConfig::newLessSpecial,
"Zombie", "zombies", 0x00AFAF, new EntityType[] { EntityType.ZOMBIE, EntityType.HUSK },
@ -50,12 +52,12 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
);
public static final MobFamily<DrownedEntity, FamilyConfig> DROWNED = new MobFamily<>( FamilyConfig::new,
"Drowned", "drowned", 0x8FF1D7, new EntityType[] { EntityType.DROWNED },
"Abyssal", "Brute", "Fishing", /*"Frozen",*/ "Giant", "Hungry", "Knight", "Plague"//, "Tropical"
); //TODO Textures! - brute, hungry, plague, frozen, tropical
"Abyssal", "Brute", "Fishing", "Frozen", "Giant", "Hungry", "Knight", "Plague"//, "Tropical"
);
public static final MobFamily<ZombifiedPiglinEntity, FamilyConfig> ZOMBIFIED_PIGLIN = new MobFamily<>( FamilyConfig::new,
"ZombifiedPiglin", "zombified piglins", 0xEA9393, new EntityType[] { EntityType.ZOMBIFIED_PIGLIN },
"Brute", "Fishing", "Giant", "Hungry", "Knight", "Plague", "Vampire"//TODO figure out crossbows
);
);//TODO crimson/warped
public static final MobFamily<AbstractSkeletonEntity, SkeletonFamilyConfig> SKELETON = new MobFamily<>( SkeletonFamilyConfig::new,
"Skeleton", "skeletons", 0xC1C1C1, new EntityType[] { EntityType.SKELETON, EntityType.STRAY },
@ -64,7 +66,7 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
public static final MobFamily<AbstractSkeletonEntity, SkeletonFamilyConfig> WITHER_SKELETON = new MobFamily<>( SkeletonFamilyConfig::new,
"WitherSkeleton", "wither skeletons", 0x141414, new EntityType[] { EntityType.WITHER_SKELETON },
"Brute", "Gatling", "Giant", "Knight", "Ninja", "Sniper", "Spitfire"
);
);//TODO crimson/warped
public static final MobFamily<SlimeEntity, SlimeFamilyConfig> SLIME = new MobFamily<>( SlimeFamilyConfig::new,
"Slime", "slimes", 0x51A03E, new EntityType[] { EntityType.SLIME },
@ -96,8 +98,8 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
public static final MobFamily<WitchEntity, WitchFamilyConfig> WITCH = new MobFamily<>( WitchFamilyConfig::new,
"Witch", "witches", 0x340000, new EntityType[] { EntityType.WITCH },
"Domination", "Shadows", "Undead", "Wilds", "Wind"
);
/*"Burned",*/ "Domination", /*"Drowned", "Ice", "Sands",*/ "Shadows", "Undead", "Wilds", "Wind"
);//TODO burned, drowned, ice, sands
public static final MobFamily<GhastEntity, GhastFamilyConfig> GHAST = new MobFamily<>( GhastFamilyConfig::new,
"Ghast", "ghasts", 0xF9F9F9, new EntityType[] { EntityType.GHAST },
@ -169,6 +171,9 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
/** This family's config. */
public final V config;
/** True if this family has any giant species. */
private Boolean hasAnyGiants;
private MobFamily( Function<MobFamily<?, ?>, V> configSupplier,
String familyName, String readableName, int eggColor, EntityType<?>[] replaceable,
String... variantNames ) {
@ -196,17 +201,34 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
}
/** Pick a new species from this family, based on the location. */
public Species<? extends T> nextVariant( World world, @Nullable BlockPos pos ) {
return nextVariant( world, pos, null, vanillaReplacement );
public Species<? extends T> nextVariant( World world, @Nullable BlockPos pos, @Nullable Predicate<Species<?>> selector ) {
return nextVariant( world, pos, selector, vanillaReplacement );
}
/** Pick a new species from this family, based on the location. */
public Species<? extends T> nextVariant( World world, @Nullable BlockPos pos, @Nullable Function<Species<?>, Boolean> selector, Species<? extends T> fallback ) {
public Species<? extends T> nextVariant( World world, @Nullable BlockPos pos, @Nullable Predicate<Species<?>> selector, Species<? extends T> fallback ) {
final Species<?> species = config.GENERAL.specialVariantList.next( world.random, world, pos, selector );
//noinspection unchecked
return species == null ? fallback : (Species<? extends T>) species;
}
/**
* @return True if this species is NOT taller (in block height) than the base vanilla entity.
* Used to reduce likelihood of suffocation due to Mob Replacement.
*/
public boolean hasAnyGiants() {
if( hasAnyGiants == null ) {
hasAnyGiants = false;
for( Species<?> species : variants ) {
if( !species.isNotGiant() ) {
hasAnyGiants = true;
break;
}
}
}
return hasAnyGiants;
}
//--------------- Species Instance Implementations ----------------
@ -250,6 +272,8 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
/** This species's config. */
public final SpeciesConfig config;
/** True if this mob is NOT taller (in block height) than the base vanilla entity. */
private Boolean isNotGiant;
/** The scale of this species's height in relation to the base vanilla entity's height. */
private float heightScale = -1.0F;
@ -331,6 +355,17 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
ConfigUtil.camelCaseToLowerSpace( specialVariantName ) + " ") + ConfigUtil.camelCaseToLowerSpace( family.name );
}
/**
* @return True if this species is NOT taller (in block height) than the base vanilla entity.
* Used to reduce likelihood of suffocation due to Mob Replacement.
*/
public boolean isNotGiant() {
if( isNotGiant == null ) {
isNotGiant = MathHelper.ceil( entityType.get().getHeight() ) <= MathHelper.ceil( family.replaceableTypes[0].getHeight() );
}
return isNotGiant;
}
/** @return The height scale. Used to calculate eye height for families that are not auto-scaled. */
public float getHeightScale() {
if( heightScale < 0.0F ) {

View file

@ -0,0 +1,178 @@
package fathertoast.specialmobs.common.block;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.core.register.SMBlocks;
import fathertoast.specialmobs.common.util.References;
import net.minecraft.block.*;
import net.minecraft.block.material.Material;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.enchantment.Enchantments;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.stats.Stats;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.LightType;
import net.minecraft.world.World;
import net.minecraft.world.server.ServerWorld;
import javax.annotation.Nullable;
import java.util.Random;
public class MeltingIceBlock extends IceBlock {
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Melting Ice",
"", "", "", "", "", "" );//TODO
}
/** @return The state that should be placed. */
public static BlockState getState( World world, BlockPos pos ) {
final BlockState currentBlock = world.getBlockState( pos );
return SMBlocks.MELTING_ICE.get().defaultBlockState().setValue( HAS_WATER,
currentBlock.is( Blocks.FROSTED_ICE ) ||
currentBlock.getBlock() == Blocks.WATER && currentBlock.getValue( FlowingFluidBlock.LEVEL ) == 0 );
}
/** Call this after placing a melting ice block to trigger its melting logic. */
public static void scheduleFirstTick( World world, BlockPos pos, Random random ) {
world.getBlockTicks().scheduleTick( pos, SMBlocks.MELTING_ICE.get(), MathHelper.nextInt( random, 60, 120 ) );
}
/** Called after each melt logic tick to schedule the next tick. */
private void scheduleTick( World world, BlockPos pos, Random random ) {
final int darkness = 15 - getLight( world, pos );
int solidNeighbors = 0;
final BlockPos.Mutable neighborPos = new BlockPos.Mutable();
for( Direction direction : Direction.Plane.HORIZONTAL ) {
if( world.getBlockState( neighborPos.setWithOffset( pos, direction ) ).getMaterial().isSolid() ) {
solidNeighbors++;
}
}
// The 'neutral' state is 0 block light and 0 solid neighbors - this gives the same tick rate as frosted ice (1-2s)
// Max delay is same as the default 'first tick' delay (3-6s)
final int delay = 5 + darkness + 10 * solidNeighbors;
world.getBlockTicks().scheduleTick( pos, this, MathHelper.nextInt( random, delay, delay << 1 ) );
}
/** @return The light level touching this block (0-15). We use this method because the block is solid. */
private static int getLight( World world, BlockPos pos ) {
int highestLight = 0;
final BlockPos.Mutable neighborPos = new BlockPos.Mutable();
for( Direction direction : Direction.values() ) {
final int neighborLight = world.getBrightness( LightType.BLOCK, neighborPos.setWithOffset( pos, direction ) );
if( neighborLight > 14 ) return 15;
if( neighborLight > highestLight ) highestLight = neighborLight;
}
return highestLight;
}
public static final IntegerProperty AGE = BlockStateProperties.AGE_3;
public static final BooleanProperty HAS_WATER = BooleanProperty.create( "has_water" );
public MeltingIceBlock() {
super( AbstractBlock.Properties.of( Material.ICE ).sound( SoundType.GLASS ).noOcclusion()
.randomTicks().friction( 0.98F ).strength( 0.5F ) );
registerDefaultState( stateDefinition.any().setValue( AGE, 0 ).setValue( HAS_WATER, true ) );
}
@Override
protected void createBlockStateDefinition( StateContainer.Builder<Block, BlockState> builder ) {
builder.add( AGE ).add( HAS_WATER );
}
@SuppressWarnings( "deprecation" )
@Override
public ItemStack getCloneItemStack( IBlockReader world, BlockPos pos, BlockState state ) { return ItemStack.EMPTY; }
@Override
public void playerDestroy( World world, PlayerEntity player, BlockPos pos, BlockState state,
@Nullable TileEntity tileEntity, ItemStack tool ) {
player.awardStat( Stats.BLOCK_MINED.get( this ) );
player.causeFoodExhaustion( 0.005F );
dropResources( state, world, pos, tileEntity, player, tool );
if( EnchantmentHelper.getItemEnchantmentLevel( Enchantments.SILK_TOUCH, tool ) == 0 ) {
melt( state, world, pos );
}
}
@Override
public void randomTick( BlockState state, ServerWorld world, BlockPos pos, Random random ) { tick( state, world, pos, random ); }
@SuppressWarnings( "deprecation" )
@Override
public void tick( BlockState state, ServerWorld world, BlockPos pos, Random random ) {
if( canMelt( world, pos ) ) {
if( slightlyMelt( state, world, pos, random ) ) {
final BlockPos.Mutable neighborPos = new BlockPos.Mutable();
trySlightlyMelt( world, neighborPos.setWithOffset( pos, Direction.DOWN ), random );
for( Direction direction : Direction.Plane.HORIZONTAL ) {
trySlightlyMelt( world, neighborPos.setWithOffset( pos, direction ), random );
}
}
}
else scheduleTick( world, pos, random );
}
/** @return True if the conditions allow melting. */
private boolean canMelt( World world, BlockPos pos ) {
// If a bright-ish (torch or higher) light source is nearby, always allow melting
if( getLight( world, pos ) > 13 ) return true;
// Otherwise, we want to prevent melting in two specific cases to get our desired melting pattern:
// * surrounded by other melting ice blocks horizontally (outside-in for ice floors/ceilings)
// * block below is non-air and block above is another melting ice block (top-down for ice walls)
final BlockPos.Mutable neighborPos = new BlockPos.Mutable();
for( Direction direction : Direction.Plane.HORIZONTAL ) {
if( !world.getBlockState( neighborPos.setWithOffset( pos, direction ) ).is( this ) ) {
//noinspection deprecation
return !world.getBlockState( neighborPos.setWithOffset( pos, Direction.UP ) ).is( this ) ||
world.getBlockState( neighborPos.setWithOffset( pos, Direction.DOWN ) ).isAir();
}
}
return false;
}
/** Attempts to slightly melt a target block. */
private void trySlightlyMelt( World world, BlockPos pos, Random random ) {
final BlockState state = world.getBlockState( pos );
if( state.is( this ) && canMelt( world, pos ) ) slightlyMelt( state, world, pos, random );
}
/** @return Increments the block's melting and returns true if the block was completely melted. */
private boolean slightlyMelt( BlockState state, World world, BlockPos pos, Random random ) {
int age = state.getValue( AGE );
if( age < 3 ) {
world.setBlock( pos, state.setValue( AGE, age + 1 ), References.SetBlockFlags.UPDATE_CLIENT );
scheduleTick( world, pos, random );
return false;
}
else {
melt( state, world, pos );
return true;
}
}
/** Melts the ice block. */
@Override
protected void melt( BlockState state, World world, BlockPos pos ) {
if( world.dimensionType().ultraWarm() || !state.getValue( HAS_WATER ) ) {
world.removeBlock( pos, false );
}
else {
world.setBlockAndUpdate( pos, Blocks.WATER.defaultBlockState() );
world.neighborChanged( pos, Blocks.WATER, pos );
}
}
}

View file

@ -0,0 +1,99 @@
package fathertoast.specialmobs.common.block;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.core.register.SMBlocks;
import fathertoast.specialmobs.common.entity.silverfish.PufferSilverfishEntity;
import fathertoast.specialmobs.common.util.References;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.block.SilverfishBlock;
import net.minecraft.block.material.Material;
import net.minecraft.entity.monster.SilverfishEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.server.ServerWorld;
import java.util.Random;
import java.util.function.Function;
/**
* A variation of the regular silverfish block intended for placement under water.
* Contains a puffer silverfish instead of a vanilla one.
*/
public class UnderwaterSilverfishBlock extends SilverfishBlock {
public enum Type {
TUBE( "tube", Blocks.TUBE_CORAL_BLOCK,
( langKey ) -> References.translations( langKey, "Infested Tube Coral Block",
"", "", "", "", "", "" ) ),//TODO
BRAIN( "brain", Blocks.BRAIN_CORAL_BLOCK,
( langKey ) -> References.translations( langKey, "Infested Brain Coral Block",
"", "", "", "", "", "" ) ),//TODO
BUBBLE( "bubble", Blocks.BUBBLE_CORAL_BLOCK,
( langKey ) -> References.translations( langKey, "Infested Bubble Coral Block",
"", "", "", "", "", "" ) ),//TODO
FIRE( "fire", Blocks.FIRE_CORAL_BLOCK,
( langKey ) -> References.translations( langKey, "Infested Fire Coral Block",
"", "", "", "", "", "" ) ),//TODO
HORN( "horn", Blocks.HORN_CORAL_BLOCK,
( langKey ) -> References.translations( langKey, "Infested Horn Coral Block",
"", "", "", "", "", "" ) );//TODO
private final String ID;
private final Block HOST_BLOCK;
private final Function<String, String[]> TRANSLATIONS;
Type( String id, Block hostBlock, Function<String, String[]> translations ) {
ID = id;
HOST_BLOCK = hostBlock;
TRANSLATIONS = translations;
}
/** @return The block id for this underwater silverfish block type. */
public String blockId() { return "infested_" + ID + "_coral_block"; }
/** @return A new underwater silverfish block for this type. */
public Block blockSupplier() { return new UnderwaterSilverfishBlock( HOST_BLOCK ); }
/** @return The 'host block' of this type; that is, the block this type imitates. */
public Block hostBlock() { return HOST_BLOCK; }
/** @return The 'infested block' of this type; that is, the actual silverfish block. */
public Block block() { return SMBlocks.INFESTED_CORAL.get( ordinal() ).get(); }
/** @return The translations of this type. */
private String[] getTranslations( String langKey ) { return TRANSLATIONS.apply( langKey ); }
/** @return Looks up and returns the translations for the type lang key. */
private static String[] getTranslationsFor( String langKey ) {
for( Type type : values() ) {
if( langKey.contains( type.ID ) ) return type.getTranslations( langKey );
}
// This will cause the lang provider to throw an exception for us
return References.translations( langKey, "", "", "", "", "", "", "" );
}
/** @return A random underwater silverfish block type. */
public static Type next( Random random ) { return values()[random.nextInt( values().length )]; }
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) { return Type.getTranslationsFor( langKey ); }
public UnderwaterSilverfishBlock( Block block ) {
super( block, Properties.of( Material.CLAY ).strength( 0.0F, 0.75F ) );
}
@Override
protected void spawnInfestation( ServerWorld world, BlockPos pos ) {
final SilverfishEntity silverfish = PufferSilverfishEntity.SPECIES.entityType.get().create( world );
if( silverfish == null ) return;
silverfish.moveTo( pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0.0F, 0.0F );
world.addFreshEntity( silverfish );
silverfish.spawnAnim();
}
}

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package fathertoast.specialmobs.common.block;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -11,6 +11,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* Represents a config field with a double value.
@ -219,13 +220,13 @@ public class DoubleField extends AbstractConfigField {
/** @return Returns a random item from this weighted list. Null if none of the items have a positive weight. */
@Nullable
public T next( Random random, World world, @Nullable BlockPos pos, @Nullable Function<T, Boolean> selector ) {
public T next( Random random, World world, @Nullable BlockPos pos, @Nullable Predicate<T> selector ) {
// Due to the 'nebulous' nature of environment-based weights, we must recalculate weights for EVERY call
final double[] weights = new double[UNDERLYING_LIST.size()];
double targetWeight = 0.0;
for( int i = 0; i < weights.length; i++ ) {
final Entry<T> entry = UNDERLYING_LIST.get( i );
if( selector == null || selector.apply( entry.VALUE ) ) {
if( selector == null || selector.test( entry.VALUE ) ) {
targetWeight += weights[i] = entry.WEIGHT.get( world, pos );
}
}

View file

@ -0,0 +1,38 @@
package fathertoast.specialmobs.common.config.species;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.config.Config;
import fathertoast.specialmobs.common.config.field.IntField;
import fathertoast.specialmobs.common.config.file.ToastConfigSpec;
import fathertoast.specialmobs.common.config.util.ConfigUtil;
public class CorporealShiftGhastSpeciesConfig extends SpeciesConfig {
public final CorporealShift CORPOREAL_SHIFT;
/** Builds the config spec that should be used for this config. */
public CorporealShiftGhastSpeciesConfig( MobFamily.Species<?> species, int corporealTime, int incorporealTime ) {
super( species );
CORPOREAL_SHIFT = new CorporealShift( SPEC, species, species.getConfigName(), corporealTime, incorporealTime );
}
public static class CorporealShift extends Config.AbstractCategory {
public final IntField corporealTicks;
public final IntField incorporealTicks;
CorporealShift( ToastConfigSpec parent, MobFamily.Species<?> species, String speciesName,
int corporealTime, int incorporealTime ) {
super( parent, ConfigUtil.camelCaseToLowerUnderscore( species.specialVariantName ),
"Options specific to " + speciesName + "." );
corporealTicks = SPEC.define( new IntField( "ticks.corporeal", corporealTime, IntField.Range.NON_NEGATIVE,
"The number of ticks " + speciesName + " stay in 'corporeal' mode before shifting (20 ticks = 1 second).",
"In corporeal mode, " + speciesName + " can be damaged and shoot like normal " + species.family.configName + "." ) );
incorporealTicks = SPEC.define( new IntField( "ticks.incorporeal", incorporealTime, IntField.Range.NON_NEGATIVE,
"The number of ticks " + speciesName + " stay in 'incorporeal' mode before shifting (20 ticks = 1 second).",
"In incorporeal mode, " + speciesName + " are immune to damage and shoot unique fireballs that punish movement." ) );
}
}
}

View file

@ -0,0 +1,47 @@
package fathertoast.specialmobs.common.config.species;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.config.Config;
import fathertoast.specialmobs.common.config.field.DoubleField;
import fathertoast.specialmobs.common.config.field.IntField;
import fathertoast.specialmobs.common.config.file.ToastConfigSpec;
import fathertoast.specialmobs.common.config.util.ConfigUtil;
public class DrowningCreeperSpeciesConfig extends CreeperSpeciesConfig {
public final Drowning DROWNING;
/** Builds the config spec that should be used for this config. */
public DrowningCreeperSpeciesConfig( MobFamily.Species<?> species,
boolean cannotExplodeWhileWet, boolean explodeWhileBurning, boolean explodeWhenShot,
double infestedChance, int minPuffPuffs, int maxPuffPuffs ) {
super( species, cannotExplodeWhileWet, explodeWhileBurning, explodeWhenShot );
DROWNING = new Drowning( SPEC, species, species.getConfigName(), infestedChance, minPuffPuffs, maxPuffPuffs );
}
public static class Drowning extends Config.AbstractCategory {
public final DoubleField infestedBlockChance;
public final IntField.RandomRange puffPuffs;
Drowning( ToastConfigSpec parent, MobFamily.Species<?> species, String speciesName,
double infestedChance, int minPuffPuffs, int maxPuffPuffs ) {
super( parent, ConfigUtil.camelCaseToLowerUnderscore( species.specialVariantName ),
"Options specific to " + speciesName + "." );
infestedBlockChance = SPEC.define( new DoubleField( "infested_chance", infestedChance, DoubleField.Range.PERCENT,
"Chance for explosion's coral shell blocks to be infested with aquatic silverfish.",
"Rolled for each coral block generated." ) );
SPEC.newLine();
puffPuffs = new IntField.RandomRange(
SPEC.define( new IntField( "pufferfish.min", minPuffPuffs, IntField.Range.NON_NEGATIVE,
"The minimum and maximum (inclusive) limit on the number of pufferfish that " + speciesName + " spawn with their explosion." ) ),
SPEC.define( new IntField( "pufferfish.max", maxPuffPuffs, IntField.Range.NON_NEGATIVE ) )
);
}
}
}

View file

@ -0,0 +1,35 @@
package fathertoast.specialmobs.common.config.species;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.config.Config;
import fathertoast.specialmobs.common.config.field.DoubleField;
import fathertoast.specialmobs.common.config.file.ToastConfigSpec;
import fathertoast.specialmobs.common.config.util.ConfigUtil;
public class SnowCreeperSpeciesConfig extends CreeperSpeciesConfig {
public final Snow SNOW;
/** Builds the config spec that should be used for this config. */
public SnowCreeperSpeciesConfig( MobFamily.Species<?> species,
boolean cannotExplodeWhileWet, boolean explodeWhileBurning, boolean explodeWhenShot,
double globeChance ) {
super( species, cannotExplodeWhileWet, explodeWhileBurning, explodeWhenShot );
SNOW = new Snow( SPEC, species, species.getConfigName(), globeChance );
}
public static class Snow extends Config.AbstractCategory {
public final DoubleField snowGlobeChance;
Snow( ToastConfigSpec parent, MobFamily.Species<?> species, String speciesName, double globeChance ) {
super( parent, ConfigUtil.camelCaseToLowerUnderscore( species.specialVariantName ),
"Options specific to " + speciesName + "." );
snowGlobeChance = SPEC.define( new DoubleField( "snow_globe_chance", globeChance, DoubleField.Range.PERCENT,
"Chance for " + speciesName + " to create a snow globe instead of regular walls when exploding.",
"The globe is always chosen if the " + species.getConfigNameSingular() + " is underwater for some reason." ) );
}
}
}

View file

@ -7,7 +7,10 @@ import fathertoast.specialmobs.common.util.References;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.world.IServerWorld;
import net.minecraft.world.World;
import net.minecraftforge.event.TickEvent;
@ -19,12 +22,20 @@ import net.minecraftforge.fml.common.Mod;
import javax.annotation.Nullable;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.function.Predicate;
@Mod.EventBusSubscriber( modid = SpecialMobs.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE )
public final class SpecialMobReplacer {
/** List of data for mobs needing replacement. */
private static final Deque<MobReplacementEntry> TO_REPLACE = new ArrayDeque<>();
/** Returns true if the species is not damaged by water. */
private static final Predicate<MobFamily.Species<?>> WATER_INSENSITIVE_SELECTOR =
( species ) -> !species.config.GENERAL.isDamagedByWater.get();
/** Returns true if the species's block height is less than or equal to the base vanilla entity's. */
private static final Predicate<MobFamily.Species<?>> NO_GIANTS_SELECTOR = MobFamily.Species::isNotGiant;
/**
* Called when any entity is spawned into the world by any means (such as natural/spawner spawns or chunk loading).
* <p>
@ -123,7 +134,9 @@ public final class SpecialMobReplacer {
// Don't copy UUID
tag.remove( "UUID" );
final MobFamily.Species<?> species = isSpecial ? mobFamily.nextVariant( world, entityPos ) : mobFamily.vanillaReplacement;
final MobFamily.Species<?> species = isSpecial ?
mobFamily.nextVariant( world, entityPos, getVariantFilter( mobFamily, entityToReplace, world, entityPos ) ) :
mobFamily.vanillaReplacement;
final LivingEntity replacement = species.entityType.get().create( world );
if( replacement == null ) {
@ -149,6 +162,32 @@ public final class SpecialMobReplacer {
entityToReplace.remove();
}
/** @return A selector that filters out variants that are likely to die a stupid death if chosen. */
@Nullable
private static Predicate<MobFamily.Species<?>> getVariantFilter( MobFamily<?, ?> mobFamily, Entity entityToReplace,
World world, BlockPos entityPos ) {
Predicate<MobFamily.Species<?>> selector = null;
// Note that we do not check for any fluids (water/lava) since that is handled by spawn logic
if( !mobFamily.vanillaReplacement.bestiaryInfo.isDamagedByWater && // Skip this check if the base vanilla mob dies in water
world.isRainingAt( entityPos ) ) {
selector = WATER_INSENSITIVE_SELECTOR;
}
// Does not consider overly wide mobs or extra-tall (>1 block taller) mobs
if( mobFamily.hasAnyGiants() ) {
final AxisAlignedBB bb = entityToReplace.getBoundingBox();
final int y = MathHelper.ceil( bb.maxY );
// Only check the FULL block above current collision - not a perfect representation, but keeps things simple
if( !world.isUnobstructed( entityToReplace, VoxelShapes.create(
new AxisAlignedBB( bb.minX, y, bb.minZ, bb.maxX, y + 1, bb.maxZ ) ) ) ) {
selector = selector == null ? NO_GIANTS_SELECTOR : selector.and( NO_GIANTS_SELECTOR );
}
}
return selector;
}
/** All data needed for a single mob we want to replace. */
private static class MobReplacementEntry {
final MobFamily<?, ?> mobFamily;

View file

@ -2,6 +2,7 @@ package fathertoast.specialmobs.common.core;
import fathertoast.specialmobs.common.compat.top.SMTheOneProbe;
import fathertoast.specialmobs.common.config.Config;
import fathertoast.specialmobs.common.core.register.SMBlocks;
import fathertoast.specialmobs.common.core.register.SMEffects;
import fathertoast.specialmobs.common.core.register.SMEntities;
import fathertoast.specialmobs.common.core.register.SMItems;
@ -26,56 +27,54 @@ import javax.annotation.Nullable;
@Mod( SpecialMobs.MOD_ID )
public class SpecialMobs {
/* TODO List:
* Reimplement all old features (see list below)
* Utility features:
* - Bestiary
*/
/* Feature List: //TODO; list may not be complete
/* Feature List:
* (KEY: - = complete in current version, o = incomplete feature from previous version,
* + = incomplete new feature, ? = feature to consider adding)
* - general
* - entity replacer
* - mob replacer
* - environment-sensitive configs
* - natural spawning
* - copied spawns
* - spiders -> cave spiders
* - endermen -> ender creepers
* - vanilla spiders -> vanilla cave spiders
* - vanilla endermen -> ender creepers
* - ocean/river spawns
* - drowning creepers
* - blueberry slimes
* - nether spawns
* - wither skeletons (outside of fortresses)
* - blazes (outside of fortresses)
* - vanilla wither skeletons (outside of fortresses)
* - vanilla blazes (outside of fortresses)
* - fire creepers/zombies/spiders
* ? warped/crimson mobs
* + phantom spawns
* - potions
* - vulnerability (opposite of resistance)
* - weight (opposite of levitation)
* - blocks
* - infested coral (spawns puffer silverfish)
* - melting ice (similar to frosted ice)
* - entities
* - nbt-driven capabilities (special mob data)
* - fish hook
* + TODO bestiary
* - configurable, nbt-driven stats (bestiary info + special mob data)
* - configurable weapon type chance
* - bone shrapnel
* - bug spit
* + bestiary
* - configurable stats
* - fish hook
* - monster families (see doc for specifics)
* - creepers
* - chance to spawn charged during thunderstorms
* + scope - perhaps delay this until 1.18 where spyglasses will be in the game
* - chance to become supercharged when charged
* - explosion stats (while wet, while burning, when shot)
* - zombies
* - transformations (husk -> any other non-water-sensitive zombie -> analogous drowned)
* - ranged attack AI (using bow)
* - use shields
* - drowned
* - AI functions in shallow water
* - use shields
* - bug fixes (can move in shallow water, alert regular zombies)
* - zombified piglins
* - ranged attack AI (using bow)
* + ranged attack AI (using crossbow)
* - use shields
* ? warped/crimson mobs
* - skeletons
* - use shields
* - melee chance
@ -84,7 +83,6 @@ public class SpecialMobs {
* - use shields
* - bow chance
* - babies
* ? warped/crimson mobs
* - slimes
* - smallest size can deal damage
* - magma cubes
@ -94,22 +92,24 @@ public class SpecialMobs {
* - ranged attack AI (spitter)
* - silverfish
* - ranged attack AI (spitter)
* - chance to spawn already calling for reinforcements
* - endermen
* - witches
* - ability to equip held items (wonky)
* - uses splash speed instead of regular
* - use splash speed instead of regular
* - ghasts
* - melee attack AI
* - remove vertical targeting restriction
* - blazes
* - melee attack AI
* ? hoglins
* ? zoglins
* - configurable fireball attack
* + guardians
* + vortex
* ? shulkers
* + phantoms
* + natural spawning
* + the goat
* ? hoglins
* ? zoglins
* ? shulkers
*/
/** Our mod ID. */
@ -130,9 +130,10 @@ public class SpecialMobs {
final IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
SMEffects.REGISTRY.register( modEventBus );
SMEntities.REGISTRY.register( modEventBus );
SMBlocks.REGISTRY.register( modEventBus );
SMItems.REGISTRY.register( modEventBus );
SMEntities.REGISTRY.register( modEventBus );
SMEffects.REGISTRY.register( modEventBus );
modEventBus.addListener( SMEntities::createAttributes );
modEventBus.addListener( this::setup );
@ -150,6 +151,7 @@ public class SpecialMobs {
NaturalSpawnManager.registerSpawnPlacements();
}
@SuppressWarnings( "SpellCheckingInspection" )
public void sendIMCMessages( InterModEnqueueEvent event ) {
if( ModList.get().isLoaded( "theoneprobe" ) ) {
InterModComms.sendTo( "theoneprobe", "getTheOneProbe", SMTheOneProbe::new );

View file

@ -0,0 +1,49 @@
package fathertoast.specialmobs.common.core.register;
import fathertoast.specialmobs.common.block.MeltingIceBlock;
import fathertoast.specialmobs.common.block.UnderwaterSilverfishBlock;
import fathertoast.specialmobs.common.core.SpecialMobs;
import net.minecraft.block.Block;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraftforge.fml.RegistryObject;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
public class SMBlocks {
/** The deferred register for this mod's blocks. */
public static final DeferredRegister<Block> REGISTRY = DeferredRegister.create( ForgeRegistries.BLOCKS, SpecialMobs.MOD_ID );
public static final RegistryObject<Block> MELTING_ICE = registerTechnicalBlock( "melting_ice", MeltingIceBlock::new );
public static final List<RegistryObject<Block>> INFESTED_CORAL;
static {
final ArrayList<RegistryObject<Block>> infestedCoral = new ArrayList<>();
for( UnderwaterSilverfishBlock.Type type : UnderwaterSilverfishBlock.Type.values() ) {
infestedCoral.add( registerBlock( type.blockId(), type::blockSupplier, ItemGroup.TAB_DECORATIONS ) );
}
infestedCoral.trimToSize();
INFESTED_CORAL = Collections.unmodifiableList( infestedCoral );
}
/** Registers a block and a simple BlockItem for it. */
private static <T extends Block> RegistryObject<T> registerBlock( String name, Supplier<T> blockSupplier, ItemGroup itemGroup ) {
final RegistryObject<T> blockRegObject = REGISTRY.register( name, blockSupplier );
SMItems.REGISTRY.register( name, () -> new BlockItem( blockRegObject.get(), new Item.Properties().tab( itemGroup ) ) );
return blockRegObject;
}
/** Registers a technical block (not visible in the creative menu) and a simple BlockItem for it. */
private static <T extends Block> RegistryObject<T> registerTechnicalBlock( String name, Supplier<T> blockSupplier ) {
final RegistryObject<T> blockRegObject = REGISTRY.register( name, blockSupplier );
SMItems.REGISTRY.register( name, () -> new BlockItem( blockRegObject.get(), new Item.Properties() ) );
return blockRegObject;
}
}

View file

@ -26,10 +26,7 @@ import net.minecraft.potion.Effects;
import net.minecraft.tags.FluidTags;
import net.minecraft.tags.ITag;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.DamageSource;
import net.minecraft.util.Hand;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvents;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.shapes.ISelectionContext;
@ -38,6 +35,8 @@ import net.minecraft.world.Difficulty;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.IServerWorld;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.util.BlockSnapshot;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.event.entity.living.LivingKnockBackEvent;
import javax.annotation.Nullable;
@ -552,4 +551,41 @@ public final class MobHelper {
}
}
}
/** @return Attempts to place a block, firing the appropriate Forge event. Returns true if successful. */
public static boolean placeBlock( Entity entity, BlockPos pos, BlockState block ) {
return placeBlock( entity, pos, block, References.SetBlockFlags.DEFAULTS );
}
/** @return Attempts to place a block, firing the appropriate Forge event. Returns true if successful. */
public static boolean placeBlock( Entity entity, BlockPos pos, Direction direction, BlockState block ) {
return placeBlock( entity, pos, direction, block, References.SetBlockFlags.DEFAULTS );
}
/** @return Attempts to place a block, firing the appropriate Forge event. Returns true if successful. */
public static boolean placeBlock( Entity entity, BlockPos pos, BlockState block, int updateFlags ) {
return placeBlock( entity, pos, Direction.UP, block, updateFlags );
}
/** @return Attempts to place a block, firing the appropriate Forge event. Returns true if successful. */
public static boolean placeBlock( Entity entity, BlockPos pos, Direction direction, BlockState block, int updateFlags ) {
if( canPlaceBlock( entity, pos, direction ) ) {
entity.level.setBlock( pos, block, updateFlags );
return true;
}
return false;
}
// Note to future self - I should probably also make 'destroy block' methods for whatever Forge event those should have,
// generally I do fire mob griefing events already for everything, though
/** @return Fires the Forge event to check if a block can be placed and returns the result. */
public static boolean canPlaceBlock( Entity entity, BlockPos pos ) {
return canPlaceBlock( entity, pos, Direction.UP );
}
/** @return Fires the Forge event to check if a block can be placed and returns the result. */
public static boolean canPlaceBlock( Entity entity, BlockPos pos, Direction direction ) {
return !ForgeEventFactory.onBlockPlace( entity, BlockSnapshot.create( entity.level.dimension(), entity.level, pos ), direction );
}
}

View file

@ -3,6 +3,7 @@ package fathertoast.specialmobs.common.entity.cavespider;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.Blocks;
@ -63,7 +64,7 @@ public class FireCaveSpiderEntity extends _SpecialCaveSpiderEntity {
if( !level.isClientSide() ) {
final BlockPos pos = target.blockPosition();
if( level.getBlockState( pos ).getMaterial().isReplaceable() ) {
level.setBlock( pos, Blocks.FIRE.defaultBlockState(), References.SetBlockFlags.DEFAULTS );
MobHelper.placeBlock( this, pos, Blocks.FIRE.defaultBlockState() );
}
}
target.setSecondsOnFire( 5 );

View file

@ -5,6 +5,7 @@ import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.config.species.SpeciesConfig;
import fathertoast.specialmobs.common.config.species.WebSpiderSpeciesConfig;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.Blocks;
@ -99,8 +100,8 @@ public class WebCaveSpiderEntity extends _SpecialCaveSpiderEntity {
/** @return Attempts to place a cobweb at the given position and returns true if successful. */
private boolean tryPlaceWeb( BlockPos pos ) {
if( level.getBlockState( pos ).getMaterial().isReplaceable() ) {
level.setBlock( pos, Blocks.COBWEB.defaultBlockState(), References.SetBlockFlags.DEFAULTS );
if( level.getBlockState( pos ).getMaterial().isReplaceable() &&
MobHelper.placeBlock( this, pos, Blocks.COBWEB.defaultBlockState() ) ) {
webCount--;
return true;
}

View file

@ -3,6 +3,7 @@ package fathertoast.specialmobs.common.entity.creeper;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.util.ExplosionHelper;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
@ -78,7 +79,7 @@ public class DirtCreeperEntity extends _SpecialCreeperEntity {
if( x * x + y * y + z * z <= radius * radius ) {
final BlockPos pos = center.offset( x, y, z );
if( level.getBlockState( pos ).getMaterial().isReplaceable() ) {
level.setBlock( pos, dirt, References.SetBlockFlags.DEFAULTS );
MobHelper.placeBlock( this, pos, dirt );
}
}
}

View file

@ -3,10 +3,13 @@ package fathertoast.specialmobs.common.entity.creeper;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.block.UnderwaterSilverfishBlock;
import fathertoast.specialmobs.common.config.species.DrowningCreeperSpeciesConfig;
import fathertoast.specialmobs.common.config.species.SpeciesConfig;
import fathertoast.specialmobs.common.config.util.EnvironmentEntry;
import fathertoast.specialmobs.common.config.util.EnvironmentList;
import fathertoast.specialmobs.common.config.util.environment.biome.BiomeCategory;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.ai.AIHelper;
import fathertoast.specialmobs.common.entity.ai.AmphibiousMovementController;
import fathertoast.specialmobs.common.entity.ai.IAmphibiousMob;
@ -38,6 +41,7 @@ import net.minecraft.world.IServerWorld;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.Biomes;
import java.util.Random;
@ -60,12 +64,19 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmp
@SpecialMob.ConfigSupplier
public static SpeciesConfig createConfig( MobFamily.Species<?> species ) {
SpeciesConfig.NEXT_NATURAL_SPAWN_CHANCE_EXCEPTIONS = new EnvironmentList(
EnvironmentEntry.builder( 0.06F ).inBiome( Biomes.WARM_OCEAN ).build(),
EnvironmentEntry.builder( 0.06F ).inBiome( Biomes.DEEP_WARM_OCEAN ).build(),
EnvironmentEntry.builder( 0.06F ).inBiomeCategory( BiomeCategory.RIVER ).build(),
EnvironmentEntry.builder( 0.02F ).inBiomeCategory( BiomeCategory.OCEAN ).belowSeaDepths().build(),
EnvironmentEntry.builder( 0.0F ).inBiomeCategory( BiomeCategory.OCEAN ).build() );
return _SpecialCreeperEntity.createConfig( species );
return new DrowningCreeperSpeciesConfig( species, false, false, false,
0.25, 2, 4 );
}
/** @return This entity's species config. */
@Override
public DrowningCreeperSpeciesConfig getConfig() { return (DrowningCreeperSpeciesConfig) getSpecies().config; }
@SpecialMob.SpawnPlacementRegistrar
public static void registerSpeciesSpawnPlacement( MobFamily.Species<? extends DrowningCreeperEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species, EntitySpawnPlacementRegistry.PlacementType.IN_WATER,
@ -114,6 +125,9 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmp
//--------------- Variant-Specific Implementations ----------------
/** The maximum number of pufferfish spawned on explosion. */
private int pufferfish;
public DrowningCreeperEntity( EntityType<? extends _SpecialCreeperEntity> entityType, World world ) {
super( entityType, world );
moveControl = new AmphibiousMovementController<>( this );
@ -121,6 +135,8 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmp
groundNavigation = new GroundPathNavigator( this, world );
maxUpStep = 1.0F;
setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() );
pufferfish = getConfig().DROWNING.puffPuffs.next( random );
}
/** Override to change this entity's AI goals. */
@ -147,17 +163,23 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmp
if( explosionMode == Explosion.Mode.NONE ) return;
final BlockState brainCoral = Blocks.BRAIN_CORAL_BLOCK.defaultBlockState();
final BlockState hornCoral = Blocks.HORN_CORAL_BLOCK.defaultBlockState();
final UnderwaterSilverfishBlock.Type mainType = UnderwaterSilverfishBlock.Type.next( random );
final UnderwaterSilverfishBlock.Type rareType = UnderwaterSilverfishBlock.Type.next( random );
final BlockState mainCoral = mainType.hostBlock().defaultBlockState();
final BlockState rareCoral = rareType.hostBlock().defaultBlockState();
final BlockState mainInfestedCoral = mainType.block().defaultBlockState();
final BlockState rareInfestedCoral = rareType.block().defaultBlockState();
final BlockState water = Blocks.WATER.defaultBlockState();
final BlockState seaPickle = Blocks.SEA_PICKLE.defaultBlockState().setValue( BlockStateProperties.WATERLOGGED, true );
final BlockState seaGrass = Blocks.SEAGRASS.defaultBlockState();
final int radius = (int) Math.floor( explosionPower );
final int radiusSq = radius * radius;
final int rMinusOneSq = (radius - 1) * (radius - 1);
final BlockPos center = new BlockPos( explosion.getPos() );
// Track how many pufferfish have been spawned so we don't spawn a bunch of them
spawnPufferfish( center.above( 1 ) );
if( pufferfish > 0 ) spawnPufferfish( center.above( 1 ) );
int pufferCount = 1;
for( int y = -radius; y <= radius; y++ ) {
@ -165,34 +187,38 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmp
for( int z = -radius; z <= radius; z++ ) {
final int distSq = x * x + y * y + z * z;
if( distSq <= radius * radius ) {
if( distSq <= radiusSq ) {
final BlockPos pos = center.offset( x, y, z );
final BlockState stateAtPos = level.getBlockState( pos );
if( stateAtPos.getMaterial().isReplaceable() || stateAtPos.is( BlockTags.LEAVES ) ) {
if( distSq > rMinusOneSq ) {
// "Coral" casing
level.setBlock( pos, random.nextFloat() < 0.25F ? brainCoral : hornCoral, References.SetBlockFlags.DEFAULTS );
}
else {
if( distSq <= rMinusOneSq ) {
// Water fill
final float fillChoice = random.nextFloat();
if( fillChoice < 0.1F && seaPickle.canSurvive( level, pos ) ) {
level.setBlock( pos, seaPickle, References.SetBlockFlags.DEFAULTS );
MobHelper.placeBlock( this, pos, seaPickle );
}
else if( fillChoice < 0.3F && seaGrass.canSurvive( level, pos ) ) {
level.setBlock( pos, seaGrass, References.SetBlockFlags.DEFAULTS );
MobHelper.placeBlock( this, pos, seaGrass );
}
else {
// Water fill
level.setBlock( pos, water, References.SetBlockFlags.DEFAULTS );
MobHelper.placeBlock( this, pos, water );
if( random.nextFloat() < 0.0075F && pufferCount < 5 ) {
if( random.nextFloat() < 0.0075F && pufferCount < pufferfish ) {
spawnPufferfish( pos );
pufferCount++;
}
}
}
else if( isCoralSafe( rMinusOneSq, x, y, z ) ) {
// Coral casing
final boolean infested = getConfig().DROWNING.infestedBlockChance.rollChance( random );
MobHelper.placeBlock( this, pos, random.nextFloat() < 0.8F ?
infested ? mainInfestedCoral : mainCoral :
infested ? rareInfestedCoral : rareCoral );
}
}
}
}
@ -210,6 +236,28 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmp
}
}
/** @return Checks the inner three-ish block distances and returns true if at least one is inside the main radius. */
@SuppressWarnings( "RedundantIfStatement" ) // The symmetry makes it look better
private boolean isCoralSafe( int rMinusOneSq, int x, int y, int z ) {
int distSq;
if( y != 0 ) {
final int innerY = y < 0 ? y + 1 : y - 1;
distSq = x * x + innerY * innerY + z * z;
if( distSq <= rMinusOneSq ) return true;
}
if( x != 0 ) {
final int innerX = x < 0 ? x + 1 : x - 1;
distSq = innerX * innerX + y * y + z * z;
if( distSq <= rMinusOneSq ) return true;
}
if( z != 0 ) {
final int innerZ = z < 0 ? z + 1 : z - 1;
distSq = x * x + y * y + innerZ * innerZ;
if( distSq <= rMinusOneSq ) return true;
}
return false;
}
// The below two methods are here to effectively override the private Entity#isInRain to always return true (always wet)
@Override
public boolean isInWaterOrRain() { return true; }
@ -217,9 +265,18 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmp
@Override
public boolean isInWaterRainOrBubble() { return true; }
/** Override to save data to this entity's NBT data. */
@Override
public void addVariantSaveData( CompoundNBT saveTag ) {
saveTag.putByte( References.TAG_SUMMONS, (byte) pufferfish );
}
/** Override to load data from this entity's NBT data. */
@Override
public void readVariantSaveData( CompoundNBT saveTag ) {
if( saveTag.contains( References.TAG_SUMMONS, References.NBT_TYPE_NUMERICAL ) )
pufferfish = saveTag.getByte( References.TAG_SUMMONS );
setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() );
}

View file

@ -3,6 +3,9 @@ package fathertoast.specialmobs.common.entity.creeper;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.block.MeltingIceBlock;
import fathertoast.specialmobs.common.config.species.SnowCreeperSpeciesConfig;
import fathertoast.specialmobs.common.config.species.SpeciesConfig;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.ai.AIHelper;
import fathertoast.specialmobs.common.entity.ai.FluidPathNavigator;
@ -12,6 +15,7 @@ import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.FlowingFluidBlock;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.SpawnReason;
import net.minecraft.entity.ai.attributes.Attributes;
@ -45,6 +49,16 @@ public class SnowCreeperEntity extends _SpecialCreeperEntity {
.addToAttribute( Attributes.MAX_HEALTH, 10.0 );
}
@SpecialMob.ConfigSupplier
public static SpeciesConfig createConfig( MobFamily.Species<?> species ) {
return new SnowCreeperSpeciesConfig( species, false, false, false,
0.33 );
}
/** @return This entity's species config. */
@Override
public SnowCreeperSpeciesConfig getConfig() { return (SnowCreeperSpeciesConfig) getSpecies().config; }
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Snow Creeper",
@ -123,7 +137,9 @@ public class SnowCreeperEntity extends _SpecialCreeperEntity {
final BlockPos center = new BlockPos( explosion.getPos() );
if( explosionMode != Explosion.Mode.NONE ) {
final BlockState ice = Blocks.ICE.defaultBlockState();
final boolean snowGlobe = isUnderWater() || random.nextDouble() < getConfig().SNOW.snowGlobeChance.get();
final int radiusSq = radius * radius;
final int rMinusOneSq = (radius - 1) * (radius - 1);
for( int y = -radius; y <= radius; y++ ) {
@ -131,19 +147,32 @@ public class SnowCreeperEntity extends _SpecialCreeperEntity {
for( int z = -radius; z <= radius; z++ ) {
final int distSq = x * x + y * y + z * z;
if( distSq <= radius * radius ) {
if( distSq <= radiusSq ) {
final BlockPos pos = center.offset( x, y, z );
// Freeze top layer of water and temporary ice within affected volume
// Freeze top layer of water sources and frosted ice within affected volume
final BlockState block = level.getBlockState( pos );
if( block.is( Blocks.FROSTED_ICE ) || block.getFluidState().is( FluidTags.WATER ) ) {
if( block.is( Blocks.FROSTED_ICE ) || block.getBlock() == Blocks.WATER && block.getValue( FlowingFluidBlock.LEVEL ) == 0 ) {
final BlockState blockAbove = level.getBlockState( pos.above() );
if( !blockAbove.getMaterial().blocksMotion() && !blockAbove.getFluidState().is( FluidTags.WATER ) )
level.setBlock( pos, ice, References.SetBlockFlags.DEFAULTS );
if( !blockAbove.getMaterial().blocksMotion() && !blockAbove.getFluidState().is( FluidTags.WATER ) &&
MobHelper.placeBlock( this, pos, MeltingIceBlock.getState( level, pos ) ) ) {
MeltingIceBlock.scheduleFirstTick( level, pos, random );
}
}
// Attempt to place pillars along circumference only
if( y == 0 && distSq > rMinusOneSq ) placePillar( pos, radius );
if( distSq > rMinusOneSq ) {
if( snowGlobe ) {
// Create spherical shell of ice
if( level.getBlockState( pos ).getMaterial().isReplaceable() &&
MobHelper.placeBlock( this, pos, MeltingIceBlock.getState( level, pos ) ) ) {
MeltingIceBlock.scheduleFirstTick( level, pos, random );
}
}
else if( y == 0 ) {
// Create ice wall
placePillar( pos, radius );
}
}
}
}
}
@ -164,13 +193,14 @@ public class SnowCreeperEntity extends _SpecialCreeperEntity {
if( shouldReplace( currentPos ) ) findGroundBelow( currentPos, radius );
else if( findGroundAbove( currentPos, radius ) ) return;
final BlockState ice = Blocks.PACKED_ICE.defaultBlockState();
final int maxY = Math.min( currentPos.getY() + 4, level.getMaxBuildHeight() - 2 );
int height = -2; // This is minimum pillar height
if( pos.getY() > currentPos.getY() ) height -= (pos.getY() - currentPos.getY()) / 2;
while( currentPos.getY() < maxY && shouldReplace( currentPos ) ) {
level.setBlock( currentPos, ice, References.SetBlockFlags.DEFAULTS );
if( MobHelper.placeBlock( this, currentPos, MeltingIceBlock.getState( level, currentPos ) ) ) {
MeltingIceBlock.scheduleFirstTick( level, currentPos, random );
}
currentPos.move( 0, 1, 0 );
if( ++height >= 0 && random.nextBoolean() ) break;

View file

@ -23,7 +23,7 @@ public class BruteDrownedEntity extends _SpecialDrownedEntity {
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0xFFF87E )
.uniqueTextureBaseOnly()
.uniqueTextureWithOverlay()
.size( 1.2F, 0.7F, 2.35F )
.addExperience( 2 )
.addToAttribute( Attributes.MAX_HEALTH, 10.0 ).addToAttribute( Attributes.ARMOR, 10.0 );

View file

@ -40,7 +40,7 @@ public class FishingDrownedEntity extends _SpecialDrownedEntity implements IAngl
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0x2D41F4 ).weight( BestiaryInfo.DefaultWeight.LOW ).theme( BestiaryInfo.Theme.FISHING )
bestiaryInfo.color( 0x2D41F4 ).weight( BestiaryInfo.DefaultWeight.LOW )
.addExperience( 2 ).drownImmune().fluidPushImmune()
.convertThrowToFishing().fishingAttack( 1.0, 40, 15.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 );

View file

@ -0,0 +1,163 @@
package fathertoast.specialmobs.common.entity.drowned;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.block.MeltingIceBlock;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.util.ExplosionHelper;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.FlowingFluidBlock;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.potion.Effects;
import net.minecraft.util.SoundEvents;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.world.Explosion;
import net.minecraft.world.World;
import javax.annotation.Nullable;
@SpecialMob
public class FrozenDrownedEntity extends _SpecialDrownedEntity {
//--------------- Static Special Mob Hooks ----------------
@SpecialMob.SpeciesReference
public static MobFamily.Species<FrozenDrownedEntity> SPECIES;
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0xDDEAEA ).weight( BestiaryInfo.DefaultWeight.LOW ).theme( BestiaryInfo.Theme.ICE )
.uniqueTextureWithOverlay()
.addExperience( 2 ).effectImmune( Effects.MOVEMENT_SLOWDOWN )
.addToAttribute( Attributes.ARMOR, 10.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Frozen Drowned",
"", "", "", "", "", "" );//TODO
}
@SpecialMob.LootTableProvider
public static void buildLootTable( LootTableBuilder loot ) {
addBaseLoot( loot );
loot.addCommonDrop( "common", Blocks.ICE );
loot.addRareDrop( "rare", Blocks.BLUE_ICE );
}
@SpecialMob.Factory
public static EntityType.IFactory<FrozenDrownedEntity> getVariantFactory() { return FrozenDrownedEntity::new; }
/** @return This entity's mob species. */
@SpecialMob.SpeciesSupplier
@Override
public MobFamily.Species<? extends FrozenDrownedEntity> getSpecies() { return SPECIES; }
//--------------- Variant-Specific Implementations ----------------
private static final int ICE_SEAL_TICKS = 4;
private int iceSealTimer;
private BlockPos iceSealPos;
public FrozenDrownedEntity( EntityType<? extends _SpecialDrownedEntity> entityType, World world ) { super( entityType, world ); }
/** Override to apply effects when this entity hits a target with a melee attack. */
@Override
protected void onVariantAttack( LivingEntity target ) {
MobHelper.applyEffect( target, Effects.MOVEMENT_SLOWDOWN, 2 );
}
/** Called each tick to update this entity's movement. */
@Override
public void aiStep() {
if( !level.isClientSide() ) {
if( iceSealPos != null ) {
// Currently creating ice seal
if( iceSealTimer++ % ICE_SEAL_TICKS == 0 ) {
final int radius = iceSealTimer / ICE_SEAL_TICKS;
makeIceSeal( iceSealPos, radius );
if( radius >= 7 ) {
iceSealTimer = 100 + random.nextInt( 100 );
iceSealPos = null;
}
}
}
else if( iceSealTimer-- <= 0 ) {
// Check if a new ice seal should be created
final LivingEntity target = getTarget();
if( target != null && target.isUnderWater() && distanceToSqr( target ) < 144.0 && random.nextInt( 20 ) == 0 ) {
final BlockPos pos = findIceSealPos( target.blockPosition(), MathHelper.ceil( target.getBbHeight() ) );
if( pos != null && ExplosionHelper.getMode( this ) != Explosion.Mode.NONE ) {
iceSealTimer = 0;
iceSealPos = pos;
}
}
}
}
super.aiStep();
}
/** @return The position to create an ice seal at, or null if the target is invalid. */
@Nullable
private BlockPos findIceSealPos( BlockPos targetPos, int targetHeight ) {
// Find the water surface
final int maxRange = 6 + targetHeight;
final BlockPos.Mutable pos = targetPos.mutable();
for( int y = 0; y <= maxRange; y++ ) {
pos.setY( targetPos.getY() + y );
if( pos.getY() >= level.getMaxBuildHeight() ) break; // Can't build here
final BlockState block = level.getBlockState( pos );
if( block.getBlock() != Blocks.WATER || block.getValue( FlowingFluidBlock.LEVEL ) != 0 ) {
if( y - 1 <= targetHeight ) break; // Don't build inside the target entity
return pos.below();
}
}
return null;
}
/** Creates an ice seal centered at the position with a certain size. */
private void makeIceSeal( BlockPos center, int radius ) {
if( !isSilent() ) {
level.playSound( null, center.getX() + 0.5, center.getY() + 0.5, center.getZ() + 0.5,
SoundEvents.GLASS_BREAK, getSoundSource(), 0.4F, 1.0F / (random.nextFloat() * 0.4F + 0.8F) );
}
if( radius <= 0 ) {
placeSealBlock( center );
return;
}
for( int x = -radius; x <= radius; x++ ) {
for( int z = -radius; z <= radius; z++ ) {
final int distSq = x * x + z * z;
// Fill circle
if( distSq <= radius * radius ) {
placeSealBlock( center.offset( x, 0, z ) );
}
}
}
}
/** Attempts to place a single seal block. */
private void placeSealBlock( BlockPos pos ) {
final BlockState block = MeltingIceBlock.getState( level, pos );
if( level.getBlockState( pos ).getMaterial().isReplaceable() && level.isUnobstructed( block, pos, ISelectionContext.empty() ) &&
MobHelper.placeBlock( this, pos, block ) ) {
MeltingIceBlock.scheduleFirstTick( level, pos, random );
}
}
}

View file

@ -22,7 +22,7 @@ public class GiantDrownedEntity extends _SpecialDrownedEntity {
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0x799C65 ).theme( BestiaryInfo.Theme.MOUNTAIN )
bestiaryInfo.color( 0x799C65 )
.size( 1.5F, 0.9F, 2.95F )
.addExperience( 1 )
.addToAttribute( Attributes.MAX_HEALTH, 20.0 )

View file

@ -34,7 +34,7 @@ public class HungryDrownedEntity extends _SpecialDrownedEntity {
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0xAB1518 )
.uniqueTextureBaseOnly()
.uniqueTextureWithOverlay()
.addExperience( 2 ).regen( 30 ).disableRangedAttack()
.addToAttribute( Attributes.MAX_HEALTH, 10.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 1.3 );

View file

@ -23,8 +23,8 @@ public class PlagueDrownedEntity extends _SpecialDrownedEntity {
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0x8AA838 ).theme( BestiaryInfo.Theme.FOREST )
.uniqueTextureBaseOnly()
bestiaryInfo.color( 0x8AA838 )
.uniqueTextureWithOverlay()
.addExperience( 1 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 1.1 );
}

View file

@ -0,0 +1,76 @@
package fathertoast.specialmobs.common.entity.drowned;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.config.species.DrownedSpeciesConfig;
import fathertoast.specialmobs.common.config.species.SpeciesConfig;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.Blocks;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.potion.Effects;
import net.minecraft.world.World;
@SpecialMob
public class TropicalDrownedEntity extends _SpecialDrownedEntity {
//--------------- Static Special Mob Hooks ----------------
@SpecialMob.SpeciesReference
public static MobFamily.Species<TropicalDrownedEntity> SPECIES;
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0xD962A3 ).weight( BestiaryInfo.DefaultWeight.LOWEST ).theme( BestiaryInfo.Theme.TROPICAL )
.uniqueTextureWithOverlay()
.addExperience( 1 )
.multiplyRangedCooldown( 0.75F ).rangedMaxRange( 15.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 1.2 );
}
@SpecialMob.ConfigSupplier
public static SpeciesConfig createConfig( MobFamily.Species<?> species ) {
return new DrownedSpeciesConfig( species, 1.0, DEFAULT_SHIELD_CHANCE );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Tropical Drowned",
"", "", "", "", "", "" );//TODO
}
@SpecialMob.LootTableProvider
public static void buildLootTable( LootTableBuilder loot ) {
addBaseLoot( loot );
loot.addCommonDrop( "common", Blocks.SEA_PICKLE );
loot.addUncommonDrop( "uncommon",
Blocks.TUBE_CORAL, Blocks.TUBE_CORAL_FAN, Blocks.TUBE_CORAL_BLOCK,
Blocks.BRAIN_CORAL, Blocks.BRAIN_CORAL_FAN, Blocks.BRAIN_CORAL_BLOCK,
Blocks.BUBBLE_CORAL, Blocks.BUBBLE_CORAL_FAN, Blocks.BUBBLE_CORAL_BLOCK,
Blocks.FIRE_CORAL, Blocks.FIRE_CORAL_FAN, Blocks.FIRE_CORAL_BLOCK,
Blocks.HORN_CORAL, Blocks.HORN_CORAL_FAN, Blocks.HORN_CORAL_BLOCK );
}
@SpecialMob.Factory
public static EntityType.IFactory<TropicalDrownedEntity> getVariantFactory() { return TropicalDrownedEntity::new; }
/** @return This entity's mob species. */
@SpecialMob.SpeciesSupplier
@Override
public MobFamily.Species<? extends TropicalDrownedEntity> getSpecies() { return SPECIES; }
//--------------- Variant-Specific Implementations ----------------
public TropicalDrownedEntity( EntityType<? extends _SpecialDrownedEntity> entityType, World world ) { super( entityType, world ); }
/** Override to apply effects when this entity hits a target with a melee attack. */
@Override
protected void onVariantAttack( LivingEntity target ) {
MobHelper.applyEffect( target, Effects.POISON );
}
}

View file

@ -3,6 +3,7 @@ package fathertoast.specialmobs.common.entity.enderman;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.util.ExplosionHelper;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
@ -120,8 +121,9 @@ public class FlameEndermanEntity extends _SpecialEndermanEntity {
while( currentPos.getY() < maxY ) {
currentPos.move( 0, 1, 0 );
if( shouldSetFire( currentPos ) )
level.setBlock( currentPos, AbstractFireBlock.getState( level, currentPos ), References.SetBlockFlags.DEFAULTS );
if( shouldSetFire( currentPos ) ) {
MobHelper.placeBlock( this, currentPos, AbstractFireBlock.getState( level, currentPos ) );
}
}
}

View file

@ -3,6 +3,8 @@ package fathertoast.specialmobs.common.entity.ghast;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.config.species.CorporealShiftGhastSpeciesConfig;
import fathertoast.specialmobs.common.config.species.SpeciesConfig;
import fathertoast.specialmobs.common.core.register.SMItems;
import fathertoast.specialmobs.common.entity.projectile.IncorporealFireballEntity;
import fathertoast.specialmobs.common.util.References;
@ -39,6 +41,14 @@ public class CorporealShiftGhastEntity extends _SpecialGhastEntity {
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 );
}
@SpecialMob.ConfigSupplier
public static SpeciesConfig createConfig( MobFamily.Species<?> species ) {
return new CorporealShiftGhastSpeciesConfig( species, 300, 200 );
}
/** @return This entity's species config. */
public CorporealShiftGhastSpeciesConfig getConfig() { return (CorporealShiftGhastSpeciesConfig) getSpecies().config; }
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Corporeal Shift Ghast",
@ -64,8 +74,7 @@ public class CorporealShiftGhastEntity extends _SpecialGhastEntity {
public static final DataParameter<Boolean> CORPOREAL = EntityDataManager.defineId( CorporealShiftGhastEntity.class, DataSerializers.BOOLEAN );
private final int maxShiftTime = 150;
private int shiftTime = maxShiftTime;
private int shiftTime;
public CorporealShiftGhastEntity( EntityType<? extends _SpecialGhastEntity> entityType, World world ) { super( entityType, world ); }
@ -73,7 +82,7 @@ public class CorporealShiftGhastEntity extends _SpecialGhastEntity {
protected void defineSynchedData() {
super.defineSynchedData();
entityData.define( CORPOREAL, false );
if( !level.isClientSide() && random.nextBoolean() ) setCorporeal( true );
if( !level.isClientSide() ) setCorporeal( random.nextBoolean() );
}
@Override
@ -81,8 +90,7 @@ public class CorporealShiftGhastEntity extends _SpecialGhastEntity {
super.tick();
if( --shiftTime <= 0 ) {
if( !level.isClientSide ) {
shiftTime = maxShiftTime + random.nextInt( maxShiftTime );
if( !level.isClientSide() ) {
setCorporeal( !isCorporeal() );
spawnShiftSmoke( (ServerWorld) level );
}
@ -106,7 +114,10 @@ public class CorporealShiftGhastEntity extends _SpecialGhastEntity {
public boolean isCorporeal() { return entityData.get( CORPOREAL ); }
private void setCorporeal( boolean value ) { entityData.set( CORPOREAL, value ); }
private void setCorporeal( boolean value ) {
entityData.set( CORPOREAL, value );
shiftTime = value ? getConfig().CORPOREAL_SHIFT.corporealTicks.get() : getConfig().CORPOREAL_SHIFT.incorporealTicks.get();
}
/** Called to attack the target with a ranged attack. */
@Override

View file

@ -1,8 +1,10 @@
package fathertoast.specialmobs.common.entity.projectile;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.core.register.SMEntities;
import fathertoast.specialmobs.common.core.register.SMItems;
import fathertoast.specialmobs.common.entity.ghast.CorporealShiftGhastEntity;
import fathertoast.specialmobs.common.util.References;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
@ -27,7 +29,7 @@ import net.minecraftforge.fml.network.NetworkHooks;
import javax.annotation.Nullable;
public class IncorporealFireballEntity extends AbstractFireballEntity {
public int explosionPower = 1;
private boolean shouldExplode = false;
@ -35,20 +37,26 @@ public class IncorporealFireballEntity extends AbstractFireballEntity {
private LivingEntity target;
public IncorporealFireballEntity(EntityType<? extends AbstractFireballEntity> entityType, World world ) {
public IncorporealFireballEntity( EntityType<? extends AbstractFireballEntity> entityType, World world ) {
super( entityType, world );
}
public IncorporealFireballEntity(World world, CorporealShiftGhastEntity ghast, double x, double y, double z ) {
public IncorporealFireballEntity( World world, CorporealShiftGhastEntity ghast, double x, double y, double z ) {
super( SMEntities.INCORPOREAL_FIREBALL.get(), ghast, x, y, z, world );
target = ghast.getTarget();
}
public IncorporealFireballEntity(World world, @Nullable PlayerEntity owner, LivingEntity target, double x, double y, double z ) {
public IncorporealFireballEntity( World world, @Nullable PlayerEntity owner, LivingEntity target, double x, double y, double z ) {
super( SMEntities.INCORPOREAL_FIREBALL.get(), owner, x, y, z, world );
this.target = target;
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Incorporeal Fireball",
"", "", "", "", "", "" );//TODO
}
@Override
public void tick() {
super.tick();
@ -95,21 +103,21 @@ public class IncorporealFireballEntity extends AbstractFireballEntity {
if( !this.level.isClientSide ) {
Entity target = traceResult.getEntity();
boolean fizzle;
if ( target instanceof PlayerEntity ) {
if( target instanceof PlayerEntity ) {
// TODO - Implement player-specific checks
fizzle = true;
}
else {
if (target.getX() != target.xo || target.getY() != target.yo || target.getZ() != target.zo) {
if( target.getX() != target.xo || target.getY() != target.yo || target.getZ() != target.zo ) {
explode();
return;
}
fizzle = true;
}
if (fizzle) {
if( fizzle ) {
playSound( SoundEvents.FIRE_EXTINGUISH, 1.0F, 1.0F );
remove();
}
@ -124,10 +132,10 @@ public class IncorporealFireballEntity extends AbstractFireballEntity {
shouldExplode = true;
return true;
}
@OnlyIn(Dist.CLIENT)
@OnlyIn( Dist.CLIENT )
public ItemStack getItem() {
return new ItemStack(SMItems.INCORPOREAL_FIREBALL.get());
return new ItemStack( SMItems.INCORPOREAL_FIREBALL.get() );
}
@Override

View file

@ -3,6 +3,7 @@ package fathertoast.specialmobs.common.entity.spider;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.Blocks;
@ -63,7 +64,7 @@ public class FireSpiderEntity extends _SpecialSpiderEntity {
if( !level.isClientSide() ) {
final BlockPos pos = target.blockPosition();
if( level.getBlockState( pos ).getMaterial().isReplaceable() ) {
level.setBlock( pos, Blocks.FIRE.defaultBlockState(), References.SetBlockFlags.DEFAULTS );
MobHelper.placeBlock( this, pos, Blocks.FIRE.defaultBlockState() );
}
}
target.setSecondsOnFire( 5 );

View file

@ -5,6 +5,7 @@ import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.config.species.SpeciesConfig;
import fathertoast.specialmobs.common.config.species.WebSpiderSpeciesConfig;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.Blocks;
@ -99,8 +100,8 @@ public class WebSpiderEntity extends _SpecialSpiderEntity {
/** @return Attempts to place a cobweb at the given position and returns true if successful. */
private boolean tryPlaceWeb( BlockPos pos ) {
if( level.getBlockState( pos ).getMaterial().isReplaceable() ) {
level.setBlock( pos, Blocks.COBWEB.defaultBlockState(), References.SetBlockFlags.DEFAULTS );
if( level.getBlockState( pos ).getMaterial().isReplaceable() &&
MobHelper.placeBlock( this, pos, Blocks.COBWEB.defaultBlockState() ) ) {
webCount--;
return true;
}

View file

@ -0,0 +1,89 @@
package fathertoast.specialmobs.common.entity.witch;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.Blocks;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.attributes.Attributes;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.potion.Effects;
import net.minecraft.potion.Potions;
import net.minecraft.world.World;
@SpecialMob
public class IceWitchEntity extends _SpecialWitchEntity {
//--------------- Static Special Mob Hooks ----------------
@SpecialMob.SpeciesReference
public static MobFamily.Species<IceWitchEntity> SPECIES;
@SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) {
bestiaryInfo.color( 0xDDEAEA ).weight( BestiaryInfo.DefaultWeight.LOW ).theme( BestiaryInfo.Theme.ICE )
.uniqueTextureBaseOnly()
.addExperience( 2 ).effectImmune( Effects.MOVEMENT_SLOWDOWN )
.addToAttribute( Attributes.ARMOR, 10.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 );
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Ice Witch",
"", "", "", "", "", "" );//TODO
}
@SpecialMob.LootTableProvider
public static void buildLootTable( LootTableBuilder loot ) {
addBaseLoot( loot );
loot.addClusterDrop( "common", Items.SNOWBALL );
loot.addUncommonDrop( "uncommon", Blocks.BLUE_ICE );
}
@SpecialMob.Factory
public static EntityType.IFactory<IceWitchEntity> getVariantFactory() { return IceWitchEntity::new; }
/** @return This entity's mob species. */
@SpecialMob.SpeciesSupplier
@Override
public MobFamily.Species<? extends IceWitchEntity> getSpecies() { return SPECIES; }
//--------------- Variant-Specific Implementations ----------------
/** Ticks before this witch can use its ice wall ability. */
private int wallDelay;
public IceWitchEntity( EntityType<? extends _SpecialWitchEntity> entityType, World world ) { super( entityType, world ); }
/** Override to modify potion attacks. Return an empty item stack to cancel the potion throw. */
@Override
protected ItemStack pickVariantThrownPotion( ItemStack originalPotion, LivingEntity target, float damageMulti, float distance ) {
if( !target.hasEffect( Effects.MOVEMENT_SLOWDOWN ) ) {
return makeSplashPotion( Potions.STRONG_SLOWNESS );
}
return originalPotion;
}
/** Called each tick to update this entity's movement. */
@Override
public void aiStep() {
final LivingEntity target = getTarget();
if( !level.isClientSide() && isAlive() && wallDelay-- <= 0 && target != null && random.nextInt( 20 ) == 0 ) {
// Create an ice wall behind the target if they are vulnerable
final double distanceSq = target.distanceToSqr( this );
if( distanceSq > 100.0 && distanceSq < 196.0 && target.hasEffect( Effects.MOVEMENT_SLOWDOWN ) && canSee( target ) ) {
wallDelay = 200;
//TODO
}
}
super.aiStep();
}
}

View file

@ -43,7 +43,6 @@ public class WindWitchEntity extends _SpecialWitchEntity {
bestiaryInfo.color( 0x6388B2 ).theme( BestiaryInfo.Theme.MOUNTAIN )
.uniqueTextureWithEyes()
.addExperience( 2 ).fallImmune()
.addToAttribute( Attributes.ATTACK_DAMAGE, 2.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 1.2 );
}

View file

@ -5,6 +5,7 @@ import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.ai.AIHelper;
import fathertoast.specialmobs.common.entity.drowned.FrozenDrownedEntity;
import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.Blocks;
@ -16,6 +17,7 @@ import net.minecraft.entity.ai.attributes.ModifiableAttributeInstance;
import net.minecraft.entity.ai.goal.LookAtGoal;
import net.minecraft.entity.ai.goal.LookRandomlyGoal;
import net.minecraft.entity.ai.goal.WaterAvoidingRandomWalkingGoal;
import net.minecraft.entity.monster.ZombieEntity;
import net.minecraft.entity.projectile.AbstractArrowEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
@ -92,6 +94,10 @@ public class FrozenZombieEntity extends _SpecialZombieEntity {
return MobHelper.tipArrow( arrow, Effects.MOVEMENT_SLOWDOWN );
}
/** Override to change the entity this converts to when drowned. */
@Override
protected EntityType<? extends ZombieEntity> getVariantConversionType() { return FrozenDrownedEntity.SPECIES.entityType.get(); }
/** Called each tick to update this entity's movement. */
@Override
public void aiStep() {

View file

@ -26,7 +26,7 @@ import net.minecraft.world.IServerWorld;
import net.minecraft.world.World;
import java.util.Random;
import java.util.function.Function;
import java.util.function.Predicate;
@SpecialMob
public class HuskZombieEntity extends _SpecialZombieEntity {
@ -109,7 +109,7 @@ public class HuskZombieEntity extends _SpecialZombieEntity {
}
/** Returns true if the species is not a husk and not damaged by water. */
private static final Function<MobFamily.Species<?>, Boolean> HUSK_CONVERSION_SELECTOR =
private static final Predicate<MobFamily.Species<?>> HUSK_CONVERSION_SELECTOR =
( species ) -> species != SPECIES && !species.config.GENERAL.isDamagedByWater.get();
/** Performs this zombie's drowning conversion. */

View file

@ -1,45 +1,69 @@
package fathertoast.specialmobs.common.item;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.network.NetworkHelper;
import fathertoast.specialmobs.common.util.EntityUtil;
import fathertoast.specialmobs.common.entity.projectile.IncorporealFireballEntity;
import fathertoast.specialmobs.common.util.References;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.projectile.ProjectileHelper;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Rarity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvents;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.EntityRayTraceResult;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.World;
import javax.annotation.Nullable;
public class IncorporealFireChargeItem extends Item {
public IncorporealFireChargeItem() {
super(new Item.Properties().stacksTo(1).tab(ItemGroup.TAB_MISC).rarity(Rarity.UNCOMMON));
}
@SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Incorporeal Fire Charge",
"", "", "", "", "", "" );//TODO
}
public IncorporealFireChargeItem() { super( new Item.Properties().tab( ItemGroup.TAB_MISC ) ); }
@Override
public ActionResult<ItemStack> use(World world, PlayerEntity player, Hand hand) {
if (world.isClientSide) {
Entity entity = EntityUtil.getClientPickEntity(player, 340.0D);
if (entity instanceof LivingEntity) {
NetworkHelper.spawnIncorporealFireball(player, (LivingEntity) entity);
world.playSound(player, player.blockPosition(), SoundEvents.FIRECHARGE_USE, SoundCategory.BLOCKS, 1.0F, (random.nextFloat() - random.nextFloat()) * 0.2F + 1.0F);
return ActionResult.consume(player.getItemInHand(hand));
public ActionResult<ItemStack> use( World world, PlayerEntity player, Hand hand ) {
final ItemStack item = player.getItemInHand( hand );
if( player.getCooldowns().isOnCooldown( item.getItem() ) ) return ActionResult.pass( item );
final Entity target = pickEntity( player, 127.0 );
if( target instanceof LivingEntity ) {
if( !world.isClientSide() ) {
world.addFreshEntity( new IncorporealFireballEntity( world, player, (LivingEntity) target,
player.getX(), player.getEyeY(), player.getZ() ) );
world.playSound( null, player.blockPosition(), SoundEvents.FIRECHARGE_USE, SoundCategory.BLOCKS, 1.0F, (random.nextFloat() - random.nextFloat()) * 0.2F + 1.0F );
if( !player.abilities.instabuild ) {
item.shrink( 1 );
}
player.getCooldowns().addCooldown( item.getItem(), 10 );
return ActionResult.consume( item );
}
return ActionResult.pass( item );
}
return ActionResult.pass(player.getItemInHand(hand));
return ActionResult.fail( item );
}
}
@Nullable
private static Entity pickEntity( PlayerEntity player, double range ) {
final Vector3d eyePos = player.getEyePosition( 1.0F );
final Vector3d viewVec = player.getViewVector( 1.0F ).scale( range );
final AxisAlignedBB bb = player.getBoundingBox().expandTowards( viewVec ).inflate( 1.0 );
final EntityRayTraceResult result = ProjectileHelper.getEntityHitResult( player.level, player, eyePos, eyePos.add( viewVec ), bb,
( entity ) -> !entity.isSpectator() && entity.isAlive() && entity.isPickable() && !player.isPassengerOfSameVehicle( entity ) );
return result == null ? null : result.getEntity();
}
}

View file

@ -1,14 +1,7 @@
package fathertoast.specialmobs.common.network;
import fathertoast.specialmobs.common.network.message.C2SSpawnIncorporealFireball;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import javax.annotation.Nonnull;
public class NetworkHelper {
public static void spawnIncorporealFireball(@Nonnull PlayerEntity player, @Nonnull LivingEntity livingEntity) {
PacketHandler.CHANNEL.sendToServer(new C2SSpawnIncorporealFireball(player.getUUID(), livingEntity.getId()));
}
}
// public static void spawnIncorporealFireball( @Nonnull PlayerEntity player, @Nonnull LivingEntity livingEntity ) {
// PacketHandler.CHANNEL.sendToServer( new C2SSpawnIncorporealFireball( player.getUUID(), livingEntity.getId() ) );
// }
}

View file

@ -1,7 +1,6 @@
package fathertoast.specialmobs.common.network;
import fathertoast.specialmobs.common.core.SpecialMobs;
import fathertoast.specialmobs.common.network.message.C2SSpawnIncorporealFireball;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.ResourceLocation;
@ -33,7 +32,7 @@ public class PacketHandler {
}
public final void registerMessages() {
registerMessage( C2SSpawnIncorporealFireball.class, C2SSpawnIncorporealFireball::encode, C2SSpawnIncorporealFireball::decode, C2SSpawnIncorporealFireball::handle );
//registerMessage( C2SSpawnIncorporealFireball.class, C2SSpawnIncorporealFireball::encode, C2SSpawnIncorporealFireball::decode, C2SSpawnIncorporealFireball::handle );
}
public <MSG> void registerMessage( Class<MSG> messageType, BiConsumer<MSG, PacketBuffer> encoder, Function<PacketBuffer, MSG> decoder, BiConsumer<MSG, Supplier<NetworkEvent.Context>> handler ) {

View file

@ -1,37 +1,29 @@
package fathertoast.specialmobs.common.network.message;
import fathertoast.specialmobs.common.network.work.ServerWork;
import net.minecraft.network.PacketBuffer;
import net.minecraftforge.fml.network.NetworkEvent;
import java.util.UUID;
import java.util.function.Supplier;
public class C2SSpawnIncorporealFireball {
public final UUID playerUUID;
public final int targetEntityID;
public C2SSpawnIncorporealFireball(UUID playerUUID, int targetEntityId) {
this.playerUUID = playerUUID;
this.targetEntityID = targetEntityId;
}
public static void handle(C2SSpawnIncorporealFireball message, Supplier<NetworkEvent.Context> contextSupplier) {
NetworkEvent.Context context = contextSupplier.get();
if (context.getDirection().getReceptionSide().isServer()) {
context.enqueueWork(() -> ServerWork.handleSpawnIncorporealFireball(message));
}
context.setPacketHandled(true);
}
public static C2SSpawnIncorporealFireball decode(PacketBuffer buffer) {
return new C2SSpawnIncorporealFireball(buffer.readUUID(), buffer.readInt());
}
public static void encode(C2SSpawnIncorporealFireball message, PacketBuffer buffer) {
buffer.writeUUID(message.playerUUID);
buffer.writeInt(message.targetEntityID);
}
}
// public final UUID playerUUID;
// public final int targetEntityID;
//
// public C2SSpawnIncorporealFireball(UUID playerUUID, int targetEntityId) {
// this.playerUUID = playerUUID;
// this.targetEntityID = targetEntityId;
// }
//
// public static void handle(C2SSpawnIncorporealFireball message, Supplier<NetworkEvent.Context> contextSupplier) {
// NetworkEvent.Context context = contextSupplier.get();
//
// if (context.getDirection().getReceptionSide().isServer()) {
// context.enqueueWork(() -> ServerWork.handleSpawnIncorporealFireball(message));
// }
// context.setPacketHandled(true);
// }
//
// public static C2SSpawnIncorporealFireball decode(PacketBuffer buffer) {
// return new C2SSpawnIncorporealFireball(buffer.readUUID(), buffer.readInt());
// }
//
// public static void encode(C2SSpawnIncorporealFireball message, PacketBuffer buffer) {
// buffer.writeUUID(message.playerUUID);
// buffer.writeInt(message.targetEntityID);
// }
}

View file

@ -1,36 +1,25 @@
package fathertoast.specialmobs.common.network.work;
import fathertoast.specialmobs.common.entity.projectile.IncorporealFireballEntity;
import fathertoast.specialmobs.common.network.message.C2SSpawnIncorporealFireball;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.LogicalSidedProvider;
public class ServerWork {
public static void handleSpawnIncorporealFireball(C2SSpawnIncorporealFireball message) {
MinecraftServer server = LogicalSidedProvider.INSTANCE.get(LogicalSide.SERVER);
if (server == null)
return;
ServerPlayerEntity player = server.getPlayerList().getPlayer(message.playerUUID);
if (player == null)
return;
ServerWorld world = (ServerWorld) player.level;
Entity entity = world.getEntity(message.targetEntityID);
if (!(entity instanceof LivingEntity))
return;
LivingEntity livingEntity = (LivingEntity) entity;
IncorporealFireballEntity fireballEntity = new IncorporealFireballEntity(world, player, livingEntity, player.getX(), player.getEyeY(), player.getZ());
world.addFreshEntity(fireballEntity);
}
}
// public static void handleSpawnIncorporealFireball(C2SSpawnIncorporealFireball message) {
// MinecraftServer server = LogicalSidedProvider.INSTANCE.get(LogicalSide.SERVER);
//
// if (server == null)
// return;
//
// ServerPlayerEntity player = server.getPlayerList().getPlayer(message.playerUUID);
//
// if (player == null)
// return;
//
// ServerWorld world = (ServerWorld) player.level;
// Entity entity = world.getEntity(message.targetEntityID);
//
// if (!(entity instanceof LivingEntity))
// return;
//
// LivingEntity livingEntity = (LivingEntity) entity;
// IncorporealFireballEntity fireballEntity = new IncorporealFireballEntity(world, player, livingEntity, player.getX(), player.getEyeY(), player.getZ());
// world.addFreshEntity(fireballEntity);
// }
}

View file

@ -5,10 +5,12 @@ import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.config.species.SpeciesConfig;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.Block;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.attributes.AttributeModifierMap;
import net.minecraft.item.Item;
import net.minecraftforge.registries.ForgeRegistryEntry;
import javax.annotation.Nullable;
import java.lang.annotation.Annotation;
@ -103,14 +105,20 @@ public final class AnnotationHelper {
}
}
/** Gets the translations from a mod item. Throws an exception if anything goes wrong. */
public static String[] getTranslations( Item item ) {
/** Gets the translations from a block. Throws an exception if anything goes wrong. */
public static String[] getTranslations( Block block ) { return getTranslations( block, block.getDescriptionId() ); }
/** Gets the translations from an item. Throws an exception if anything goes wrong. */
public static String[] getTranslations( Item item ) { return getTranslations( item, item.getDescriptionId() ); }
/** Gets the translations from a registry entry. Throws an exception if anything goes wrong. */
public static String[] getTranslations( ForgeRegistryEntry<?> entry, String key ) {
try {
return (String[]) getMethod( item.getClass(), SpecialMob.LanguageProvider.class )
.invoke( null, item.getDescriptionId() );
return (String[]) getMethod( entry.getClass(), SpecialMob.LanguageProvider.class )
.invoke( null, key );
}
catch( NoSuchMethodException | InvocationTargetException | IllegalAccessException ex ) {
throw new RuntimeException( "Item class for " + item.getRegistryName() + " has invalid language provider method", ex );
throw new RuntimeException( "Class for " + entry.getRegistryName() + " has invalid language provider method", ex );
}
}

View file

@ -1,31 +0,0 @@
package fathertoast.specialmobs.common.util;
import fathertoast.specialmobs.common.core.SpecialMobs;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.projectile.ProjectileHelper;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.EntityRayTraceResult;
import net.minecraft.util.math.vector.Vector3d;
import javax.annotation.Nullable;
public class EntityUtil {
@Nullable
public static Entity getClientPickEntity( PlayerEntity player, double pickRange ) {
if( !player.level.isClientSide ) {
SpecialMobs.LOG.error( "Tried to fetch player \"mouse-over\" entity from server side. This can't be right?" );
return null;
}
Vector3d eyePos = player.getEyePosition( 1.0F );
Vector3d viewVec = player.getViewVector( 1.0F );
Vector3d targetVec = eyePos.add( viewVec.x * pickRange, viewVec.y * pickRange, viewVec.z * pickRange );
AxisAlignedBB AABB = player.getBoundingBox().expandTowards( viewVec.scale( pickRange ) ).inflate( 1.0D, 1.0D, 1.0D );
EntityRayTraceResult result = ProjectileHelper.getEntityHitResult( player, eyePos, targetVec, AABB,
( entity ) -> !entity.isSpectator() && entity.isPickable(), pickRange );
return result != null ? result.getEntity() : null;
}
}

View file

@ -256,7 +256,7 @@ public final class References {
// Spawner mobs
public static final String TAG_BABIES = "Babies"; // Mother (Cave) Spider, Wilds Witch, Queen Ghast, Wildfire Blaze
public static final String TAG_EXTRA_BABIES = "ExtraBabies"; // Splitting Creeper, Mother (Cave) Spider, Wilds Witch
public static final String TAG_SUMMONS = "Summons"; // Undead Witch, Wilds Witch, Queen Ghast, Wildfire Blaze
public static final String TAG_SUMMONS = "Summons"; // Drowning Creeper, Undead Witch, Wilds Witch, Queen Ghast, Wildfire Blaze
// Growing mobs
public static final String TAG_GROWTH_LEVEL = "GrowthLevel"; // Hungry Spider, Conflagration Blaze

View file

@ -16,6 +16,7 @@ public class DataGatherListener {
DataGenerator generator = event.getGenerator();
if( event.includeClient() ) {
generator.addProvider( new SMBlockStateAndModelProvider( generator, event.getExistingFileHelper() ) );
generator.addProvider( new SMItemModelProvider( generator, event.getExistingFileHelper() ) );
for( Map.Entry<String, SMLanguageProvider.TranslationKey> entry : SMLanguageProvider.LANG_CODE_MAP.entrySet() ) {
generator.addProvider( new SMLanguageProvider( generator, entry.getKey(), entry.getValue() ) );

View file

@ -0,0 +1,45 @@
package fathertoast.specialmobs.datagen;
import fathertoast.specialmobs.common.block.MeltingIceBlock;
import fathertoast.specialmobs.common.block.UnderwaterSilverfishBlock;
import fathertoast.specialmobs.common.core.SpecialMobs;
import fathertoast.specialmobs.common.core.register.SMBlocks;
import net.minecraft.block.Blocks;
import net.minecraft.data.DataGenerator;
import net.minecraftforge.client.model.generators.BlockStateProvider;
import net.minecraftforge.client.model.generators.ModelProvider;
import net.minecraftforge.client.model.generators.VariantBlockStateBuilder;
import net.minecraftforge.common.data.ExistingFileHelper;
import java.util.Objects;
public class SMBlockStateAndModelProvider extends BlockStateProvider {
public SMBlockStateAndModelProvider( DataGenerator gen, ExistingFileHelper existingFileHelper ) {
super( gen, SpecialMobs.MOD_ID, existingFileHelper );
}
@Override
protected void registerStatesAndModels() {
String name;
// Melting ice
final VariantBlockStateBuilder builder = getVariantBuilder( SMBlocks.MELTING_ICE.get() );
name = Objects.requireNonNull( Blocks.FROSTED_ICE.getRegistryName() ).getPath();
for( int age = 0; age <= 3; age++ ) {
builder.partialState().with( MeltingIceBlock.AGE, age ).modelForState().modelFile( models().getExistingFile(
mcLoc( ModelProvider.BLOCK_FOLDER + "/" + name + "_" + age ) ) ).addModel();
}
itemModels().withExistingParent( SMBlocks.MELTING_ICE.getId().getPath(),
mcLoc( ModelProvider.BLOCK_FOLDER + "/" + name + "_0" ) );
// Infested coral
for( UnderwaterSilverfishBlock.Type type : UnderwaterSilverfishBlock.Type.values() ) {
name = Objects.requireNonNull( type.hostBlock().getRegistryName() ).getPath();
getVariantBuilder( type.block() ).partialState().modelForState().modelFile( models().getExistingFile(
mcLoc( ModelProvider.BLOCK_FOLDER + "/" + name ) ) ).addModel();
itemModels().withExistingParent( type.blockId(), mcLoc( ModelProvider.BLOCK_FOLDER + "/" + name ) );
}
}
}

View file

@ -22,15 +22,16 @@ public class SMItemModelProvider extends ItemModelProvider {
protected void registerModels() {
// Bestiary-generated spawn egg models
final ResourceLocation spawnEggParent = modLoc( ITEM_FOLDER + "/template_sm_spawn_egg" );
for( MobFamily.Species<?> species : MobFamily.getAllSpecies() )
for( MobFamily.Species<?> species : MobFamily.getAllSpecies() ) {
withExistingParent( species.spawnEgg.getId().getPath(), spawnEggParent );
}
// Simple items
for(RegistryObject<? extends Item> regObject : SMItems.SIMPLE_ITEMS) {
String name = Objects.requireNonNull(regObject.getId()).getPath();
withExistingParent(name, mcLoc("item/generated"))
.texture("layer0", modLoc(ITEM_FOLDER + "/" + name));
for( RegistryObject<? extends Item> regObject : SMItems.SIMPLE_ITEMS ) {
final String name = Objects.requireNonNull( regObject.getId() ).getPath();
withExistingParent( name, mcLoc( ITEM_FOLDER + "/generated" ) )
.texture( "layer0", modLoc( ITEM_FOLDER + "/" + name ) );
}
}
}

View file

@ -2,12 +2,15 @@ package fathertoast.specialmobs.datagen;
import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.core.SpecialMobs;
import fathertoast.specialmobs.common.core.register.SMBlocks;
import fathertoast.specialmobs.common.core.register.SMEffects;
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 net.minecraft.block.Block;
import net.minecraft.data.DataGenerator;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraftforge.common.ForgeSpawnEggItem;
import net.minecraftforge.common.data.LanguageProvider;
@ -71,13 +74,18 @@ public class SMLanguageProvider extends LanguageProvider {
translationList.add( References.translations( SMEntities.FISHING_BOBBER.get().getDescriptionId(), "Fishing Bobber",
"", "", "", "", "", "" ) ); //TODO
// Blocks
for( RegistryObject<Block> regObject : SMBlocks.REGISTRY.getEntries() ) {
translationList.add( AnnotationHelper.getTranslations( regObject.get() ) );
}
// Items
for( RegistryObject<Item> regObject : SMItems.REGISTRY.getEntries() ) {
// Lazy method of avoiding duplicate entries for now
if( regObject.get() instanceof ForgeSpawnEggItem ) continue;
if( regObject.get() instanceof BlockItem ) continue;
final String[] itemTranslations = AnnotationHelper.getTranslations( regObject.get() );
translationList.add( itemTranslations );
translationList.add( AnnotationHelper.getTranslations( regObject.get() ) );
}
// Misc

View file

@ -9,6 +9,9 @@ public net.minecraft.client.renderer.RenderState field_228528_t_ # LIGHTMAP
public net.minecraft.client.renderer.RenderState field_228530_v_ # OVERLAY
public net.minecraft.client.renderer.RenderState field_228515_g_ # TRANSLUCENT_TRANSPARENCY
# Blocks
protected net.minecraft.block.SilverfishBlock func_235505_a_(Lnet/minecraft/world/server/ServerWorld;Lnet/minecraft/util/math/BlockPos;)V # spawnInfestation(ServerWorld, BlockPos)
# Entity registration
public net.minecraft.entity.EntityType field_233593_bg_ # immuneTo
public net.minecraft.entity.ai.attributes.AttributeModifierMap$MutableAttribute field_233811_a_ # builder

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,017 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Some files were not shown because too many files have changed in this diff Show more