package net.minecraft.world.entity.item; import com.mojang.logging.LogUtils; import java.util.function.Predicate; import net.minecraft.CrashReportCategory; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.network.syncher.SynchedEntityData.Builder; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerEntity; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.BlockTags; import net.minecraft.tags.FluidTags; import net.minecraft.util.Mth; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntitySelector; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.MoverType; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.DirectionalPlaceContext; import net.minecraft.world.level.ClipContext; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; import net.minecraft.world.level.ClipContext.Fluid; import net.minecraft.world.level.block.AnvilBlock; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.ConcretePowderBlock; import net.minecraft.world.level.block.Fallable; import net.minecraft.world.level.block.FallingBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.material.Fluids; import net.minecraft.world.level.portal.TeleportTransition; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.HitResult.Type; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class FallingBlockEntity extends Entity { private static final Logger LOGGER = LogUtils.getLogger(); private BlockState blockState = Blocks.SAND.defaultBlockState(); public int time; public boolean dropItem = true; private boolean cancelDrop; private boolean hurtEntities; private int fallDamageMax = 40; private float fallDamagePerDistance; @Nullable public CompoundTag blockData; public boolean forceTickAfterTeleportToDuplicate; protected static final EntityDataAccessor DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS); public FallingBlockEntity(EntityType entityType, Level level) { super(entityType, level); } private FallingBlockEntity(Level level, double x, double y, double z, BlockState state) { this(EntityType.FALLING_BLOCK, level); this.blockState = state; this.blocksBuilding = true; this.setPos(x, y, z); this.setDeltaMovement(Vec3.ZERO); this.xo = x; this.yo = y; this.zo = z; this.setStartPos(this.blockPosition()); } public static FallingBlockEntity fall(Level level, BlockPos pos, BlockState blockState) { FallingBlockEntity fallingBlockEntity = new FallingBlockEntity( level, pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, blockState.hasProperty(BlockStateProperties.WATERLOGGED) ? blockState.setValue(BlockStateProperties.WATERLOGGED, false) : blockState ); level.setBlock(pos, blockState.getFluidState().createLegacyBlock(), 3); level.addFreshEntity(fallingBlockEntity); return fallingBlockEntity; } @Override public boolean isAttackable() { return false; } @Override public final boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) { if (!this.isInvulnerableToBase(damageSource)) { this.markHurt(); } return false; } public void setStartPos(BlockPos startPos) { this.entityData.set(DATA_START_POS, startPos); } public BlockPos getStartPos() { return this.entityData.get(DATA_START_POS); } @Override protected Entity.MovementEmission getMovementEmission() { return Entity.MovementEmission.NONE; } @Override protected void defineSynchedData(Builder builder) { builder.define(DATA_START_POS, BlockPos.ZERO); } @Override public boolean isPickable() { return !this.isRemoved(); } @Override protected double getDefaultGravity() { return 0.04; } @Override public void tick() { if (this.blockState.isAir()) { this.discard(); } else { Block block = this.blockState.getBlock(); this.time++; this.applyGravity(); this.move(MoverType.SELF, this.getDeltaMovement()); this.applyEffectsFromBlocks(); this.handlePortal(); if (this.level() instanceof ServerLevel serverLevel && (this.isAlive() || this.forceTickAfterTeleportToDuplicate)) { BlockPos blockPos = this.blockPosition(); boolean bl = this.blockState.getBlock() instanceof ConcretePowderBlock; boolean bl2 = bl && this.level().getFluidState(blockPos).is(FluidTags.WATER); double d = this.getDeltaMovement().lengthSqr(); if (bl && d > 1.0) { BlockHitResult blockHitResult = this.level() .clip( new ClipContext(new Vec3(this.xo, this.yo, this.zo), this.position(), net.minecraft.world.level.ClipContext.Block.COLLIDER, Fluid.SOURCE_ONLY, this) ); if (blockHitResult.getType() != Type.MISS && this.level().getFluidState(blockHitResult.getBlockPos()).is(FluidTags.WATER)) { blockPos = blockHitResult.getBlockPos(); bl2 = true; } } if (!this.onGround() && !bl2) { if (this.time > 100 && (blockPos.getY() <= this.level().getMinY() || blockPos.getY() > this.level().getMaxY()) || this.time > 600) { if (this.dropItem && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { this.spawnAtLocation(serverLevel, block); } this.discard(); } } else { BlockState blockState = this.level().getBlockState(blockPos); this.setDeltaMovement(this.getDeltaMovement().multiply(0.7, -0.5, 0.7)); if (!blockState.is(Blocks.MOVING_PISTON)) { if (!this.cancelDrop) { boolean bl3 = blockState.canBeReplaced(new DirectionalPlaceContext(this.level(), blockPos, Direction.DOWN, ItemStack.EMPTY, Direction.UP)); boolean bl4 = FallingBlock.isFree(this.level().getBlockState(blockPos.below())) && (!bl || !bl2); boolean bl5 = this.blockState.canSurvive(this.level(), blockPos) && !bl4; if (bl3 && bl5) { if (this.blockState.hasProperty(BlockStateProperties.WATERLOGGED) && this.level().getFluidState(blockPos).getType() == Fluids.WATER) { this.blockState = this.blockState.setValue(BlockStateProperties.WATERLOGGED, true); } if (this.level().setBlock(blockPos, this.blockState, 3)) { ((ServerLevel)this.level()) .getChunkSource() .chunkMap .broadcast(this, new ClientboundBlockUpdatePacket(blockPos, this.level().getBlockState(blockPos))); this.discard(); if (block instanceof Fallable) { ((Fallable)block).onLand(this.level(), blockPos, this.blockState, blockState, this); } if (this.blockData != null && this.blockState.hasBlockEntity()) { BlockEntity blockEntity = this.level().getBlockEntity(blockPos); if (blockEntity != null) { CompoundTag compoundTag = blockEntity.saveWithoutMetadata(this.level().registryAccess()); for (String string : this.blockData.getAllKeys()) { compoundTag.put(string, this.blockData.get(string).copy()); } try { blockEntity.loadWithComponents(compoundTag, this.level().registryAccess()); } catch (Exception var16) { LOGGER.error("Failed to load block entity from falling block", (Throwable)var16); } blockEntity.setChanged(); } } } else if (this.dropItem && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { this.discard(); this.callOnBrokenAfterFall(block, blockPos); this.spawnAtLocation(serverLevel, block); } } else { this.discard(); if (this.dropItem && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { this.callOnBrokenAfterFall(block, blockPos); this.spawnAtLocation(serverLevel, block); } } } else { this.discard(); this.callOnBrokenAfterFall(block, blockPos); } } } } this.setDeltaMovement(this.getDeltaMovement().scale(0.98)); } } public void callOnBrokenAfterFall(Block block, BlockPos pos) { if (block instanceof Fallable) { ((Fallable)block).onBrokenAfterFall(this.level(), pos, this); } } @Override public boolean causeFallDamage(float fallDistance, float multiplier, DamageSource source) { if (!this.hurtEntities) { return false; } else { int i = Mth.ceil(fallDistance - 1.0F); if (i < 0) { return false; } else { Predicate predicate = EntitySelector.NO_CREATIVE_OR_SPECTATOR.and(EntitySelector.LIVING_ENTITY_STILL_ALIVE); DamageSource damageSource = this.blockState.getBlock() instanceof Fallable fallable ? fallable.getFallDamageSource(this) : this.damageSources().fallingBlock(this); float f = Math.min(Mth.floor(i * this.fallDamagePerDistance), this.fallDamageMax); this.level().getEntities(this, this.getBoundingBox(), predicate).forEach(entity -> entity.hurt(damageSource, f)); boolean bl = this.blockState.is(BlockTags.ANVIL); if (bl && f > 0.0F && this.random.nextFloat() < 0.05F + i * 0.05F) { BlockState blockState = AnvilBlock.damage(this.blockState); if (blockState == null) { this.cancelDrop = true; } else { this.blockState = blockState; } } return false; } } } @Override protected void addAdditionalSaveData(CompoundTag tag) { tag.put("BlockState", NbtUtils.writeBlockState(this.blockState)); tag.putInt("Time", this.time); tag.putBoolean("DropItem", this.dropItem); tag.putBoolean("HurtEntities", this.hurtEntities); tag.putFloat("FallHurtAmount", this.fallDamagePerDistance); tag.putInt("FallHurtMax", this.fallDamageMax); if (this.blockData != null) { tag.put("TileEntityData", this.blockData); } tag.putBoolean("CancelDrop", this.cancelDrop); } @Override protected void readAdditionalSaveData(CompoundTag tag) { this.blockState = NbtUtils.readBlockState(this.level().holderLookup(Registries.BLOCK), tag.getCompound("BlockState")); this.time = tag.getInt("Time"); if (tag.contains("HurtEntities", 99)) { this.hurtEntities = tag.getBoolean("HurtEntities"); this.fallDamagePerDistance = tag.getFloat("FallHurtAmount"); this.fallDamageMax = tag.getInt("FallHurtMax"); } else if (this.blockState.is(BlockTags.ANVIL)) { this.hurtEntities = true; } if (tag.contains("DropItem", 99)) { this.dropItem = tag.getBoolean("DropItem"); } if (tag.contains("TileEntityData", 10)) { this.blockData = tag.getCompound("TileEntityData").copy(); } this.cancelDrop = tag.getBoolean("CancelDrop"); if (this.blockState.isAir()) { this.blockState = Blocks.SAND.defaultBlockState(); } } public void setHurtsEntities(float fallDamagePerDistance, int fallDamageMax) { this.hurtEntities = true; this.fallDamagePerDistance = fallDamagePerDistance; this.fallDamageMax = fallDamageMax; } public void disableDrop() { this.cancelDrop = true; } @Override public boolean displayFireAnimation() { return false; } @Override public void fillCrashReportCategory(CrashReportCategory category) { super.fillCrashReportCategory(category); category.setDetail("Immitating BlockState", this.blockState.toString()); } public BlockState getBlockState() { return this.blockState; } @Override protected Component getTypeName() { return Component.translatable("entity.minecraft.falling_block_type", this.blockState.getBlock().getName()); } @Override public Packet getAddEntityPacket(ServerEntity entity) { return new ClientboundAddEntityPacket(this, entity, Block.getId(this.getBlockState())); } @Override public void recreateFromPacket(ClientboundAddEntityPacket packet) { super.recreateFromPacket(packet); this.blockState = Block.stateById(packet.getData()); this.blocksBuilding = true; double d = packet.getX(); double e = packet.getY(); double f = packet.getZ(); this.setPos(d, e, f); this.setStartPos(this.blockPosition()); } @Nullable @Override public Entity teleport(TeleportTransition teleportTransition) { ResourceKey resourceKey = teleportTransition.newLevel().dimension(); ResourceKey resourceKey2 = this.level().dimension(); boolean bl = (resourceKey2 == Level.END || resourceKey == Level.END) && resourceKey2 != resourceKey; Entity entity = super.teleport(teleportTransition); this.forceTickAfterTeleportToDuplicate = entity != null && bl; return entity; } }