Added first VERY rough draft of jumpy bug, creeping horror, lurking terror
This commit is contained in:
1 parent
70e4479f19
commit
fb8fd3c927
112 files changed
+16339
-8
No files matched your search
@@ -271,6 +271,10 @@ public final class AntarchySettings {
|
||||
private static double bedBugAttackLungeHorizontalSpeed = 0.42D;
|
||||
private static double bedBugAttackLungeVerticalSpeed = 0.1D;
|
||||
private static float bedBugHealAmount = 2.0F;
|
||||
private static double jumpyBugHealth = 12.0D;
|
||||
private static double jumpyBugPounceDamage = 2.0D;
|
||||
private static double jumpyBugLatchDamage = 1.0D;
|
||||
private static double jumpyBugCamouflageAlpha = 0.18D;
|
||||
private static double brutalflyArmor = 10.0D;
|
||||
private static double brutalflyArmorToughness = 4.0D;
|
||||
private static double brutalflyKnockbackResistance = 0.7D;
|
||||
@@ -451,11 +455,16 @@ public final class AntarchySettings {
|
||||
private static double toreterrorSpinDamage = 8.0D;
|
||||
private static double toreterrorSpinKnockback = 1.5D;
|
||||
private static double toreterrorRangedWaterBombChance = 0.5D;
|
||||
private static double toreterrorProjectileDamageMultiplier = 0.5D;
|
||||
private static double waterBombDamage = 6.0D;
|
||||
private static int waterBombLifetimeTicks = 120;
|
||||
private static double waterBombGravity = 0.12D;
|
||||
private static double waterBombKnockback = 1.2D;
|
||||
private static double waterCannonCooldownSeconds = 1.5D;
|
||||
private static double creepingHorrorHealth = 15.0D;
|
||||
private static double creepingHorrorAttackDamage = 6.0D;
|
||||
private static double lurkingTerrorHealth = 15.0D;
|
||||
private static double lurkingTerrorAttackDamage = 6.0D;
|
||||
|
||||
private AntarchySettings() {
|
||||
}
|
||||
@@ -1811,6 +1820,18 @@ public final class AntarchySettings {
|
||||
public static float bedBugHealAmount() { return bedBugHealAmount; }
|
||||
public static void setBedBugHealAmount(float value) { bedBugHealAmount = value; }
|
||||
|
||||
public static double jumpyBugHealth() { return jumpyBugHealth; }
|
||||
public static void setJumpyBugHealth(double value) { jumpyBugHealth = value; }
|
||||
|
||||
public static double jumpyBugPounceDamage() { return jumpyBugPounceDamage; }
|
||||
public static void setJumpyBugPounceDamage(double value) { jumpyBugPounceDamage = value; }
|
||||
|
||||
public static double jumpyBugLatchDamage() { return jumpyBugLatchDamage; }
|
||||
public static void setJumpyBugLatchDamage(double value) { jumpyBugLatchDamage = value; }
|
||||
|
||||
public static double jumpyBugCamouflageAlpha() { return jumpyBugCamouflageAlpha; }
|
||||
public static void setJumpyBugCamouflageAlpha(double value) { jumpyBugCamouflageAlpha = value; }
|
||||
|
||||
public static double brutalflyArmor() { return brutalflyArmor; }
|
||||
public static void setBrutalflyArmor(double value) { brutalflyArmor = value; }
|
||||
|
||||
@@ -2334,6 +2355,8 @@ public final class AntarchySettings {
|
||||
public static void setToreterrorSpinKnockback(double v) { toreterrorSpinKnockback = v; }
|
||||
public static double toreterrorRangedWaterBombChance() { return toreterrorRangedWaterBombChance; }
|
||||
public static void setToreterrorRangedWaterBombChance(double v) { toreterrorRangedWaterBombChance = v; }
|
||||
public static double toreterrorProjectileDamageMultiplier() { return toreterrorProjectileDamageMultiplier; }
|
||||
public static void setToreterrorProjectileDamageMultiplier(double v) { toreterrorProjectileDamageMultiplier = v; }
|
||||
public static double waterBombDamage() { return waterBombDamage; }
|
||||
public static void setWaterBombDamage(double v) { waterBombDamage = v; }
|
||||
public static int waterBombLifetimeTicks() { return waterBombLifetimeTicks; }
|
||||
@@ -2344,4 +2367,13 @@ public final class AntarchySettings {
|
||||
public static void setWaterBombKnockback(double v) { waterBombKnockback = v; }
|
||||
public static double waterCannonCooldownSeconds() { return waterCannonCooldownSeconds; }
|
||||
public static void setWaterCannonCooldownSeconds(double v) { waterCannonCooldownSeconds = v; }
|
||||
|
||||
public static double creepingHorrorHealth() { return creepingHorrorHealth; }
|
||||
public static void setCreepingHorrorHealth(double v) { creepingHorrorHealth = v; }
|
||||
public static double creepingHorrorAttackDamage() { return creepingHorrorAttackDamage; }
|
||||
public static void setCreepingHorrorAttackDamage(double v) { creepingHorrorAttackDamage = v; }
|
||||
public static double lurkingTerrorHealth() { return lurkingTerrorHealth; }
|
||||
public static void setLurkingTerrorHealth(double v) { lurkingTerrorHealth = v; }
|
||||
public static double lurkingTerrorAttackDamage() { return lurkingTerrorAttackDamage; }
|
||||
public static void setLurkingTerrorAttackDamage(double v) { lurkingTerrorAttackDamage = v; }
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import com.craisinlord.antarchy.content.entity.EasterBunnyEntity;
|
||||
import com.craisinlord.antarchy.content.entity.HushProjectileEntity;
|
||||
import com.craisinlord.antarchy.content.entity.WaterBombEntity;
|
||||
import com.craisinlord.antarchy.content.entity.TriffidEntity;
|
||||
import com.craisinlord.antarchy.content.entity.CreepingHorrorEntity;
|
||||
import com.craisinlord.antarchy.content.entity.LurkingTerrorEntity;
|
||||
import com.craisinlord.antarchy.content.entity.ToreterrorEntity;
|
||||
import com.craisinlord.antarchy.content.entity.WaspEntity;
|
||||
import com.craisinlord.antarchy.content.entity.brutalfly.BrutalflyEntity;
|
||||
@@ -79,6 +81,10 @@ public final class AntarchyObjects {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<EntityType<WaterBombEntity>> WATER_BOMB = (Supplier<EntityType<WaterBombEntity>>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<EntityType<CreepingHorrorEntity>> CREEPING_HORROR = (Supplier<EntityType<CreepingHorrorEntity>>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<EntityType<LurkingTerrorEntity>> LURKING_TERROR = (Supplier<EntityType<LurkingTerrorEntity>>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> DUPLICATOR_LOG = (Supplier<Block>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> DUPLICATOR_SAPLING = (Supplier<Block>) UNBOUND;
|
||||
@@ -185,6 +191,10 @@ public final class AntarchyObjects {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> BED_BUG_EGG = (Supplier<Block>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> CREEPING_HORROR_EGGS = (Supplier<Block>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> LURKING_TERROR_EGGS = (Supplier<Block>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> WASP_NEST = (Supplier<Block>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> HUSHWEED = (Supplier<Block>) UNBOUND;
|
||||
@@ -222,6 +232,8 @@ public final class AntarchyObjects {
|
||||
Supplier<EntityType<HushProjectileEntity>> hushProjectile,
|
||||
Supplier<EntityType<ToreterrorEntity>> toreterror,
|
||||
Supplier<EntityType<WaterBombEntity>> waterBomb,
|
||||
Supplier<EntityType<CreepingHorrorEntity>> creepingHorror,
|
||||
Supplier<EntityType<LurkingTerrorEntity>> lurkingTerror,
|
||||
Supplier<Block> duplicatorLog,
|
||||
Supplier<Block> duplicatorSapling,
|
||||
Supplier<Block> ductTape,
|
||||
@@ -272,6 +284,8 @@ public final class AntarchyObjects {
|
||||
Supplier<Block> pinkMilkweed,
|
||||
Supplier<Block> torchflowerBush,
|
||||
Supplier<Block> bedBugEggs,
|
||||
Supplier<Block> creepingHorrorEggs,
|
||||
Supplier<Block> lurkingTerrorEggs,
|
||||
Supplier<Block> waspNest,
|
||||
Supplier<Block> hushweed,
|
||||
Supplier<Block> squirrelNestBlock,
|
||||
@@ -302,6 +316,8 @@ public final class AntarchyObjects {
|
||||
HUSH_PROJECTILE = hushProjectile;
|
||||
TORETERROR = toreterror;
|
||||
WATER_BOMB = waterBomb;
|
||||
CREEPING_HORROR = creepingHorror;
|
||||
LURKING_TERROR = lurkingTerror;
|
||||
DUPLICATOR_LOG = duplicatorLog;
|
||||
DUPLICATOR_SAPLING = duplicatorSapling;
|
||||
DUCT_TAPE = ductTape;
|
||||
@@ -352,6 +368,8 @@ public final class AntarchyObjects {
|
||||
PINK_MILKWEED = pinkMilkweed;
|
||||
TORCHFLOWER_BUSH = torchflowerBush;
|
||||
BED_BUG_EGG = bedBugEggs;
|
||||
CREEPING_HORROR_EGGS = creepingHorrorEggs;
|
||||
LURKING_TERROR_EGGS = lurkingTerrorEggs;
|
||||
WASP_NEST = waspNest;
|
||||
HUSHWEED = hushweed;
|
||||
SQUIRREL_NEST_BLOCK = squirrelNestBlock;
|
||||
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
package com.craisinlord.antarchy.content.block;
|
||||
|
||||
import com.craisinlord.antarchy.content.AntarchyObjects;
|
||||
import com.craisinlord.antarchy.content.entity.CreepingHorrorEntity;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.ambient.Bat;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.IntegerProperty;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
public class CreepingHorrorEggBlock extends Block {
|
||||
public static final MapCodec<CreepingHorrorEggBlock> CODEC = Block.simpleCodec(CreepingHorrorEggBlock::new);
|
||||
public static final IntegerProperty HATCH = BlockStateProperties.HATCH;
|
||||
public static final IntegerProperty EGGS = BlockStateProperties.EGGS;
|
||||
private static final int MAX_HATCH = 2;
|
||||
private static final int MAX_EGGS = 4;
|
||||
private static final VoxelShape SINGLE_SHAPE = Block.box(3.0D, 0.0D, 3.0D, 13.0D, 7.0D, 13.0D);
|
||||
private static final VoxelShape MULTI_SHAPE = Block.box(1.0D, 0.0D, 1.0D, 15.0D, 7.0D, 15.0D);
|
||||
|
||||
public CreepingHorrorEggBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
this.registerDefaultState(this.stateDefinition.any().setValue(HATCH, 0).setValue(EGGS, 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapCodec<CreepingHorrorEggBlock> codec() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
|
||||
return state.getValue(EGGS) > 1 ? MULTI_SHAPE : SINGLE_SHAPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
|
||||
return level.getBlockState(pos.below()).isFaceSturdy(level, pos.below(), Direction.UP);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
|
||||
return state.canSurvive(level, pos) ? super.updateShape(state, direction, neighborState, level, pos, neighborPos) : Blocks.AIR.defaultBlockState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeReplaced(BlockState state, BlockPlaceContext context) {
|
||||
return !context.isSecondaryUseActive()
|
||||
&& context.getItemInHand().is(this.asItem())
|
||||
&& state.getValue(EGGS) < MAX_EGGS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getStateForPlacement(BlockPlaceContext context) {
|
||||
BlockState existingState = context.getLevel().getBlockState(context.getClickedPos());
|
||||
if (existingState.is(this)) {
|
||||
return existingState.setValue(EGGS, Math.min(MAX_EGGS, existingState.getValue(EGGS) + 1));
|
||||
}
|
||||
return this.defaultBlockState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRandomlyTicking(BlockState state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
|
||||
if (!this.shouldUpdateHatchLevel(level) || !hasHatchSupport(level, pos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int hatch = state.getValue(HATCH);
|
||||
if (hatch < MAX_HATCH) {
|
||||
level.setBlock(pos, state.setValue(HATCH, hatch + 1), 2);
|
||||
level.playSound(null, pos, SoundEvents.TURTLE_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.95F + random.nextFloat() * 0.1F);
|
||||
return;
|
||||
}
|
||||
|
||||
this.hatchEggs(level, pos, state, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) {
|
||||
if (!entity.isSteppingCarefully()) {
|
||||
this.destroyEgg(level, state, pos, entity, 100);
|
||||
}
|
||||
super.stepOn(level, pos, state, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
|
||||
this.destroyEgg(level, state, pos, entity, 3);
|
||||
super.fallOn(level, state, pos, entity, fallDistance);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
|
||||
if (hasHatchSupport(level, pos) && !level.isClientSide) {
|
||||
level.levelEvent(2012, pos, 15);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, BlockEntity blockEntity, net.minecraft.world.item.ItemStack stack) {
|
||||
super.playerDestroy(level, player, pos, state, blockEntity, stack);
|
||||
this.decreaseEggs(level, pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
||||
builder.add(HATCH, EGGS);
|
||||
}
|
||||
|
||||
public static boolean hasHatchSupport(BlockGetter level, BlockPos pos) {
|
||||
return isSupportedGround(level, pos.below());
|
||||
}
|
||||
|
||||
public static boolean isSupportedGround(BlockGetter level, BlockPos pos) {
|
||||
return level.getBlockState(pos).is(BlockTags.SAND) || level.getBlockState(pos).blocksMotion();
|
||||
}
|
||||
|
||||
private void destroyEgg(Level level, BlockState state, BlockPos pos, Entity entity, int chance) {
|
||||
if (level.isClientSide || !this.canDestroyEgg(level, entity) || level.random.nextInt(chance) != 0) {
|
||||
return;
|
||||
}
|
||||
level.playSound(null, pos, SoundEvents.TURTLE_EGG_BREAK, SoundSource.BLOCKS, 0.7F, 0.9F + level.random.nextFloat() * 0.2F);
|
||||
this.decreaseEggs(level, pos, state);
|
||||
}
|
||||
|
||||
private void hatchEggs(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) {
|
||||
level.removeBlock(pos, false);
|
||||
level.playSound(null, pos, SoundEvents.TURTLE_EGG_HATCH, SoundSource.BLOCKS, 0.75F, 0.95F + random.nextFloat() * 0.1F);
|
||||
|
||||
for (int i = 0; i < state.getValue(EGGS); i++) {
|
||||
CreepingHorrorEntity entity = AntarchyObjects.CREEPING_HORROR.get().create(level);
|
||||
if (entity == null) {
|
||||
continue;
|
||||
}
|
||||
double spawnX = pos.getX() + 0.25D + random.nextDouble() * 0.5D;
|
||||
double spawnZ = pos.getZ() + 0.25D + random.nextDouble() * 0.5D;
|
||||
entity.moveTo(spawnX, pos.getY() + 0.1D, spawnZ, random.nextFloat() * 360.0F, 0.0F);
|
||||
level.addFreshEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void decreaseEggs(Level level, BlockPos pos, BlockState state) {
|
||||
int eggs = state.getValue(EGGS);
|
||||
if (eggs <= 1) {
|
||||
level.destroyBlock(pos, false);
|
||||
return;
|
||||
}
|
||||
level.setBlock(pos, state.setValue(EGGS, eggs - 1), 2);
|
||||
}
|
||||
|
||||
private boolean shouldUpdateHatchLevel(Level level) {
|
||||
return level.random.nextInt(500) == 0;
|
||||
}
|
||||
|
||||
private boolean canDestroyEgg(Level level, Entity entity) {
|
||||
if (entity instanceof CreepingHorrorEntity || entity instanceof Bat) {
|
||||
return false;
|
||||
}
|
||||
if (entity instanceof net.minecraft.world.entity.LivingEntity) {
|
||||
return entity instanceof Player || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
package com.craisinlord.antarchy.content.block;
|
||||
|
||||
import com.craisinlord.antarchy.content.AntarchyObjects;
|
||||
import com.craisinlord.antarchy.content.entity.LurkingTerrorEntity;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.ambient.Bat;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.IntegerProperty;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
public class LurkingTerrorEggBlock extends Block {
|
||||
public static final MapCodec<LurkingTerrorEggBlock> CODEC = Block.simpleCodec(LurkingTerrorEggBlock::new);
|
||||
public static final IntegerProperty HATCH = BlockStateProperties.HATCH;
|
||||
public static final IntegerProperty EGGS = BlockStateProperties.EGGS;
|
||||
private static final int MAX_HATCH = 2;
|
||||
private static final int MAX_EGGS = 4;
|
||||
private static final VoxelShape SINGLE_SHAPE = Block.box(3.0D, 0.0D, 3.0D, 13.0D, 7.0D, 13.0D);
|
||||
private static final VoxelShape MULTI_SHAPE = Block.box(1.0D, 0.0D, 1.0D, 15.0D, 7.0D, 15.0D);
|
||||
|
||||
public LurkingTerrorEggBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
this.registerDefaultState(this.stateDefinition.any().setValue(HATCH, 0).setValue(EGGS, 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapCodec<LurkingTerrorEggBlock> codec() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
|
||||
return state.getValue(EGGS) > 1 ? MULTI_SHAPE : SINGLE_SHAPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
|
||||
return level.getBlockState(pos.below()).isFaceSturdy(level, pos.below(), Direction.UP);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
|
||||
return state.canSurvive(level, pos) ? super.updateShape(state, direction, neighborState, level, pos, neighborPos) : Blocks.AIR.defaultBlockState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeReplaced(BlockState state, BlockPlaceContext context) {
|
||||
return !context.isSecondaryUseActive()
|
||||
&& context.getItemInHand().is(this.asItem())
|
||||
&& state.getValue(EGGS) < MAX_EGGS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getStateForPlacement(BlockPlaceContext context) {
|
||||
BlockState existingState = context.getLevel().getBlockState(context.getClickedPos());
|
||||
if (existingState.is(this)) {
|
||||
return existingState.setValue(EGGS, Math.min(MAX_EGGS, existingState.getValue(EGGS) + 1));
|
||||
}
|
||||
return this.defaultBlockState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRandomlyTicking(BlockState state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
|
||||
if (!this.shouldUpdateHatchLevel(level) || !hasHatchSupport(level, pos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int hatch = state.getValue(HATCH);
|
||||
if (hatch < MAX_HATCH) {
|
||||
level.setBlock(pos, state.setValue(HATCH, hatch + 1), 2);
|
||||
level.playSound(null, pos, SoundEvents.TURTLE_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.95F + random.nextFloat() * 0.1F);
|
||||
return;
|
||||
}
|
||||
|
||||
this.hatchEggs(level, pos, state, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) {
|
||||
if (!entity.isSteppingCarefully()) {
|
||||
this.destroyEgg(level, state, pos, entity, 100);
|
||||
}
|
||||
super.stepOn(level, pos, state, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
|
||||
this.destroyEgg(level, state, pos, entity, 3);
|
||||
super.fallOn(level, state, pos, entity, fallDistance);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
|
||||
if (hasHatchSupport(level, pos) && !level.isClientSide) {
|
||||
level.levelEvent(2012, pos, 15);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, BlockEntity blockEntity, net.minecraft.world.item.ItemStack stack) {
|
||||
super.playerDestroy(level, player, pos, state, blockEntity, stack);
|
||||
this.decreaseEggs(level, pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
||||
builder.add(HATCH, EGGS);
|
||||
}
|
||||
|
||||
public static boolean hasHatchSupport(BlockGetter level, BlockPos pos) {
|
||||
return isSupportedGround(level, pos.below());
|
||||
}
|
||||
|
||||
public static boolean isSupportedGround(BlockGetter level, BlockPos pos) {
|
||||
return level.getBlockState(pos).is(BlockTags.SAND) || level.getBlockState(pos).blocksMotion();
|
||||
}
|
||||
|
||||
private void destroyEgg(Level level, BlockState state, BlockPos pos, Entity entity, int chance) {
|
||||
if (level.isClientSide || !this.canDestroyEgg(level, entity) || level.random.nextInt(chance) != 0) {
|
||||
return;
|
||||
}
|
||||
level.playSound(null, pos, SoundEvents.TURTLE_EGG_BREAK, SoundSource.BLOCKS, 0.7F, 0.9F + level.random.nextFloat() * 0.2F);
|
||||
this.decreaseEggs(level, pos, state);
|
||||
}
|
||||
|
||||
private void hatchEggs(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) {
|
||||
level.removeBlock(pos, false);
|
||||
level.playSound(null, pos, SoundEvents.TURTLE_EGG_HATCH, SoundSource.BLOCKS, 0.75F, 0.95F + random.nextFloat() * 0.1F);
|
||||
|
||||
for (int i = 0; i < state.getValue(EGGS); i++) {
|
||||
LurkingTerrorEntity entity = AntarchyObjects.LURKING_TERROR.get().create(level);
|
||||
if (entity == null) {
|
||||
continue;
|
||||
}
|
||||
double spawnX = pos.getX() + 0.25D + random.nextDouble() * 0.5D;
|
||||
double spawnZ = pos.getZ() + 0.25D + random.nextDouble() * 0.5D;
|
||||
entity.moveTo(spawnX, pos.getY() + 0.1D, spawnZ, random.nextFloat() * 360.0F, 0.0F);
|
||||
level.addFreshEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void decreaseEggs(Level level, BlockPos pos, BlockState state) {
|
||||
int eggs = state.getValue(EGGS);
|
||||
if (eggs <= 1) {
|
||||
level.destroyBlock(pos, false);
|
||||
return;
|
||||
}
|
||||
level.setBlock(pos, state.setValue(EGGS, eggs - 1), 2);
|
||||
}
|
||||
|
||||
private boolean shouldUpdateHatchLevel(Level level) {
|
||||
return level.random.nextInt(500) == 0;
|
||||
}
|
||||
|
||||
private boolean canDestroyEgg(Level level, Entity entity) {
|
||||
if (entity instanceof LurkingTerrorEntity || entity instanceof Bat) {
|
||||
return false;
|
||||
}
|
||||
if (entity instanceof net.minecraft.world.entity.LivingEntity) {
|
||||
return entity instanceof Player || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.craisinlord.antarchy.content.client.model;
|
||||
|
||||
import com.craisinlord.antarchy.Antarchy;
|
||||
import com.craisinlord.antarchy.content.entity.CreepingHorrorEntity;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import software.bernie.geckolib.model.GeoModel;
|
||||
|
||||
public class CreepingHorrorModel extends GeoModel<CreepingHorrorEntity> {
|
||||
@Override
|
||||
public ResourceLocation getModelResource(CreepingHorrorEntity animatable) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/creeping_horror.geo.json");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTextureResource(CreepingHorrorEntity animatable) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/creeping_horror.png");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getAnimationResource(CreepingHorrorEntity animatable) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/creeping_horror.animation.json");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.craisinlord.antarchy.content.client.model;
|
||||
|
||||
import com.craisinlord.antarchy.Antarchy;
|
||||
import com.craisinlord.antarchy.content.entity.JumpyBugEntity;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import software.bernie.geckolib.model.GeoModel;
|
||||
|
||||
public class JumpyBugModel extends GeoModel<JumpyBugEntity> {
|
||||
@Override
|
||||
public ResourceLocation getModelResource(JumpyBugEntity animatable) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/jumpy_bug.geo.json");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTextureResource(JumpyBugEntity animatable) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/jumpy_bug.png");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getAnimationResource(JumpyBugEntity animatable) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/jumpy_bug.animation.json");
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.craisinlord.antarchy.content.client.model;
|
||||
|
||||
import com.craisinlord.antarchy.Antarchy;
|
||||
import com.craisinlord.antarchy.content.entity.LurkingTerrorEntity;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import software.bernie.geckolib.model.GeoModel;
|
||||
|
||||
public class LurkingTerrorModel extends GeoModel<LurkingTerrorEntity> {
|
||||
@Override
|
||||
public ResourceLocation getModelResource(LurkingTerrorEntity animatable) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/lurking_terror.geo.json");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTextureResource(LurkingTerrorEntity animatable) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/lurking_terror.png");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getAnimationResource(LurkingTerrorEntity animatable) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/lurking_terror.animation.json");
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.craisinlord.antarchy.content.client.renderer;
|
||||
|
||||
import com.craisinlord.antarchy.content.client.model.CreepingHorrorModel;
|
||||
import com.craisinlord.antarchy.content.entity.CreepingHorrorEntity;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.mojang.math.Axis;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import software.bernie.geckolib.cache.object.BakedGeoModel;
|
||||
import software.bernie.geckolib.renderer.GeoEntityRenderer;
|
||||
|
||||
public class CreepingHorrorRenderer extends GeoEntityRenderer<CreepingHorrorEntity> {
|
||||
public CreepingHorrorRenderer(EntityRendererProvider.Context context) {
|
||||
super(context, new CreepingHorrorModel());
|
||||
this.shadowRadius = 0.5F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRender(PoseStack poseStack, CreepingHorrorEntity animatable, BakedGeoModel model, @Nullable MultiBufferSource bufferSource, @Nullable VertexConsumer buffer, boolean isReRender, float partialTick, int packedLight, int packedOverlay, int colour) {
|
||||
super.preRender(poseStack, animatable, model, bufferSource, buffer, isReRender, partialTick, packedLight, packedOverlay, colour);
|
||||
if (animatable.isClimbing()) {
|
||||
poseStack.mulPose(Axis.XP.rotationDegrees(-90.0F));
|
||||
poseStack.translate(0, -0.5, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.craisinlord.antarchy.content.client.renderer;
|
||||
|
||||
import com.craisinlord.antarchy.content.client.model.JumpyBugModel;
|
||||
import com.craisinlord.antarchy.content.entity.JumpyBugEntity;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.mojang.math.Axis;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import software.bernie.geckolib.cache.object.BakedGeoModel;
|
||||
import software.bernie.geckolib.renderer.GeoEntityRenderer;
|
||||
import software.bernie.geckolib.util.Color;
|
||||
|
||||
public class JumpyBugRenderer extends GeoEntityRenderer<JumpyBugEntity> {
|
||||
public JumpyBugRenderer(EntityRendererProvider.Context context) {
|
||||
super(context, new JumpyBugModel());
|
||||
this.shadowRadius = 3.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderType getRenderType(JumpyBugEntity animatable, ResourceLocation texture, @Nullable MultiBufferSource bufferSource, float partialTick) {
|
||||
return animatable.isCamouflaged() ? RenderType.entityTranslucent(texture) : RenderType.entityCutoutNoCull(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getRenderColor(JumpyBugEntity animatable, float partialTick, int packedLight) {
|
||||
int alpha = Math.max(10, Math.min(255, Math.round(animatable.getVisualAlpha(partialTick) * 255.0F)));
|
||||
return Color.ofRGBA(255, 255, 255, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRender(PoseStack poseStack, JumpyBugEntity animatable, BakedGeoModel model, @Nullable MultiBufferSource bufferSource, @Nullable VertexConsumer buffer, boolean isReRender, float partialTick, int packedLight, int packedOverlay, int colour) {
|
||||
super.preRender(poseStack, animatable, model, bufferSource, buffer, isReRender, partialTick, packedLight, packedOverlay, colour);
|
||||
this.shadowRadius = animatable.isCamouflaged() ? 1.0F : 3.0F;
|
||||
|
||||
if (animatable.isClingingToCeiling()) {
|
||||
poseStack.translate(0.0F, animatable.getBbHeight() + 0.05F, 0.0F);
|
||||
poseStack.mulPose(Axis.ZP.rotationDegrees(180.0F));
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.craisinlord.antarchy.content.client.renderer;
|
||||
|
||||
import com.craisinlord.antarchy.content.client.model.LurkingTerrorModel;
|
||||
import com.craisinlord.antarchy.content.entity.LurkingTerrorEntity;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import software.bernie.geckolib.renderer.GeoEntityRenderer;
|
||||
|
||||
public class LurkingTerrorRenderer extends GeoEntityRenderer<LurkingTerrorEntity> {
|
||||
public LurkingTerrorRenderer(EntityRendererProvider.Context context) {
|
||||
super(context, new LurkingTerrorModel());
|
||||
this.shadowRadius = 0.5F;
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package com.craisinlord.antarchy.content.entity;
|
||||
|
||||
import com.craisinlord.antarchy.config.AntarchySettings;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.network.syncher.EntityDataSerializers;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.ai.goal.FloatGoal;
|
||||
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
|
||||
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
|
||||
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
|
||||
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
|
||||
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
|
||||
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
|
||||
import net.minecraft.world.entity.monster.Monster;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
import software.bernie.geckolib.animatable.GeoEntity;
|
||||
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
|
||||
import software.bernie.geckolib.animation.AnimatableManager;
|
||||
import software.bernie.geckolib.animation.AnimationController;
|
||||
import software.bernie.geckolib.animation.AnimationState;
|
||||
import software.bernie.geckolib.animation.PlayState;
|
||||
import software.bernie.geckolib.animation.RawAnimation;
|
||||
import software.bernie.geckolib.util.GeckoLibUtil;
|
||||
|
||||
public class CreepingHorrorEntity extends Monster implements GeoEntity {
|
||||
private static final EntityDataAccessor<Boolean> CLIMBING =
|
||||
SynchedEntityData.defineId(JumpyBugEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
|
||||
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
|
||||
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
|
||||
private static final RawAnimation ATTACK_ANIM = RawAnimation.begin().thenLoop("attack");
|
||||
private static final byte ATTACK_ANIM_EVENT = 4;
|
||||
|
||||
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
|
||||
private int attackAnimTicks = 0;
|
||||
|
||||
public CreepingHorrorEntity(EntityType<? extends CreepingHorrorEntity> type, Level level) {
|
||||
super(type, level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxUpStep() {
|
||||
return 1.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void defineSynchedData(SynchedEntityData.Builder builder) {
|
||||
super.defineSynchedData(builder);
|
||||
builder.define(CLIMBING, false);
|
||||
}
|
||||
|
||||
public static AttributeSupplier.Builder createAttributes() {
|
||||
return Mob.createMobAttributes()
|
||||
.add(Attributes.MAX_HEALTH, AntarchySettings.creepingHorrorHealth())
|
||||
.add(Attributes.MOVEMENT_SPEED, 0.30D)
|
||||
.add(Attributes.ATTACK_DAMAGE, AntarchySettings.creepingHorrorAttackDamage())
|
||||
.add(Attributes.FOLLOW_RANGE, 16.0D);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerGoals() {
|
||||
this.goalSelector.addGoal(0, new FloatGoal(this));
|
||||
this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0D, true));
|
||||
this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 0.85D));
|
||||
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 8.0F));
|
||||
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
|
||||
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
|
||||
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onClimbable() {
|
||||
return this.horizontalCollision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
|
||||
controllers.add(new AnimationController<>(this, "main_controller", 3, this::mainAnimController));
|
||||
}
|
||||
|
||||
private PlayState mainAnimController(AnimationState<CreepingHorrorEntity> state) {
|
||||
if (attackAnimTicks > 0) {
|
||||
return state.setAndContinue(ATTACK_ANIM);
|
||||
}
|
||||
if (state.isMoving()) {
|
||||
return state.setAndContinue(WALK_ANIM);
|
||||
}
|
||||
return state.setAndContinue(IDLE_ANIM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimatableInstanceCache getAnimatableInstanceCache() {
|
||||
return geoCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doHurtTarget(net.minecraft.world.entity.Entity target) {
|
||||
boolean result = super.doHurtTarget(target);
|
||||
if (result) {
|
||||
attackAnimTicks = 20;
|
||||
this.level().broadcastEntityEvent(this, ATTACK_ANIM_EVENT);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleEntityEvent(byte id) {
|
||||
if (id == ATTACK_ANIM_EVENT) {
|
||||
attackAnimTicks = 20;
|
||||
return;
|
||||
}
|
||||
super.handleEntityEvent(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
if (attackAnimTicks > 0) attackAnimTicks--;
|
||||
}
|
||||
|
||||
//TODO IT NEVER ACTUALLY SETS TO CLIMBING
|
||||
public boolean isClimbing() {
|
||||
return this.entityData.get(CLIMBING);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,738 @@
|
||||
package com.craisinlord.antarchy.content.entity;
|
||||
|
||||
import com.craisinlord.antarchy.config.AntarchySettings;
|
||||
import com.mojang.serialization.Codec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.network.syncher.EntityDataSerializers;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.util.ByIdMap;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.util.StringRepresentable;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.MobSpawnType;
|
||||
import net.minecraft.world.entity.MoverType;
|
||||
import net.minecraft.world.entity.SpawnGroupData;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.ai.goal.FloatGoal;
|
||||
import net.minecraft.world.entity.ai.goal.Goal;
|
||||
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
|
||||
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
|
||||
import net.minecraft.world.entity.ai.goal.RandomStrollGoal;
|
||||
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
|
||||
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
|
||||
import net.minecraft.world.entity.monster.Monster;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.ServerLevelAccessor;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import software.bernie.geckolib.animatable.GeoEntity;
|
||||
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
|
||||
import software.bernie.geckolib.animation.AnimatableManager;
|
||||
import software.bernie.geckolib.animation.AnimationController;
|
||||
import software.bernie.geckolib.animation.AnimationState;
|
||||
import software.bernie.geckolib.animation.PlayState;
|
||||
import software.bernie.geckolib.animation.RawAnimation;
|
||||
import software.bernie.geckolib.util.GeckoLibUtil;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
public class JumpyBugEntity extends Monster implements GeoEntity {
|
||||
private static final EntityDataAccessor<Boolean> CAMOUFLAGED =
|
||||
SynchedEntityData.defineId(JumpyBugEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
private static final EntityDataAccessor<Boolean> CLINGING_TO_CEILING =
|
||||
SynchedEntityData.defineId(JumpyBugEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
private static final EntityDataAccessor<Integer> LATCHED_TARGET_ID =
|
||||
SynchedEntityData.defineId(JumpyBugEntity.class, EntityDataSerializers.INT);
|
||||
private static final EntityDataAccessor<Integer> ATTACK_STATE =
|
||||
SynchedEntityData.defineId(JumpyBugEntity.class, EntityDataSerializers.INT);
|
||||
|
||||
private static final String POUNCE_COOLDOWN_KEY = "PounceCooldown";
|
||||
private static final String LATCH_COOLDOWN_KEY = "LatchCooldown";
|
||||
private static final String LATCH_TICKS_KEY = "LatchTicksRemaining";
|
||||
private static final String BITE_COOLDOWN_KEY = "BiteCooldown";
|
||||
private static final String RECOVERY_TICKS_KEY = "RecoveryTicks";
|
||||
private static final String POUNCE_TICKS_KEY = "PounceTicks";
|
||||
|
||||
private static final double DEFAULT_MOVEMENT_SPEED = 0.25D;
|
||||
private static final double DEFAULT_FOLLOW_RANGE = 16.0D;
|
||||
private static final double DEFAULT_POUNCE_RANGE = 8.0D;
|
||||
private static final int DEFAULT_LATCH_DURATION = 80;
|
||||
private static final int DEFAULT_BITE_INTERVAL = 15;
|
||||
private static final int DEFAULT_POUNCE_COOLDOWN = 60;
|
||||
private static final int DEFAULT_LATCH_COOLDOWN = 80;
|
||||
private static final int DEFAULT_RECOVERY_TICKS = 20;
|
||||
private static final double POUNCE_HORIZONTAL_SPEED = 0.92D;
|
||||
private static final double POUNCE_UPWARD_SPEED = 0.45D;
|
||||
private static final double QUICK_MOVE_SQR = 0.14D * 0.14D;
|
||||
private static final double CLING_SEARCH_RANGE = 1.5D;
|
||||
private static final double LATCH_CHECK_RANGE = 0.85D;
|
||||
|
||||
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
|
||||
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
|
||||
private static final RawAnimation JUMP_ANIM = RawAnimation.begin().thenLoop("jump");
|
||||
private static final RawAnimation ATTACK_ANIM = RawAnimation.begin().thenPlay("attack");
|
||||
|
||||
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
|
||||
|
||||
private int pounceCooldownTicks;
|
||||
private int latchCooldownTicks;
|
||||
private int latchTicksRemaining;
|
||||
private int biteCooldownTicks;
|
||||
private int recoveryTicks;
|
||||
private int pounceTicks;
|
||||
private float visualAlpha = 1.0F;
|
||||
private float previousVisualAlpha = 1.0F;
|
||||
|
||||
public JumpyBugEntity(EntityType<? extends JumpyBugEntity> entityType, Level level) {
|
||||
super(entityType, level);
|
||||
this.xpReward = 6;
|
||||
}
|
||||
|
||||
public static AttributeSupplier.Builder createAttributes() {
|
||||
return Mob.createMobAttributes()
|
||||
.add(Attributes.MAX_HEALTH, AntarchySettings.jumpyBugHealth())
|
||||
.add(Attributes.ATTACK_DAMAGE, AntarchySettings.jumpyBugPounceDamage())
|
||||
.add(Attributes.MOVEMENT_SPEED, DEFAULT_MOVEMENT_SPEED)
|
||||
.add(Attributes.FOLLOW_RANGE, DEFAULT_FOLLOW_RANGE);
|
||||
}
|
||||
|
||||
public static boolean canSpawn(EntityType<JumpyBugEntity> entityType, ServerLevelAccessor level, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
|
||||
if (spawnReason == MobSpawnType.SPAWN_EGG || spawnReason == MobSpawnType.SPAWNER || spawnReason == MobSpawnType.COMMAND) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return level.getDifficulty() != Difficulty.PEACEFUL
|
||||
&& Monster.checkMonsterSpawnRules(entityType, level, spawnReason, pos, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void defineSynchedData(SynchedEntityData.Builder builder) {
|
||||
super.defineSynchedData(builder);
|
||||
builder.define(CAMOUFLAGED, false);
|
||||
builder.define(CLINGING_TO_CEILING, false);
|
||||
builder.define(LATCHED_TARGET_ID, -1);
|
||||
builder.define(ATTACK_STATE, AttackState.GROUND.id());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerGoals() {
|
||||
this.goalSelector.addGoal(0, new FloatGoal(this));
|
||||
this.goalSelector.addGoal(1, new LatchedGoal());
|
||||
this.goalSelector.addGoal(2, new PounceAttackGoal());
|
||||
this.goalSelector.addGoal(3, new CeilingClingGoal());
|
||||
this.goalSelector.addGoal(4, new ChaseTargetGoal());
|
||||
this.goalSelector.addGoal(5, new WanderGoal());
|
||||
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 10.0F));
|
||||
this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
|
||||
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
|
||||
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
|
||||
controllers.add(new AnimationController<>(this, "main_controller", 2, this::mainAnimController));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimatableInstanceCache getAnimatableInstanceCache() {
|
||||
return this.geoCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSaveData(CompoundTag tag) {
|
||||
super.addAdditionalSaveData(tag);
|
||||
tag.putInt(POUNCE_COOLDOWN_KEY, this.pounceCooldownTicks);
|
||||
tag.putInt(LATCH_COOLDOWN_KEY, this.latchCooldownTicks);
|
||||
tag.putInt(LATCH_TICKS_KEY, this.latchTicksRemaining);
|
||||
tag.putInt(BITE_COOLDOWN_KEY, this.biteCooldownTicks);
|
||||
tag.putInt(RECOVERY_TICKS_KEY, this.recoveryTicks);
|
||||
tag.putInt(POUNCE_TICKS_KEY, this.pounceTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAdditionalSaveData(CompoundTag tag) {
|
||||
super.readAdditionalSaveData(tag);
|
||||
this.pounceCooldownTicks = tag.getInt(POUNCE_COOLDOWN_KEY);
|
||||
this.latchCooldownTicks = tag.getInt(LATCH_COOLDOWN_KEY);
|
||||
this.latchTicksRemaining = tag.getInt(LATCH_TICKS_KEY);
|
||||
this.biteCooldownTicks = tag.getInt(BITE_COOLDOWN_KEY);
|
||||
this.recoveryTicks = tag.getInt(RECOVERY_TICKS_KEY);
|
||||
this.pounceTicks = tag.getInt(POUNCE_TICKS_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData spawnGroupData) {
|
||||
this.setHealth((float) AntarchySettings.jumpyBugHealth());
|
||||
return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
this.previousVisualAlpha = this.visualAlpha;
|
||||
this.visualAlpha += (this.getTargetVisualAlpha() - this.visualAlpha) * 0.25F;
|
||||
|
||||
super.tick();
|
||||
|
||||
if (this.level().isClientSide) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pounceCooldownTicks > 0) {
|
||||
this.pounceCooldownTicks--;
|
||||
}
|
||||
if (this.latchCooldownTicks > 0) {
|
||||
this.latchCooldownTicks--;
|
||||
}
|
||||
if (this.biteCooldownTicks > 0) {
|
||||
this.biteCooldownTicks--;
|
||||
}
|
||||
if (this.recoveryTicks > 0) {
|
||||
this.recoveryTicks--;
|
||||
}
|
||||
|
||||
if (this.isLatched()) {
|
||||
this.tickLatched();
|
||||
} else if (this.isClingingToCeiling()) {
|
||||
this.tickClinging();
|
||||
} else if (this.isPouncing()) {
|
||||
this.tickPouncing();
|
||||
} else {
|
||||
this.setNoGravity(false);
|
||||
this.noPhysics = false;
|
||||
if (this.getAttackState() != AttackState.RECOVERING || this.recoveryTicks <= 0) {
|
||||
this.setAttackState(AttackState.GROUND);
|
||||
}
|
||||
}
|
||||
|
||||
this.updateCamouflage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void travel(Vec3 travelVector) {
|
||||
if (this.isLatched() || this.isClingingToCeiling()) {
|
||||
this.setDeltaMovement(Vec3.ZERO);
|
||||
return;
|
||||
}
|
||||
super.travel(travelVector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hurt(DamageSource source, float amount) {
|
||||
boolean hurt = super.hurt(source, amount);
|
||||
if (!hurt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setCamouflaged(false);
|
||||
this.recoveryTicks = Math.max(this.recoveryTicks, DEFAULT_RECOVERY_TICKS);
|
||||
if (this.isLatched()) {
|
||||
this.detachFromTarget(true);
|
||||
}
|
||||
if (this.isClingingToCeiling()) {
|
||||
this.stopClinging();
|
||||
}
|
||||
if (this.isPouncing()) {
|
||||
this.finishPounce(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean causeFallDamage(float fallDistance, float multiplier, DamageSource source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getAmbientSound() {
|
||||
return SoundEvents.SPIDER_AMBIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getHurtSound(DamageSource damageSource) {
|
||||
return SoundEvents.SPIDER_HURT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getDeathSound() {
|
||||
return SoundEvents.SPIDER_DEATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void playStepSound(BlockPos pos, BlockState blockState) {
|
||||
this.playSound(SoundEvents.SPIDER_STEP, 0.12F, 1.1F);
|
||||
}
|
||||
|
||||
public boolean isCamouflaged() {
|
||||
return this.entityData.get(CAMOUFLAGED);
|
||||
}
|
||||
|
||||
public boolean isClingingToCeiling() {
|
||||
return this.entityData.get(CLINGING_TO_CEILING);
|
||||
}
|
||||
|
||||
public boolean isLatched() {
|
||||
return this.entityData.get(LATCHED_TARGET_ID) != -1;
|
||||
}
|
||||
|
||||
public boolean isLatchedTo(LivingEntity entity) {
|
||||
return this.entityData.get(LATCHED_TARGET_ID) == entity.getId();
|
||||
}
|
||||
|
||||
public boolean isPouncing() {
|
||||
return this.getAttackState() == AttackState.POUNCING;
|
||||
}
|
||||
|
||||
public float getVisualAlpha(float partialTick) {
|
||||
return Mth.lerp(partialTick, this.previousVisualAlpha, this.visualAlpha);
|
||||
}
|
||||
|
||||
public AttackState getAttackState() {
|
||||
return AttackState.BY_ID.apply(this.entityData.get(ATTACK_STATE));
|
||||
}
|
||||
|
||||
private void setCamouflaged(boolean value) {
|
||||
this.entityData.set(CAMOUFLAGED, value);
|
||||
}
|
||||
|
||||
private void setClingingToCeiling(boolean value) {
|
||||
this.entityData.set(CLINGING_TO_CEILING, value);
|
||||
}
|
||||
|
||||
private void setAttackState(AttackState state) {
|
||||
this.entityData.set(ATTACK_STATE, state.id());
|
||||
}
|
||||
|
||||
private float getTargetVisualAlpha() {
|
||||
return this.isCamouflaged() ? (float) AntarchySettings.jumpyBugCamouflageAlpha() : 1.0F;
|
||||
}
|
||||
|
||||
private PlayState mainAnimController(AnimationState<JumpyBugEntity> state) {
|
||||
AttackState attackState = this.getAttackState();
|
||||
if (attackState == AttackState.POUNCING || attackState == AttackState.LATCHED || attackState == AttackState.CLINGING) {
|
||||
return state.setAndContinue(JUMP_ANIM);
|
||||
}
|
||||
if (attackState == AttackState.RECOVERING) {
|
||||
return state.setAndContinue(ATTACK_ANIM);
|
||||
}
|
||||
return state.setAndContinue(state.isMoving() ? WALK_ANIM : IDLE_ANIM);
|
||||
}
|
||||
|
||||
private void tickClinging() {
|
||||
BlockPos clingPos = this.findClingBlockPos();
|
||||
if (clingPos == null) {
|
||||
this.stopClinging();
|
||||
return;
|
||||
}
|
||||
|
||||
this.setAttackState(AttackState.CLINGING);
|
||||
this.setCamouflaged(true);
|
||||
this.setNoGravity(true);
|
||||
this.noPhysics = true;
|
||||
this.getNavigation().stop();
|
||||
this.setDeltaMovement(Vec3.ZERO);
|
||||
this.move(MoverType.SELF, Vec3.ZERO);
|
||||
this.setPos(this.getX(), clingPos.getY() - this.getBbHeight() - 0.01D, this.getZ());
|
||||
}
|
||||
|
||||
private void tickPouncing() {
|
||||
this.setAttackState(AttackState.POUNCING);
|
||||
this.setCamouflaged(false);
|
||||
this.pounceTicks++;
|
||||
LivingEntity target = this.getTarget();
|
||||
if (target != null && target.isAlive() && this.distanceToSqr(target) <= (this.getBbWidth() + target.getBbWidth() + LATCH_CHECK_RANGE) * (this.getBbWidth() + target.getBbWidth() + LATCH_CHECK_RANGE)) {
|
||||
if (this.tryLatch(target)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.onGround() && this.pounceTicks > 4) {
|
||||
this.finishPounce(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void tickLatched() {
|
||||
LivingEntity target = this.getLatchedTarget();
|
||||
if (target == null || !target.isAlive() || target.level() != this.level() || target.isRemoved() || target.isInWater()) {
|
||||
this.detachFromTarget(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setAttackState(AttackState.LATCHED);
|
||||
this.setCamouflaged(false);
|
||||
this.setNoGravity(true);
|
||||
this.noPhysics = true;
|
||||
this.getNavigation().stop();
|
||||
this.setDeltaMovement(Vec3.ZERO);
|
||||
|
||||
Vec3 latchPos = this.getLatchPosition(target);
|
||||
this.setPos(latchPos.x, latchPos.y, latchPos.z);
|
||||
this.yRotO = this.getYRot();
|
||||
this.setYRot(target.getYHeadRot());
|
||||
this.setYBodyRot(target.getYHeadRot());
|
||||
this.setYHeadRot(target.getYHeadRot());
|
||||
this.setXRot(-20.0F);
|
||||
|
||||
if (--this.latchTicksRemaining <= 0) {
|
||||
this.detachFromTarget(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.biteCooldownTicks <= 0) {
|
||||
target.hurt(this.damageSources().mobAttack(this), (float) AntarchySettings.jumpyBugLatchDamage());
|
||||
this.biteCooldownTicks = DEFAULT_BITE_INTERVAL;
|
||||
this.playSound(SoundEvents.SPIDER_HURT, 0.35F, 1.5F + this.random.nextFloat() * 0.2F);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCamouflage() {
|
||||
if (this.isLatched() || this.isPouncing()) {
|
||||
this.setCamouflaged(false);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean idle = this.getTarget() == null && this.getDeltaMovement().horizontalDistanceSqr() < 0.003D;
|
||||
boolean stalking = this.isClingingToCeiling();
|
||||
boolean movingQuickly = this.getDeltaMovement().lengthSqr() > QUICK_MOVE_SQR;
|
||||
boolean shouldCamouflage = !movingQuickly && (idle || stalking);
|
||||
this.setCamouflaged(shouldCamouflage);
|
||||
}
|
||||
|
||||
private boolean tryLatch(LivingEntity target) {
|
||||
if (!(target instanceof Player) || this.latchCooldownTicks > 0 || this.isLatched()) {
|
||||
return false;
|
||||
}
|
||||
if (this.level().getEntitiesOfClass(JumpyBugEntity.class, target.getBoundingBox().inflate(0.75D),
|
||||
bug -> bug != this && bug.isLatchedTo(target)).isEmpty()) {
|
||||
this.entityData.set(LATCHED_TARGET_ID, target.getId());
|
||||
this.latchTicksRemaining = DEFAULT_LATCH_DURATION;
|
||||
this.biteCooldownTicks = 0;
|
||||
this.pounceTicks = 0;
|
||||
this.setAttackState(AttackState.LATCHED);
|
||||
this.setTarget(target);
|
||||
target.hurt(this.damageSources().mobAttack(this), (float) AntarchySettings.jumpyBugPounceDamage());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void detachFromTarget(boolean knockAway) {
|
||||
LivingEntity target = this.getLatchedTarget();
|
||||
this.entityData.set(LATCHED_TARGET_ID, -1);
|
||||
this.latchTicksRemaining = 0;
|
||||
this.biteCooldownTicks = DEFAULT_BITE_INTERVAL;
|
||||
this.latchCooldownTicks = DEFAULT_LATCH_COOLDOWN;
|
||||
this.setAttackState(AttackState.RECOVERING);
|
||||
this.recoveryTicks = DEFAULT_RECOVERY_TICKS;
|
||||
this.setNoGravity(false);
|
||||
this.noPhysics = false;
|
||||
|
||||
if (knockAway && target != null) {
|
||||
Vec3 away = this.position().subtract(target.position());
|
||||
if (away.lengthSqr() < 1.0E-4D) {
|
||||
away = target.getLookAngle().scale(-1.0D);
|
||||
}
|
||||
away = away.normalize();
|
||||
this.setDeltaMovement(away.x * 0.35D, 0.25D, away.z * 0.35D);
|
||||
this.hasImpulse = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private LivingEntity getLatchedTarget() {
|
||||
int entityId = this.entityData.get(LATCHED_TARGET_ID);
|
||||
if (entityId == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Entity entity = this.level().getEntity(entityId);
|
||||
return entity instanceof LivingEntity livingEntity ? livingEntity : null;
|
||||
}
|
||||
|
||||
private Vec3 getLatchPosition(LivingEntity target) {
|
||||
Vec3 eyePos = target.position().add(0.0D, target.getEyeHeight() - 0.15D, 0.0D);
|
||||
Vec3 backOffset = target.getLookAngle().normalize().scale(-0.16D);
|
||||
return eyePos.add(backOffset).add(0.0D, -0.18D, 0.0D);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BlockPos findClingBlockPos() {
|
||||
BlockPos origin = this.blockPosition();
|
||||
for (int dy = 1; dy <= 2; dy++) {
|
||||
BlockPos candidate = origin.above(dy);
|
||||
if (this.isCeilingBlock(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isCeilingBlock(BlockPos pos) {
|
||||
return this.level().getBlockState(pos).isFaceSturdy(this.level(), pos, net.minecraft.core.Direction.DOWN);
|
||||
}
|
||||
|
||||
private boolean canClingHere() {
|
||||
return this.findClingBlockPos() != null;
|
||||
}
|
||||
|
||||
private void beginClinging() {
|
||||
this.setClingingToCeiling(true);
|
||||
this.setAttackState(AttackState.CLINGING);
|
||||
this.setNoGravity(true);
|
||||
this.noPhysics = true;
|
||||
this.getNavigation().stop();
|
||||
this.setDeltaMovement(Vec3.ZERO);
|
||||
}
|
||||
|
||||
private void stopClinging() {
|
||||
this.setClingingToCeiling(false);
|
||||
this.setAttackState(AttackState.GROUND);
|
||||
this.setNoGravity(false);
|
||||
this.noPhysics = false;
|
||||
}
|
||||
|
||||
private void beginPounce(LivingEntity target) {
|
||||
this.stopClinging();
|
||||
this.setTarget(target);
|
||||
this.setAttackState(AttackState.POUNCING);
|
||||
this.setCamouflaged(false);
|
||||
this.getNavigation().stop();
|
||||
this.pounceCooldownTicks = DEFAULT_POUNCE_COOLDOWN;
|
||||
this.pounceTicks = 0;
|
||||
|
||||
Vec3 from = this.position();
|
||||
Vec3 to = target.getEyePosition().subtract(from);
|
||||
Vec3 horizontal = new Vec3(to.x, 0.0D, to.z);
|
||||
Vec3 direction = horizontal.lengthSqr() > 1.0E-4D ? horizontal.normalize() : target.getLookAngle();
|
||||
double upward = POUNCE_UPWARD_SPEED + Mth.clamp(to.y * 0.08D, -0.04D, 0.22D);
|
||||
this.setDeltaMovement(direction.x * POUNCE_HORIZONTAL_SPEED, upward, direction.z * POUNCE_HORIZONTAL_SPEED);
|
||||
this.hasImpulse = true;
|
||||
}
|
||||
|
||||
private void finishPounce(boolean keepMomentum) {
|
||||
this.pounceTicks = 0;
|
||||
if (!keepMomentum) {
|
||||
Vec3 movement = this.getDeltaMovement();
|
||||
this.setDeltaMovement(movement.x * 0.25D, Math.max(0.0D, movement.y), movement.z * 0.25D);
|
||||
}
|
||||
this.setAttackState(AttackState.RECOVERING);
|
||||
this.recoveryTicks = DEFAULT_RECOVERY_TICKS;
|
||||
}
|
||||
|
||||
private boolean canUseAmbush() {
|
||||
return !this.isLatched()
|
||||
&& !this.isPouncing()
|
||||
&& this.recoveryTicks <= 0
|
||||
&& this.latchCooldownTicks <= 0;
|
||||
}
|
||||
|
||||
private enum AttackState implements StringRepresentable {
|
||||
GROUND(0, "ground"),
|
||||
CLINGING(1, "clinging"),
|
||||
POUNCING(2, "pouncing"),
|
||||
LATCHED(3, "latched"),
|
||||
RECOVERING(4, "recovering");
|
||||
|
||||
private static final IntFunction<AttackState> BY_ID = ByIdMap.continuous(AttackState::id, values(), ByIdMap.OutOfBoundsStrategy.ZERO);
|
||||
public static final Codec<AttackState> CODEC = StringRepresentable.fromEnum(AttackState::values);
|
||||
|
||||
private final int id;
|
||||
private final String name;
|
||||
|
||||
AttackState(int id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerializedName() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
private final class LatchedGoal extends Goal {
|
||||
private LatchedGoal() {
|
||||
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK, Flag.JUMP));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
return JumpyBugEntity.this.isLatched();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canContinueToUse() {
|
||||
return JumpyBugEntity.this.isLatched();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
JumpyBugEntity.this.getNavigation().stop();
|
||||
}
|
||||
}
|
||||
|
||||
private final class PounceAttackGoal extends Goal {
|
||||
private PounceAttackGoal() {
|
||||
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK, Flag.JUMP));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
LivingEntity target = JumpyBugEntity.this.getTarget();
|
||||
return target != null
|
||||
&& target.isAlive()
|
||||
&& JumpyBugEntity.this.canUseAmbush()
|
||||
&& JumpyBugEntity.this.pounceCooldownTicks <= 0
|
||||
&& JumpyBugEntity.this.distanceToSqr(target) <= DEFAULT_POUNCE_RANGE * DEFAULT_POUNCE_RANGE
|
||||
&& (JumpyBugEntity.this.hasLineOfSight(target) || JumpyBugEntity.this.distanceToSqr(target) <= 9.0D);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canContinueToUse() {
|
||||
return JumpyBugEntity.this.isPouncing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
LivingEntity target = JumpyBugEntity.this.getTarget();
|
||||
if (target != null) {
|
||||
JumpyBugEntity.this.beginPounce(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class CeilingClingGoal extends Goal {
|
||||
private CeilingClingGoal() {
|
||||
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK, Flag.JUMP));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
if (!JumpyBugEntity.this.canUseAmbush() || JumpyBugEntity.this.isClingingToCeiling()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LivingEntity target = JumpyBugEntity.this.getTarget();
|
||||
if (target == null) {
|
||||
return JumpyBugEntity.this.random.nextInt(40) == 0 && JumpyBugEntity.this.canClingHere();
|
||||
}
|
||||
|
||||
return JumpyBugEntity.this.distanceToSqr(target) <= (DEFAULT_POUNCE_RANGE + CLING_SEARCH_RANGE) * (DEFAULT_POUNCE_RANGE + CLING_SEARCH_RANGE)
|
||||
&& JumpyBugEntity.this.canClingHere();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canContinueToUse() {
|
||||
return JumpyBugEntity.this.isClingingToCeiling();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
JumpyBugEntity.this.beginClinging();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (JumpyBugEntity.this.isClingingToCeiling()) {
|
||||
JumpyBugEntity.this.stopClinging();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
LivingEntity target = JumpyBugEntity.this.getTarget();
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
JumpyBugEntity.this.getLookControl().setLookAt(target, 30.0F, 30.0F);
|
||||
if (JumpyBugEntity.this.pounceCooldownTicks <= 0
|
||||
&& JumpyBugEntity.this.distanceToSqr(target) <= DEFAULT_POUNCE_RANGE * DEFAULT_POUNCE_RANGE
|
||||
&& (JumpyBugEntity.this.hasLineOfSight(target) || JumpyBugEntity.this.distanceToSqr(target) <= 4.0D)) {
|
||||
JumpyBugEntity.this.stopClinging();
|
||||
JumpyBugEntity.this.beginPounce(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ChaseTargetGoal extends Goal {
|
||||
private ChaseTargetGoal() {
|
||||
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
LivingEntity target = JumpyBugEntity.this.getTarget();
|
||||
return target != null
|
||||
&& target.isAlive()
|
||||
&& !JumpyBugEntity.this.isLatched()
|
||||
&& !JumpyBugEntity.this.isClingingToCeiling()
|
||||
&& !JumpyBugEntity.this.isPouncing()
|
||||
&& JumpyBugEntity.this.recoveryTicks <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canContinueToUse() {
|
||||
return this.canUse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
LivingEntity target = JumpyBugEntity.this.getTarget();
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
JumpyBugEntity.this.getLookControl().setLookAt(target, 30.0F, 30.0F);
|
||||
if (JumpyBugEntity.this.distanceToSqr(target) > DEFAULT_POUNCE_RANGE * DEFAULT_POUNCE_RANGE) {
|
||||
JumpyBugEntity.this.getNavigation().moveTo(target, 1.05D);
|
||||
} else {
|
||||
JumpyBugEntity.this.getNavigation().stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class WanderGoal extends RandomStrollGoal {
|
||||
private WanderGoal() {
|
||||
super(JumpyBugEntity.this, 0.8D);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
return !JumpyBugEntity.this.isLatched()
|
||||
&& !JumpyBugEntity.this.isClingingToCeiling()
|
||||
&& !JumpyBugEntity.this.isPouncing()
|
||||
&& JumpyBugEntity.this.getTarget() == null
|
||||
&& super.canUse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canContinueToUse() {
|
||||
return !JumpyBugEntity.this.isLatched()
|
||||
&& !JumpyBugEntity.this.isClingingToCeiling()
|
||||
&& !JumpyBugEntity.this.isPouncing()
|
||||
&& super.canContinueToUse();
|
||||
}
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package com.craisinlord.antarchy.content.entity;
|
||||
|
||||
import com.craisinlord.antarchy.config.AntarchySettings;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.ai.control.FlyingMoveControl;
|
||||
import net.minecraft.world.entity.ai.goal.FloatGoal;
|
||||
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
|
||||
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
|
||||
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
|
||||
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
|
||||
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
|
||||
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
|
||||
import net.minecraft.world.entity.ai.navigation.FlyingPathNavigation;
|
||||
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||
import net.minecraft.world.entity.monster.Monster;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
import software.bernie.geckolib.animatable.GeoEntity;
|
||||
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
|
||||
import software.bernie.geckolib.animation.AnimatableManager;
|
||||
import software.bernie.geckolib.animation.AnimationController;
|
||||
import software.bernie.geckolib.animation.AnimationState;
|
||||
import software.bernie.geckolib.animation.PlayState;
|
||||
import software.bernie.geckolib.animation.RawAnimation;
|
||||
import software.bernie.geckolib.util.GeckoLibUtil;
|
||||
|
||||
public class LurkingTerrorEntity extends Monster implements GeoEntity {
|
||||
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
|
||||
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
|
||||
private static final RawAnimation ATTACK_LAND_ANIM = RawAnimation.begin().thenLoop("attackland");
|
||||
private static final RawAnimation ATTACK_AIR_ANIM = RawAnimation.begin().thenLoop("attackair");
|
||||
private static final byte ATTACK_ANIM_EVENT = 5;
|
||||
|
||||
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
|
||||
private int attackAnimTicks = 0;
|
||||
|
||||
public LurkingTerrorEntity(EntityType<? extends LurkingTerrorEntity> type, Level level) {
|
||||
super(type, level);
|
||||
this.moveControl = new FlyingMoveControl(this, 10, true);
|
||||
}
|
||||
|
||||
public static AttributeSupplier.Builder createAttributes() {
|
||||
return Mob.createMobAttributes()
|
||||
.add(Attributes.MAX_HEALTH, AntarchySettings.lurkingTerrorHealth())
|
||||
.add(Attributes.MOVEMENT_SPEED, 0.25D)
|
||||
.add(Attributes.ATTACK_DAMAGE, AntarchySettings.lurkingTerrorAttackDamage())
|
||||
.add(Attributes.FOLLOW_RANGE, 16.0D)
|
||||
.add(Attributes.FLYING_SPEED, 0.4D);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PathNavigation createNavigation(Level level) {
|
||||
FlyingPathNavigation nav = new FlyingPathNavigation(this, level);
|
||||
nav.setCanOpenDoors(false);
|
||||
nav.setCanFloat(true);
|
||||
nav.setCanPassDoors(true);
|
||||
return nav;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerGoals() {
|
||||
this.goalSelector.addGoal(0, new FloatGoal(this));
|
||||
this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0D, true));
|
||||
this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 0.85D));
|
||||
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 8.0F));
|
||||
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
|
||||
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
|
||||
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
|
||||
controllers.add(new AnimationController<>(this, "main_controller", 3, this::mainAnimController));
|
||||
}
|
||||
|
||||
private PlayState mainAnimController(AnimationState<LurkingTerrorEntity> state) {
|
||||
if (attackAnimTicks > 0) {
|
||||
// if (!this.isOnGround()) {
|
||||
// return state.setAndContinue(ATTACK_AIR_ANIM);
|
||||
// }
|
||||
return state.setAndContinue(ATTACK_LAND_ANIM);
|
||||
}
|
||||
if (state.isMoving()) {
|
||||
return state.setAndContinue(WALK_ANIM);
|
||||
}
|
||||
return state.setAndContinue(IDLE_ANIM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimatableInstanceCache getAnimatableInstanceCache() {
|
||||
return geoCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doHurtTarget(net.minecraft.world.entity.Entity target) {
|
||||
boolean result = super.doHurtTarget(target);
|
||||
if (result) {
|
||||
attackAnimTicks = 20;
|
||||
this.level().broadcastEntityEvent(this, ATTACK_ANIM_EVENT);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleEntityEvent(byte id) {
|
||||
if (id == ATTACK_ANIM_EVENT) {
|
||||
attackAnimTicks = 20;
|
||||
return;
|
||||
}
|
||||
super.handleEntityEvent(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
if (attackAnimTicks > 0) attackAnimTicks--;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean causeFallDamage(float fallDistance, float multiplier, net.minecraft.world.damagesource.DamageSource source) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.world.BossEvent;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.tags.DamageTypeTags;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
@@ -219,6 +220,10 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
|
||||
|
||||
@Override
|
||||
public boolean hurt(DamageSource source, float amount) {
|
||||
if (source.is(net.minecraft.world.damagesource.DamageTypes.DROWN)) return false;
|
||||
if (source.is(DamageTypeTags.IS_PROJECTILE)) {
|
||||
amount *= (float) AntarchySettings.toreterrorProjectileDamageMultiplier();
|
||||
}
|
||||
if (!this.isPhase2()) {
|
||||
float halfHealth = this.getMaxHealth() * 0.5F;
|
||||
if (this.getHealth() - amount <= halfHealth) {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.craisinlord.antarchy.content.item;
|
||||
|
||||
public final class JumpyBootsHelper {
|
||||
public static final int CHARGE_TICKS_MAX = 30;
|
||||
public static final int PRIME_WINDOW_TICKS = 15;
|
||||
public static final int COOLDOWN_TICKS = 40;
|
||||
|
||||
public static final float MAX_VERTICAL_BOOST = 2.0F;
|
||||
public static final float SPRINT_FORWARD_BOOST = 0.3F;
|
||||
public static final long FALL_PROTECTION_TICKS = 120L;
|
||||
public static final String FALL_PROTECTION_NBT_KEY = "antarchy:jumpy_boots_protection_until";
|
||||
|
||||
private JumpyBootsHelper() {
|
||||
}
|
||||
|
||||
public static float verticalBoostFor(int chargeTicks) {
|
||||
float ratio = Math.min(chargeTicks / (float) CHARGE_TICKS_MAX, 1.0F);
|
||||
return ratio * MAX_VERTICAL_BOOST;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.craisinlord.antarchy.content.item;
|
||||
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.ArmorItem;
|
||||
import net.minecraft.world.item.ArmorMaterial;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class JumpyBootsItem extends ArmorItem {
|
||||
public JumpyBootsItem(Holder<ArmorMaterial> material, Properties properties) {
|
||||
super(material, Type.BOOTS, properties);
|
||||
}
|
||||
|
||||
public static boolean isWearingJumpyBoots(net.minecraft.world.entity.LivingEntity entity) {
|
||||
return entity != null && entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.FEET).getItem() instanceof JumpyBootsItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnchantable(ItemStack stack) {
|
||||
return stack.getCount() == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
|
||||
tooltipComponents.add(Component.translatable("tooltip.antarchy.jumpy_boots").withStyle(ChatFormatting.DARK_RED));
|
||||
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.craisinlord.antarchy.content.network;
|
||||
|
||||
import com.craisinlord.antarchy.Antarchy;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public record JumpyBootsLaunchPayload(int chargeTicks, boolean sprinting) implements CustomPacketPayload {
|
||||
public static final Type<JumpyBootsLaunchPayload> TYPE =
|
||||
new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "jumpy_boots_launch"));
|
||||
public static final StreamCodec<ByteBuf, JumpyBootsLaunchPayload> STREAM_CODEC =
|
||||
StreamCodec.composite(
|
||||
ByteBufCodecs.VAR_INT, JumpyBootsLaunchPayload::chargeTicks,
|
||||
ByteBufCodecs.BOOL, JumpyBootsLaunchPayload::sprinting,
|
||||
JumpyBootsLaunchPayload::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<JumpyBootsLaunchPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user