minecraft-src/net/minecraft/world/level/block/entity/BlockEntity.java
2025-07-04 03:45:38 +03:00

333 lines
11 KiB
Java

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<BlockEntityType<?>> 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<ClientGamePacketListener> 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<String>)(() -> 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<DataComponentType<?>> 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> T get(DataComponentType<? extends T> component) {
set.add(component);
return dataComponentMap.get(component);
}
@Override
public <T> T getOrDefault(DataComponentType<? extends T> 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<DataComponentMap> COMPONENTS_CODEC = DataComponentMap.CODEC.optionalFieldOf("components", DataComponentMap.EMPTY);
private ComponentHelper() {
}
}
}