package net.minecraft.world.level.block.entity; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import java.util.HashSet; import java.util.Set; import net.minecraft.CrashReportCategory; import net.minecraft.CrashReportDetail; import net.minecraft.core.BlockPos; import net.minecraft.core.HolderLookup; import net.minecraft.core.SectionPos; import net.minecraft.core.component.DataComponentGetter; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.component.DataComponentType; import net.minecraft.core.component.DataComponents; import net.minecraft.core.component.PatchedDataComponentMap; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.ComponentSerialization; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.world.Container; import net.minecraft.world.Containers; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public abstract class BlockEntity { private static final Codec> TYPE_CODEC = BuiltInRegistries.BLOCK_ENTITY_TYPE.byNameCodec(); private static final Logger LOGGER = LogUtils.getLogger(); private final BlockEntityType type; @Nullable protected Level level; protected final BlockPos worldPosition; protected boolean remove; private BlockState blockState; private DataComponentMap components = DataComponentMap.EMPTY; public BlockEntity(BlockEntityType type, BlockPos pos, BlockState blockState) { this.type = type; this.worldPosition = pos.immutable(); this.validateBlockState(blockState); this.blockState = blockState; } private void validateBlockState(BlockState state) { if (!this.isValidBlockState(state)) { throw new IllegalStateException("Invalid block entity " + this.getNameForReporting() + " state at " + this.worldPosition + ", got " + state); } } public boolean isValidBlockState(BlockState state) { return this.type.isValid(state); } public static BlockPos getPosFromTag(ChunkPos chunkPos, CompoundTag tag) { int i = tag.getIntOr("x", 0); int j = tag.getIntOr("y", 0); int k = tag.getIntOr("z", 0); int l = SectionPos.blockToSectionCoord(i); int m = SectionPos.blockToSectionCoord(k); if (l != chunkPos.x || m != chunkPos.z) { LOGGER.warn("Block entity {} found in a wrong chunk, expected position from chunk {}", tag, chunkPos); i = chunkPos.getBlockX(SectionPos.sectionRelative(i)); k = chunkPos.getBlockZ(SectionPos.sectionRelative(k)); } return new BlockPos(i, j, k); } @Nullable public Level getLevel() { return this.level; } public void setLevel(Level level) { this.level = level; } /** * @return whether this BlockEntity's level has been set */ public boolean hasLevel() { return this.level != null; } protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) { } public final void loadWithComponents(CompoundTag tag, HolderLookup.Provider registries) { this.loadAdditional(tag, registries); this.components = (DataComponentMap)tag.read(BlockEntity.ComponentHelper.COMPONENTS_CODEC, registries.createSerializationContext(NbtOps.INSTANCE)) .orElse(DataComponentMap.EMPTY); } public final void loadCustomOnly(CompoundTag tag, HolderLookup.Provider registries) { this.loadAdditional(tag, registries); } protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) { } public final CompoundTag saveWithFullMetadata(HolderLookup.Provider registries) { CompoundTag compoundTag = this.saveWithoutMetadata(registries); this.saveMetadata(compoundTag); return compoundTag; } public final CompoundTag saveWithId(HolderLookup.Provider registries) { CompoundTag compoundTag = this.saveWithoutMetadata(registries); this.saveId(compoundTag); return compoundTag; } public final CompoundTag saveWithoutMetadata(HolderLookup.Provider registries) { CompoundTag compoundTag = new CompoundTag(); this.saveAdditional(compoundTag, registries); compoundTag.store(BlockEntity.ComponentHelper.COMPONENTS_CODEC, registries.createSerializationContext(NbtOps.INSTANCE), this.components); return compoundTag; } public final CompoundTag saveCustomOnly(HolderLookup.Provider registries) { CompoundTag compoundTag = new CompoundTag(); this.saveAdditional(compoundTag, registries); return compoundTag; } public final CompoundTag saveCustomAndMetadata(HolderLookup.Provider registries) { CompoundTag compoundTag = this.saveCustomOnly(registries); this.saveMetadata(compoundTag); return compoundTag; } private void saveId(CompoundTag tag) { addEntityType(tag, this.getType()); } public static void addEntityType(CompoundTag tag, BlockEntityType entityType) { tag.store("id", TYPE_CODEC, entityType); } private void saveMetadata(CompoundTag tag) { this.saveId(tag); tag.putInt("x", this.worldPosition.getX()); tag.putInt("y", this.worldPosition.getY()); tag.putInt("z", this.worldPosition.getZ()); } @Nullable public static BlockEntity loadStatic(BlockPos pos, BlockState state, CompoundTag tag, HolderLookup.Provider registries) { BlockEntityType blockEntityType = (BlockEntityType)tag.read("id", TYPE_CODEC).orElse(null); if (blockEntityType == null) { LOGGER.error("Skipping block entity with invalid type: {}", tag.get("id")); return null; } else { BlockEntity blockEntity; try { blockEntity = blockEntityType.create(pos, state); } catch (Throwable var8) { LOGGER.error("Failed to create block entity {} for block {} at position {} ", blockEntityType, pos, state, var8); return null; } try { blockEntity.loadWithComponents(tag, registries); return blockEntity; } catch (Throwable var7) { LOGGER.error("Failed to load data for block entity {} for block {} at position {}", blockEntityType, pos, state, var7); return null; } } } public void setChanged() { if (this.level != null) { setChanged(this.level, this.worldPosition, this.blockState); } } protected static void setChanged(Level level, BlockPos pos, BlockState state) { level.blockEntityChanged(pos); if (!state.isAir()) { level.updateNeighbourForOutputSignal(pos, state.getBlock()); } } public BlockPos getBlockPos() { return this.worldPosition; } public BlockState getBlockState() { return this.blockState; } @Nullable public Packet getUpdatePacket() { return null; } public CompoundTag getUpdateTag(HolderLookup.Provider registries) { return new CompoundTag(); } public boolean isRemoved() { return this.remove; } /** * Marks this {@code BlockEntity} as no longer valid (removed from the level). */ public void setRemoved() { this.remove = true; } /** * Marks this {@code BlockEntity} as valid again (no longer removed from the level). */ public void clearRemoved() { this.remove = false; } public void preRemoveSideEffects(BlockPos pos, BlockState state) { if (this instanceof Container container && this.level != null) { Containers.dropContents(this.level, pos, container); } } public boolean triggerEvent(int id, int type) { return false; } public void fillCrashReportCategory(CrashReportCategory reportCategory) { reportCategory.setDetail("Name", this::getNameForReporting); reportCategory.setDetail("Cached block", this.getBlockState()::toString); if (this.level == null) { reportCategory.setDetail("Block location", (CrashReportDetail)(() -> this.worldPosition + " (world missing)")); } else { reportCategory.setDetail("Actual block", this.level.getBlockState(this.worldPosition)::toString); CrashReportCategory.populateBlockLocationDetails(reportCategory, this.level, this.worldPosition); } } private String getNameForReporting() { return BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(this.getType()) + " // " + this.getClass().getCanonicalName(); } public BlockEntityType getType() { return this.type; } @Deprecated public void setBlockState(BlockState blockState) { this.validateBlockState(blockState); this.blockState = blockState; } protected void applyImplicitComponents(DataComponentGetter componentGetter) { } public final void applyComponentsFromItemStack(ItemStack stack) { this.applyComponents(stack.getPrototype(), stack.getComponentsPatch()); } public final void applyComponents(DataComponentMap components, DataComponentPatch patch) { final Set> set = new HashSet(); set.add(DataComponents.BLOCK_ENTITY_DATA); set.add(DataComponents.BLOCK_STATE); final DataComponentMap dataComponentMap = PatchedDataComponentMap.fromPatch(components, patch); this.applyImplicitComponents(new DataComponentGetter() { @Nullable @Override public T get(DataComponentType component) { set.add(component); return dataComponentMap.get(component); } @Override public T getOrDefault(DataComponentType component, T defaultValue) { set.add(component); return dataComponentMap.getOrDefault(component, defaultValue); } }); DataComponentPatch dataComponentPatch = patch.forget(set::contains); this.components = dataComponentPatch.split().added(); } protected void collectImplicitComponents(DataComponentMap.Builder components) { } @Deprecated public void removeComponentsFromTag(CompoundTag tag) { } public final DataComponentMap collectComponents() { DataComponentMap.Builder builder = DataComponentMap.builder(); builder.addAll(this.components); this.collectImplicitComponents(builder); return builder.build(); } public DataComponentMap components() { return this.components; } public void setComponents(DataComponentMap components) { this.components = components; } @Nullable public static Component parseCustomNameSafe(@Nullable Tag tag, HolderLookup.Provider registries) { return tag == null ? null : (Component)ComponentSerialization.CODEC .parse(registries.createSerializationContext(NbtOps.INSTANCE), tag) .resultOrPartial(string -> LOGGER.warn("Failed to parse custom name, discarding: {}", string)) .orElse(null); } static class ComponentHelper { public static final MapCodec COMPONENTS_CODEC = DataComponentMap.CODEC.optionalFieldOf("components", DataComponentMap.EMPTY); private ComponentHelper() { } } }