package net.minecraft.world.level.block.state; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; 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 net.fabricmc.fabric.api.block.v1.FabricBlock.FabricProperties; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Holder; import net.minecraft.core.TypedInstance; import net.minecraft.core.BlockPos.MutableBlockPos; import net.minecraft.core.registries.Registries; 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.util.Util; 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.InsideBlockEffectApplier; 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.Explosion.BlockInteraction; 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.LootParams.Builder; 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.jspecify.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; protected final boolean isRandomlyTicking; protected final SoundType soundType; protected final float friction; protected final float speedFactor; protected final float jumpFactor; protected final float bounceRestitution; protected final boolean dynamicShape; protected final FeatureFlagSet requiredFeatures; protected final BlockBehaviour.Properties properties; protected final Optional> drops; protected final String descriptionId; public BlockBehaviour(final 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.bounceRestitution = properties.bounceRestitution; 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(final Function constructor) { return RecordCodecBuilder.mapCodec(i -> i.group(propertiesCodec()).apply(i, constructor)); } protected void updateIndirectNeighbourShapes( final BlockState state, final LevelAccessor level, final BlockPos pos, @Block.UpdateFlags final int updateFlags, final int updateLimit ) { } protected boolean isPathfindable(final BlockState state, final PathComputationType type) { return switch (type) { case LAND -> !state.isCollisionShapeFullBlock(EmptyBlockGetter.INSTANCE, BlockPos.ZERO); case WATER -> state.getFluidState().is(FluidTags.WATER); case AIR -> !state.isCollisionShapeFullBlock(EmptyBlockGetter.INSTANCE, BlockPos.ZERO); default -> false; }; } protected BlockState updateShape( final BlockState state, final LevelReader level, final ScheduledTickAccess ticks, final BlockPos pos, final Direction directionToNeighbour, final BlockPos neighbourPos, final BlockState neighbourState, final RandomSource random ) { return state; } protected boolean skipRendering(final BlockState state, final BlockState neighborState, final Direction direction) { return false; } protected void neighborChanged( final BlockState state, final Level level, final BlockPos pos, final Block block, @Nullable final Orientation orientation, final boolean movedByPiston ) { } protected void onPlace(final BlockState state, final Level level, final BlockPos pos, final BlockState oldState, final boolean movedByPiston) { } protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { } protected void onExplosionHit( final BlockState state, final ServerLevel level, final BlockPos pos, final Explosion explosion, final BiConsumer onHit ) { if (!state.isAir() && explosion.getBlockInteraction() != BlockInteraction.TRIGGER_BLOCK) { Block block = state.getBlock(); boolean doDropExperienceHack = explosion.getIndirectSourceEntity() instanceof Player; if (block.dropFromExplosion(explosion)) { BlockEntity blockEntity = state.hasBlockEntity() ? level.getBlockEntity(pos) : null; Builder params = new Builder(level) .withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos)) .withParameter(LootContextParams.TOOL, ItemStack.EMPTY) .withOptionalParameter(LootContextParams.BLOCK_ENTITY, blockEntity) .withOptionalParameter(LootContextParams.THIS_ENTITY, explosion.getDirectSourceEntity()); if (explosion.getBlockInteraction() == BlockInteraction.DESTROY_WITH_DECAY) { params.withParameter(LootContextParams.EXPLOSION_RADIUS, explosion.radius()); } state.spawnAfterBreak(level, pos, ItemStack.EMPTY, doDropExperienceHack); state.getDrops(params).forEach(stack -> onHit.accept(stack, pos)); } level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3); block.wasExploded(level, pos, explosion); } } protected InteractionResult useWithoutItem(final BlockState state, final Level level, final BlockPos pos, final Player player, final BlockHitResult hitResult) { return InteractionResult.PASS; } protected InteractionResult useItemOn( final ItemStack itemStack, final BlockState state, final Level level, final BlockPos pos, final Player player, final InteractionHand hand, final BlockHitResult hitResult ) { return InteractionResult.TRY_WITH_EMPTY_HAND; } protected boolean triggerEvent(final BlockState state, final Level level, final BlockPos pos, final int b0, final int b1) { return false; } protected RenderShape getRenderShape(final BlockState state) { return RenderShape.MODEL; } protected boolean useShapeForLightOcclusion(final BlockState state) { return false; } protected FluidState getFluidState(final BlockState state) { return Fluids.EMPTY.defaultFluidState(); } protected boolean hasAnalogOutputSignal(final BlockState state) { return false; } protected float getMaxHorizontalOffset() { return 0.25F; } protected float getMaxVerticalOffset() { return 0.2F; } @Override public FeatureFlagSet requiredFeatures() { return this.requiredFeatures; } protected boolean shouldChangedStateKeepBlockEntity(final BlockState oldState) { return false; } protected BlockState rotate(final BlockState state, final Rotation rotation) { return state; } protected BlockState mirror(final BlockState state, final Mirror mirror) { return state; } protected boolean canBeReplaced(final BlockState state, final BlockPlaceContext context) { return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())); } protected boolean canBeReplaced(final BlockState state, final Fluid fluid) { return state.canBeReplaced() || !state.isSolid(); } protected List getDrops(final BlockState state, final Builder params) { if (this.drops.isEmpty()) { return Collections.emptyList(); } else { LootParams lootParams = params.withParameter(LootContextParams.BLOCK_STATE, state).create(LootContextParamSets.BLOCK); ServerLevel level = lootParams.getLevel(); LootTable table = level.getServer().reloadableRegistries().getLootTable((ResourceKey)this.drops.get()); return table.getRandomItems(lootParams); } } protected long getSeed(final BlockState state, final BlockPos pos) { return Mth.getSeed(pos); } protected VoxelShape getOcclusionShape(final BlockState state) { return state.getShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO); } protected VoxelShape getBlockSupportShape(final BlockState state, final BlockGetter level, final BlockPos pos) { return this.getCollisionShape(state, level, pos, CollisionContext.empty()); } protected VoxelShape getInteractionShape(final BlockState state, final BlockGetter level, final BlockPos pos) { return Shapes.empty(); } protected int getLightDampening(final BlockState state) { if (state.isSolidRender()) { return 15; } else { return state.propagatesSkylightDown() ? 0 : 1; } } @Nullable protected MenuProvider getMenuProvider(final BlockState state, final Level level, final BlockPos pos) { return null; } protected boolean canSurvive(final BlockState state, final LevelReader level, final BlockPos pos) { return true; } protected float getShadeBrightness(final BlockState state, final BlockGetter level, final BlockPos pos) { return state.isCollisionShapeFullBlock(level, pos) ? 0.2F : 1.0F; } protected int getAnalogOutputSignal(final BlockState state, final Level level, final BlockPos pos, final Direction direction) { return 0; } protected VoxelShape getShape(final BlockState state, final BlockGetter level, final BlockPos pos, final CollisionContext context) { return Shapes.block(); } protected VoxelShape getCollisionShape(final BlockState state, final BlockGetter level, final BlockPos pos, final CollisionContext context) { return this.hasCollision ? state.getShape(level, pos) : Shapes.empty(); } protected VoxelShape getEntityInsideCollisionShape(final BlockState state, final BlockGetter level, final BlockPos pos, final Entity entity) { return Shapes.block(); } protected boolean isCollisionShapeFullBlock(final BlockState state, final BlockGetter level, final BlockPos pos) { return Block.isShapeFullBlock(state.getCollisionShape(level, pos)); } protected VoxelShape getVisualShape(final BlockState state, final BlockGetter level, final BlockPos pos, final CollisionContext context) { return this.getCollisionShape(state, level, pos, context); } protected void randomTick(final BlockState state, final ServerLevel level, final BlockPos pos, final RandomSource random) { } protected void tick(final BlockState state, final ServerLevel level, final BlockPos pos, final RandomSource random) { } protected float getDestroyProgress(final BlockState state, final Player player, final BlockGetter level, final BlockPos pos) { float destroySpeed = state.getDestroySpeed(level, pos); if (destroySpeed == -1.0F) { return 0.0F; } else { int modifier = player.hasCorrectToolForDrops(state) ? 30 : 100; return player.getDestroySpeed(state) / destroySpeed / modifier; } } protected void spawnAfterBreak(final BlockState state, final ServerLevel level, final BlockPos pos, final ItemStack tool, final boolean dropExperience) { } protected void attack(final BlockState state, final Level level, final BlockPos pos, final Player player) { } protected boolean isSignalSource(final BlockState state) { return false; } protected int getSignal(final BlockState state, final BlockGetter level, final BlockPos pos, final Direction direction) { return this.ownSignal(state, level, pos); } protected int ownSignal(final BlockState state, final BlockGetter level, final BlockPos pos) { return 0; } protected void entityInside( final BlockState state, final Level level, final BlockPos pos, final Entity entity, final InsideBlockEffectApplier effectApplier, final boolean isPrecise ) { } protected int getDirectSignal(final BlockState state, final BlockGetter level, final BlockPos pos, final Direction direction) { return 0; } public final Optional> getLootTable() { return this.drops; } public final String getDescriptionId() { return this.descriptionId; } protected void onProjectileHit(final Level level, final BlockState state, final BlockHitResult blockHit, final Projectile projectile) { } protected boolean propagatesSkylightDown(final BlockState state) { return !Block.isShapeFullBlock(state.getShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)) && state.getFluidState().isEmpty(); } protected boolean isRandomlyTicking(final BlockState state) { return this.isRandomlyTicking; } protected SoundType getSoundType(final BlockState state) { return this.soundType; } protected ItemStack getCloneItemStack(final LevelReader level, final BlockPos pos, final BlockState state, final boolean includeData) { return new ItemStack(this.asItem()); } 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 implements TypedInstance { private static final Direction[] DIRECTIONS = Direction.values(); private static final VoxelShape[] EMPTY_OCCLUSION_SHAPES = Util.make(new VoxelShape[DIRECTIONS.length], s -> Arrays.fill(s, Shapes.empty())); private static final VoxelShape[] FULL_BLOCK_OCCLUSION_SHAPES = Util.make(new VoxelShape[DIRECTIONS.length], s -> Arrays.fill(s, 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.PostProcess postProcess; private final Predicate 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 lightDampening; protected BlockStateBase(final Block owner, final Property[] propertyKeys, final Comparable[] propertyValues) { super(owner, propertyKeys, propertyValues); 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.postProcess = properties.postProcess; 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 shape = this.cache.collisionShape; if (shape.isEmpty()) { return false; } else { AABB bounds = shape.bounds(); return bounds.getSize() >= 0.7291666666666666 ? true : bounds.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.lightDampening = this.owner.getLightDampening(this.asState()); } public Block getBlock() { return this.owner; } @Override public Holder typeHolder() { return this.getBlock().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(final BlockGetter level, final BlockPos pos, final EntityType type) { return this.getBlock().properties.isValidSpawn.test(this.asState(), level, pos, type); } public boolean propagatesSkylightDown() { return this.propagatesSkylightDown; } public int getLightDampening() { return this.lightDampening; } public VoxelShape getFaceOcclusionShape(final 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(final BlockGetter level, final BlockPos pos) { return this.mapColor; } public BlockState rotate(final Rotation rotation) { return this.getBlock().rotate(this.asState(), rotation); } public BlockState mirror(final Mirror mirror) { return this.getBlock().mirror(this.asState(), mirror); } public RenderShape getRenderShape() { return this.getBlock().getRenderShape(this.asState()); } public boolean emissiveRendering() { return this.emissiveRendering.test(this.asState()); } public float getShadeBrightness(final BlockGetter level, final BlockPos pos) { return this.getBlock().getShadeBrightness(this.asState(), level, pos); } public boolean isRedstoneConductor(final BlockGetter level, final BlockPos pos) { return this.isRedstoneConductor.test(this.asState(), level, pos); } public boolean isSignalSource() { return this.getBlock().isSignalSource(this.asState()); } public int getOwnSignal(final BlockGetter level, final BlockPos pos) { return this.getBlock().ownSignal(this.asState(), level, pos); } public int getSignal(final BlockGetter level, final BlockPos pos, final Direction direction) { return this.getBlock().getSignal(this.asState(), level, pos, direction); } public boolean hasAnalogOutputSignal() { return this.getBlock().hasAnalogOutputSignal(this.asState()); } public int getAnalogOutputSignal(final Level level, final BlockPos pos, final Direction direction) { return this.getBlock().getAnalogOutputSignal(this.asState(), level, pos, direction); } public float getDestroySpeed(final BlockGetter level, final BlockPos pos) { return this.destroySpeed; } public float getDestroyProgress(final Player player, final BlockGetter level, final BlockPos pos) { return this.getBlock().getDestroyProgress(this.asState(), player, level, pos); } public int getDirectSignal(final BlockGetter level, final BlockPos pos, final 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(final BlockState neighborState, final Direction direction) { return this.getBlock().skipRendering(this.asState(), neighborState, direction); } public VoxelShape getShape(final BlockGetter level, final BlockPos pos) { return this.getShape(level, pos, CollisionContext.empty()); } public VoxelShape getShape(final BlockGetter level, final BlockPos pos, final CollisionContext context) { return this.getBlock().getShape(this.asState(), level, pos, context); } public VoxelShape getCollisionShape(final BlockGetter level, final BlockPos pos) { return this.cache != null ? this.cache.collisionShape : this.getCollisionShape(level, pos, CollisionContext.empty()); } public VoxelShape getCollisionShape(final BlockGetter level, final BlockPos pos, final CollisionContext context) { return this.getBlock().getCollisionShape(this.asState(), level, pos, context); } public VoxelShape getEntityInsideCollisionShape(final BlockGetter level, final BlockPos pos, final Entity entity) { return this.getBlock().getEntityInsideCollisionShape(this.asState(), level, pos, entity); } public VoxelShape getBlockSupportShape(final BlockGetter level, final BlockPos pos) { return this.getBlock().getBlockSupportShape(this.asState(), level, pos); } public VoxelShape getVisualShape(final BlockGetter level, final BlockPos pos, final CollisionContext context) { return this.getBlock().getVisualShape(this.asState(), level, pos, context); } public VoxelShape getInteractionShape(final BlockGetter level, final BlockPos pos) { return this.getBlock().getInteractionShape(this.asState(), level, pos); } public final boolean entityCanStandOn(final BlockGetter level, final BlockPos pos, final Entity entity) { return this.entityCanStandOnFace(level, pos, entity, Direction.UP); } public final boolean entityCanStandOnFace(final BlockGetter level, final BlockPos pos, final Entity entity, final Direction faceDirection) { return Block.isFaceFull(this.getCollisionShape(level, pos, CollisionContext.of(entity)), faceDirection); } public Vec3 getOffset(final BlockPos pos) { BlockBehaviour.OffsetFunction function = this.offsetFunction; return function != null ? function.evaluate(this.asState(), pos) : Vec3.ZERO; } public boolean hasOffsetFunction() { return this.offsetFunction != null; } public boolean triggerEvent(final Level level, final BlockPos pos, final int b0, final int b1) { return this.getBlock().triggerEvent(this.asState(), level, pos, b0, b1); } public void handleNeighborChanged( final Level level, final BlockPos pos, final Block block, @Nullable final Orientation orientation, final boolean movedByPiston ) { this.getBlock().neighborChanged(this.asState(), level, pos, block, orientation, movedByPiston); } public final void updateNeighbourShapes(final LevelAccessor level, final BlockPos pos, @Block.UpdateFlags final int updateFlags) { this.updateNeighbourShapes(level, pos, updateFlags, 512); } public final void updateNeighbourShapes(final LevelAccessor level, final BlockPos pos, @Block.UpdateFlags final int updateFlags, final int updateLimit) { MutableBlockPos blockPos = new MutableBlockPos(); for (Direction direction : BlockBehaviour.UPDATE_SHAPE_ORDER) { blockPos.setWithOffset(pos, direction); level.neighborShapeChanged(direction.getOpposite(), blockPos, pos, this.asState(), updateFlags, updateLimit); } } public final void updateIndirectNeighbourShapes(final LevelAccessor level, final BlockPos pos, @Block.UpdateFlags final int updateFlags) { this.updateIndirectNeighbourShapes(level, pos, updateFlags, 512); } public void updateIndirectNeighbourShapes(final LevelAccessor level, final BlockPos pos, @Block.UpdateFlags final int updateFlags, final int updateLimit) { this.getBlock().updateIndirectNeighbourShapes(this.asState(), level, pos, updateFlags, updateLimit); } public void onPlace(final Level level, final BlockPos pos, final BlockState oldState, final boolean movedByPiston) { this.getBlock().onPlace(this.asState(), level, pos, oldState, movedByPiston); } public void affectNeighborsAfterRemoval(final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { this.getBlock().affectNeighborsAfterRemoval(this.asState(), level, pos, movedByPiston); } public void onExplosionHit(final ServerLevel level, final BlockPos pos, final Explosion explosion, final BiConsumer onHit) { this.getBlock().onExplosionHit(this.asState(), level, pos, explosion, onHit); } public void tick(final ServerLevel level, final BlockPos pos, final RandomSource random) { this.getBlock().tick(this.asState(), level, pos, random); } public void randomTick(final ServerLevel level, final BlockPos pos, final RandomSource random) { this.getBlock().randomTick(this.asState(), level, pos, random); } public void entityInside(final Level level, final BlockPos pos, final Entity entity, final InsideBlockEffectApplier effectApplier, final boolean isPrecise) { this.getBlock().entityInside(this.asState(), level, pos, entity, effectApplier, isPrecise); } public void spawnAfterBreak(final ServerLevel level, final BlockPos pos, final ItemStack tool, final boolean dropExperience) { this.getBlock().spawnAfterBreak(this.asState(), level, pos, tool, dropExperience); } public List getDrops(final Builder params) { return this.getBlock().getDrops(this.asState(), params); } public InteractionResult useItemOn( final ItemStack itemStack, final Level level, final Player player, final InteractionHand hand, final BlockHitResult hitResult ) { return this.getBlock().useItemOn(itemStack, this.asState(), level, hitResult.getBlockPos(), player, hand, hitResult); } public InteractionResult useWithoutItem(final Level level, final Player player, final BlockHitResult hitResult) { return this.getBlock().useWithoutItem(this.asState(), level, hitResult.getBlockPos(), player, hitResult); } public void attack(final Level level, final BlockPos pos, final Player player) { this.getBlock().attack(this.asState(), level, pos, player); } public boolean isSuffocating(final BlockGetter level, final BlockPos pos) { return this.isSuffocating.test(this.asState(), level, pos); } public boolean isViewBlocking(final BlockGetter level, final BlockPos pos) { return this.isViewBlocking.test(this.asState(), level, pos); } public BlockState updateShape( final LevelReader level, final ScheduledTickAccess ticks, final BlockPos pos, final Direction directionToNeighbour, final BlockPos neighbourPos, final BlockState neighbourState, final RandomSource random ) { return this.getBlock().updateShape(this.asState(), level, ticks, pos, directionToNeighbour, neighbourPos, neighbourState, random); } public boolean isPathfindable(final PathComputationType type) { return this.getBlock().isPathfindable(this.asState(), type); } public boolean canBeReplaced(final BlockPlaceContext context) { return this.getBlock().canBeReplaced(this.asState(), context); } public boolean canBeReplaced(final Fluid fluid) { return this.getBlock().canBeReplaced(this.asState(), fluid); } public boolean canBeReplaced() { return this.replaceable; } public boolean canSurvive(final LevelReader level, final BlockPos pos) { return this.getBlock().canSurvive(this.asState(), level, pos); } @Nullable public BlockPos getPostProcessPos(final BlockGetter level, final BlockPos pos) { return this.postProcess.getPostProcessPos(this.asState(), level, pos); } @Nullable public MenuProvider getMenuProvider(final Level level, final BlockPos pos) { return this.getBlock().getMenuProvider(this.asState(), level, pos); } public boolean is(final TagKey tag, final Predicate predicate) { return this.is(tag) && predicate.test(this); } public boolean hasBlockEntity() { return this.getBlock() instanceof EntityBlock; } public boolean shouldChangedStateKeepBlockEntity(final BlockState oldState) { return this.getBlock().shouldChangedStateKeepBlockEntity(oldState); } @Nullable public BlockEntityTicker getTicker(final Level level, final BlockEntityType type) { return this.getBlock() instanceof EntityBlock ? ((EntityBlock)this.getBlock()).getTicker(level, this.asState(), type) : null; } public FluidState getFluidState() { return this.fluidState; } public boolean isRandomlyTicking() { return this.isRandomlyTicking; } public long getSeed(final BlockPos pos) { return this.getBlock().getSeed(this.asState(), pos); } public SoundType getSoundType() { return this.getBlock().getSoundType(this.asState()); } public void onProjectileHit(final Level level, final BlockState state, final BlockHitResult blockHit, final Projectile entity) { this.getBlock().onProjectileHit(level, state, blockHit, entity); } public boolean isFaceSturdy(final BlockGetter level, final BlockPos pos, final Direction direction) { return this.isFaceSturdy(level, pos, direction, SupportType.FULL); } public boolean isFaceSturdy(final BlockGetter level, final BlockPos pos, final Direction direction, final SupportType supportType) { return this.cache != null ? this.cache.isFaceSturdy(direction, supportType) : supportType.isSupporting(this.asState(), level, pos, direction); } public boolean isCollisionShapeFullBlock(final BlockGetter level, final BlockPos pos) { return this.cache != null ? this.cache.isCollisionShapeFullBlock : this.getBlock().isCollisionShapeFullBlock(this.asState(), level, pos); } public ItemStack getCloneItemStack(final LevelReader level, final BlockPos pos, final boolean includeData) { return this.getBlock().getCloneItemStack(level, pos, this.asState(), includeData); } 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 state, BlockPos pos); } public static enum OffsetType { NONE, XZ, XYZ; } @FunctionalInterface public interface PostProcess { @Nullable BlockPos getPostProcessPos(BlockState state, BlockGetter level, BlockPos pos); } public static class Properties implements FabricProperties { public static final Codec CODEC = MapCodec.unitCodec((Supplier)(() -> of())); private Function mapColor = state -> MapColor.NONE; private boolean hasCollision = true; private SoundType soundType = SoundType.STONE; private ToIntFunction lightEmission = state -> 0; private float explosionResistance; private float destroyTime; private boolean requiresCorrectToolForDrops; private boolean isRandomlyTicking; private float friction = 0.6F; private float speedFactor = 1.0F; private float jumpFactor = 1.0F; private float bounceRestitution; @Nullable private ResourceKey id; private DependantName>> drops = id -> Optional.of( ResourceKey.create(Registries.LOOT_TABLE, id.identifier().withPrefix("blocks/")) ); private DependantName descriptionId = id -> Util.makeDescriptionId("block", id.identifier()); private boolean canOcclude = true; private boolean isAir; private boolean ignitedByLava; @Deprecated private boolean liquid; @Deprecated private boolean forceSolidOff; private boolean forceSolidOn; private PushReaction pushReaction = PushReaction.NORMAL; private boolean spawnTerrainParticles = true; private NoteBlockInstrument instrument = NoteBlockInstrument.HARP; private boolean replaceable; private BlockBehaviour.StateArgumentPredicate> isValidSpawn = (state, level, pos, entityType) -> state.isFaceSturdy(level, pos, Direction.UP) && state.getLightEmission() < 14; private BlockBehaviour.StatePredicate isRedstoneConductor = BlockBehaviour.BlockStateBase::isCollisionShapeFullBlock; private BlockBehaviour.StatePredicate isSuffocating = (state, level, pos) -> state.blocksMotion() && state.isCollisionShapeFullBlock(level, pos); private BlockBehaviour.StatePredicate isViewBlocking = this.isSuffocating; private BlockBehaviour.PostProcess postProcess = (state, level, pos) -> null; private Predicate emissiveRendering = var0 -> false; private boolean dynamicShape; private FeatureFlagSet requiredFeatures = FeatureFlags.VANILLA_SET; @Nullable private BlockBehaviour.OffsetFunction offsetFunction; private Properties() { } public static BlockBehaviour.Properties of() { return new BlockBehaviour.Properties(); } public static BlockBehaviour.Properties ofFullCopy(final BlockBehaviour block) { BlockBehaviour.Properties copyTo = ofLegacyCopy(block); BlockBehaviour.Properties copyFrom = block.properties; copyTo.jumpFactor = copyFrom.jumpFactor; copyTo.isRedstoneConductor = copyFrom.isRedstoneConductor; copyTo.isValidSpawn = copyFrom.isValidSpawn; copyTo.postProcess = copyFrom.postProcess; copyTo.isSuffocating = copyFrom.isSuffocating; copyTo.isViewBlocking = copyFrom.isViewBlocking; copyTo.drops = copyFrom.drops; copyTo.descriptionId = copyFrom.descriptionId; return copyTo; } @Deprecated public static BlockBehaviour.Properties ofLegacyCopy(final BlockBehaviour block) { BlockBehaviour.Properties copyTo = new BlockBehaviour.Properties(); BlockBehaviour.Properties copyFrom = block.properties; copyTo.destroyTime = copyFrom.destroyTime; copyTo.explosionResistance = copyFrom.explosionResistance; copyTo.hasCollision = copyFrom.hasCollision; copyTo.isRandomlyTicking = copyFrom.isRandomlyTicking; copyTo.lightEmission = copyFrom.lightEmission; copyTo.mapColor = copyFrom.mapColor; copyTo.soundType = copyFrom.soundType; copyTo.friction = copyFrom.friction; copyTo.speedFactor = copyFrom.speedFactor; copyTo.bounceRestitution = copyFrom.bounceRestitution; copyTo.dynamicShape = copyFrom.dynamicShape; copyTo.canOcclude = copyFrom.canOcclude; copyTo.isAir = copyFrom.isAir; copyTo.ignitedByLava = copyFrom.ignitedByLava; copyTo.liquid = copyFrom.liquid; copyTo.forceSolidOff = copyFrom.forceSolidOff; copyTo.forceSolidOn = copyFrom.forceSolidOn; copyTo.pushReaction = copyFrom.pushReaction; copyTo.requiresCorrectToolForDrops = copyFrom.requiresCorrectToolForDrops; copyTo.offsetFunction = copyFrom.offsetFunction; copyTo.spawnTerrainParticles = copyFrom.spawnTerrainParticles; copyTo.requiredFeatures = copyFrom.requiredFeatures; copyTo.emissiveRendering = copyFrom.emissiveRendering; copyTo.instrument = copyFrom.instrument; copyTo.replaceable = copyFrom.replaceable; return copyTo; } public BlockBehaviour.Properties mapColor(final DyeColor dyeColor) { this.mapColor = state -> dyeColor.getMapColor(); return this; } public BlockBehaviour.Properties mapColor(final MapColor mapColor) { this.mapColor = state -> mapColor; return this; } public BlockBehaviour.Properties mapColor(final Function mapColor) { this.mapColor = mapColor; return this; } public BlockBehaviour.Properties noCollision() { this.hasCollision = false; this.canOcclude = false; return this; } public BlockBehaviour.Properties noOcclusion() { this.canOcclude = false; return this; } public BlockBehaviour.Properties friction(final float friction) { this.friction = friction; return this; } public BlockBehaviour.Properties speedFactor(final float speedFactor) { this.speedFactor = speedFactor; return this; } public BlockBehaviour.Properties jumpFactor(final float jumpFactor) { this.jumpFactor = jumpFactor; return this; } public BlockBehaviour.Properties bounceRestitution(final float bounceRestitution) { this.bounceRestitution = bounceRestitution; return this; } public BlockBehaviour.Properties sound(final SoundType soundType) { this.soundType = soundType; return this; } public BlockBehaviour.Properties lightLevel(final ToIntFunction lightEmission) { this.lightEmission = lightEmission; return this; } public BlockBehaviour.Properties strength(final float destroyTime, final float explosionResistance) { return this.destroyTime(destroyTime).explosionResistance(explosionResistance); } public BlockBehaviour.Properties instabreak() { return this.strength(0.0F); } public BlockBehaviour.Properties strength(final float destroyTime) { this.strength(destroyTime, destroyTime); 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(final Optional> table) { this.drops = DependantName.fixed(table); 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(final PushReaction pushReaction) { this.pushReaction = pushReaction; return this; } public BlockBehaviour.Properties air() { this.isAir = true; return this; } public BlockBehaviour.Properties isValidSpawn(final BlockBehaviour.StateArgumentPredicate> isValidSpawn) { this.isValidSpawn = isValidSpawn; return this; } public BlockBehaviour.Properties isRedstoneConductor(final BlockBehaviour.StatePredicate isRedstoneConductor) { this.isRedstoneConductor = isRedstoneConductor; return this; } public BlockBehaviour.Properties isSuffocating(final BlockBehaviour.StatePredicate isSuffocating) { this.isSuffocating = isSuffocating; return this; } public BlockBehaviour.Properties isViewBlocking(final BlockBehaviour.StatePredicate isViewBlocking) { this.isViewBlocking = isViewBlocking; return this; } public BlockBehaviour.Properties postProcess(final BlockBehaviour.PostProcess postProcess) { this.postProcess = postProcess; return this; } public BlockBehaviour.Properties emissiveRendering(final Predicate emissiveRendering) { this.emissiveRendering = emissiveRendering; return this; } public BlockBehaviour.Properties requiresCorrectToolForDrops() { this.requiresCorrectToolForDrops = true; return this; } public BlockBehaviour.Properties destroyTime(final float destroyTime) { this.destroyTime = destroyTime; return this; } public BlockBehaviour.Properties explosionResistance(final float explosionResistance) { this.explosionResistance = Math.max(0.0F, explosionResistance); return this; } public BlockBehaviour.Properties offsetType(final BlockBehaviour.OffsetType offsetType) { this.offsetFunction = switch (offsetType) { case NONE -> null; case XZ -> (state, pos) -> { Block block = state.getBlock(); long seed = Mth.getSeed(pos.getX(), 0, pos.getZ()); float maxHorizontalOffset = block.getMaxHorizontalOffset(); double x = Mth.clamp(((float)(seed & 15L) / 15.0F - 0.5) * 0.5, (double)(-maxHorizontalOffset), (double)maxHorizontalOffset); double z = Mth.clamp(((float)(seed >> 8 & 15L) / 15.0F - 0.5) * 0.5, (double)(-maxHorizontalOffset), (double)maxHorizontalOffset); return new Vec3(x, 0.0, z); }; case XYZ -> (state, pos) -> { Block block = state.getBlock(); long seed = Mth.getSeed(pos.getX(), 0, pos.getZ()); double y = ((float)(seed >> 4 & 15L) / 15.0F - 1.0) * block.getMaxVerticalOffset(); float maxHorizontalOffset = block.getMaxHorizontalOffset(); double x = Mth.clamp(((float)(seed & 15L) / 15.0F - 0.5) * 0.5, (double)(-maxHorizontalOffset), (double)maxHorizontalOffset); double z = Mth.clamp(((float)(seed >> 8 & 15L) / 15.0F - 0.5) * 0.5, (double)(-maxHorizontalOffset), (double)maxHorizontalOffset); return new Vec3(x, y, z); }; }; return this; } public BlockBehaviour.Properties noTerrainParticles() { this.spawnTerrainParticles = false; return this; } public BlockBehaviour.Properties requiredFeatures(final FeatureFlag... flags) { this.requiredFeatures = FeatureFlags.REGISTRY.subset(flags); return this; } public BlockBehaviour.Properties instrument(final NoteBlockInstrument instrument) { this.instrument = instrument; return this; } public BlockBehaviour.Properties replaceable() { this.replaceable = true; return this; } public BlockBehaviour.Properties setId(final ResourceKey id) { this.id = id; return this; } public BlockBehaviour.Properties overrideDescription(final String descriptionId) { this.descriptionId = DependantName.fixed(descriptionId); 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 state, BlockGetter level, BlockPos pos, A a); } @FunctionalInterface public interface StatePredicate { boolean test(BlockState state, BlockGetter level, BlockPos pos); } }