minecraft-src/net/minecraft/world/entity/item/FallingBlockEntity.java
2025-07-04 02:49:36 +03:00

378 lines
13 KiB
Java

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<BlockPos> DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS);
public FallingBlockEntity(EntityType<? extends FallingBlockEntity> 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<Entity> 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 boolean onlyOpCanSetNbt() {
return true;
}
@Override
public Packet<ClientGamePacketListener> 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<Level> resourceKey = teleportTransition.newLevel().dimension();
ResourceKey<Level> 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;
}
}