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
|
@ -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() );
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
@MethodsReturnNonnullByDefault
|
||||
@ParametersAreNonnullByDefault
|
||||
package fathertoast.specialmobs.common.block;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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." ) );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 ) )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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." ) );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() );
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 )
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
@ -49,6 +51,12 @@ public class IncorporealFireballEntity extends AbstractFireballEntity {
|
|||
this.target = target;
|
||||
}
|
||||
|
||||
@SpecialMob.LanguageProvider
|
||||
public static String[] getTranslations( String langKey ) {
|
||||
return References.translations( langKey, "Incorporeal Fireball",
|
||||
"", "", "", "", "", "" );//TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
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;
|
||||
|
||||
public class IncorporealFireChargeItem extends Item {
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public IncorporealFireChargeItem() {
|
||||
super(new Item.Properties().stacksTo(1).tab(ItemGroup.TAB_MISC).rarity(Rarity.UNCOMMON));
|
||||
}
|
||||
public class IncorporealFireChargeItem extends Item {
|
||||
|
||||
@SpecialMob.LanguageProvider
|
||||
public static String[] getTranslations( String langKey ) {
|
||||
|
@ -29,17 +29,41 @@ public class IncorporealFireChargeItem extends Item {
|
|||
"", "", "", "", "", "" );//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);
|
||||
final ItemStack item = player.getItemInHand( hand );
|
||||
if( player.getCooldowns().isOnCooldown( item.getItem() ) ) return ActionResult.pass( item );
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
return ActionResult.pass(player.getItemInHand(hand));
|
||||
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.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();
|
||||
}
|
||||
}
|
|
@ -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() ) );
|
||||
// }
|
||||
}
|
|
@ -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 ) {
|
||||
|
|
|
@ -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);
|
||||
// }
|
||||
}
|
|
@ -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);
|
||||
// }
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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() ) );
|
||||
|
|
|
@ -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 ) );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,14 +22,15 @@ 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();
|
||||
final String name = Objects.requireNonNull( regObject.getId() ).getPath();
|
||||
|
||||
withExistingParent(name, mcLoc("item/generated"))
|
||||
withExistingParent( name, mcLoc( ITEM_FOLDER + "/generated" ) )
|
||||
.texture( "layer0", modLoc( ITEM_FOLDER + "/" + name ) );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 625 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 665 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 858 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1,017 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 229 B |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 212 B |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 506 B |
Before Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 474 B |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 261 B |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 117 B |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 438 B |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 118 B |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 578 B |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 129 B |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 422 B |
Before Width: | Height: | Size: 235 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 674 B |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 655 B |
Before Width: | Height: | Size: 2.2 KiB |