package net.minecraft.world.level.block.state; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.function.ToIntFunction; import java.util.stream.Stream; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; import net.minecraft.core.registries.Registries; import net.minecraft.network.protocol.game.DebugPackets; import net.minecraft.resources.DependantName; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.FluidTags; import net.minecraft.tags.TagKey; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.MenuProvider; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.Projectile; import net.minecraft.world.flag.FeatureElement; import net.minecraft.world.flag.FeatureFlag; import net.minecraft.world.flag.FeatureFlagSet; import net.minecraft.world.flag.FeatureFlags; import net.minecraft.world.item.DyeColor; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.EmptyBlockGetter; import net.minecraft.world.level.Explosion; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.ScheduledTickAccess; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.Mirror; import net.minecraft.world.level.block.RenderShape; import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.block.SoundType; import net.minecraft.world.level.block.SupportType; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase.Cache; import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; import net.minecraft.world.level.block.state.properties.Property; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.material.Fluids; import net.minecraft.world.level.material.MapColor; import net.minecraft.world.level.material.PushReaction; import net.minecraft.world.level.pathfinder.PathComputationType; import net.minecraft.world.level.redstone.Orientation; import net.minecraft.world.level.storage.loot.LootParams; import net.minecraft.world.level.storage.loot.LootTable; import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; import net.minecraft.world.level.storage.loot.parameters.LootContextParams; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; import org.jetbrains.annotations.Nullable; public abstract class BlockBehaviour implements FeatureElement { protected static final Direction[] UPDATE_SHAPE_ORDER = new Direction[]{ Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP }; protected final boolean hasCollision; protected final float explosionResistance; /** * Whether this blocks receives random ticks */ protected final boolean isRandomlyTicking; protected final SoundType soundType; /** * Determines how much velocity is maintained while moving on top of this block */ protected final float friction; protected final float speedFactor; protected final float jumpFactor; protected final boolean dynamicShape; protected final FeatureFlagSet requiredFeatures; protected final BlockBehaviour.Properties properties; protected final Optional> drops; protected final String descriptionId; public BlockBehaviour(BlockBehaviour.Properties properties) { this.hasCollision = properties.hasCollision; this.drops = properties.effectiveDrops(); this.descriptionId = properties.effectiveDescriptionId(); this.explosionResistance = properties.explosionResistance; this.isRandomlyTicking = properties.isRandomlyTicking; this.soundType = properties.soundType; this.friction = properties.friction; this.speedFactor = properties.speedFactor; this.jumpFactor = properties.jumpFactor; this.dynamicShape = properties.dynamicShape; this.requiredFeatures = properties.requiredFeatures; this.properties = properties; } public BlockBehaviour.Properties properties() { return this.properties; } protected abstract MapCodec codec(); protected static RecordCodecBuilder propertiesCodec() { return BlockBehaviour.Properties.CODEC.fieldOf("properties").forGetter(BlockBehaviour::properties); } public static MapCodec simpleCodec(Function factory) { return RecordCodecBuilder.mapCodec(instance -> instance.group(propertiesCodec()).apply(instance, factory)); } /** * Updates the shapes of indirect neighbors of this block. This method is analogous to * {@link net.minecraft.world.level.block.state.BlockBehaviour$BlockStateBase#updateNeighbourShapes} * but where that method affects the 6 direct neighbors of this block, this method affects * the indirect neighbors, if any. * *

* Currently the only implementation of this method is {@link net.minecraft.world.level.block.RedStoneWireBlock#updateIndirectNeighbourShapes} * where it is used to validate diagonal connections of redstone wire blocks. */ protected void updateIndirectNeighbourShapes(BlockState state, LevelAccessor level, BlockPos pos, int flags, int recursionLeft) { } protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) { switch (pathComputationType) { case LAND: return !state.isCollisionShapeFullBlock(EmptyBlockGetter.INSTANCE, BlockPos.ZERO); case WATER: return state.getFluidState().is(FluidTags.WATER); case AIR: return !state.isCollisionShapeFullBlock(EmptyBlockGetter.INSTANCE, BlockPos.ZERO); default: return false; } } protected BlockState updateShape( BlockState blockState, LevelReader levelReader, ScheduledTickAccess scheduledTickAccess, BlockPos blockPos, Direction direction, BlockPos blockPos2, BlockState blockState2, RandomSource randomSource ) { return blockState; } protected boolean skipRendering(BlockState state, BlockState adjacentState, Direction direction) { return false; } protected void neighborChanged(BlockState blockState, Level level, BlockPos blockPos, Block block, @Nullable Orientation orientation, boolean bl) { } protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) { } protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) { if (state.hasBlockEntity() && !state.is(newState.getBlock())) { level.removeBlockEntity(pos); } } protected void onExplosionHit( BlockState blockState, ServerLevel serverLevel, BlockPos blockPos, Explosion explosion, BiConsumer biConsumer ) { if (!blockState.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK) { Block block = blockState.getBlock(); boolean bl = explosion.getIndirectSourceEntity() instanceof Player; if (block.dropFromExplosion(explosion)) { BlockEntity blockEntity = blockState.hasBlockEntity() ? serverLevel.getBlockEntity(blockPos) : null; LootParams.Builder builder = new LootParams.Builder(serverLevel) .withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(blockPos)) .withParameter(LootContextParams.TOOL, ItemStack.EMPTY) .withOptionalParameter(LootContextParams.BLOCK_ENTITY, blockEntity) .withOptionalParameter(LootContextParams.THIS_ENTITY, explosion.getDirectSourceEntity()); if (explosion.getBlockInteraction() == Explosion.BlockInteraction.DESTROY_WITH_DECAY) { builder.withParameter(LootContextParams.EXPLOSION_RADIUS, explosion.radius()); } blockState.spawnAfterBreak(serverLevel, blockPos, ItemStack.EMPTY, bl); blockState.getDrops(builder).forEach(itemStack -> biConsumer.accept(itemStack, blockPos)); } serverLevel.setBlock(blockPos, Blocks.AIR.defaultBlockState(), 3); block.wasExploded(serverLevel, blockPos, explosion); } } protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { return InteractionResult.PASS; } protected InteractionResult useItemOn( ItemStack itemStack, BlockState blockState, Level level, BlockPos blockPos, Player player, InteractionHand interactionHand, BlockHitResult blockHitResult ) { return InteractionResult.TRY_WITH_EMPTY_HAND; } /** * Called on server when {@link net.minecraft.world.level.Level#blockEvent} is called. If server returns true, then also called on the client. On the Server, this may perform additional changes to the world, like pistons replacing the block with an extended base. On the client, the update may involve replacing block entities or effects such as sounds or particles */ protected boolean triggerEvent(BlockState state, Level level, BlockPos pos, int id, int param) { return false; } /** * The type of render function called. MODEL for mixed tesr and static model, MODELBLOCK_ANIMATED for TESR-only, LIQUID for vanilla liquids, INVISIBLE to skip all rendering */ protected RenderShape getRenderShape(BlockState state) { return RenderShape.MODEL; } protected boolean useShapeForLightOcclusion(BlockState state) { return false; } /** * Returns whether this block is capable of emitting redstone signals. */ protected boolean isSignalSource(BlockState state) { return false; } protected FluidState getFluidState(BlockState state) { return Fluids.EMPTY.defaultFluidState(); } protected boolean hasAnalogOutputSignal(BlockState state) { return false; } protected float getMaxHorizontalOffset() { return 0.25F; } protected float getMaxVerticalOffset() { return 0.2F; } @Override public FeatureFlagSet requiredFeatures() { return this.requiredFeatures; } /** * Returns the blockstate with the given rotation from the passed blockstate. If inapplicable, returns the passed blockstate. */ protected BlockState rotate(BlockState state, Rotation rotation) { return state; } /** * Returns the blockstate with the given mirror of the passed blockstate. If inapplicable, returns the passed blockstate. */ protected BlockState mirror(BlockState state, Mirror mirror) { return state; } protected boolean canBeReplaced(BlockState state, BlockPlaceContext useContext) { return state.canBeReplaced() && (useContext.getItemInHand().isEmpty() || !useContext.getItemInHand().is(this.asItem())); } protected boolean canBeReplaced(BlockState state, Fluid fluid) { return state.canBeReplaced() || !state.isSolid(); } protected List getDrops(BlockState state, LootParams.Builder params) { if (this.drops.isEmpty()) { return Collections.emptyList(); } else { LootParams lootParams = params.withParameter(LootContextParams.BLOCK_STATE, state).create(LootContextParamSets.BLOCK); ServerLevel serverLevel = lootParams.getLevel(); LootTable lootTable = serverLevel.getServer().reloadableRegistries().getLootTable((ResourceKey)this.drops.get()); return lootTable.getRandomItems(lootParams); } } /** * Return a random long to be passed to {@link net.minecraft.client.resources.model.BakedModel#getQuads}, used for random model rotations */ protected long getSeed(BlockState state, BlockPos pos) { return Mth.getSeed(pos); } protected VoxelShape getOcclusionShape(BlockState blockState) { return blockState.getShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO); } protected VoxelShape getBlockSupportShape(BlockState state, BlockGetter level, BlockPos pos) { return this.getCollisionShape(state, level, pos, CollisionContext.empty()); } protected VoxelShape getInteractionShape(BlockState state, BlockGetter level, BlockPos pos) { return Shapes.empty(); } protected int getLightBlock(BlockState blockState) { if (blockState.isSolidRender()) { return 15; } else { return blockState.propagatesSkylightDown() ? 0 : 1; } } @Nullable protected MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) { return null; } protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) { return true; } protected float getShadeBrightness(BlockState state, BlockGetter level, BlockPos pos) { return state.isCollisionShapeFullBlock(level, pos) ? 0.2F : 1.0F; } /** * Returns the analog signal this block emits. This is the signal a comparator can read from it. */ protected int getAnalogOutputSignal(BlockState state, Level level, BlockPos pos) { return 0; } protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { return Shapes.block(); } protected VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { return this.hasCollision ? state.getShape(level, pos) : Shapes.empty(); } protected boolean isCollisionShapeFullBlock(BlockState state, BlockGetter level, BlockPos pos) { return Block.isShapeFullBlock(state.getCollisionShape(level, pos)); } protected VoxelShape getVisualShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { return this.getCollisionShape(state, level, pos, context); } /** * Performs a random tick on a block. */ protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { } protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { } /** * Get the hardness of this Block relative to the ability of the given player */ protected float getDestroyProgress(BlockState state, Player player, BlockGetter level, BlockPos pos) { float f = state.getDestroySpeed(level, pos); if (f == -1.0F) { return 0.0F; } else { int i = player.hasCorrectToolForDrops(state) ? 30 : 100; return player.getDestroySpeed(state) / f / i; } } /** * Perform side-effects from block dropping, such as creating silverfish */ protected void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) { } protected void attack(BlockState state, Level level, BlockPos pos, Player player) { } /** * Returns the signal this block emits in the given direction. * *

* NOTE: directions in redstone signal related methods are backwards, so this method * checks for the signal emitted in the opposite direction of the one given. */ protected int getSignal(BlockState state, BlockGetter level, BlockPos pos, Direction direction) { return 0; } protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) { } protected VoxelShape getEntityInsideCollisionShape(BlockState blockState, Level level, BlockPos blockPos) { return Shapes.block(); } /** * Returns the direct signal this block emits in the given direction. * *

* NOTE: directions in redstone signal related methods are backwards, so this method * checks for the signal emitted in the opposite direction of the one given. */ protected int getDirectSignal(BlockState state, BlockGetter level, BlockPos pos, Direction direction) { return 0; } public final Optional> getLootTable() { return this.drops; } public final String getDescriptionId() { return this.descriptionId; } protected void onProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile) { } protected boolean propagatesSkylightDown(BlockState blockState) { return !Block.isShapeFullBlock(blockState.getShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)) && blockState.getFluidState().isEmpty(); } protected boolean isRandomlyTicking(BlockState state) { return this.isRandomlyTicking; } protected SoundType getSoundType(BlockState state) { return this.soundType; } public abstract Item asItem(); protected abstract Block asBlock(); public MapColor defaultMapColor() { return (MapColor)this.properties.mapColor.apply(this.asBlock().defaultBlockState()); } public float defaultDestroyTime() { return this.properties.destroyTime; } public abstract static class BlockStateBase extends StateHolder { private static final Direction[] DIRECTIONS = Direction.values(); private static final VoxelShape[] EMPTY_OCCLUSION_SHAPES = Util.make( new VoxelShape[DIRECTIONS.length], voxelShapes -> Arrays.fill(voxelShapes, Shapes.empty()) ); private static final VoxelShape[] FULL_BLOCK_OCCLUSION_SHAPES = Util.make( new VoxelShape[DIRECTIONS.length], voxelShapes -> Arrays.fill(voxelShapes, Shapes.block()) ); private final int lightEmission; private final boolean useShapeForLightOcclusion; private final boolean isAir; private final boolean ignitedByLava; @Deprecated private final boolean liquid; @Deprecated private boolean legacySolid; private final PushReaction pushReaction; private final MapColor mapColor; private final float destroySpeed; private final boolean requiresCorrectToolForDrops; private final boolean canOcclude; private final BlockBehaviour.StatePredicate isRedstoneConductor; private final BlockBehaviour.StatePredicate isSuffocating; private final BlockBehaviour.StatePredicate isViewBlocking; private final BlockBehaviour.StatePredicate hasPostProcess; private final BlockBehaviour.StatePredicate emissiveRendering; @Nullable private final BlockBehaviour.OffsetFunction offsetFunction; private final boolean spawnTerrainParticles; private final NoteBlockInstrument instrument; private final boolean replaceable; @Nullable private Cache cache; private FluidState fluidState = Fluids.EMPTY.defaultFluidState(); private boolean isRandomlyTicking; private boolean solidRender; private VoxelShape occlusionShape; private VoxelShape[] occlusionShapesByFace; private boolean propagatesSkylightDown; private int lightBlock; protected BlockStateBase(Block owner, Reference2ObjectArrayMap, Comparable> values, MapCodec propertiesCodec) { super(owner, values, propertiesCodec); BlockBehaviour.Properties properties = owner.properties; this.lightEmission = properties.lightEmission.applyAsInt(this.asState()); this.useShapeForLightOcclusion = owner.useShapeForLightOcclusion(this.asState()); this.isAir = properties.isAir; this.ignitedByLava = properties.ignitedByLava; this.liquid = properties.liquid; this.pushReaction = properties.pushReaction; this.mapColor = (MapColor)properties.mapColor.apply(this.asState()); this.destroySpeed = properties.destroyTime; this.requiresCorrectToolForDrops = properties.requiresCorrectToolForDrops; this.canOcclude = properties.canOcclude; this.isRedstoneConductor = properties.isRedstoneConductor; this.isSuffocating = properties.isSuffocating; this.isViewBlocking = properties.isViewBlocking; this.hasPostProcess = properties.hasPostProcess; this.emissiveRendering = properties.emissiveRendering; this.offsetFunction = properties.offsetFunction; this.spawnTerrainParticles = properties.spawnTerrainParticles; this.instrument = properties.instrument; this.replaceable = properties.replaceable; } private boolean calculateSolid() { if (this.owner.properties.forceSolidOn) { return true; } else if (this.owner.properties.forceSolidOff) { return false; } else if (this.cache == null) { return false; } else { VoxelShape voxelShape = this.cache.collisionShape; if (voxelShape.isEmpty()) { return false; } else { AABB aABB = voxelShape.bounds(); return aABB.getSize() >= 0.7291666666666666 ? true : aABB.getYsize() >= 1.0; } } } public void initCache() { this.fluidState = this.owner.getFluidState(this.asState()); this.isRandomlyTicking = this.owner.isRandomlyTicking(this.asState()); if (!this.getBlock().hasDynamicShape()) { this.cache = new Cache(this.asState()); } this.legacySolid = this.calculateSolid(); this.occlusionShape = this.canOcclude ? this.owner.getOcclusionShape(this.asState()) : Shapes.empty(); this.solidRender = Block.isShapeFullBlock(this.occlusionShape); if (this.occlusionShape.isEmpty()) { this.occlusionShapesByFace = EMPTY_OCCLUSION_SHAPES; } else if (this.solidRender) { this.occlusionShapesByFace = FULL_BLOCK_OCCLUSION_SHAPES; } else { this.occlusionShapesByFace = new VoxelShape[DIRECTIONS.length]; for (Direction direction : DIRECTIONS) { this.occlusionShapesByFace[direction.ordinal()] = this.occlusionShape.getFaceShape(direction); } } this.propagatesSkylightDown = this.owner.propagatesSkylightDown(this.asState()); this.lightBlock = this.owner.getLightBlock(this.asState()); } public Block getBlock() { return this.owner; } public Holder getBlockHolder() { return this.owner.builtInRegistryHolder(); } @Deprecated public boolean blocksMotion() { Block block = this.getBlock(); return block != Blocks.COBWEB && block != Blocks.BAMBOO_SAPLING && this.isSolid(); } @Deprecated public boolean isSolid() { return this.legacySolid; } public boolean isValidSpawn(BlockGetter level, BlockPos pos, EntityType entityType) { return this.getBlock().properties.isValidSpawn.test(this.asState(), level, pos, entityType); } public boolean propagatesSkylightDown() { return this.propagatesSkylightDown; } public int getLightBlock() { return this.lightBlock; } public VoxelShape getFaceOcclusionShape(Direction direction) { return this.occlusionShapesByFace[direction.ordinal()]; } public VoxelShape getOcclusionShape() { return this.occlusionShape; } public boolean hasLargeCollisionShape() { return this.cache == null || this.cache.largeCollisionShape; } public boolean useShapeForLightOcclusion() { return this.useShapeForLightOcclusion; } public int getLightEmission() { return this.lightEmission; } public boolean isAir() { return this.isAir; } public boolean ignitedByLava() { return this.ignitedByLava; } @Deprecated public boolean liquid() { return this.liquid; } public MapColor getMapColor(BlockGetter level, BlockPos pos) { return this.mapColor; } /** * @return the blockstate with the given rotation. If inapplicable, returns itself. */ public BlockState rotate(Rotation rotation) { return this.getBlock().rotate(this.asState(), rotation); } /** * @return the blockstate mirrored in the given way. If inapplicable, returns itself. */ public BlockState mirror(Mirror mirror) { return this.getBlock().mirror(this.asState(), mirror); } public RenderShape getRenderShape() { return this.getBlock().getRenderShape(this.asState()); } public boolean emissiveRendering(BlockGetter level, BlockPos pos) { return this.emissiveRendering.test(this.asState(), level, pos); } public float getShadeBrightness(BlockGetter level, BlockPos pos) { return this.getBlock().getShadeBrightness(this.asState(), level, pos); } public boolean isRedstoneConductor(BlockGetter level, BlockPos pos) { return this.isRedstoneConductor.test(this.asState(), level, pos); } public boolean isSignalSource() { return this.getBlock().isSignalSource(this.asState()); } public int getSignal(BlockGetter level, BlockPos pos, Direction direction) { return this.getBlock().getSignal(this.asState(), level, pos, direction); } public boolean hasAnalogOutputSignal() { return this.getBlock().hasAnalogOutputSignal(this.asState()); } public int getAnalogOutputSignal(Level level, BlockPos pos) { return this.getBlock().getAnalogOutputSignal(this.asState(), level, pos); } public float getDestroySpeed(BlockGetter level, BlockPos pos) { return this.destroySpeed; } public float getDestroyProgress(Player player, BlockGetter level, BlockPos pos) { return this.getBlock().getDestroyProgress(this.asState(), player, level, pos); } public int getDirectSignal(BlockGetter level, BlockPos pos, Direction direction) { return this.getBlock().getDirectSignal(this.asState(), level, pos, direction); } public PushReaction getPistonPushReaction() { return this.pushReaction; } public boolean isSolidRender() { return this.solidRender; } public boolean canOcclude() { return this.canOcclude; } public boolean skipRendering(BlockState state, Direction face) { return this.getBlock().skipRendering(this.asState(), state, face); } public VoxelShape getShape(BlockGetter level, BlockPos pos) { return this.getShape(level, pos, CollisionContext.empty()); } public VoxelShape getShape(BlockGetter level, BlockPos pos, CollisionContext context) { return this.getBlock().getShape(this.asState(), level, pos, context); } public VoxelShape getCollisionShape(BlockGetter level, BlockPos pos) { return this.cache != null ? this.cache.collisionShape : this.getCollisionShape(level, pos, CollisionContext.empty()); } public VoxelShape getCollisionShape(BlockGetter level, BlockPos pos, CollisionContext context) { return this.getBlock().getCollisionShape(this.asState(), level, pos, context); } public VoxelShape getBlockSupportShape(BlockGetter level, BlockPos pos) { return this.getBlock().getBlockSupportShape(this.asState(), level, pos); } public VoxelShape getVisualShape(BlockGetter level, BlockPos pos, CollisionContext context) { return this.getBlock().getVisualShape(this.asState(), level, pos, context); } public VoxelShape getInteractionShape(BlockGetter level, BlockPos pos) { return this.getBlock().getInteractionShape(this.asState(), level, pos); } public final boolean entityCanStandOn(BlockGetter level, BlockPos pos, Entity entity) { return this.entityCanStandOnFace(level, pos, entity, Direction.UP); } /** * @return true if the collision box of this state covers the entire upper face of the blockspace */ public final boolean entityCanStandOnFace(BlockGetter level, BlockPos pos, Entity entity, Direction face) { return Block.isFaceFull(this.getCollisionShape(level, pos, CollisionContext.of(entity)), face); } public Vec3 getOffset(BlockPos blockPos) { BlockBehaviour.OffsetFunction offsetFunction = this.offsetFunction; return offsetFunction != null ? offsetFunction.evaluate(this.asState(), blockPos) : Vec3.ZERO; } public boolean hasOffsetFunction() { return this.offsetFunction != null; } public boolean triggerEvent(Level level, BlockPos pos, int id, int param) { return this.getBlock().triggerEvent(this.asState(), level, pos, id, param); } public void handleNeighborChanged(Level level, BlockPos blockPos, Block block, @Nullable Orientation orientation, boolean bl) { DebugPackets.sendNeighborsUpdatePacket(level, blockPos); this.getBlock().neighborChanged(this.asState(), level, blockPos, block, orientation, bl); } public final void updateNeighbourShapes(LevelAccessor level, BlockPos pos, int flags) { this.updateNeighbourShapes(level, pos, flags, 512); } public final void updateNeighbourShapes(LevelAccessor level, BlockPos pos, int flags, int recursionLeft) { BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); for (Direction direction : BlockBehaviour.UPDATE_SHAPE_ORDER) { mutableBlockPos.setWithOffset(pos, direction); level.neighborShapeChanged(direction.getOpposite(), mutableBlockPos, pos, this.asState(), flags, recursionLeft); } } public final void updateIndirectNeighbourShapes(LevelAccessor level, BlockPos pos, int flags) { this.updateIndirectNeighbourShapes(level, pos, flags, 512); } public void updateIndirectNeighbourShapes(LevelAccessor level, BlockPos pos, int flags, int recursionLeft) { this.getBlock().updateIndirectNeighbourShapes(this.asState(), level, pos, flags, recursionLeft); } public void onPlace(Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) { this.getBlock().onPlace(this.asState(), level, pos, oldState, movedByPiston); } public void onRemove(Level level, BlockPos pos, BlockState newState, boolean movedByPiston) { this.getBlock().onRemove(this.asState(), level, pos, newState, movedByPiston); } public void onExplosionHit(ServerLevel serverLevel, BlockPos blockPos, Explosion explosion, BiConsumer biConsumer) { this.getBlock().onExplosionHit(this.asState(), serverLevel, blockPos, explosion, biConsumer); } public void tick(ServerLevel level, BlockPos pos, RandomSource random) { this.getBlock().tick(this.asState(), level, pos, random); } public void randomTick(ServerLevel level, BlockPos pos, RandomSource random) { this.getBlock().randomTick(this.asState(), level, pos, random); } public void entityInside(Level level, BlockPos pos, Entity entity) { this.getBlock().entityInside(this.asState(), level, pos, entity); } public VoxelShape getEntityInsideCollisionShape(Level level, BlockPos blockPos) { return this.getBlock().getEntityInsideCollisionShape(this.asState(), level, blockPos); } public void spawnAfterBreak(ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) { this.getBlock().spawnAfterBreak(this.asState(), level, pos, stack, dropExperience); } public List getDrops(LootParams.Builder lootParams) { return this.getBlock().getDrops(this.asState(), lootParams); } public InteractionResult useItemOn(ItemStack itemStack, Level level, Player player, InteractionHand interactionHand, BlockHitResult blockHitResult) { return this.getBlock().useItemOn(itemStack, this.asState(), level, blockHitResult.getBlockPos(), player, interactionHand, blockHitResult); } public InteractionResult useWithoutItem(Level level, Player player, BlockHitResult hitResult) { return this.getBlock().useWithoutItem(this.asState(), level, hitResult.getBlockPos(), player, hitResult); } public void attack(Level level, BlockPos pos, Player player) { this.getBlock().attack(this.asState(), level, pos, player); } public boolean isSuffocating(BlockGetter level, BlockPos pos) { return this.isSuffocating.test(this.asState(), level, pos); } public boolean isViewBlocking(BlockGetter level, BlockPos pos) { return this.isViewBlocking.test(this.asState(), level, pos); } public BlockState updateShape( LevelReader levelReader, ScheduledTickAccess scheduledTickAccess, BlockPos blockPos, Direction direction, BlockPos blockPos2, BlockState blockState, RandomSource randomSource ) { return this.getBlock().updateShape(this.asState(), levelReader, scheduledTickAccess, blockPos, direction, blockPos2, blockState, randomSource); } public boolean isPathfindable(PathComputationType pathComputationType) { return this.getBlock().isPathfindable(this.asState(), pathComputationType); } public boolean canBeReplaced(BlockPlaceContext useContext) { return this.getBlock().canBeReplaced(this.asState(), useContext); } public boolean canBeReplaced(Fluid fluid) { return this.getBlock().canBeReplaced(this.asState(), fluid); } public boolean canBeReplaced() { return this.replaceable; } public boolean canSurvive(LevelReader level, BlockPos pos) { return this.getBlock().canSurvive(this.asState(), level, pos); } public boolean hasPostProcess(BlockGetter level, BlockPos pos) { return this.hasPostProcess.test(this.asState(), level, pos); } @Nullable public MenuProvider getMenuProvider(Level level, BlockPos pos) { return this.getBlock().getMenuProvider(this.asState(), level, pos); } public boolean is(TagKey tag) { return this.getBlock().builtInRegistryHolder().is(tag); } public boolean is(TagKey tag, Predicate predicate) { return this.is(tag) && predicate.test(this); } public boolean is(HolderSet holder) { return holder.contains(this.getBlock().builtInRegistryHolder()); } public boolean is(Holder block) { return this.is(block.value()); } public Stream> getTags() { return this.getBlock().builtInRegistryHolder().tags(); } public boolean hasBlockEntity() { return this.getBlock() instanceof EntityBlock; } @Nullable public BlockEntityTicker getTicker(Level level, BlockEntityType blockEntityType) { return this.getBlock() instanceof EntityBlock ? ((EntityBlock)this.getBlock()).getTicker(level, this.asState(), blockEntityType) : null; } public boolean is(Block block) { return this.getBlock() == block; } public boolean is(ResourceKey block) { return this.getBlock().builtInRegistryHolder().is(block); } public FluidState getFluidState() { return this.fluidState; } public boolean isRandomlyTicking() { return this.isRandomlyTicking; } public long getSeed(BlockPos pos) { return this.getBlock().getSeed(this.asState(), pos); } public SoundType getSoundType() { return this.getBlock().getSoundType(this.asState()); } public void onProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile) { this.getBlock().onProjectileHit(level, state, hit, projectile); } public boolean isFaceSturdy(BlockGetter level, BlockPos pos, Direction direction) { return this.isFaceSturdy(level, pos, direction, SupportType.FULL); } public boolean isFaceSturdy(BlockGetter level, BlockPos pos, Direction face, SupportType supportType) { return this.cache != null ? this.cache.isFaceSturdy(face, supportType) : supportType.isSupporting(this.asState(), level, pos, face); } public boolean isCollisionShapeFullBlock(BlockGetter level, BlockPos pos) { return this.cache != null ? this.cache.isCollisionShapeFullBlock : this.getBlock().isCollisionShapeFullBlock(this.asState(), level, pos); } protected abstract BlockState asState(); public boolean requiresCorrectToolForDrops() { return this.requiresCorrectToolForDrops; } public boolean shouldSpawnTerrainParticles() { return this.spawnTerrainParticles; } public NoteBlockInstrument instrument() { return this.instrument; } } @FunctionalInterface public interface OffsetFunction { Vec3 evaluate(BlockState blockState, BlockPos blockPos); } public static enum OffsetType { NONE, XZ, XYZ; } public static class Properties { public static final Codec CODEC = Codec.unit((Supplier)(() -> of())); Function mapColor = blockState -> MapColor.NONE; boolean hasCollision = true; SoundType soundType = SoundType.STONE; ToIntFunction lightEmission = blockState -> 0; float explosionResistance; float destroyTime; boolean requiresCorrectToolForDrops; boolean isRandomlyTicking; float friction = 0.6F; float speedFactor = 1.0F; float jumpFactor = 1.0F; @Nullable private ResourceKey id; private DependantName>> drops = resourceKey -> Optional.of( ResourceKey.create(Registries.LOOT_TABLE, resourceKey.location().withPrefix("blocks/")) ); private DependantName descriptionId = resourceKey -> Util.makeDescriptionId("block", resourceKey.location()); boolean canOcclude = true; boolean isAir; boolean ignitedByLava; @Deprecated boolean liquid; @Deprecated boolean forceSolidOff; boolean forceSolidOn; PushReaction pushReaction = PushReaction.NORMAL; boolean spawnTerrainParticles = true; NoteBlockInstrument instrument = NoteBlockInstrument.HARP; boolean replaceable; BlockBehaviour.StateArgumentPredicate> isValidSpawn = (blockState, blockGetter, blockPos, entityType) -> blockState.isFaceSturdy( blockGetter, blockPos, Direction.UP ) && blockState.getLightEmission() < 14; BlockBehaviour.StatePredicate isRedstoneConductor = (blockState, blockGetter, blockPos) -> blockState.isCollisionShapeFullBlock(blockGetter, blockPos); BlockBehaviour.StatePredicate isSuffocating = (blockState, blockGetter, blockPos) -> blockState.blocksMotion() && blockState.isCollisionShapeFullBlock(blockGetter, blockPos); /** * If it blocks vision on the client side. */ BlockBehaviour.StatePredicate isViewBlocking = this.isSuffocating; BlockBehaviour.StatePredicate hasPostProcess = (blockState, blockGetter, blockPos) -> false; BlockBehaviour.StatePredicate emissiveRendering = (blockState, blockGetter, blockPos) -> false; boolean dynamicShape; FeatureFlagSet requiredFeatures = FeatureFlags.VANILLA_SET; @Nullable BlockBehaviour.OffsetFunction offsetFunction; private Properties() { } public static BlockBehaviour.Properties of() { return new BlockBehaviour.Properties(); } public static BlockBehaviour.Properties ofFullCopy(BlockBehaviour blockBehaviour) { BlockBehaviour.Properties properties = ofLegacyCopy(blockBehaviour); BlockBehaviour.Properties properties2 = blockBehaviour.properties; properties.jumpFactor = properties2.jumpFactor; properties.isRedstoneConductor = properties2.isRedstoneConductor; properties.isValidSpawn = properties2.isValidSpawn; properties.hasPostProcess = properties2.hasPostProcess; properties.isSuffocating = properties2.isSuffocating; properties.isViewBlocking = properties2.isViewBlocking; properties.drops = properties2.drops; properties.descriptionId = properties2.descriptionId; return properties; } @Deprecated public static BlockBehaviour.Properties ofLegacyCopy(BlockBehaviour blockBehaviour) { BlockBehaviour.Properties properties = new BlockBehaviour.Properties(); BlockBehaviour.Properties properties2 = blockBehaviour.properties; properties.destroyTime = properties2.destroyTime; properties.explosionResistance = properties2.explosionResistance; properties.hasCollision = properties2.hasCollision; properties.isRandomlyTicking = properties2.isRandomlyTicking; properties.lightEmission = properties2.lightEmission; properties.mapColor = properties2.mapColor; properties.soundType = properties2.soundType; properties.friction = properties2.friction; properties.speedFactor = properties2.speedFactor; properties.dynamicShape = properties2.dynamicShape; properties.canOcclude = properties2.canOcclude; properties.isAir = properties2.isAir; properties.ignitedByLava = properties2.ignitedByLava; properties.liquid = properties2.liquid; properties.forceSolidOff = properties2.forceSolidOff; properties.forceSolidOn = properties2.forceSolidOn; properties.pushReaction = properties2.pushReaction; properties.requiresCorrectToolForDrops = properties2.requiresCorrectToolForDrops; properties.offsetFunction = properties2.offsetFunction; properties.spawnTerrainParticles = properties2.spawnTerrainParticles; properties.requiredFeatures = properties2.requiredFeatures; properties.emissiveRendering = properties2.emissiveRendering; properties.instrument = properties2.instrument; properties.replaceable = properties2.replaceable; return properties; } public BlockBehaviour.Properties mapColor(DyeColor mapColor) { this.mapColor = blockState -> mapColor.getMapColor(); return this; } public BlockBehaviour.Properties mapColor(MapColor mapColor) { this.mapColor = blockState -> mapColor; return this; } public BlockBehaviour.Properties mapColor(Function mapColor) { this.mapColor = mapColor; return this; } public BlockBehaviour.Properties noCollission() { this.hasCollision = false; this.canOcclude = false; return this; } public BlockBehaviour.Properties noOcclusion() { this.canOcclude = false; return this; } public BlockBehaviour.Properties friction(float friction) { this.friction = friction; return this; } public BlockBehaviour.Properties speedFactor(float speedFactor) { this.speedFactor = speedFactor; return this; } public BlockBehaviour.Properties jumpFactor(float jumpFactor) { this.jumpFactor = jumpFactor; return this; } public BlockBehaviour.Properties sound(SoundType soundType) { this.soundType = soundType; return this; } public BlockBehaviour.Properties lightLevel(ToIntFunction lightEmission) { this.lightEmission = lightEmission; return this; } public BlockBehaviour.Properties strength(float destroyTime, float explosionResistance) { return this.destroyTime(destroyTime).explosionResistance(explosionResistance); } public BlockBehaviour.Properties instabreak() { return this.strength(0.0F); } public BlockBehaviour.Properties strength(float strength) { this.strength(strength, strength); return this; } public BlockBehaviour.Properties randomTicks() { this.isRandomlyTicking = true; return this; } public BlockBehaviour.Properties dynamicShape() { this.dynamicShape = true; return this; } public BlockBehaviour.Properties noLootTable() { this.drops = DependantName.fixed(Optional.empty()); return this; } public BlockBehaviour.Properties overrideLootTable(Optional> optional) { this.drops = DependantName.fixed(optional); return this; } protected Optional> effectiveDrops() { return this.drops.get((ResourceKey)Objects.requireNonNull(this.id, "Block id not set")); } public BlockBehaviour.Properties ignitedByLava() { this.ignitedByLava = true; return this; } public BlockBehaviour.Properties liquid() { this.liquid = true; return this; } public BlockBehaviour.Properties forceSolidOn() { this.forceSolidOn = true; return this; } @Deprecated public BlockBehaviour.Properties forceSolidOff() { this.forceSolidOff = true; return this; } public BlockBehaviour.Properties pushReaction(PushReaction pushReaction) { this.pushReaction = pushReaction; return this; } public BlockBehaviour.Properties air() { this.isAir = true; return this; } public BlockBehaviour.Properties isValidSpawn(BlockBehaviour.StateArgumentPredicate> isValidSpawn) { this.isValidSpawn = isValidSpawn; return this; } public BlockBehaviour.Properties isRedstoneConductor(BlockBehaviour.StatePredicate isRedstoneConductor) { this.isRedstoneConductor = isRedstoneConductor; return this; } public BlockBehaviour.Properties isSuffocating(BlockBehaviour.StatePredicate isSuffocating) { this.isSuffocating = isSuffocating; return this; } /** * If it blocks vision on the client side. */ public BlockBehaviour.Properties isViewBlocking(BlockBehaviour.StatePredicate isViewBlocking) { this.isViewBlocking = isViewBlocking; return this; } public BlockBehaviour.Properties hasPostProcess(BlockBehaviour.StatePredicate hasPostProcess) { this.hasPostProcess = hasPostProcess; return this; } public BlockBehaviour.Properties emissiveRendering(BlockBehaviour.StatePredicate emissiveRendering) { this.emissiveRendering = emissiveRendering; return this; } public BlockBehaviour.Properties requiresCorrectToolForDrops() { this.requiresCorrectToolForDrops = true; return this; } public BlockBehaviour.Properties destroyTime(float destroyTime) { this.destroyTime = destroyTime; return this; } public BlockBehaviour.Properties explosionResistance(float explosionResistance) { this.explosionResistance = Math.max(0.0F, explosionResistance); return this; } public BlockBehaviour.Properties offsetType(BlockBehaviour.OffsetType offsetType) { this.offsetFunction = switch (offsetType) { case NONE -> null; case XZ -> (blockState, blockPos) -> { Block block = blockState.getBlock(); long l = Mth.getSeed(blockPos.getX(), 0, blockPos.getZ()); float f = block.getMaxHorizontalOffset(); double d = Mth.clamp(((float)(l & 15L) / 15.0F - 0.5) * 0.5, (double)(-f), (double)f); double e = Mth.clamp(((float)(l >> 8 & 15L) / 15.0F - 0.5) * 0.5, (double)(-f), (double)f); return new Vec3(d, 0.0, e); }; case XYZ -> (blockState, blockPos) -> { Block block = blockState.getBlock(); long l = Mth.getSeed(blockPos.getX(), 0, blockPos.getZ()); double d = ((float)(l >> 4 & 15L) / 15.0F - 1.0) * block.getMaxVerticalOffset(); float f = block.getMaxHorizontalOffset(); double e = Mth.clamp(((float)(l & 15L) / 15.0F - 0.5) * 0.5, (double)(-f), (double)f); double g = Mth.clamp(((float)(l >> 8 & 15L) / 15.0F - 0.5) * 0.5, (double)(-f), (double)f); return new Vec3(e, d, g); }; }; return this; } public BlockBehaviour.Properties noTerrainParticles() { this.spawnTerrainParticles = false; return this; } public BlockBehaviour.Properties requiredFeatures(FeatureFlag... requiredFeatures) { this.requiredFeatures = FeatureFlags.REGISTRY.subset(requiredFeatures); return this; } public BlockBehaviour.Properties instrument(NoteBlockInstrument instrument) { this.instrument = instrument; return this; } public BlockBehaviour.Properties replaceable() { this.replaceable = true; return this; } public BlockBehaviour.Properties setId(ResourceKey resourceKey) { this.id = resourceKey; return this; } public BlockBehaviour.Properties overrideDescription(String string) { this.descriptionId = DependantName.fixed(string); return this; } protected String effectiveDescriptionId() { return this.descriptionId.get((ResourceKey)Objects.requireNonNull(this.id, "Block id not set")); } } @FunctionalInterface public interface StateArgumentPredicate { boolean test(BlockState blockState, BlockGetter blockGetter, BlockPos blockPos, A object); } @FunctionalInterface public interface StatePredicate { boolean test(BlockState blockState, BlockGetter blockGetter, BlockPos blockPos); } }