package net.minecraft.world.level.block.entity; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; 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.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.util.ProblemReporter; 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 net.minecraft.world.level.storage.TagValueInput; import net.minecraft.world.level.storage.TagValueOutput; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; 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(ValueInput input) { } public final void loadWithComponents(ValueInput input) { this.loadAdditional(input); this.components = (DataComponentMap)input.read("components", DataComponentMap.CODEC).orElse(DataComponentMap.EMPTY); } public final void loadCustomOnly(ValueInput input) { this.loadAdditional(input); } protected void saveAdditional(ValueOutput output) { } public final CompoundTag saveWithFullMetadata(HolderLookup.Provider registries) { CompoundTag var4; try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(this.problemPath(), LOGGER)) { TagValueOutput tagValueOutput = TagValueOutput.createWithContext(scopedCollector, registries); this.saveWithFullMetadata(tagValueOutput); var4 = tagValueOutput.buildResult(); } return var4; } public void saveWithFullMetadata(ValueOutput output) { this.saveWithoutMetadata(output); this.saveMetadata(output); } public void saveWithId(ValueOutput output) { this.saveWithoutMetadata(output); this.saveId(output); } public final CompoundTag saveWithoutMetadata(HolderLookup.Provider registries) { CompoundTag var4; try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(this.problemPath(), LOGGER)) { TagValueOutput tagValueOutput = TagValueOutput.createWithContext(scopedCollector, registries); this.saveWithoutMetadata(tagValueOutput); var4 = tagValueOutput.buildResult(); } return var4; } public void saveWithoutMetadata(ValueOutput output) { this.saveAdditional(output); output.store("components", DataComponentMap.CODEC, this.components); } public final CompoundTag saveCustomOnly(HolderLookup.Provider registries) { CompoundTag var4; try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(this.problemPath(), LOGGER)) { TagValueOutput tagValueOutput = TagValueOutput.createWithContext(scopedCollector, registries); this.saveCustomOnly(tagValueOutput); var4 = tagValueOutput.buildResult(); } return var4; } public void saveCustomOnly(ValueOutput output) { this.saveAdditional(output); } private void saveId(ValueOutput output) { addEntityType(output, this.getType()); } public static void addEntityType(ValueOutput output, BlockEntityType entityType) { output.store("id", TYPE_CODEC, entityType); } private void saveMetadata(ValueOutput output) { this.saveId(output); output.putInt("x", this.worldPosition.getX()); output.putInt("y", this.worldPosition.getY()); output.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 var12) { LOGGER.error("Failed to create block entity {} for block {} at position {} ", blockEntityType, pos, state, var12); return null; } try { BlockEntity var7; try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(blockEntity.problemPath(), LOGGER)) { blockEntity.loadWithComponents(TagValueInput.create(scopedCollector, registries, tag)); var7 = blockEntity; } return var7; } catch (Throwable var11) { LOGGER.error("Failed to load data for block entity {} for block {} at position {}", blockEntityType, pos, state, var11); 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); } } public 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(ValueOutput output) { } 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(ValueInput input, String customName) { return (Component)input.read(customName, ComponentSerialization.CODEC).orElse(null); } public ProblemReporter.PathElement problemPath() { return new BlockEntity.BlockEntityPathElement(this); } record BlockEntityPathElement(BlockEntity blockEntity) implements ProblemReporter.PathElement { @Override public String get() { return this.blockEntity.getNameForReporting() + "@" + this.blockEntity.getBlockPos(); } } }