ruined arena
new ant sacrifice portals dorrie overhaul new foliage in overworld biomes
This commit is contained in:
1 parent
ae76303716
commit
4e67661448
92 files changed
+5802
-2512
No files matched your search
@@ -447,6 +447,10 @@ public final class AntarchySettings {
|
||||
private static double primordialArmorKnockbackPerPiece = 0.5D;
|
||||
private static int fallenKingCrownArmorValue = 2;
|
||||
private static double fallenKingCrownArmorToughness = 0.0D;
|
||||
private static boolean permanentPortalsEnabled = true;
|
||||
private static boolean elythiaPortalEnabled = true;
|
||||
private static boolean thoraxisPortalEnabled = true;
|
||||
private static boolean cavarynPortalEnabled = true;
|
||||
private static boolean elythiaFireflyParticlesEnabled = true;
|
||||
private static int bloodCrystalArmorShieldRechargeTicks = 600;
|
||||
private static int bloodCrystalAppleShieldCount = 2;
|
||||
@@ -957,6 +961,22 @@ public final class AntarchySettings {
|
||||
rainbowAntsLeadToInfinityDimensions = value;
|
||||
}
|
||||
|
||||
public static void setPermanentPortalsEnabled(boolean value) {
|
||||
permanentPortalsEnabled = value;
|
||||
}
|
||||
|
||||
public static void setElythiaPortalEnabled(boolean value) {
|
||||
elythiaPortalEnabled = value;
|
||||
}
|
||||
|
||||
public static void setThoraxisPortalEnabled(boolean value) {
|
||||
thoraxisPortalEnabled = value;
|
||||
}
|
||||
|
||||
public static void setCavarynPortalEnabled(boolean value) {
|
||||
cavarynPortalEnabled = value;
|
||||
}
|
||||
|
||||
public static void setBrownAntRequiresReagent(boolean value) {
|
||||
brownAntRequiresReagent = value;
|
||||
}
|
||||
@@ -2434,6 +2454,11 @@ public final class AntarchySettings {
|
||||
public static double fallenKingCrownArmorToughness() { return fallenKingCrownArmorToughness; }
|
||||
public static void setFallenKingCrownArmorToughness(double value) { fallenKingCrownArmorToughness = value; }
|
||||
|
||||
public static boolean permanentPortalsEnabled() { return permanentPortalsEnabled; }
|
||||
public static boolean elythiaPortalEnabled() { return elythiaPortalEnabled; }
|
||||
public static boolean thoraxisPortalEnabled() { return thoraxisPortalEnabled; }
|
||||
public static boolean cavarynPortalEnabled() { return cavarynPortalEnabled; }
|
||||
|
||||
public static boolean elythiaFireflyParticlesEnabled() { return elythiaFireflyParticlesEnabled; }
|
||||
public static void setElythiaFireflyParticlesEnabled(boolean value) { elythiaFireflyParticlesEnabled = value; }
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ public final class AntarchyTags {
|
||||
public static final TagKey<Block> TERMITE_FOODS = TagKey.create(Registries.BLOCK, id("termite_foods"));
|
||||
public static final TagKey<Block> UMBRAL_MOSS_REPLACEABLE = TagKey.create(Registries.BLOCK, id("umbral_moss_replaceable"));
|
||||
public static final TagKey<Block> HERCULES_BEETLE_CHARGE_IMMUNE_BLOCKS = TagKey.create(Registries.BLOCK, id("hercules_beetle_charge_immune_blocks"));
|
||||
public static final TagKey<Block> ELYTHIA_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("elythia_portal_frames"));
|
||||
public static final TagKey<Block> THORAXIS_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("thoraxis_portal_frames"));
|
||||
public static final TagKey<Block> CAVARYN_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("cavaryn_portal_frames"));
|
||||
|
||||
private Blocks() {
|
||||
}
|
||||
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
package com.craisinlord.antarchy.content.block;
|
||||
|
||||
import com.craisinlord.antarchy.content.portal.PermanentPortalManager;
|
||||
import com.craisinlord.antarchy.content.portal.PermanentPortalType;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.particles.ParticleOptions;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.RenderShape;
|
||||
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.EnumProperty;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
public final class AntDimensionPortalBlock extends Block {
|
||||
public static final EnumProperty<Direction.Axis> AXIS = BlockStateProperties.HORIZONTAL_AXIS;
|
||||
private static final VoxelShape X_SHAPE = Block.box(0.0D, 0.0D, 6.0D, 16.0D, 16.0D, 10.0D);
|
||||
private static final VoxelShape Z_SHAPE = Block.box(6.0D, 0.0D, 0.0D, 10.0D, 16.0D, 16.0D);
|
||||
|
||||
private final PermanentPortalType type;
|
||||
private final MapCodec<AntDimensionPortalBlock> codec;
|
||||
|
||||
public AntDimensionPortalBlock(PermanentPortalType type, BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
this.type = type;
|
||||
this.codec = Block.simpleCodec(ignored -> this);
|
||||
this.registerDefaultState(this.stateDefinition.any().setValue(AXIS, Direction.Axis.X));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapCodec<AntDimensionPortalBlock> codec() {
|
||||
return this.codec;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
||||
builder.add(AXIS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
|
||||
return state.getValue(AXIS) == Direction.Axis.X ? X_SHAPE : Z_SHAPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
|
||||
return Shapes.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VoxelShape getVisualShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
|
||||
return Shapes.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VoxelShape getBlockSupportShape(BlockState state, BlockGetter level, BlockPos pos) {
|
||||
return Shapes.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RenderShape getRenderShape(BlockState state) {
|
||||
return RenderShape.MODEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean skipRendering(BlockState state, BlockState adjacentBlockState, Direction side) {
|
||||
return adjacentBlockState.is(this) || super.skipRendering(state, adjacentBlockState, side);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
|
||||
if (!level.isClientSide()) {
|
||||
PermanentPortalManager.handleEntityInsidePortal(entity, this.type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
|
||||
super.onPlace(state, level, pos, oldState, movedByPiston);
|
||||
if (!level.isClientSide() && !state.is(oldState.getBlock())) {
|
||||
level.scheduleTick(pos, this, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BlockState updateShape(
|
||||
BlockState state,
|
||||
Direction direction,
|
||||
BlockState neighborState,
|
||||
LevelAccessor level,
|
||||
BlockPos pos,
|
||||
BlockPos neighborPos
|
||||
) {
|
||||
if (level instanceof ServerLevel serverLevel) {
|
||||
serverLevel.scheduleTick(pos, this, 1);
|
||||
}
|
||||
return super.updateShape(state, direction, neighborState, level, pos, neighborPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
|
||||
if (!PermanentPortalManager.isPortalStillValid(level, pos, this.type, state.getValue(AXIS))) {
|
||||
level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) {
|
||||
if (random.nextInt(100) == 0) {
|
||||
level.playLocalSound(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, SoundEvents.PORTAL_AMBIENT, SoundSource.BLOCKS, 0.35F, 0.9F + random.nextFloat() * 0.2F, false);
|
||||
}
|
||||
|
||||
ParticleOptions particle = switch (this.type) {
|
||||
case ELYTHIA -> ParticleTypes.SPORE_BLOSSOM_AIR;
|
||||
case THORAXIS -> ParticleTypes.PORTAL;
|
||||
case CAVARYN -> ParticleTypes.MYCELIUM;
|
||||
};
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double x = pos.getX() + random.nextDouble();
|
||||
double y = pos.getY() + random.nextDouble();
|
||||
double z = pos.getZ() + random.nextDouble();
|
||||
if (state.getValue(AXIS) == Direction.Axis.X) {
|
||||
z = pos.getZ() + 0.5D + (random.nextDouble() - 0.5D) * 0.25D;
|
||||
} else {
|
||||
x = pos.getX() + 0.5D + (random.nextDouble() - 0.5D) * 0.25D;
|
||||
}
|
||||
level.addParticle(
|
||||
particle,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
(random.nextDouble() - 0.5D) * 0.2D,
|
||||
(random.nextDouble() - 0.5D) * 0.2D,
|
||||
(random.nextDouble() - 0.5D) * 0.2D
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,11 @@ import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.ItemInteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
@@ -22,6 +27,7 @@ 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.DirectionProperty;
|
||||
import net.minecraft.world.level.block.state.properties.IntegerProperty;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
@@ -251,4 +257,35 @@ public class BigBushBlock extends BushBlock implements BonemealableBlock {
|
||||
}
|
||||
placeStage(level, target.origin(), target.state());
|
||||
}
|
||||
|
||||
/** Pops a bonus item when bone meal is used on a fully-grown (age 2) bush; shared with {@link BigBushPartBlock}. */
|
||||
public static boolean grantBonemealBonus(Level level, BlockPos originPos, BlockState originState, Player player, ItemStack stack) {
|
||||
if (!(originState.getBlock() instanceof BigBushBlock bigBush) || originState.getValue(AGE) != 2) {
|
||||
return false;
|
||||
}
|
||||
if (!level.isClientSide) {
|
||||
bigBush.popResource(level, originPos, new ItemStack(bigBush, 1));
|
||||
if (!player.getAbilities().instabuild) {
|
||||
stack.shrink(1);
|
||||
}
|
||||
level.levelEvent(1505, originPos, 15);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemInteractionResult useItemOn(
|
||||
ItemStack stack,
|
||||
BlockState state,
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
Player player,
|
||||
InteractionHand hand,
|
||||
BlockHitResult hitResult
|
||||
) {
|
||||
if (stack.is(Items.BONE_MEAL) && grantBonemealBonus(level, pos, state, player, stack)) {
|
||||
return ItemInteractionResult.sidedSuccess(level.isClientSide);
|
||||
}
|
||||
return super.useItemOn(stack, state, level, pos, player, hand, hitResult);
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,11 @@ import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.ItemInteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
@@ -16,6 +19,7 @@ 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.BooleanProperty;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
@@ -119,6 +123,25 @@ public class BigBushPartBlock extends Block {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemInteractionResult useItemOn(
|
||||
ItemStack stack,
|
||||
BlockState state,
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
Player player,
|
||||
InteractionHand hand,
|
||||
BlockHitResult hitResult
|
||||
) {
|
||||
if (stack.is(Items.BONE_MEAL)) {
|
||||
BlockPos origin = findOrigin(level, pos);
|
||||
if (origin != null && BigBushBlock.grantBonemealBonus(level, origin, level.getBlockState(origin), player, stack)) {
|
||||
return ItemInteractionResult.sidedSuccess(level.isClientSide);
|
||||
}
|
||||
}
|
||||
return super.useItemOn(stack, state, level, pos, player, hand, hitResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState state) {
|
||||
BlockPos origin = findOrigin(level, pos);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.craisinlord.antarchy.content.block;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.WeepingVinesBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public class OuranwoodVineBlock extends WeepingVinesBlock {
|
||||
public static final MapCodec<OuranwoodVineBlock> CODEC = simpleCodec(OuranwoodVineBlock::new);
|
||||
private static Block bodyBlock;
|
||||
|
||||
public OuranwoodVineBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapCodec<WeepingVinesBlock> codec() {
|
||||
return (MapCodec) CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Block getBodyBlock() {
|
||||
return bodyBlock;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
|
||||
return Shapes.block();
|
||||
}
|
||||
|
||||
public static void bindBodyBlock(Block block) {
|
||||
bodyBlock = block;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.craisinlord.antarchy.content.block;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.block.GrowingPlantHeadBlock;
|
||||
import net.minecraft.world.level.block.WeepingVinesBlock;
|
||||
import net.minecraft.world.level.block.WeepingVinesPlantBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public class OuranwoodVinePlantBlock extends WeepingVinesPlantBlock {
|
||||
public static final MapCodec<OuranwoodVinePlantBlock> CODEC = simpleCodec(OuranwoodVinePlantBlock::new);
|
||||
private static GrowingPlantHeadBlock headBlock;
|
||||
|
||||
public OuranwoodVinePlantBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapCodec<WeepingVinesPlantBlock> codec() {
|
||||
return (MapCodec) CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GrowingPlantHeadBlock getHeadBlock() {
|
||||
return headBlock;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
|
||||
return Shapes.block();
|
||||
}
|
||||
|
||||
public static void bindHeadBlock(WeepingVinesBlock block) {
|
||||
headBlock = block;
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,10 @@ public class AlphaMantisEntity extends MantisEntity {
|
||||
}
|
||||
|
||||
public static boolean checkAlphaMantisSpawnRules(EntityType<AlphaMantisEntity> entityType, ServerLevelAccessor level, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
|
||||
if (spawnReason == MobSpawnType.SPAWN_EGG || spawnReason == MobSpawnType.SPAWNER || spawnReason == MobSpawnType.COMMAND) {
|
||||
if (spawnReason == MobSpawnType.SPAWN_EGG
|
||||
|| spawnReason == MobSpawnType.SPAWNER
|
||||
|| spawnReason == MobSpawnType.TRIAL_SPAWNER
|
||||
|| spawnReason == MobSpawnType.COMMAND) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -73,14 +76,12 @@ public class AlphaMantisEntity extends MantisEntity {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only one alpha at a time in the area
|
||||
if (!level.getLevel().getEntitiesOfClass(AlphaMantisEntity.class, new AABB(pos).inflate(128.0D), Entity::isAlive).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean ignoreLightLevel = AntarchySettings.mantisIgnoreLightLevel();
|
||||
|
||||
// Daytime overworld spawns can't pass the vanilla darkness check, so no light gate here
|
||||
if (level.getLevel().isDay() && level.getBiome(pos).is(AntarchyTags.Biomes.MANTIS_OVERWORLD_SPAWN_BIOMES)) {
|
||||
return level.canSeeSky(pos);
|
||||
}
|
||||
@@ -181,7 +182,6 @@ public class AlphaMantisEntity extends MantisEntity {
|
||||
@Override
|
||||
public void setTarget(@Nullable LivingEntity target) {
|
||||
super.setTarget(target);
|
||||
// A boss that despawns mid-fight is no boss — stay loaded once engaged
|
||||
if (target != null) {
|
||||
this.setPersistenceRequired();
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import net.minecraft.world.entity.LivingEntity;
|
||||
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.MoveControl;
|
||||
import net.minecraft.world.entity.ai.control.SmoothSwimmingLookControl;
|
||||
import net.minecraft.world.entity.ai.control.SmoothSwimmingMoveControl;
|
||||
import net.minecraft.world.entity.ai.goal.BreathAirGoal;
|
||||
@@ -30,8 +31,10 @@ 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.WaterAvoidingRandomStrollGoal;
|
||||
import net.minecraft.world.entity.ai.goal.RandomSwimmingGoal;
|
||||
import net.minecraft.world.entity.ai.goal.TryFindWaterGoal;
|
||||
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
|
||||
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||
import net.minecraft.world.entity.ai.navigation.WaterBoundPathNavigation;
|
||||
import net.minecraft.world.entity.animal.Animal;
|
||||
@@ -39,6 +42,7 @@ import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.pathfinder.PathType;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import software.bernie.geckolib.animatable.GeoEntity;
|
||||
@@ -59,18 +63,29 @@ public class DorrieEntity extends Animal implements GeoEntity {
|
||||
SynchedEntityData.defineId(DorrieEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
private static final EntityDataAccessor<Integer> JUMP_CHARGE =
|
||||
SynchedEntityData.defineId(DorrieEntity.class, EntityDataSerializers.INT);
|
||||
private static final EntityDataAccessor<Integer> QUIRK_TICKS =
|
||||
SynchedEntityData.defineId(DorrieEntity.class, EntityDataSerializers.INT);
|
||||
|
||||
private static final int MAX_CHARGE_TICKS = 40;
|
||||
private static final float WATER_RIDE_SPEED = 1.26F;
|
||||
private static final float LAND_BEACH_SPEED = 0.04F;
|
||||
private static final int QUIRK_DURATION_TICKS = 65;
|
||||
private static final int MIN_QUIRK_COOLDOWN_TICKS = 180;
|
||||
private static final int MAX_QUIRK_COOLDOWN_TICKS = 320;
|
||||
private static final double MOVING_ANIM_THRESHOLD_SQ = 0.0025D;
|
||||
|
||||
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
|
||||
private static final RawAnimation IDLE_GROUND_ANIM = RawAnimation.begin().thenLoop("idle_ground");
|
||||
private static final RawAnimation IDLE_WATER_ANIM = RawAnimation.begin().thenLoop("idle_water");
|
||||
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
|
||||
private static final RawAnimation SWIM_ANIM = RawAnimation.begin().thenLoop("swim");
|
||||
private static final RawAnimation JUMP_START_ANIM = RawAnimation.begin().thenPlay("jump_1");
|
||||
private static final RawAnimation JUMP_LOOP_ANIM = RawAnimation.begin().thenLoop("jump_2");
|
||||
private static final RawAnimation JUMP_LAND_ANIM = RawAnimation.begin().thenPlay("jump_3");
|
||||
private static final RawAnimation JUMP_START_ANIM = RawAnimation.begin().thenPlay("jump_start");
|
||||
private static final RawAnimation JUMP_LOOP_ANIM = RawAnimation.begin().thenLoop("jump_idle");
|
||||
private static final RawAnimation JUMP_LAND_ANIM = RawAnimation.begin().thenPlay("jump_end");
|
||||
private static final RawAnimation QUIRK_ANIM = RawAnimation.begin().thenPlay("quirk");
|
||||
|
||||
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
|
||||
private final MoveControl landMoveControl;
|
||||
private final MoveControl waterMoveControl;
|
||||
|
||||
private static final int RIDES_TO_TAME = 4;
|
||||
private static final int BUCK_DELAY_TICKS = 60;
|
||||
@@ -83,11 +98,17 @@ public class DorrieEntity extends Animal implements GeoEntity {
|
||||
private int rideAttempts = 0;
|
||||
private int buckTimer = 0;
|
||||
private int swimForwardTicks = 0;
|
||||
private int quirkCooldownTicks = MIN_QUIRK_COOLDOWN_TICKS;
|
||||
private boolean usingLandNavigation = true;
|
||||
|
||||
public DorrieEntity(EntityType<? extends DorrieEntity> entityType, Level level) {
|
||||
super(entityType, level);
|
||||
this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 1.0F, true);
|
||||
this.landMoveControl = new MoveControl(this);
|
||||
this.waterMoveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 1.0F, true);
|
||||
this.moveControl = this.landMoveControl;
|
||||
this.lookControl = new SmoothSwimmingLookControl(this, 10);
|
||||
this.setPathfindingMalus(PathType.WATER, 0.0F);
|
||||
this.setPathfindingMalus(PathType.WATER_BORDER, 0.0F);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,11 +117,12 @@ public class DorrieEntity extends Animal implements GeoEntity {
|
||||
builder.define(HAS_SADDLE, false);
|
||||
builder.define(TAMED, false);
|
||||
builder.define(JUMP_CHARGE, 0);
|
||||
builder.define(QUIRK_TICKS, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PathNavigation createNavigation(Level level) {
|
||||
return new WaterBoundPathNavigation(this, level);
|
||||
return this.createLandNavigation(level);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,8 +132,9 @@ public class DorrieEntity extends Animal implements GeoEntity {
|
||||
this.goalSelector.addGoal(2, new TryFindWaterGoal(this));
|
||||
this.goalSelector.addGoal(3, new HuntCheepGoal(this));
|
||||
this.goalSelector.addGoal(4, new RandomSwimmingGoal(this, 1.0D, 10));
|
||||
this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 6.0F));
|
||||
this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
|
||||
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.45D, 60));
|
||||
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));
|
||||
this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
|
||||
}
|
||||
|
||||
public static AttributeSupplier.Builder createAttributes() {
|
||||
@@ -146,6 +169,14 @@ public class DorrieEntity extends Animal implements GeoEntity {
|
||||
this.entityData.set(JUMP_CHARGE, charge);
|
||||
}
|
||||
|
||||
public int getQuirkTicks() {
|
||||
return this.entityData.get(QUIRK_TICKS);
|
||||
}
|
||||
|
||||
private void setQuirkTicks(int ticks) {
|
||||
this.entityData.set(QUIRK_TICKS, ticks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSaveData(CompoundTag tag) {
|
||||
super.addAdditionalSaveData(tag);
|
||||
@@ -173,7 +204,18 @@ public class DorrieEntity extends Animal implements GeoEntity {
|
||||
return InteractionResult.sidedSuccess(this.level().isClientSide);
|
||||
}
|
||||
|
||||
if (this.hasSaddle() && stack.isEmpty() && !player.isPassengerOfSameVehicle(this)) {
|
||||
if (this.hasSaddle() && !player.isPassengerOfSameVehicle(this)) {
|
||||
if (!stack.isEmpty()) {
|
||||
return super.mobInteract(player, hand);
|
||||
}
|
||||
|
||||
if (!player.isShiftKeyDown()) {
|
||||
if (!this.level().isClientSide) {
|
||||
player.startRiding(this);
|
||||
}
|
||||
return InteractionResult.sidedSuccess(this.level().isClientSide);
|
||||
}
|
||||
|
||||
if (!this.level().isClientSide) {
|
||||
this.setSaddle(false);
|
||||
this.spawnAtLocation(new ItemStack(Items.SADDLE));
|
||||
@@ -193,13 +235,6 @@ public class DorrieEntity extends Animal implements GeoEntity {
|
||||
return InteractionResult.sidedSuccess(this.level().isClientSide);
|
||||
}
|
||||
|
||||
if (this.hasSaddle() && !player.isPassengerOfSameVehicle(this)) {
|
||||
if (!this.level().isClientSide) {
|
||||
player.startRiding(this);
|
||||
}
|
||||
return InteractionResult.sidedSuccess(this.level().isClientSide);
|
||||
}
|
||||
|
||||
return super.mobInteract(player, hand);
|
||||
}
|
||||
|
||||
@@ -234,6 +269,7 @@ public class DorrieEntity extends Animal implements GeoEntity {
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
this.updateNavigationMode();
|
||||
|
||||
if (this.getControllingPassenger() instanceof Player player) {
|
||||
this.setYRot(player.getYRot());
|
||||
@@ -275,6 +311,10 @@ public class DorrieEntity extends Animal implements GeoEntity {
|
||||
setJumpCharge((int) ((chargeTicks / (float) MAX_CHARGE_TICKS) * 100));
|
||||
}
|
||||
|
||||
if (!this.level().isClientSide) {
|
||||
this.tickQuirkAnimation();
|
||||
}
|
||||
|
||||
if (this.isInWater() && !isLeaping) {
|
||||
Vec3 mov = this.getDeltaMovement();
|
||||
double push = this.isUnderWater() ? 0.07D : 0.015D;
|
||||
@@ -384,13 +424,29 @@ public class DorrieEntity extends Animal implements GeoEntity {
|
||||
}
|
||||
|
||||
private PlayState mainAnimController(AnimationState<DorrieEntity> state) {
|
||||
state.getController().setAnimationSpeed(1.0D);
|
||||
|
||||
if (landAnimTicks > 0) {
|
||||
return state.setAndContinue(JUMP_LAND_ANIM);
|
||||
}
|
||||
if (isLeaping) {
|
||||
return state.setAndContinue(wasInAir ? JUMP_LOOP_ANIM : JUMP_START_ANIM);
|
||||
}
|
||||
return state.setAndContinue(state.isMoving() ? SWIM_ANIM : IDLE_ANIM);
|
||||
if (this.getQuirkTicks() > 0) {
|
||||
return state.setAndContinue(QUIRK_ANIM);
|
||||
}
|
||||
|
||||
boolean moving = this.isMovingForAnimation(state);
|
||||
if (this.isInWater() || this.isInLava()) {
|
||||
return state.setAndContinue(moving ? SWIM_ANIM : IDLE_WATER_ANIM);
|
||||
}
|
||||
|
||||
if (moving) {
|
||||
state.getController().setAnimationSpeed(0.75D);
|
||||
return state.setAndContinue(WALK_ANIM);
|
||||
}
|
||||
|
||||
return state.setAndContinue(IDLE_GROUND_ANIM);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -407,6 +463,78 @@ public class DorrieEntity extends Animal implements GeoEntity {
|
||||
return !stack.isEmpty() && stack.is(BuiltInRegistries.ITEM.getOptional(CHEEP_ITEM_ID).orElse(Items.AIR));
|
||||
}
|
||||
|
||||
private PathNavigation createLandNavigation(Level level) {
|
||||
GroundPathNavigation navigation = new GroundPathNavigation(this, level);
|
||||
navigation.setCanFloat(true);
|
||||
navigation.setCanOpenDoors(false);
|
||||
navigation.setCanPassDoors(true);
|
||||
return navigation;
|
||||
}
|
||||
|
||||
private PathNavigation createWaterNavigation(Level level) {
|
||||
return new WaterBoundPathNavigation(this, level);
|
||||
}
|
||||
|
||||
private void updateNavigationMode() {
|
||||
boolean shouldUseLandNavigation = !this.isInWater() && !this.isUnderWater() && !this.isInLava();
|
||||
if (this.navigation == null || this.usingLandNavigation != shouldUseLandNavigation) {
|
||||
this.usingLandNavigation = shouldUseLandNavigation;
|
||||
if (this.navigation != null) {
|
||||
this.navigation.stop();
|
||||
}
|
||||
this.navigation = shouldUseLandNavigation
|
||||
? this.createLandNavigation(this.level())
|
||||
: this.createWaterNavigation(this.level());
|
||||
this.moveControl = shouldUseLandNavigation ? this.landMoveControl : this.waterMoveControl;
|
||||
}
|
||||
}
|
||||
|
||||
private void tickQuirkAnimation() {
|
||||
if (this.getQuirkTicks() > 0) {
|
||||
if (this.canUseQuirkAnimation()) {
|
||||
this.setQuirkTicks(this.getQuirkTicks() - 1);
|
||||
} else {
|
||||
this.setQuirkTicks(0);
|
||||
this.quirkCooldownTicks = this.nextQuirkCooldown();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.quirkCooldownTicks > 0) {
|
||||
this.quirkCooldownTicks--;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.canUseQuirkAnimation()) {
|
||||
this.setQuirkTicks(QUIRK_DURATION_TICKS);
|
||||
this.quirkCooldownTicks = this.nextQuirkCooldown();
|
||||
}
|
||||
}
|
||||
|
||||
private int nextQuirkCooldown() {
|
||||
return MIN_QUIRK_COOLDOWN_TICKS + this.random.nextInt(MAX_QUIRK_COOLDOWN_TICKS - MIN_QUIRK_COOLDOWN_TICKS + 1);
|
||||
}
|
||||
|
||||
private boolean canUseQuirkAnimation() {
|
||||
return this.isInWater()
|
||||
&& this.getControllingPassenger() == null
|
||||
&& !this.isLeaping
|
||||
&& this.landAnimTicks <= 0
|
||||
&& !this.isCharging
|
||||
&& !this.isMovingForAnimation(null);
|
||||
}
|
||||
|
||||
private boolean isMovingForAnimation(@Nullable AnimationState<DorrieEntity> state) {
|
||||
if (state != null && state.isMoving()) {
|
||||
return true;
|
||||
}
|
||||
if (this.getControllingPassenger() instanceof Player player) {
|
||||
return Math.abs(player.zza) > 0.05F || Math.abs(player.xxa) > 0.05F;
|
||||
}
|
||||
return this.getDeltaMovement().horizontalDistanceSqr() > MOVING_ANIM_THRESHOLD_SQ
|
||||
|| this.getNavigation().isInProgress();
|
||||
}
|
||||
|
||||
private static class HuntCheepGoal extends Goal {
|
||||
private static final int COOLDOWN_TICKS = 600;
|
||||
private static final double HUNT_RANGE = 16.0D;
|
||||
|
||||
@@ -90,7 +90,10 @@ public class MantisEntity extends Monster implements GeoEntity {
|
||||
}
|
||||
|
||||
public static boolean canSpawn(EntityType<MantisEntity> entityType, ServerLevelAccessor level, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
|
||||
if (spawnReason == MobSpawnType.SPAWN_EGG || spawnReason == MobSpawnType.SPAWNER || spawnReason == MobSpawnType.COMMAND) {
|
||||
if (spawnReason == MobSpawnType.SPAWN_EGG
|
||||
|| spawnReason == MobSpawnType.SPAWNER
|
||||
|| spawnReason == MobSpawnType.TRIAL_SPAWNER
|
||||
|| spawnReason == MobSpawnType.COMMAND) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
package com.craisinlord.antarchy.content.portal;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class PermanentPortalManager {
|
||||
private static final int TELEPORT_DELAY_TICKS = 20;
|
||||
private static final int ACTIVATION_SEARCH_RADIUS = 2;
|
||||
private static final Map<UUID, PortalWarmup> WARMUPS = new HashMap<>();
|
||||
|
||||
private PermanentPortalManager() {
|
||||
}
|
||||
|
||||
public static void handleSacrifice(LivingEntity entity) {
|
||||
PermanentPortalType type = PermanentPortalType.fromSacrifice(entity);
|
||||
if (type == null || !(entity.level() instanceof ServerLevel serverLevel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PermanentPortalShape shape = findInactiveShape(serverLevel, entity.getBoundingBox(), type);
|
||||
if (shape == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
shape.fill(serverLevel);
|
||||
Vec3 center = shape.center();
|
||||
serverLevel.playSound(null, center.x, center.y, center.z, SoundEvents.END_PORTAL_SPAWN, SoundSource.BLOCKS, 0.9F, 0.9F);
|
||||
spawnActivationParticles(serverLevel, type, center);
|
||||
}
|
||||
|
||||
public static void handleEntityInsidePortal(Entity entity, PermanentPortalType type) {
|
||||
if (!(entity.level() instanceof ServerLevel serverLevel) || !type.isEnabled() || entity.isPassenger() || entity.isVehicle()) {
|
||||
return;
|
||||
}
|
||||
if (entity.isOnPortalCooldown()) {
|
||||
WARMUPS.remove(entity.getUUID());
|
||||
return;
|
||||
}
|
||||
|
||||
long gameTime = serverLevel.getGameTime();
|
||||
PortalWarmup warmup = WARMUPS.computeIfAbsent(entity.getUUID(), ignored -> new PortalWarmup());
|
||||
if (warmup.type != type || warmup.lastSeenTick < gameTime - 1) {
|
||||
warmup.ticks = 0;
|
||||
}
|
||||
warmup.type = type;
|
||||
if (warmup.lastSeenTick != gameTime) {
|
||||
warmup.ticks++;
|
||||
warmup.lastSeenTick = gameTime;
|
||||
}
|
||||
|
||||
if (warmup.ticks < TELEPORT_DELAY_TICKS) {
|
||||
return;
|
||||
}
|
||||
|
||||
WARMUPS.remove(entity.getUUID());
|
||||
PermanentPortalTeleporter.teleport(entity, type);
|
||||
if (entity.level() instanceof ServerLevel currentLevel) {
|
||||
currentLevel.playSound(null, entity.getX(), entity.getY(), entity.getZ(), SoundEvents.ENDERMAN_TELEPORT, SoundSource.PLAYERS, 0.8F, 1.0F);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPortalStillValid(ServerLevel level, BlockPos pos, PermanentPortalType type, Direction.Axis axis) {
|
||||
PermanentPortalShape shape = PermanentPortalShape.findActive(level, pos, type, axis);
|
||||
return shape != null && shape.contains(pos);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PermanentPortalShape findInactiveShape(ServerLevel level, AABB bounds, PermanentPortalType type) {
|
||||
int minX = (int) Math.floor(bounds.minX + 0.001D) - ACTIVATION_SEARCH_RADIUS;
|
||||
int minY = (int) Math.floor(bounds.minY + 0.001D) - ACTIVATION_SEARCH_RADIUS;
|
||||
int minZ = (int) Math.floor(bounds.minZ + 0.001D) - ACTIVATION_SEARCH_RADIUS;
|
||||
int maxX = (int) Math.floor(bounds.maxX - 0.001D) + ACTIVATION_SEARCH_RADIUS;
|
||||
int maxY = (int) Math.floor(bounds.maxY - 0.001D) + ACTIVATION_SEARCH_RADIUS;
|
||||
int maxZ = (int) Math.floor(bounds.maxZ - 0.001D) + ACTIVATION_SEARCH_RADIUS;
|
||||
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
for (int y = minY; y <= maxY; y++) {
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
BlockPos candidate = new BlockPos(x, y, z);
|
||||
PermanentPortalShape xShape = PermanentPortalShape.findInactive(level, candidate, type, Direction.Axis.X);
|
||||
if (xShape != null && xShape.intersects(bounds)) {
|
||||
return xShape;
|
||||
}
|
||||
PermanentPortalShape zShape = PermanentPortalShape.findInactive(level, candidate, type, Direction.Axis.Z);
|
||||
if (zShape != null && zShape.intersects(bounds)) {
|
||||
return zShape;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void spawnActivationParticles(ServerLevel level, PermanentPortalType type, Vec3 center) {
|
||||
switch (type) {
|
||||
case ELYTHIA -> {
|
||||
level.sendParticles(ParticleTypes.SPORE_BLOSSOM_AIR, center.x, center.y, center.z, 24, 0.8D, 1.2D, 0.8D, 0.02D);
|
||||
level.sendParticles(ParticleTypes.HAPPY_VILLAGER, center.x, center.y, center.z, 12, 0.6D, 1.0D, 0.6D, 0.02D);
|
||||
}
|
||||
case THORAXIS -> {
|
||||
level.sendParticles(ParticleTypes.PORTAL, center.x, center.y, center.z, 32, 0.8D, 1.2D, 0.8D, 0.15D);
|
||||
level.sendParticles(ParticleTypes.REVERSE_PORTAL, center.x, center.y, center.z, 10, 0.5D, 0.8D, 0.5D, 0.01D);
|
||||
}
|
||||
case CAVARYN -> {
|
||||
level.sendParticles(ParticleTypes.MYCELIUM, center.x, center.y, center.z, 24, 0.8D, 1.2D, 0.8D, 0.01D);
|
||||
level.sendParticles(ParticleTypes.FALLING_SPORE_BLOSSOM, center.x, center.y, center.z, 16, 0.6D, 1.0D, 0.6D, 0.01D);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PortalWarmup {
|
||||
private PermanentPortalType type;
|
||||
private long lastSeenTick;
|
||||
private int ticks;
|
||||
}
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
package com.craisinlord.antarchy.content.portal;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class PermanentPortalShape {
|
||||
public static final int MIN_WIDTH = 2;
|
||||
public static final int MIN_HEIGHT = 3;
|
||||
public static final int MAX_SIZE = 21;
|
||||
|
||||
private final PermanentPortalType type;
|
||||
private final Direction.Axis axis;
|
||||
private final BlockPos bottomLeft;
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
private PermanentPortalShape(PermanentPortalType type, Direction.Axis axis, BlockPos bottomLeft, int width, int height) {
|
||||
this.type = type;
|
||||
this.axis = axis;
|
||||
this.bottomLeft = bottomLeft;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PermanentPortalShape findInactive(BlockGetter level, BlockPos interiorPos, PermanentPortalType type, Direction.Axis axis) {
|
||||
return find(level, interiorPos, type, axis, state -> state.isAir());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PermanentPortalShape findActive(BlockGetter level, BlockPos interiorPos, PermanentPortalType type, Direction.Axis axis) {
|
||||
Block portalBlock = type.portalBlock();
|
||||
return find(level, interiorPos, type, axis, state -> state.is(portalBlock));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PermanentPortalShape find(
|
||||
BlockGetter level,
|
||||
BlockPos interiorPos,
|
||||
PermanentPortalType type,
|
||||
Direction.Axis axis,
|
||||
Predicate<BlockState> interiorPredicate
|
||||
) {
|
||||
if (axis == Direction.Axis.Y || !interiorPredicate.test(level.getBlockState(interiorPos))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Direction widthDirection = axis == Direction.Axis.X ? Direction.EAST : Direction.SOUTH;
|
||||
Direction leftDirection = widthDirection.getOpposite();
|
||||
BlockPos cursor = interiorPos;
|
||||
|
||||
for (int i = 0; i < MAX_SIZE && interiorPredicate.test(level.getBlockState(cursor.below())); i++) {
|
||||
cursor = cursor.below();
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_SIZE && interiorPredicate.test(level.getBlockState(cursor.relative(leftDirection))); i++) {
|
||||
cursor = cursor.relative(leftDirection);
|
||||
}
|
||||
|
||||
int width = measureWidth(level, cursor, widthDirection, type.frameTag(), interiorPredicate);
|
||||
if (width < MIN_WIDTH) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int height = measureHeight(level, cursor, widthDirection, width, type.frameTag(), interiorPredicate);
|
||||
if (height < MIN_HEIGHT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PermanentPortalShape(type, axis, cursor.immutable(), width, height);
|
||||
}
|
||||
|
||||
private static int measureWidth(
|
||||
BlockGetter level,
|
||||
BlockPos bottomLeft,
|
||||
Direction widthDirection,
|
||||
TagKey<Block> frameTag,
|
||||
Predicate<BlockState> interiorPredicate
|
||||
) {
|
||||
if (!level.getBlockState(bottomLeft.relative(widthDirection.getOpposite())).is(frameTag)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int width = 0;
|
||||
while (width < MAX_SIZE && interiorPredicate.test(level.getBlockState(bottomLeft.relative(widthDirection, width)))) {
|
||||
if (!level.getBlockState(bottomLeft.relative(widthDirection, width).below()).is(frameTag)) {
|
||||
return 0;
|
||||
}
|
||||
width++;
|
||||
}
|
||||
|
||||
if (width < MIN_WIDTH || width > MAX_SIZE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return level.getBlockState(bottomLeft.relative(widthDirection, width)).is(frameTag) ? width : 0;
|
||||
}
|
||||
|
||||
private static int measureHeight(
|
||||
BlockGetter level,
|
||||
BlockPos bottomLeft,
|
||||
Direction widthDirection,
|
||||
int width,
|
||||
TagKey<Block> frameTag,
|
||||
Predicate<BlockState> interiorPredicate
|
||||
) {
|
||||
BlockPos leftFrameBase = bottomLeft.relative(widthDirection.getOpposite());
|
||||
BlockPos rightFrameBase = bottomLeft.relative(widthDirection, width);
|
||||
|
||||
for (int height = 0; height < MAX_SIZE; height++) {
|
||||
if (!level.getBlockState(leftFrameBase.above(height)).is(frameTag)
|
||||
|| !level.getBlockState(rightFrameBase.above(height)).is(frameTag)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (int x = 0; x < width; x++) {
|
||||
BlockPos interiorPos = bottomLeft.relative(widthDirection, x).above(height);
|
||||
if (!interiorPredicate.test(level.getBlockState(interiorPos))) {
|
||||
if (height < MIN_HEIGHT) {
|
||||
return 0;
|
||||
}
|
||||
return hasTopFrame(level, bottomLeft, widthDirection, width, height, frameTag) ? height : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasTopFrame(level, bottomLeft, widthDirection, width, MAX_SIZE, frameTag) ? MAX_SIZE : 0;
|
||||
}
|
||||
|
||||
private static boolean hasTopFrame(
|
||||
BlockGetter level,
|
||||
BlockPos bottomLeft,
|
||||
Direction widthDirection,
|
||||
int width,
|
||||
int topY,
|
||||
TagKey<Block> frameTag
|
||||
) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
if (!level.getBlockState(bottomLeft.relative(widthDirection, x).above(topY)).is(frameTag)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void fill(ServerLevel level) {
|
||||
BlockState portalState = this.type.portalBlock().defaultBlockState().setValue(BlockStateProperties.HORIZONTAL_AXIS, this.axis);
|
||||
this.forEachInterior(pos -> level.setBlock(pos, portalState, Block.UPDATE_ALL));
|
||||
}
|
||||
|
||||
public boolean contains(BlockPos pos) {
|
||||
if (pos.getY() < this.bottomLeft.getY() || pos.getY() >= this.bottomLeft.getY() + this.height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.axis == Direction.Axis.X) {
|
||||
return pos.getZ() == this.bottomLeft.getZ()
|
||||
&& pos.getX() >= this.bottomLeft.getX()
|
||||
&& pos.getX() < this.bottomLeft.getX() + this.width;
|
||||
}
|
||||
|
||||
return pos.getX() == this.bottomLeft.getX()
|
||||
&& pos.getZ() >= this.bottomLeft.getZ()
|
||||
&& pos.getZ() < this.bottomLeft.getZ() + this.width;
|
||||
}
|
||||
|
||||
public boolean intersects(AABB box) {
|
||||
final boolean[] intersects = {false};
|
||||
this.forEachInterior(pos -> {
|
||||
if (box.intersects(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1.0D, pos.getY() + 1.0D, pos.getZ() + 1.0D)) {
|
||||
intersects[0] = true;
|
||||
}
|
||||
});
|
||||
return intersects[0];
|
||||
}
|
||||
|
||||
public void forEachInterior(Consumer<BlockPos> consumer) {
|
||||
Direction widthDirection = this.axis == Direction.Axis.X ? Direction.EAST : Direction.SOUTH;
|
||||
for (int y = 0; y < this.height; y++) {
|
||||
for (int x = 0; x < this.width; x++) {
|
||||
consumer.accept(this.bottomLeft.relative(widthDirection, x).above(y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vec3 center() {
|
||||
if (this.axis == Direction.Axis.X) {
|
||||
return new Vec3(this.bottomLeft.getX() + (this.width / 2.0D), this.bottomLeft.getY() + (this.height / 2.0D), this.bottomLeft.getZ() + 0.5D);
|
||||
}
|
||||
return new Vec3(this.bottomLeft.getX() + 0.5D, this.bottomLeft.getY() + (this.height / 2.0D), this.bottomLeft.getZ() + (this.width / 2.0D));
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
package com.craisinlord.antarchy.content.portal;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.vehicle.DismountHelper;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.portal.DimensionTransition;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class PermanentPortalTeleporter {
|
||||
private static final int SEARCH_RADIUS = 8;
|
||||
private static final int VERTICAL_SEARCH = 24;
|
||||
|
||||
private PermanentPortalTeleporter() {
|
||||
}
|
||||
|
||||
public static void teleport(Entity entity, PermanentPortalType type) {
|
||||
if (!(entity.level() instanceof ServerLevel sourceLevel) || !entity.isAlive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerLevel destination = resolveDestination(sourceLevel, type);
|
||||
if (destination == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vec3 arrival = findArrivalPosition(entity, destination, entity.blockPosition(), type.platformBlock().defaultBlockState());
|
||||
Entity movedEntity = moveEntity(entity, destination, arrival);
|
||||
if (movedEntity != null) {
|
||||
movedEntity.setPortalCooldown();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ServerLevel resolveDestination(ServerLevel sourceLevel, PermanentPortalType type) {
|
||||
if (sourceLevel.dimension() == type.primaryDimension()) {
|
||||
return sourceLevel.getServer().overworld();
|
||||
}
|
||||
return sourceLevel.getServer().getLevel(type.primaryDimension());
|
||||
}
|
||||
|
||||
private static Vec3 findArrivalPosition(Entity entity, ServerLevel destination, BlockPos preferredPos, BlockState platformState) {
|
||||
Vec3 safe = findSafeArrivalPosition(entity, destination, preferredPos);
|
||||
return safe != null ? safe : createFallbackPlatform(destination, preferredPos, platformState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Vec3 findSafeArrivalPosition(Entity entity, ServerLevel destination, BlockPos preferredPos) {
|
||||
Set<BlockPos> candidates = new LinkedHashSet<>();
|
||||
addCandidate(candidates, preferredPos);
|
||||
|
||||
for (int radius = 0; radius <= SEARCH_RADIUS; radius++) {
|
||||
for (int xOff = -radius; xOff <= radius; xOff++) {
|
||||
for (int zOff = -radius; zOff <= radius; zOff++) {
|
||||
if (radius > 0 && Math.abs(xOff) != radius && Math.abs(zOff) != radius) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockPos horizontalPos = preferredPos.offset(xOff, 0, zOff);
|
||||
addCandidate(candidates, destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, horizontalPos));
|
||||
addCandidate(candidates, destination.getHeightmapPos(Heightmap.Types.WORLD_SURFACE, horizontalPos));
|
||||
for (int yOff = 0; yOff <= VERTICAL_SEARCH; yOff++) {
|
||||
addCandidate(candidates, horizontalPos.above(yOff));
|
||||
if (yOff > 0) {
|
||||
addCandidate(candidates, horizontalPos.below(yOff));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (BlockPos candidate : candidates) {
|
||||
Vec3 safe = tryFindSafePosition(entity, destination, candidate);
|
||||
if (safe != null) {
|
||||
return safe;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void addCandidate(Set<BlockPos> candidates, @Nullable BlockPos pos) {
|
||||
if (pos == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
candidates.add(pos);
|
||||
candidates.add(pos.above());
|
||||
if (pos.getY() > Integer.MIN_VALUE) {
|
||||
candidates.add(pos.below());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Vec3 tryFindSafePosition(Entity entity, ServerLevel destination, BlockPos candidate) {
|
||||
Vec3 safe = DismountHelper.findSafeDismountLocation(entity.getType(), destination, candidate, true);
|
||||
if (safe != null) {
|
||||
return safe;
|
||||
}
|
||||
|
||||
for (Direction direction : Direction.Plane.HORIZONTAL) {
|
||||
safe = DismountHelper.findSafeDismountLocation(entity.getType(), destination, candidate.relative(direction), true);
|
||||
if (safe != null) {
|
||||
return safe;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Vec3 createFallbackPlatform(ServerLevel destination, BlockPos preferredPos, BlockState platformState) {
|
||||
int minY = destination.getMinBuildHeight() + 2;
|
||||
int maxY = destination.getMaxBuildHeight() - 4;
|
||||
BlockPos center = new BlockPos(preferredPos.getX(), Mth.clamp(preferredPos.getY(), minY, maxY), preferredPos.getZ());
|
||||
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
destination.setBlock(center.offset(dx, -1, dz), platformState, Block.UPDATE_ALL);
|
||||
destination.removeBlock(center.offset(dx, 0, dz), false);
|
||||
destination.removeBlock(center.offset(dx, 1, dz), false);
|
||||
}
|
||||
}
|
||||
|
||||
return Vec3.atBottomCenterOf(center);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Entity moveEntity(Entity entity, ServerLevel destination, Vec3 destinationPos) {
|
||||
if (entity instanceof ServerPlayer player) {
|
||||
player.teleportTo(destination, destinationPos.x, destinationPos.y, destinationPos.z, player.getYRot(), player.getXRot());
|
||||
return player;
|
||||
}
|
||||
|
||||
if (entity.level() == destination) {
|
||||
entity.teleportTo(destinationPos.x, destinationPos.y, destinationPos.z);
|
||||
return entity;
|
||||
}
|
||||
|
||||
return entity.changeDimension(new DimensionTransition(destination, destinationPos, Vec3.ZERO, entity.getYRot(), entity.getXRot(), DimensionTransition.DO_NOTHING));
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
package com.craisinlord.antarchy.content.portal;
|
||||
|
||||
import com.craisinlord.antarchy.content.AntarchyTags;
|
||||
import com.craisinlord.antarchy.config.AntarchySettings;
|
||||
import com.craisinlord.antarchy.content.entity.ant.BrownAntEntity;
|
||||
import com.craisinlord.antarchy.content.entity.ant.RedAntEntity;
|
||||
import com.craisinlord.antarchy.content.entity.ant.TermiteEntity;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public enum PermanentPortalType {
|
||||
ELYTHIA("elythia", AntarchySettings::brownAntDestinationDimension, AntarchySettings::elythiaPortalEnabled, AntarchyTags.Blocks.ELYTHIA_PORTAL_FRAMES) {
|
||||
@Override
|
||||
boolean matchesSacrifice(LivingEntity entity) {
|
||||
return entity instanceof BrownAntEntity;
|
||||
}
|
||||
},
|
||||
THORAXIS("thoraxis", AntarchySettings::redAntDestinationDimension, AntarchySettings::thoraxisPortalEnabled, AntarchyTags.Blocks.THORAXIS_PORTAL_FRAMES) {
|
||||
@Override
|
||||
boolean matchesSacrifice(LivingEntity entity) {
|
||||
return entity instanceof RedAntEntity;
|
||||
}
|
||||
},
|
||||
CAVARYN("cavaryn", AntarchySettings::termiteDestinationDimension, AntarchySettings::cavarynPortalEnabled, AntarchyTags.Blocks.CAVARYN_PORTAL_FRAMES) {
|
||||
@Override
|
||||
boolean matchesSacrifice(LivingEntity entity) {
|
||||
return entity instanceof TermiteEntity;
|
||||
}
|
||||
};
|
||||
|
||||
private final String id;
|
||||
private final Supplier<ResourceKey<Level>> primaryDimensionSupplier;
|
||||
private final Supplier<Boolean> enabledSupplier;
|
||||
private final TagKey<Block> frameTag;
|
||||
private Supplier<Block> platformBlockSupplier;
|
||||
private Supplier<Block> portalBlockSupplier;
|
||||
|
||||
PermanentPortalType(String id, Supplier<ResourceKey<Level>> primaryDimensionSupplier, Supplier<Boolean> enabledSupplier, TagKey<Block> frameTag) {
|
||||
this.id = id;
|
||||
this.primaryDimensionSupplier = primaryDimensionSupplier;
|
||||
this.enabledSupplier = enabledSupplier;
|
||||
this.frameTag = frameTag;
|
||||
}
|
||||
|
||||
public static void bindBlocks(
|
||||
Supplier<Block> elythiaPlatform,
|
||||
Supplier<Block> elythiaPortal,
|
||||
Supplier<Block> thoraxisPlatform,
|
||||
Supplier<Block> thoraxisPortal,
|
||||
Supplier<Block> cavarynPlatform,
|
||||
Supplier<Block> cavarynPortal
|
||||
) {
|
||||
ELYTHIA.bind(elythiaPlatform, elythiaPortal);
|
||||
THORAXIS.bind(thoraxisPlatform, thoraxisPortal);
|
||||
CAVARYN.bind(cavarynPlatform, cavarynPortal);
|
||||
}
|
||||
|
||||
public static boolean portalsEnabled() {
|
||||
return AntarchySettings.permanentPortalsEnabled();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PermanentPortalType fromSacrifice(LivingEntity entity) {
|
||||
if (!portalsEnabled()) {
|
||||
return null;
|
||||
}
|
||||
for (PermanentPortalType type : values()) {
|
||||
if (type.isEnabled() && type.matchesSacrifice(entity)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void bind(Supplier<Block> platformBlockSupplier, Supplier<Block> portalBlockSupplier) {
|
||||
this.platformBlockSupplier = Objects.requireNonNull(platformBlockSupplier);
|
||||
this.portalBlockSupplier = Objects.requireNonNull(portalBlockSupplier);
|
||||
}
|
||||
|
||||
public String id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return portalsEnabled() && this.enabledSupplier.get();
|
||||
}
|
||||
|
||||
public ResourceKey<Level> primaryDimension() {
|
||||
return this.primaryDimensionSupplier.get();
|
||||
}
|
||||
|
||||
public TagKey<Block> frameTag() {
|
||||
return this.frameTag;
|
||||
}
|
||||
|
||||
public Block platformBlock() {
|
||||
return Objects.requireNonNull(this.platformBlockSupplier, "Platform block supplier not bound for " + this.id).get();
|
||||
}
|
||||
|
||||
public Block portalBlock() {
|
||||
return Objects.requireNonNull(this.portalBlockSupplier, "Portal block supplier not bound for " + this.id).get();
|
||||
}
|
||||
|
||||
abstract boolean matchesSacrifice(LivingEntity entity);
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.craisinlord.antarchy.content.worldgen;
|
||||
|
||||
import com.craisinlord.antarchy.content.block.BigBushBlock;
|
||||
import com.mojang.serialization.Codec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.levelgen.feature.Feature;
|
||||
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
|
||||
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Places a {@link BigBushBlock} at a fixed growth stage (age), used for sparse overworld generation. */
|
||||
public class BigBushWorldgenFeature extends Feature<NoneFeatureConfiguration> {
|
||||
private final int age;
|
||||
|
||||
public BigBushWorldgenFeature(Codec<NoneFeatureConfiguration> codec, int age) {
|
||||
super(codec);
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean place(FeaturePlaceContext<NoneFeatureConfiguration> context) {
|
||||
WorldGenLevel level = context.level();
|
||||
BlockPos origin = context.origin();
|
||||
RandomSource random = context.random();
|
||||
|
||||
Block block = BuiltInRegistries.BLOCK.get(ResourceLocation.fromNamespaceAndPath("antarchy", "big_bush"));
|
||||
if (!(block instanceof BigBushBlock)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Direction> facings = new ArrayList<>(Direction.Plane.HORIZONTAL.stream().toList());
|
||||
for (int i = facings.size() - 1; i > 0; i--) {
|
||||
Direction swap = facings.set(random.nextInt(i + 1), facings.get(i));
|
||||
facings.set(i, swap);
|
||||
}
|
||||
|
||||
for (Direction facing : facings) {
|
||||
BlockState grown = block.defaultBlockState().setValue(BigBushBlock.AGE, age).setValue(BigBushBlock.FACING, facing);
|
||||
if (canPlace(level, origin, grown)) {
|
||||
BigBushBlock.placeBushBlocks(level, origin, grown, Block.UPDATE_ALL);
|
||||
return true;
|
||||
}
|
||||
if (age == 0) {
|
||||
// Facing is irrelevant at age 0 (single block); no need to try every rotation.
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean canPlace(WorldGenLevel level, BlockPos origin, BlockState grown) {
|
||||
if (!BigBushBlock.hasFullSupport(grown, level, origin)) {
|
||||
return false;
|
||||
}
|
||||
List<BlockPos> positions = new ArrayList<>(BigBushBlock.partPositions(grown, origin));
|
||||
positions.add(origin);
|
||||
for (BlockPos pos : positions) {
|
||||
if (!level.getBlockState(pos).canBeReplaced()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.craisinlord.antarchy.content.worldgen;
|
||||
|
||||
import com.craisinlord.antarchy.content.block.VeryTallGrassBlock;
|
||||
import com.mojang.serialization.Codec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.levelgen.feature.Feature;
|
||||
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
|
||||
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
|
||||
|
||||
/** Places a single 3-tall {@link VeryTallGrassBlock} column, used for sparse overworld generation. */
|
||||
public class VeryTallGrassWorldgenFeature extends Feature<NoneFeatureConfiguration> {
|
||||
public VeryTallGrassWorldgenFeature(Codec<NoneFeatureConfiguration> codec) {
|
||||
super(codec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean place(FeaturePlaceContext<NoneFeatureConfiguration> context) {
|
||||
WorldGenLevel level = context.level();
|
||||
BlockPos pos = context.origin();
|
||||
if (!level.getBlockState(pos).isAir()
|
||||
|| !level.getBlockState(pos.above()).isAir()
|
||||
|| !level.getBlockState(pos.above(2)).isAir()) {
|
||||
return false;
|
||||
}
|
||||
BlockPos below = pos.below();
|
||||
if (!level.getBlockState(below).isFaceSturdy(level, below, Direction.UP)) {
|
||||
return false;
|
||||
}
|
||||
VeryTallGrassBlock.placeAt(level, pos, Block.UPDATE_ALL);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.craisinlord.antarchy.mixins;
|
||||
|
||||
import com.craisinlord.antarchy.content.block.TorchflowerBushBlock;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.item.BoneMealItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.DoublePlantBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(BoneMealItem.class)
|
||||
public abstract class BoneMealItemTorchflowerBushMixin {
|
||||
@Inject(method = "useOn", at = @At("HEAD"), cancellable = true)
|
||||
private void antarchy$growTorchflowerBush(UseOnContext context, CallbackInfoReturnable<InteractionResult> cir) {
|
||||
Level level = context.getLevel();
|
||||
BlockPos pos = context.getClickedPos();
|
||||
BlockState clickedState = level.getBlockState(pos);
|
||||
if (!clickedState.is(Blocks.TORCHFLOWER)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Block block = BuiltInRegistries.BLOCK.get(ResourceLocation.fromNamespaceAndPath("antarchy", "torchflower_bush"));
|
||||
if (!(block instanceof TorchflowerBushBlock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BlockState lowerState = block.defaultBlockState();
|
||||
if (!level.getBlockState(pos.above()).canBeReplaced() || !lowerState.canSurvive(level, pos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!level.isClientSide) {
|
||||
DoublePlantBlock.placeAt(level, lowerState, pos, Block.UPDATE_ALL);
|
||||
if (context.getPlayer() == null || !context.getPlayer().getAbilities().instabuild) {
|
||||
ItemStack stack = context.getItemInHand();
|
||||
stack.shrink(1);
|
||||
}
|
||||
level.levelEvent(1505, pos, 15);
|
||||
}
|
||||
|
||||
cir.setReturnValue(InteractionResult.sidedSuccess(level.isClientSide));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user