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

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

View file

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

View file

@ -43,16 +43,31 @@ public class BestiaryInfo {
NONE( new EnvironmentList() ), NONE( new EnvironmentList() ),
FIRE( new EnvironmentList( FIRE( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inUltraWarmDimension().build(), 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.HIGHEST.value ).isHot().build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).isWarm().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( ICE( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inUltraWarmDimension().build(), EnvironmentEntry.builder( DefaultWeight.LOWEST.value ).inUltraWarmDimension().build(),
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).isFreezing().build(), EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).isFreezing().build(),
EnvironmentEntry.builder( DefaultWeight.LOW.value ).isWarm().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( DESERT( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inUltraWarmDimension().build(), EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inUltraWarmDimension().build(),
@ -89,6 +104,13 @@ public class BestiaryInfo {
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).isRaining().build(), EnvironmentEntry.builder( DefaultWeight.HIGH.value ).isRaining().build(),
EnvironmentEntry.builder( DefaultWeight.LOW.value ).cannotSeeSky().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( FISHING( new EnvironmentList(
EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inWaterBiome().build(), EnvironmentEntry.builder( DefaultWeight.HIGHEST.value ).inWaterBiome().build(),
EnvironmentEntry.builder( DefaultWeight.HIGH.value ).atMaxMoonLight().build(), EnvironmentEntry.builder( DefaultWeight.HIGH.value ).atMaxMoonLight().build(),

View file

@ -13,6 +13,7 @@ import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.monster.*; import net.minecraft.entity.monster.*;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.common.ForgeSpawnEggItem; import net.minecraftforge.common.ForgeSpawnEggItem;
import net.minecraftforge.fml.RegistryObject; import net.minecraftforge.fml.RegistryObject;
@ -20,6 +21,7 @@ import net.minecraftforge.fml.RegistryObject;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.*; import java.util.*;
import java.util.function.Function; 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 * 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 }, "Creeper", "creepers", 0x0DA70B, new EntityType[] { EntityType.CREEPER },
"Dark", "Death", "Dirt", "Doom", "Drowning", "Ender", "Fire", "Gravel", "Jumping", "Lightning", "Dark", "Death", "Dirt", "Doom", "Drowning", "Ender", "Fire", "Gravel", "Jumping", "Lightning",
"Mini", "Sand", /*"Scope",*/ "Snow", "Skeleton", "Splitting" "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, public static final MobFamily<ZombieEntity, FamilyConfig> ZOMBIE = new MobFamily<>( FamilyConfig::newLessSpecial,
"Zombie", "zombies", 0x00AFAF, new EntityType[] { EntityType.ZOMBIE, EntityType.HUSK }, "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, public static final MobFamily<DrownedEntity, FamilyConfig> DROWNED = new MobFamily<>( FamilyConfig::new,
"Drowned", "drowned", 0x8FF1D7, new EntityType[] { EntityType.DROWNED }, "Drowned", "drowned", 0x8FF1D7, new EntityType[] { EntityType.DROWNED },
"Abyssal", "Brute", "Fishing", /*"Frozen",*/ "Giant", "Hungry", "Knight", "Plague"//, "Tropical" "Abyssal", "Brute", "Fishing", "Frozen", "Giant", "Hungry", "Knight", "Plague"//, "Tropical"
); //TODO Textures! - brute, hungry, plague, frozen, tropical );
public static final MobFamily<ZombifiedPiglinEntity, FamilyConfig> ZOMBIFIED_PIGLIN = new MobFamily<>( FamilyConfig::new, public static final MobFamily<ZombifiedPiglinEntity, FamilyConfig> ZOMBIFIED_PIGLIN = new MobFamily<>( FamilyConfig::new,
"ZombifiedPiglin", "zombified piglins", 0xEA9393, new EntityType[] { EntityType.ZOMBIFIED_PIGLIN }, "ZombifiedPiglin", "zombified piglins", 0xEA9393, new EntityType[] { EntityType.ZOMBIFIED_PIGLIN },
"Brute", "Fishing", "Giant", "Hungry", "Knight", "Plague", "Vampire"//TODO figure out crossbows "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, public static final MobFamily<AbstractSkeletonEntity, SkeletonFamilyConfig> SKELETON = new MobFamily<>( SkeletonFamilyConfig::new,
"Skeleton", "skeletons", 0xC1C1C1, new EntityType[] { EntityType.SKELETON, EntityType.STRAY }, "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, public static final MobFamily<AbstractSkeletonEntity, SkeletonFamilyConfig> WITHER_SKELETON = new MobFamily<>( SkeletonFamilyConfig::new,
"WitherSkeleton", "wither skeletons", 0x141414, new EntityType[] { EntityType.WITHER_SKELETON }, "WitherSkeleton", "wither skeletons", 0x141414, new EntityType[] { EntityType.WITHER_SKELETON },
"Brute", "Gatling", "Giant", "Knight", "Ninja", "Sniper", "Spitfire" "Brute", "Gatling", "Giant", "Knight", "Ninja", "Sniper", "Spitfire"
); );//TODO crimson/warped
public static final MobFamily<SlimeEntity, SlimeFamilyConfig> SLIME = new MobFamily<>( SlimeFamilyConfig::new, public static final MobFamily<SlimeEntity, SlimeFamilyConfig> SLIME = new MobFamily<>( SlimeFamilyConfig::new,
"Slime", "slimes", 0x51A03E, new EntityType[] { EntityType.SLIME }, "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, public static final MobFamily<WitchEntity, WitchFamilyConfig> WITCH = new MobFamily<>( WitchFamilyConfig::new,
"Witch", "witches", 0x340000, new EntityType[] { EntityType.WITCH }, "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, public static final MobFamily<GhastEntity, GhastFamilyConfig> GHAST = new MobFamily<>( GhastFamilyConfig::new,
"Ghast", "ghasts", 0xF9F9F9, new EntityType[] { EntityType.GHAST }, "Ghast", "ghasts", 0xF9F9F9, new EntityType[] { EntityType.GHAST },
@ -169,6 +171,9 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
/** This family's config. */ /** This family's config. */
public final V config; public final V config;
/** True if this family has any giant species. */
private Boolean hasAnyGiants;
private MobFamily( Function<MobFamily<?, ?>, V> configSupplier, private MobFamily( Function<MobFamily<?, ?>, V> configSupplier,
String familyName, String readableName, int eggColor, EntityType<?>[] replaceable, String familyName, String readableName, int eggColor, EntityType<?>[] replaceable,
String... variantNames ) { 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. */ /** Pick a new species from this family, based on the location. */
public Species<? extends T> nextVariant( World world, @Nullable BlockPos pos ) { public Species<? extends T> nextVariant( World world, @Nullable BlockPos pos, @Nullable Predicate<Species<?>> selector ) {
return nextVariant( world, pos, null, vanillaReplacement ); return nextVariant( world, pos, selector, vanillaReplacement );
} }
/** Pick a new species from this family, based on the location. */ /** 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 ); final Species<?> species = config.GENERAL.specialVariantList.next( world.random, world, pos, selector );
//noinspection unchecked //noinspection unchecked
return species == null ? fallback : (Species<? extends T>) species; 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 ---------------- //--------------- Species Instance Implementations ----------------
@ -250,6 +272,8 @@ public class MobFamily<T extends LivingEntity, V extends FamilyConfig> {
/** This species's config. */ /** This species's config. */
public final SpeciesConfig 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. */ /** The scale of this species's height in relation to the base vanilla entity's height. */
private float heightScale = -1.0F; 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 ); 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. */ /** @return The height scale. Used to calculate eye height for families that are not auto-scaled. */
public float getHeightScale() { public float getHeightScale() {
if( heightScale < 0.0F ) { if( heightScale < 0.0F ) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,7 +7,10 @@ import fathertoast.specialmobs.common.util.References;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
import net.minecraft.nbt.CompoundNBT; import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos; 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.IServerWorld;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.TickEvent;
@ -19,12 +22,20 @@ import net.minecraftforge.fml.common.Mod;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import java.util.function.Predicate;
@Mod.EventBusSubscriber( modid = SpecialMobs.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE ) @Mod.EventBusSubscriber( modid = SpecialMobs.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE )
public final class SpecialMobReplacer { public final class SpecialMobReplacer {
/** List of data for mobs needing replacement. */ /** List of data for mobs needing replacement. */
private static final Deque<MobReplacementEntry> TO_REPLACE = new ArrayDeque<>(); 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). * Called when any entity is spawned into the world by any means (such as natural/spawner spawns or chunk loading).
* <p> * <p>
@ -123,7 +134,9 @@ public final class SpecialMobReplacer {
// Don't copy UUID // Don't copy UUID
tag.remove( "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 ); final LivingEntity replacement = species.entityType.get().create( world );
if( replacement == null ) { if( replacement == null ) {
@ -149,6 +162,32 @@ public final class SpecialMobReplacer {
entityToReplace.remove(); 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. */ /** All data needed for a single mob we want to replace. */
private static class MobReplacementEntry { private static class MobReplacementEntry {
final MobFamily<?, ?> mobFamily; final MobFamily<?, ?> mobFamily;

View file

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

View file

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

View file

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

View file

@ -3,6 +3,7 @@ package fathertoast.specialmobs.common.entity.cavespider;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob; import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
@ -63,7 +64,7 @@ public class FireCaveSpiderEntity extends _SpecialCaveSpiderEntity {
if( !level.isClientSide() ) { if( !level.isClientSide() ) {
final BlockPos pos = target.blockPosition(); final BlockPos pos = target.blockPosition();
if( level.getBlockState( pos ).getMaterial().isReplaceable() ) { 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 ); target.setSecondsOnFire( 5 );

View file

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

View file

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

View file

@ -3,10 +3,13 @@ package fathertoast.specialmobs.common.entity.creeper;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob; 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.species.SpeciesConfig;
import fathertoast.specialmobs.common.config.util.EnvironmentEntry; import fathertoast.specialmobs.common.config.util.EnvironmentEntry;
import fathertoast.specialmobs.common.config.util.EnvironmentList; import fathertoast.specialmobs.common.config.util.EnvironmentList;
import fathertoast.specialmobs.common.config.util.environment.biome.BiomeCategory; 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.AIHelper;
import fathertoast.specialmobs.common.entity.ai.AmphibiousMovementController; import fathertoast.specialmobs.common.entity.ai.AmphibiousMovementController;
import fathertoast.specialmobs.common.entity.ai.IAmphibiousMob; 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.IWorldReader;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.Biomes;
import java.util.Random; import java.util.Random;
@ -60,12 +64,19 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmp
@SpecialMob.ConfigSupplier @SpecialMob.ConfigSupplier
public static SpeciesConfig createConfig( MobFamily.Species<?> species ) { public static SpeciesConfig createConfig( MobFamily.Species<?> species ) {
SpeciesConfig.NEXT_NATURAL_SPAWN_CHANCE_EXCEPTIONS = new EnvironmentList( 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.06F ).inBiomeCategory( BiomeCategory.RIVER ).build(),
EnvironmentEntry.builder( 0.02F ).inBiomeCategory( BiomeCategory.OCEAN ).belowSeaDepths().build(), EnvironmentEntry.builder( 0.02F ).inBiomeCategory( BiomeCategory.OCEAN ).belowSeaDepths().build(),
EnvironmentEntry.builder( 0.0F ).inBiomeCategory( BiomeCategory.OCEAN ).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 @SpecialMob.SpawnPlacementRegistrar
public static void registerSpeciesSpawnPlacement( MobFamily.Species<? extends DrowningCreeperEntity> species ) { public static void registerSpeciesSpawnPlacement( MobFamily.Species<? extends DrowningCreeperEntity> species ) {
NaturalSpawnManager.registerSpawnPlacement( species, EntitySpawnPlacementRegistry.PlacementType.IN_WATER, NaturalSpawnManager.registerSpawnPlacement( species, EntitySpawnPlacementRegistry.PlacementType.IN_WATER,
@ -114,6 +125,9 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmp
//--------------- Variant-Specific Implementations ---------------- //--------------- Variant-Specific Implementations ----------------
/** The maximum number of pufferfish spawned on explosion. */
private int pufferfish;
public DrowningCreeperEntity( EntityType<? extends _SpecialCreeperEntity> entityType, World world ) { public DrowningCreeperEntity( EntityType<? extends _SpecialCreeperEntity> entityType, World world ) {
super( entityType, world ); super( entityType, world );
moveControl = new AmphibiousMovementController<>( this ); moveControl = new AmphibiousMovementController<>( this );
@ -121,6 +135,8 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmp
groundNavigation = new GroundPathNavigator( this, world ); groundNavigation = new GroundPathNavigator( this, world );
maxUpStep = 1.0F; maxUpStep = 1.0F;
setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() ); setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() );
pufferfish = getConfig().DROWNING.puffPuffs.next( random );
} }
/** Override to change this entity's AI goals. */ /** 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; if( explosionMode == Explosion.Mode.NONE ) return;
final BlockState brainCoral = Blocks.BRAIN_CORAL_BLOCK.defaultBlockState(); final UnderwaterSilverfishBlock.Type mainType = UnderwaterSilverfishBlock.Type.next( random );
final BlockState hornCoral = Blocks.HORN_CORAL_BLOCK.defaultBlockState(); 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 water = Blocks.WATER.defaultBlockState();
final BlockState seaPickle = Blocks.SEA_PICKLE.defaultBlockState().setValue( BlockStateProperties.WATERLOGGED, true ); final BlockState seaPickle = Blocks.SEA_PICKLE.defaultBlockState().setValue( BlockStateProperties.WATERLOGGED, true );
final BlockState seaGrass = Blocks.SEAGRASS.defaultBlockState(); final BlockState seaGrass = Blocks.SEAGRASS.defaultBlockState();
final int radius = (int) Math.floor( explosionPower ); final int radius = (int) Math.floor( explosionPower );
final int radiusSq = radius * radius;
final int rMinusOneSq = (radius - 1) * (radius - 1); final int rMinusOneSq = (radius - 1) * (radius - 1);
final BlockPos center = new BlockPos( explosion.getPos() ); final BlockPos center = new BlockPos( explosion.getPos() );
// Track how many pufferfish have been spawned so we don't spawn a bunch of them // 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; int pufferCount = 1;
for( int y = -radius; y <= radius; y++ ) { 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++ ) { for( int z = -radius; z <= radius; z++ ) {
final int distSq = x * x + y * y + z * 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 BlockPos pos = center.offset( x, y, z );
final BlockState stateAtPos = level.getBlockState( pos ); final BlockState stateAtPos = level.getBlockState( pos );
if( stateAtPos.getMaterial().isReplaceable() || stateAtPos.is( BlockTags.LEAVES ) ) { if( stateAtPos.getMaterial().isReplaceable() || stateAtPos.is( BlockTags.LEAVES ) ) {
if( distSq > rMinusOneSq ) { if( distSq <= rMinusOneSq ) {
// "Coral" casing // Water fill
level.setBlock( pos, random.nextFloat() < 0.25F ? brainCoral : hornCoral, References.SetBlockFlags.DEFAULTS );
}
else {
final float fillChoice = random.nextFloat(); final float fillChoice = random.nextFloat();
if( fillChoice < 0.1F && seaPickle.canSurvive( level, pos ) ) { 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 ) ) { else if( fillChoice < 0.3F && seaGrass.canSurvive( level, pos ) ) {
level.setBlock( pos, seaGrass, References.SetBlockFlags.DEFAULTS ); MobHelper.placeBlock( this, pos, seaGrass );
} }
else { else {
// Water fill // 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 ); spawnPufferfish( pos );
pufferCount++; 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) // The below two methods are here to effectively override the private Entity#isInRain to always return true (always wet)
@Override @Override
public boolean isInWaterOrRain() { return true; } public boolean isInWaterOrRain() { return true; }
@ -217,9 +265,18 @@ public class DrowningCreeperEntity extends _SpecialCreeperEntity implements IAmp
@Override @Override
public boolean isInWaterRainOrBubble() { return true; } 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 to load data from this entity's NBT data. */
@Override @Override
public void readVariantSaveData( CompoundNBT saveTag ) { 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() ); setPathfindingMalus( PathNodeType.WATER, PathNodeType.WALKABLE.getMalus() );
} }

View file

@ -3,6 +3,9 @@ package fathertoast.specialmobs.common.entity.creeper;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob; 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.MobHelper;
import fathertoast.specialmobs.common.entity.ai.AIHelper; import fathertoast.specialmobs.common.entity.ai.AIHelper;
import fathertoast.specialmobs.common.entity.ai.FluidPathNavigator; import fathertoast.specialmobs.common.entity.ai.FluidPathNavigator;
@ -12,6 +15,7 @@ import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
import net.minecraft.block.FlowingFluidBlock;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.entity.SpawnReason; import net.minecraft.entity.SpawnReason;
import net.minecraft.entity.ai.attributes.Attributes; import net.minecraft.entity.ai.attributes.Attributes;
@ -45,6 +49,16 @@ public class SnowCreeperEntity extends _SpecialCreeperEntity {
.addToAttribute( Attributes.MAX_HEALTH, 10.0 ); .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 @SpecialMob.LanguageProvider
public static String[] getTranslations( String langKey ) { public static String[] getTranslations( String langKey ) {
return References.translations( langKey, "Snow Creeper", return References.translations( langKey, "Snow Creeper",
@ -123,7 +137,9 @@ public class SnowCreeperEntity extends _SpecialCreeperEntity {
final BlockPos center = new BlockPos( explosion.getPos() ); final BlockPos center = new BlockPos( explosion.getPos() );
if( explosionMode != Explosion.Mode.NONE ) { 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); final int rMinusOneSq = (radius - 1) * (radius - 1);
for( int y = -radius; y <= radius; y++ ) { for( int y = -radius; y <= radius; y++ ) {
@ -131,19 +147,32 @@ public class SnowCreeperEntity extends _SpecialCreeperEntity {
for( int z = -radius; z <= radius; z++ ) { for( int z = -radius; z <= radius; z++ ) {
final int distSq = x * x + y * y + z * 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 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 ); 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() ); final BlockState blockAbove = level.getBlockState( pos.above() );
if( !blockAbove.getMaterial().blocksMotion() && !blockAbove.getFluidState().is( FluidTags.WATER ) ) if( !blockAbove.getMaterial().blocksMotion() && !blockAbove.getFluidState().is( FluidTags.WATER ) &&
level.setBlock( pos, ice, References.SetBlockFlags.DEFAULTS ); MobHelper.placeBlock( this, pos, MeltingIceBlock.getState( level, pos ) ) ) {
MeltingIceBlock.scheduleFirstTick( level, pos, random );
}
} }
// Attempt to place pillars along circumference only if( distSq > rMinusOneSq ) {
if( y == 0 && distSq > rMinusOneSq ) placePillar( pos, radius ); 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 ); if( shouldReplace( currentPos ) ) findGroundBelow( currentPos, radius );
else if( findGroundAbove( currentPos, radius ) ) return; else if( findGroundAbove( currentPos, radius ) ) return;
final BlockState ice = Blocks.PACKED_ICE.defaultBlockState();
final int maxY = Math.min( currentPos.getY() + 4, level.getMaxBuildHeight() - 2 ); final int maxY = Math.min( currentPos.getY() + 4, level.getMaxBuildHeight() - 2 );
int height = -2; // This is minimum pillar height int height = -2; // This is minimum pillar height
if( pos.getY() > currentPos.getY() ) height -= (pos.getY() - currentPos.getY()) / 2; if( pos.getY() > currentPos.getY() ) height -= (pos.getY() - currentPos.getY()) / 2;
while( currentPos.getY() < maxY && shouldReplace( currentPos ) ) { 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 ); currentPos.move( 0, 1, 0 );
if( ++height >= 0 && random.nextBoolean() ) break; if( ++height >= 0 && random.nextBoolean() ) break;

View file

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

View file

@ -40,7 +40,7 @@ public class FishingDrownedEntity extends _SpecialDrownedEntity implements IAngl
@SpecialMob.BestiaryInfoSupplier @SpecialMob.BestiaryInfoSupplier
public static void getBestiaryInfo( BestiaryInfo.Builder bestiaryInfo ) { 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() .addExperience( 2 ).drownImmune().fluidPushImmune()
.convertThrowToFishing().fishingAttack( 1.0, 40, 15.0 ) .convertThrowToFishing().fishingAttack( 1.0, 40, 15.0 )
.multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 ); .multiplyAttribute( Attributes.MOVEMENT_SPEED, 0.8 );

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,6 +3,7 @@ package fathertoast.specialmobs.common.entity.spider;
import fathertoast.specialmobs.common.bestiary.BestiaryInfo; import fathertoast.specialmobs.common.bestiary.BestiaryInfo;
import fathertoast.specialmobs.common.bestiary.MobFamily; import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob; import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
@ -63,7 +64,7 @@ public class FireSpiderEntity extends _SpecialSpiderEntity {
if( !level.isClientSide() ) { if( !level.isClientSide() ) {
final BlockPos pos = target.blockPosition(); final BlockPos pos = target.blockPosition();
if( level.getBlockState( pos ).getMaterial().isReplaceable() ) { 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 ); target.setSecondsOnFire( 5 );

View file

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

View file

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

View file

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

View file

@ -5,6 +5,7 @@ import fathertoast.specialmobs.common.bestiary.MobFamily;
import fathertoast.specialmobs.common.bestiary.SpecialMob; import fathertoast.specialmobs.common.bestiary.SpecialMob;
import fathertoast.specialmobs.common.entity.MobHelper; import fathertoast.specialmobs.common.entity.MobHelper;
import fathertoast.specialmobs.common.entity.ai.AIHelper; import fathertoast.specialmobs.common.entity.ai.AIHelper;
import fathertoast.specialmobs.common.entity.drowned.FrozenDrownedEntity;
import fathertoast.specialmobs.common.util.References; import fathertoast.specialmobs.common.util.References;
import fathertoast.specialmobs.datagen.loot.LootTableBuilder; import fathertoast.specialmobs.datagen.loot.LootTableBuilder;
import net.minecraft.block.Blocks; 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.LookAtGoal;
import net.minecraft.entity.ai.goal.LookRandomlyGoal; import net.minecraft.entity.ai.goal.LookRandomlyGoal;
import net.minecraft.entity.ai.goal.WaterAvoidingRandomWalkingGoal; import net.minecraft.entity.ai.goal.WaterAvoidingRandomWalkingGoal;
import net.minecraft.entity.monster.ZombieEntity;
import net.minecraft.entity.projectile.AbstractArrowEntity; import net.minecraft.entity.projectile.AbstractArrowEntity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.item.Items; import net.minecraft.item.Items;
@ -92,6 +94,10 @@ public class FrozenZombieEntity extends _SpecialZombieEntity {
return MobHelper.tipArrow( arrow, Effects.MOVEMENT_SLOWDOWN ); 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. */ /** Called each tick to update this entity's movement. */
@Override @Override
public void aiStep() { public void aiStep() {

View file

@ -26,7 +26,7 @@ import net.minecraft.world.IServerWorld;
import net.minecraft.world.World; import net.minecraft.world.World;
import java.util.Random; import java.util.Random;
import java.util.function.Function; import java.util.function.Predicate;
@SpecialMob @SpecialMob
public class HuskZombieEntity extends _SpecialZombieEntity { 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. */ /** 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(); ( species ) -> species != SPECIES && !species.config.GENERAL.isDamagedByWater.get();
/** Performs this zombie's drowning conversion. */ /** Performs this zombie's drowning conversion. */

View file

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

View file

@ -1,14 +1,7 @@
package fathertoast.specialmobs.common.network; 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 class NetworkHelper {
// public static void spawnIncorporealFireball( @Nonnull PlayerEntity player, @Nonnull LivingEntity livingEntity ) {
public static void spawnIncorporealFireball(@Nonnull PlayerEntity player, @Nonnull LivingEntity livingEntity) { // PacketHandler.CHANNEL.sendToServer( new C2SSpawnIncorporealFireball( player.getUUID(), livingEntity.getId() ) );
PacketHandler.CHANNEL.sendToServer(new C2SSpawnIncorporealFireball(player.getUUID(), livingEntity.getId())); // }
} }
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -256,7 +256,7 @@ public final class References {
// Spawner mobs // Spawner mobs
public static final String TAG_BABIES = "Babies"; // Mother (Cave) Spider, Wilds Witch, Queen Ghast, Wildfire Blaze 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_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 // Growing mobs
public static final String TAG_GROWTH_LEVEL = "GrowthLevel"; // Hungry Spider, Conflagration Blaze public static final String TAG_GROWTH_LEVEL = "GrowthLevel"; // Hungry Spider, Conflagration Blaze

View file

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

View file

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

View file

@ -22,15 +22,16 @@ public class SMItemModelProvider extends ItemModelProvider {
protected void registerModels() { protected void registerModels() {
// Bestiary-generated spawn egg models // Bestiary-generated spawn egg models
final ResourceLocation spawnEggParent = modLoc( ITEM_FOLDER + "/template_sm_spawn_egg" ); 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 ); withExistingParent( species.spawnEgg.getId().getPath(), spawnEggParent );
}
// Simple items // Simple items
for(RegistryObject<? extends Item> regObject : SMItems.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)); .texture( "layer0", modLoc( ITEM_FOLDER + "/" + name ) );
} }
} }
} }

View file

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

View file

@ -9,6 +9,9 @@ public net.minecraft.client.renderer.RenderState field_228528_t_ # LIGHTMAP
public net.minecraft.client.renderer.RenderState field_228530_v_ # OVERLAY public net.minecraft.client.renderer.RenderState field_228530_v_ # OVERLAY
public net.minecraft.client.renderer.RenderState field_228515_g_ # TRANSLUCENT_TRANSPARENCY 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 # Entity registration
public net.minecraft.entity.EntityType field_233593_bg_ # immuneTo public net.minecraft.entity.EntityType field_233593_bg_ # immuneTo
public net.minecraft.entity.ai.attributes.AttributeModifierMap$MutableAttribute field_233811_a_ # builder public net.minecraft.entity.ai.attributes.AttributeModifierMap$MutableAttribute field_233811_a_ # builder

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

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