From ea04d711a23f754e8e8e6f0d644d8fbd53e608bd Mon Sep 17 00:00:00 2001 From: Nightenom <17338378+Nightenom@users.noreply.github.com> Date: Mon, 28 Oct 2024 08:06:41 +0100 Subject: [PATCH] Fix tag block related issues (#704) --- gradle.properties | 2 +- .../structurize/api/RotationMirror.java | 33 +++ .../BlockEntityTagSubstitution.java | 264 +++++------------- .../structurize/blueprints/v1/Blueprint.java | 22 +- .../structurize/client/BlueprintRenderer.java | 8 +- .../client/TagSubstitutionRenderer.java | 67 +++-- .../fakelevel/BlueprintBlockAccess.java | 4 +- .../structurize/component/CapturedBlock.java | 116 ++++++++ .../component/ModDataComponents.java | 2 + .../event/ClientLifecycleSubscriber.java | 17 ++ .../items/ItemTagSubstitution.java | 70 +---- .../placement/StructurePlacer.java | 8 +- 12 files changed, 304 insertions(+), 309 deletions(-) create mode 100644 src/main/java/com/ldtteam/structurize/component/CapturedBlock.java diff --git a/gradle.properties b/gradle.properties index f976bf32b..ddbd51fa4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,7 +25,7 @@ minecraftVersion=1.21.1 #Comma seperated list of mc versions, which are marked as compatible on curseforge additionalMinecraftVersions=1.21 -blockUiVersion=1.0.188-1.21.1-snapshot +blockUiVersion=1.0.191-1.21.1-snapshot domumOrnamentumVersion=1.0.203-1.21.1-snapshot githubUrl=https://github.com/ldtteam/Structurize diff --git a/src/main/java/com/ldtteam/structurize/api/RotationMirror.java b/src/main/java/com/ldtteam/structurize/api/RotationMirror.java index 65344ce74..806308265 100644 --- a/src/main/java/com/ldtteam/structurize/api/RotationMirror.java +++ b/src/main/java/com/ldtteam/structurize/api/RotationMirror.java @@ -1,13 +1,16 @@ package com.ldtteam.structurize.api; import com.ldtteam.common.codec.Codecs; +import com.ldtteam.structurize.blueprints.FacingFixer; import com.mojang.serialization.Codec; import io.netty.buffer.ByteBuf; import net.minecraft.core.BlockPos; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Mirror; import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; import net.minecraft.world.phys.Vec3; @@ -181,6 +184,36 @@ public Vec3 applyToPos(final Vec3 pos, final BlockPos pivot) return StructureTemplate.transform(pos, mirror, rotation, pivot); } + /** + * @param blockState blockState to transform + * @return transformed blockState using this rot+mir + * @deprecated use {@link #applyToBlockState(BlockState, Level, BlockPos)}, see vanilla methods for more info + */ + @Deprecated + public BlockState applyToBlockState(BlockState blockState) + { + if (isMirrored()) + { + blockState = FacingFixer.fixMirroredFacing(blockState.mirror(mirror), blockState); + } + return blockState.rotate(rotation); + } + + /** + * @param blockState blockState to transform + * @param level in which given blockState lives + * @param pos where the given blockState is in given level + * @return transformed blockState using this rot+mir + */ + public BlockState applyToBlockState(BlockState blockState, final Level level, final BlockPos pos) + { + if (isMirrored()) + { + blockState = FacingFixer.fixMirroredFacing(blockState.mirror(mirror), blockState); + } + return blockState.rotate(level, pos, rotation); + } + /** * @param end in which state we should end * @return end - this = what it takes from this to end diff --git a/src/main/java/com/ldtteam/structurize/blockentities/BlockEntityTagSubstitution.java b/src/main/java/com/ldtteam/structurize/blockentities/BlockEntityTagSubstitution.java index 41c60efc6..c7f93b764 100644 --- a/src/main/java/com/ldtteam/structurize/blockentities/BlockEntityTagSubstitution.java +++ b/src/main/java/com/ldtteam/structurize/blockentities/BlockEntityTagSubstitution.java @@ -1,22 +1,24 @@ package com.ldtteam.structurize.blockentities; import com.ldtteam.structurize.blockentities.interfaces.IBlueprintDataProviderBE; -import com.ldtteam.structurize.blueprints.v1.Blueprint; -import com.ldtteam.structurize.api.RotationMirror; +import com.ldtteam.structurize.api.Log; +import com.ldtteam.structurize.component.CapturedBlock; +import com.ldtteam.structurize.component.ModDataComponents; +import com.mojang.serialization.DynamicOps; import net.minecraft.core.BlockPos; import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.NbtUtils; +import net.minecraft.nbt.Tag; import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; import net.minecraft.util.Tuple; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.*; @@ -25,6 +27,12 @@ */ public class BlockEntityTagSubstitution extends BlockEntity implements IBlueprintDataProviderBE { + public static final String CAPTURED_BLOCK_TAG = "captured_block"; + /** + * Up to 1.21.1 + */ + public static final String CAPTURED_BLOCK_TAG_OLD = "replacement"; + /** * The schematic name of the block. */ @@ -54,7 +62,7 @@ public class BlockEntityTagSubstitution extends BlockEntity implements IBlueprin /** * Replacement block. */ - private ReplacementBlock replacement = new ReplacementBlock(); + private CapturedBlock replacement = CapturedBlock.EMPTY; public BlockEntityTagSubstitution(final BlockPos pos, final BlockState state) { @@ -114,7 +122,7 @@ public BlockPos getTilePos() * @return the replacement block details */ @NotNull - public ReplacementBlock getReplacement() + public CapturedBlock getReplacement() { return this.replacement; } @@ -123,16 +131,49 @@ public ReplacementBlock getReplacement() public void loadAdditional( @NotNull final CompoundTag compound, final HolderLookup.Provider provider) { super.loadAdditional(compound, provider); + final DynamicOps dynamicOps = provider.createSerializationContext(NbtOps.INSTANCE); + IBlueprintDataProviderBE.super.readSchematicDataFromNBT(compound); - this.replacement = new ReplacementBlock(compound, provider); + if (compound.contains(CAPTURED_BLOCK_TAG_OLD, Tag.TAG_COMPOUND)) + { + final CompoundTag oldNbt = compound.getCompound(CAPTURED_BLOCK_TAG_OLD); + replacement = new CapturedBlock(NbtUtils.readBlockState(BuiltInRegistries.BLOCK.asLookup(), oldNbt.getCompound("b")), + Optional.of(oldNbt.getCompound("e")), + oldNbt.contains("i") ? ItemStack.parseOptional(provider, oldNbt.getCompound("i")) : ItemStack.EMPTY); + } + else + { + replacement = deserializeReplacement(compound, dynamicOps); + } + } + + public static CapturedBlock deserializeReplacement(final CompoundTag compound, final DynamicOps dynamicOps) + { + if (compound.getCompound(CAPTURED_BLOCK_TAG).isEmpty()) + { + return CapturedBlock.EMPTY; + } + return CapturedBlock.CODEC.parse(dynamicOps, compound.get(CAPTURED_BLOCK_TAG)).resultOrPartial(error -> { + Log.getLogger() + .error("Parsing {} with data {}: {}", ModBlockEntities.TAG_SUBSTITUTION.getRegisteredName(), compound, error); + Log.getLogger().error("", new RuntimeException()); + }).orElse(CapturedBlock.EMPTY); } @Override public void saveAdditional(@NotNull final CompoundTag compound, final HolderLookup.Provider provider) { super.saveAdditional(compound, provider); + final DynamicOps dynamicOps = provider.createSerializationContext(NbtOps.INSTANCE); writeSchematicDataToNBT(compound); - this.replacement.write(compound, provider); + + // this is still needed even with data components as of 1.21 + serializeReplacement(compound, dynamicOps, replacement); + } + + public static void serializeReplacement(final CompoundTag compound, final DynamicOps dynamicOps, final CapturedBlock replacement) + { + compound.put(CAPTURED_BLOCK_TAG, CapturedBlock.CODEC.encodeStart(dynamicOps, replacement).getOrThrow()); } @Override @@ -169,199 +210,26 @@ public String getBlueprintPath() @Override public CompoundTag getUpdateTag(final HolderLookup.Provider provider) { - final CompoundTag tag = new CompoundTag(); - this.saveAdditional(tag, provider); - return tag; + return saveCustomOnly(provider); } - /** - * Storage for information about the replacement block, if any. - */ - public static class ReplacementBlock + @Override + protected void applyImplicitComponents(final BlockEntity.DataComponentInput componentInput) { - private static final String TAG_REPLACEMENT = "replacement"; - - private final BlockState blockstate; - private final CompoundTag blockentitytag; - private final ItemStack itemstack; - - @Nullable private BlockEntity cachedBlockentity; - - /** - * Construct - * @param blockstate the block state - * @param blockentity the block entity, if any - * @param itemstack the item stack - */ - @Deprecated(forRemoval = true, since = "1.21") - public ReplacementBlock(@NotNull final BlockState blockstate, - @Nullable final BlockEntity blockentity, - @NotNull final ItemStack itemstack) - { - throw new UnsupportedOperationException("Use compound tag ctor"); - } - - /** - * Construct - * @param blockstate the block state - * @param blockentity the block entity tag, if any - * @param itemstack the item stack - */ - public ReplacementBlock(@NotNull final BlockState blockstate, - @Nullable final CompoundTag blockentity, - @NotNull final ItemStack itemstack) - { - this.blockstate = blockstate; - this.blockentitytag = blockentity == null ? new CompoundTag() : blockentity.copy(); - this.itemstack = itemstack; - } - - /** - * Construct from tag - * @param tag the tag to load - */ - public ReplacementBlock(@NotNull CompoundTag tag, final HolderLookup.Provider provider) - { - final CompoundTag replacement = tag.getCompound(TAG_REPLACEMENT); - this.blockstate = NbtUtils.readBlockState(BuiltInRegistries.BLOCK.asLookup(), replacement.getCompound("b")); - this.blockentitytag = replacement.getCompound("e"); - this.itemstack = replacement.contains("i") ? ItemStack.parseOptional(provider, replacement.getCompound("i")) : ItemStack.EMPTY; - } - - /** - * Empty instance - */ - public ReplacementBlock() - { - this.blockstate = Blocks.AIR.defaultBlockState(); - this.blockentitytag = new CompoundTag(); - this.itemstack = ItemStack.EMPTY; - } - - /** - * @return true if there is no replacement block set (assume air) - */ - public boolean isEmpty() - { - return this.blockstate.isAir(); - } - - /** - * @return the block state - */ - @NotNull - public BlockState getBlockState() - { - return this.blockstate; - } - - /** - * @return the block entity tag - */ - @NotNull - public CompoundTag getBlockEntityTag() - { - return this.blockentitytag; - } - - /** - * @return the item stack - */ - @NotNull - public ItemStack getItemStack() - { - return this.itemstack; - } - - /** - * Creates and loads (once) the replacement block entity, or returns the preloaded one. - * @param pos the blockpos to use (ignored if already loaded) - * @return the new or cached entity, or null if there isn't one - */ - @Nullable - public BlockEntity getBlockEntity(final BlockPos pos, final HolderLookup.Provider provider) - { - if (this.cachedBlockentity == null) - { - this.cachedBlockentity = createBlockEntity(pos, provider); - } - return this.cachedBlockentity; - } - - /** - * Always creates and loads a new replacement block entity, if needed. - * @param pos the blockpos to use - * @return the new entity, or null if there isn't one - */ - @Nullable - public BlockEntity createBlockEntity(final BlockPos pos, final HolderLookup.Provider provider) - { - return this.blockentitytag.isEmpty() - ? null - : BlockEntity.loadStatic(pos, this.blockstate, this.blockentitytag, provider); - } - - /** - * Serialisation - * @param tag the target tag - * @return the target tag, for convenience - */ - @NotNull - public CompoundTag write(@NotNull CompoundTag tag, final HolderLookup.Provider provider) - { - if (isEmpty()) - { - tag.remove(TAG_REPLACEMENT); - } - else - { - final CompoundTag replacement = new CompoundTag(); - replacement.put("b", NbtUtils.writeBlockState(this.blockstate)); - if (this.blockentitytag.isEmpty()) - { - replacement.remove("e"); - } - else - { - replacement.put("e", this.blockentitytag); - } - replacement.put("i", this.itemstack.save(provider)); - - tag.put(TAG_REPLACEMENT, replacement); - } - return tag; - } - - /** - * Creates a new single-block {@link Blueprint} for the replacement block. - * @return the blueprint - */ - @NotNull - public Blueprint createBlueprint(final HolderLookup.Provider provider) - { - final Blueprint blueprint = new Blueprint((short) 1, (short) 1, (short) 1, provider); - blueprint.addBlockState(BlockPos.ZERO, getBlockState()); - blueprint.getTileEntities()[0][0][0] = getBlockEntityTag().isEmpty() ? null : getBlockEntityTag().copy(); - return blueprint; - } + super.applyImplicitComponents(componentInput); + replacement = componentInput.getOrDefault(ModDataComponents.CAPTURED_BLOCK, CapturedBlock.EMPTY); + } - /** - * Rotates and mirrors the replacement data, in response to a blueprint containing this replacement block - * being rotated or mirrored. - * - * @param pos the world location for the replacement block - * @param rotationMirror the relative rotation/mirror - * @param level the (actual) world - * @return the new replacement data - */ - public ReplacementBlock rotateWithMirror(final BlockPos pos, final RotationMirror rotationMirror, final Level level) - { - final Blueprint blueprint = createBlueprint(level.registryAccess()); - blueprint.setRotationMirrorRelative(rotationMirror, level); + @Override + protected void collectImplicitComponents(final DataComponentMap.Builder componentBuilder) + { + super.collectImplicitComponents(componentBuilder); + componentBuilder.set(ModDataComponents.CAPTURED_BLOCK, replacement); + } - final BlockState newBlockState = blueprint.getBlockState(BlockPos.ZERO); - final CompoundTag newBlockData = blueprint.getTileEntityData(pos, BlockPos.ZERO); - return new ReplacementBlock(newBlockState, newBlockData, this.getItemStack()); - } + @Override + public void removeComponentsFromTag(final CompoundTag itemStackTag) + { + itemStackTag.remove(CAPTURED_BLOCK_TAG); } } diff --git a/src/main/java/com/ldtteam/structurize/blueprints/v1/Blueprint.java b/src/main/java/com/ldtteam/structurize/blueprints/v1/Blueprint.java index 4a6ee1d2e..a771e084e 100644 --- a/src/main/java/com/ldtteam/structurize/blueprints/v1/Blueprint.java +++ b/src/main/java/com/ldtteam/structurize/blueprints/v1/Blueprint.java @@ -8,17 +8,20 @@ import com.ldtteam.structurize.blockentities.ModBlockEntities; import com.ldtteam.structurize.blocks.ModBlocks; import com.ldtteam.structurize.blocks.interfaces.IAnchorBlock; -import com.ldtteam.structurize.blueprints.FacingFixer; +import com.ldtteam.structurize.component.CapturedBlock; import com.ldtteam.structurize.blockentities.interfaces.IBlueprintDataProviderBE; import com.ldtteam.structurize.util.BlockInfo; import com.ldtteam.structurize.util.BlockUtils; import com.ldtteam.structurize.util.BlueprintPositionInfo; +import com.mojang.serialization.DynamicOps; import com.ldtteam.structurize.api.RotationMirror; import net.minecraft.CrashReportCategory; import net.minecraft.core.BlockPos; import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.decoration.HangingEntity; @@ -653,6 +656,7 @@ public void setRotationMirrorRelative(final RotationMirror transformBy, final Le return; } + final DynamicOps dynamicNbtOps = registryAccess.createSerializationContext(NbtOps.INSTANCE); final BlockPos primaryOffset = getPrimaryBlockOffset(); final short newSizeX, newSizeZ, newSizeY = sizeY; @@ -677,14 +681,7 @@ public void setRotationMirrorRelative(final RotationMirror transformBy, final Le final List palette = new ArrayList<>(); for (int i = 0; i < this.palette.size(); i++) { - BlockState bs = this.palette.get(i); - - if (transformBy.isMirrored()) - { - bs = FacingFixer.fixMirroredFacing(bs.mirror(transformBy.mirror()), bs); - } - - palette.add(i, bs.rotate(transformBy.rotation())); + palette.add(i, transformBy.applyToBlockState(this.palette.get(i))); } final BlockPos extremes = transformBy.applyToPos(new BlockPos(sizeX, sizeY, sizeZ)); @@ -723,10 +720,9 @@ public void setRotationMirrorRelative(final RotationMirror transformBy, final Le // for now this is the minimal requirement. if (compound.getString("id").equals(ModBlockEntities.TAG_SUBSTITUTION.getId().toString())) { - BlockEntityTagSubstitution.ReplacementBlock replacement = - new BlockEntityTagSubstitution.ReplacementBlock(compound, registryAccess); - replacement = replacement.rotateWithMirror(tempPos, transformBy, level); - replacement.write(compound, registryAccess); + CapturedBlock replacement = BlockEntityTagSubstitution.deserializeReplacement(compound, dynamicNbtOps); + replacement = replacement.applyRotationMirror(transformBy, level); + BlockEntityTagSubstitution.serializeReplacement(compound, dynamicNbtOps, replacement); } if (compound.contains(TAG_BLUEPRINTDATA)) diff --git a/src/main/java/com/ldtteam/structurize/client/BlueprintRenderer.java b/src/main/java/com/ldtteam/structurize/client/BlueprintRenderer.java index 7eb75ee16..876fcb170 100644 --- a/src/main/java/com/ldtteam/structurize/client/BlueprintRenderer.java +++ b/src/main/java/com/ldtteam/structurize/client/BlueprintRenderer.java @@ -6,6 +6,7 @@ import com.ldtteam.structurize.blueprints.v1.Blueprint; import com.ldtteam.structurize.blueprints.v1.BlueprintUtils; import com.ldtteam.structurize.client.fakelevel.BlueprintBlockAccess; +import com.ldtteam.structurize.component.CapturedBlock; import com.ldtteam.structurize.storage.rendering.types.BlueprintPreviewData; import com.ldtteam.structurize.tag.ModTags; import com.ldtteam.structurize.util.BlockInfo; @@ -79,7 +80,6 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; /** @@ -159,10 +159,10 @@ private void init(final Blueprint blueprint, final Map suppre { if (tileEntitiesMap.remove(blockPos) instanceof final BlockEntityTagSubstitution tagTE) { - final BlockEntityTagSubstitution.ReplacementBlock replacement = tagTE.getReplacement(); - state = replacement.getBlockState(); + final CapturedBlock replacement = tagTE.getReplacement(); + state = replacement.blockState(); - Optional.ofNullable(replacement.createBlockEntity(blockPos, blueprint.getRegistryAccess())).ifPresent(newBe -> { + replacement.serializedBE().map(tag -> BlockEntity.loadStatic(blockPos, replacement.blockState(), tag, blueprint.getRegistryAccess())).ifPresent(newBe -> { newBe.setLevel(blockAccess); teModelData.put(blockPos, newBe.getModelData()); tileEntitiesMap.put(blockPos, newBe); diff --git a/src/main/java/com/ldtteam/structurize/client/TagSubstitutionRenderer.java b/src/main/java/com/ldtteam/structurize/client/TagSubstitutionRenderer.java index 428b9c5d4..ce8fbc729 100644 --- a/src/main/java/com/ldtteam/structurize/client/TagSubstitutionRenderer.java +++ b/src/main/java/com/ldtteam/structurize/client/TagSubstitutionRenderer.java @@ -1,8 +1,8 @@ package com.ldtteam.structurize.client; +import com.ldtteam.common.fakelevel.SingleBlockFakeLevel; import com.ldtteam.structurize.blockentities.BlockEntityTagSubstitution; -import com.ldtteam.structurize.blueprints.v1.Blueprint; -import com.ldtteam.structurize.client.fakelevel.BlueprintBlockAccess; +import com.ldtteam.structurize.component.CapturedBlock; import com.ldtteam.structurize.items.ItemTagSubstitution; import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; @@ -15,7 +15,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.neoforged.neoforge.client.NeoForgeRenderTypes; import net.neoforged.neoforge.client.model.data.ModelData; @@ -35,6 +35,7 @@ public static TagSubstitutionRenderer getInstance() private final BlockEntityRendererProvider.Context context; + private SingleBlockFakeLevel renderLevel; public TagSubstitutionRenderer(@NotNull final BlockEntityRendererProvider.Context context) { @@ -72,11 +73,11 @@ public void renderByItem(@NotNull final ItemStack stack, this.context.getBlockRenderDispatcher().renderSingleBlock(anchor.getBlock().defaultBlockState(), poseStack, buffers, packedLight, packedOverlay, ModelData.EMPTY, renderType); - render(anchor.getAbsorbedBlock(stack, context.getBlockEntityRenderDispatcher().level.registryAccess()), BlockPos.ZERO, 0, poseStack, buffers, packedLight, packedOverlay, renderType); + render(CapturedBlock.readFromItemStack(stack), BlockPos.ZERO, 0, poseStack, buffers, packedLight, packedOverlay, renderType); } } - private void render(@NotNull final BlockEntityTagSubstitution.ReplacementBlock replacement, + private void render(@NotNull final CapturedBlock replacement, @NotNull final BlockPos pos, final float partialTick, @NotNull PoseStack poseStack, @@ -85,35 +86,45 @@ private void render(@NotNull final BlockEntityTagSubstitution.ReplacementBlock r final int packedOverlay, @NotNull final RenderType renderType) { - if (!replacement.isEmpty()) + if (replacement.blockState().isAir()) { - poseStack.pushPose(); - poseStack.scale(0.98f,0.98f,0.98f); - poseStack.translate(0.01f, 0.01f, 0.01f); + return; + } - final BlockRenderDispatcher dispatcher = this.context.getBlockRenderDispatcher(); + poseStack.pushPose(); + poseStack.scale(0.995f, 0.995f, 0.995f); + poseStack.translate(0.0025f, 0.0025f, 0.0025f); - final BlockEntity replacementEntity = replacement.getBlockEntity(pos, context.getBlockEntityRenderDispatcher().level.registryAccess()); - if (replacementEntity != null) - { - // seems a little silly to create a blueprint, but the entityDispatcher won't render without a level... - final Blueprint blueprint = replacement.createBlueprint(context.getBlockEntityRenderDispatcher().level.registryAccess()); - final BlueprintBlockAccess blockAccess = new BlueprintBlockAccess(blueprint); - replacementEntity.setLevel(blockAccess); - - final BlockEntityRenderDispatcher entityDispatcher = this.context.getBlockEntityRenderDispatcher(); - if (replacement.getBlockState().getRenderShape() == RenderShape.MODEL) - { - dispatcher.renderSingleBlock(replacement.getBlockState(), poseStack, buffers, packedLight, packedOverlay, replacementEntity.getModelData(), renderType); - } - entityDispatcher.render(replacementEntity, partialTick, poseStack, buffers); - } - else + if (replacement.hasBlockEntity()) + { + final BlockEntityRenderDispatcher entityDispatcher = this.context.getBlockEntityRenderDispatcher(); + final Level realLevel = entityDispatcher.level; + if (renderLevel == null) { - dispatcher.renderSingleBlock(replacement.getBlockState(), poseStack, buffers, packedLight, packedOverlay, ModelData.EMPTY, renderType); + renderLevel = new SingleBlockFakeLevel(realLevel); } - poseStack.popPose(); + renderLevel.withFakeLevelContext(replacement.blockState(), + BlockEntity.loadStatic(BlockPos.ZERO, replacement.blockState(), replacement.serializedBE().get(), realLevel.registryAccess()), + realLevel, + fakeLevel -> { + context.getBlockRenderDispatcher() + .renderSingleBlock(replacement.blockState(), + poseStack, + buffers, + packedLight, + packedOverlay, + renderLevel.getLevelSource().blockEntity.getModelData(), + renderType); + entityDispatcher.render(renderLevel.getLevelSource().blockEntity, partialTick, poseStack, buffers); + }); } + else + { + context.getBlockRenderDispatcher() + .renderSingleBlock(replacement.blockState(), poseStack, buffers, packedLight, packedOverlay, ModelData.EMPTY, renderType); + } + + poseStack.popPose(); } } diff --git a/src/main/java/com/ldtteam/structurize/client/fakelevel/BlueprintBlockAccess.java b/src/main/java/com/ldtteam/structurize/client/fakelevel/BlueprintBlockAccess.java index d127d0ea3..ea9b137a9 100644 --- a/src/main/java/com/ldtteam/structurize/client/fakelevel/BlueprintBlockAccess.java +++ b/src/main/java/com/ldtteam/structurize/client/fakelevel/BlueprintBlockAccess.java @@ -58,9 +58,9 @@ else if (state.getBlock() == ModBlocks.blockSubstitution.get()) } else if (state.getBlock() == ModBlocks.blockTagSubstitution.get()) { - if (super.getBlockEntity(pos) instanceof final BlockEntityTagSubstitution tag && !tag.getReplacement().isEmpty()) + if (super.getBlockEntity(pos) instanceof final BlockEntityTagSubstitution tag) { - return tag.getReplacement().getBlockState(); + return tag.getReplacement().blockState(); } return Blocks.AIR.defaultBlockState(); } diff --git a/src/main/java/com/ldtteam/structurize/component/CapturedBlock.java b/src/main/java/com/ldtteam/structurize/component/CapturedBlock.java new file mode 100644 index 000000000..4890ed694 --- /dev/null +++ b/src/main/java/com/ldtteam/structurize/component/CapturedBlock.java @@ -0,0 +1,116 @@ +package com.ldtteam.structurize.component; + +import com.ldtteam.structurize.api.RotationMirror; +import com.ldtteam.structurize.blueprints.v1.Blueprint; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; +import java.util.Optional; +import java.util.function.UnaryOperator; + +/** + * @param blockState state of captured block + * @param serializedBE related blockEntity data if needed + * @param itemStack itemStack representing both block and blockEntity + */ +public record CapturedBlock(BlockState blockState, Optional serializedBE, ItemStack itemStack) +{ + public static final CapturedBlock EMPTY = new CapturedBlock(Blocks.AIR.defaultBlockState(), Optional.empty(), ItemStack.EMPTY); + + public static final Codec CODEC = RecordCodecBuilder.create( + builder -> builder + .group(BlockState.CODEC.fieldOf("state").forGetter(CapturedBlock::blockState), + CompoundTag.CODEC.optionalFieldOf("entity").forGetter(CapturedBlock::serializedBE), + ItemStack.OPTIONAL_CODEC.fieldOf("item").forGetter(CapturedBlock::itemStack)) + .apply(builder, CapturedBlock::new)); + + public static final StreamCodec STREAM_CODEC = + StreamCodec.composite(ByteBufCodecs.idMapper(Block.BLOCK_STATE_REGISTRY), + CapturedBlock::blockState, + ByteBufCodecs.OPTIONAL_COMPOUND_TAG, + CapturedBlock::serializedBE, + ItemStack.STREAM_CODEC, + CapturedBlock::itemStack, + CapturedBlock::new); + + /** + * Serializes given BE. + * + * @param blockState state of captured block + * @param blockEntity related blockEntity data if needed + * @param provider registry access + * @param itemStack itemStack representing both block and blockEntity + */ + public CapturedBlock(final BlockState blockState, + @Nullable final BlockEntity blockEntity, + final HolderLookup.Provider provider, + final ItemStack itemStack) + { + this(blockState, blockEntity == null ? Optional.empty() : Optional.of(blockEntity.saveWithId(provider)), itemStack); + } + + /** + * @param rotationMirror relative rotation and mirror + * @param level registry access + */ + public CapturedBlock applyRotationMirror(final RotationMirror rotationMirror, final Level level) + { + if (serializedBE.isEmpty()) + { + return new CapturedBlock(rotationMirror.applyToBlockState(blockState), serializedBE, itemStack); + } + + final Blueprint blueprint = new Blueprint((short) 1, (short) 1, (short) 1, level.registryAccess()); + blueprint.addBlockState(BlockPos.ZERO, blockState); + blueprint.getTileEntities()[0][0][0] = serializedBE.get(); + blueprint.setCachePrimaryOffset(BlockPos.ZERO); + blueprint.setRotationMirrorRelative(rotationMirror, level); + + return new CapturedBlock(blueprint.getPalette()[blueprint.getPalleteSize()], + Optional.of(blueprint.getTileEntities()[0][0][0]), + itemStack); + } + + public boolean hasBlockEntity() + { + return serializedBE.isPresent() && !serializedBE.get().isEmpty(); + } + + /** + * Writes this posSelection into given itemStack. + * + * @see BlockEntity#saveToItem(ItemStack, net.minecraft.core.HolderLookup.Provider) + */ + public void writeToItemStack(final ItemStack itemStack) + { + itemStack.set(ModDataComponents.CAPTURED_BLOCK, this); + } + + /** + * @return posSelection stored in given itemStack (or empty instance) + */ + public static CapturedBlock readFromItemStack(final ItemStack itemStack) + { + return itemStack.getOrDefault(ModDataComponents.CAPTURED_BLOCK, CapturedBlock.EMPTY); + } + + /** + * Performs updating of posSelection in given itemStack + */ + public static void updateItemStack(final ItemStack itemStack, final UnaryOperator updater) + { + updater.apply(readFromItemStack(itemStack)).writeToItemStack(itemStack); + } +} diff --git a/src/main/java/com/ldtteam/structurize/component/ModDataComponents.java b/src/main/java/com/ldtteam/structurize/component/ModDataComponents.java index 9ae1de6e0..b3613f44a 100644 --- a/src/main/java/com/ldtteam/structurize/component/ModDataComponents.java +++ b/src/main/java/com/ldtteam/structurize/component/ModDataComponents.java @@ -21,6 +21,8 @@ public class ModDataComponents savedSynced("tags", TagData.CODEC, TagData.STREAM_CODEC); public static final DeferredHolder, DataComponentType> SCAN_TOOL = savedSynced("scan_tool", ScanToolData.CODEC, ScanToolData.STREAM_CODEC); + public static final DeferredHolder, DataComponentType> CAPTURED_BLOCK = + savedSynced("captured_block", CapturedBlock.CODEC, CapturedBlock.STREAM_CODEC); private static DeferredHolder, DataComponentType> savedSynced(final String name, final Codec codec, diff --git a/src/main/java/com/ldtteam/structurize/event/ClientLifecycleSubscriber.java b/src/main/java/com/ldtteam/structurize/event/ClientLifecycleSubscriber.java index 4d7292b8d..d585edcb6 100644 --- a/src/main/java/com/ldtteam/structurize/event/ClientLifecycleSubscriber.java +++ b/src/main/java/com/ldtteam/structurize/event/ClientLifecycleSubscriber.java @@ -7,9 +7,11 @@ import com.ldtteam.structurize.api.constants.Constants; import com.ldtteam.structurize.client.model.OverlaidModelLoader; import com.ldtteam.structurize.items.ItemStackTooltip; +import com.ldtteam.structurize.items.ModItems; import com.ldtteam.structurize.placement.handlers.placement.PlacementHandlers.ContainerPlacementHandler; import com.ldtteam.structurize.storage.ClientStructurePackLoader; import com.ldtteam.structurize.util.WorldRenderMacros; +import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.RenderType; import net.minecraft.core.registries.BuiltInRegistries; @@ -30,6 +32,8 @@ import net.neoforged.neoforge.client.event.RegisterClientTooltipComponentFactoriesEvent; import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent; import net.neoforged.neoforge.client.event.RegisterRenderBuffersEvent; +import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions; +import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent; import java.util.Collections; import java.util.IdentityHashMap; @@ -113,4 +117,17 @@ public static void registerGlobablRenderBuffers(final RegisterRenderBuffersEvent { WorldRenderMacros.RenderTypes.registerBuffer(event); } + + @SubscribeEvent + public static void registerClientExtensions(final RegisterClientExtensionsEvent event) + { + event.registerItem(new IClientItemExtensions() + { + @Override + public BlockEntityWithoutLevelRenderer getCustomRenderer() + { + return TagSubstitutionRenderer.getInstance(); + } + }, ModItems.blockTagSubstitution.get()); + } } diff --git a/src/main/java/com/ldtteam/structurize/items/ItemTagSubstitution.java b/src/main/java/com/ldtteam/structurize/items/ItemTagSubstitution.java index 3c34db8c0..d2b2bc339 100644 --- a/src/main/java/com/ldtteam/structurize/items/ItemTagSubstitution.java +++ b/src/main/java/com/ldtteam/structurize/items/ItemTagSubstitution.java @@ -3,21 +3,16 @@ import com.ldtteam.structurize.api.ISpecialBlockPickItem; import com.ldtteam.structurize.api.Utils; import com.ldtteam.structurize.blockentities.BlockEntityTagSubstitution; -import com.ldtteam.structurize.blockentities.ModBlockEntities; import com.ldtteam.structurize.blocks.ModBlocks; -import com.ldtteam.structurize.client.TagSubstitutionRenderer; +import com.ldtteam.structurize.component.CapturedBlock; +import com.ldtteam.structurize.component.ModDataComponents; import com.ldtteam.structurize.network.messages.AbsorbBlockMessage; import com.ldtteam.structurize.tag.ModTags; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; import net.minecraft.core.BlockPos; -import net.minecraft.core.HolderLookup; import net.minecraft.core.HolderSet; -import net.minecraft.core.RegistryAccess; -import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraft.tags.BlockTags; @@ -26,37 +21,19 @@ import net.minecraft.world.inventory.tooltip.TooltipComponent; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.component.CustomData; -import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; -import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions; -import net.neoforged.neoforge.server.ServerLifecycleHooks; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; import java.util.Optional; -import java.util.function.Consumer; public class ItemTagSubstitution extends BlockItem implements ISpecialBlockPickItem { public ItemTagSubstitution() { - super(ModBlocks.blockTagSubstitution.get(), new Properties()); - } - - @Override - public void initializeClient(@NotNull final Consumer consumer) - { - consumer.accept(new IClientItemExtensions() - { - @Override - public BlockEntityWithoutLevelRenderer getCustomRenderer() - { - return TagSubstitutionRenderer.getInstance(); - } - }); + super(ModBlocks.blockTagSubstitution.get(), new Properties().component(ModDataComponents.CAPTURED_BLOCK, CapturedBlock.EMPTY)); } @NotNull @@ -70,7 +47,7 @@ public InteractionResult onBlockPick(@NotNull Player player, { if (!player.level().isClientSide()) { - clearAbsorbedBlock(stack); + CapturedBlock.EMPTY.writeToItemStack(stack); } return InteractionResult.SUCCESS; } @@ -81,7 +58,7 @@ public InteractionResult onBlockPick(@NotNull Player player, // this way lies madness, and/or Sparta... if (!player.level().isClientSide()) { - clearAbsorbedBlock(stack); + CapturedBlock.EMPTY.writeToItemStack(stack); } return InteractionResult.SUCCESS; } @@ -103,11 +80,6 @@ private ItemStack getPickedBlock(@NotNull Player player, @NotNull BlockPos pos, return blockstate.getCloneItemStack(Minecraft.getInstance().hitResult, player.level(), pos, player); } - private void clearAbsorbedBlock(@NotNull ItemStack stack) - { - setBlockEntityData(stack, ModBlockEntities.TAG_SUBSTITUTION.get(), new CompoundTag()); - } - public void onAbsorbBlock(@NotNull final ServerPlayer player, @NotNull final ItemStack stack, @NotNull final BlockPos pos, @@ -116,7 +88,7 @@ public void onAbsorbBlock(@NotNull final ServerPlayer player, final BlockState blockstate = player.level().getBlockState(pos); final BlockEntity blockentity = player.level().getBlockEntity(pos); - final BlockEntityTagSubstitution.ReplacementBlock replacement; + final CapturedBlock replacement; if (blockentity instanceof BlockEntityTagSubstitution blockception) { replacement = blockception.getReplacement(); @@ -128,10 +100,10 @@ else if (!isAllowed(blockentity)) } else { - replacement = new BlockEntityTagSubstitution.ReplacementBlock(blockstate, blockentity.saveWithoutMetadata(player.level().registryAccess()), absorbItem); + replacement = new CapturedBlock(blockstate, blockentity, player.level().registryAccess(), absorbItem); } - setBlockEntityData(stack, ModBlockEntities.TAG_SUBSTITUTION.get(), replacement.write(new CompoundTag(), player.level().registryAccess())); + replacement.writeToItemStack(stack); } private boolean isAllowed(@Nullable final BlockEntity blockentity) @@ -142,28 +114,16 @@ private boolean isAllowed(@Nullable final BlockEntity blockentity) return tag.contains(blockentity.getType().builtInRegistryHolder()); } - /** - * Gets the absorbed replacement block from the stack. - * @param stack the stack - * @return the replacement block data (without loading blockentity) - */ - @NotNull - public BlockEntityTagSubstitution.ReplacementBlock getAbsorbedBlock(@NotNull ItemStack stack, HolderLookup.Provider provider) - { - final CompoundTag tag = stack.getOrDefault(DataComponents.BLOCK_ENTITY_DATA, CustomData.EMPTY).getUnsafe(); - return new BlockEntityTagSubstitution.ReplacementBlock(tag, provider); - } - @Override public Component getHighlightTip(@NotNull final ItemStack stack, @NotNull final Component displayName) { - final BlockEntityTagSubstitution.ReplacementBlock absorbed = getAbsorbedBlock(stack, ServerLifecycleHooks.getCurrentServer().registryAccess()); + final ItemStack absorbed = CapturedBlock.readFromItemStack(stack).itemStack(); if (!absorbed.isEmpty()) { return Component.empty() .append(super.getHighlightTip(stack, displayName)) .append(Component.literal(" - ").withStyle(ChatFormatting.GRAY)) - .append(absorbed.getItemStack().getHoverName()); + .append(absorbed.getHoverName()); } return super.getHighlightTip(stack, displayName); @@ -173,8 +133,7 @@ public Component getHighlightTip(@NotNull final ItemStack stack, @NotNull final @Override public Optional getTooltipImage(@NotNull final ItemStack stack) { - final BlockEntityTagSubstitution.ReplacementBlock absorbed = getAbsorbedBlock(stack, ServerLifecycleHooks.getCurrentServer().registryAccess()); - final ItemStack absorbedItem = absorbed.getItemStack(); + final ItemStack absorbedItem = CapturedBlock.readFromItemStack(stack).itemStack(); if (!absorbedItem.isEmpty()) { @@ -183,11 +142,4 @@ public Optional getTooltipImage(@NotNull final ItemStack stack return super.getTooltipImage(stack); } - - @Nullable - @Override - protected BlockState getPlacementState(@NotNull final BlockPlaceContext context) - { - return super.getPlacementState(context); - } } diff --git a/src/main/java/com/ldtteam/structurize/placement/StructurePlacer.java b/src/main/java/com/ldtteam/structurize/placement/StructurePlacer.java index a5ea67ffd..87c0d5b56 100644 --- a/src/main/java/com/ldtteam/structurize/placement/StructurePlacer.java +++ b/src/main/java/com/ldtteam/structurize/placement/StructurePlacer.java @@ -333,8 +333,8 @@ else if (requiredItems == null) { if (tileEntityData != null && BlockEntity.loadStatic(localPos, localState, tileEntityData, world.registryAccess()) instanceof BlockEntityTagSubstitution tagEntity) { - localState = tagEntity.getReplacement().getBlockState(); - tileEntityData = tagEntity.getReplacement().getBlockEntityTag(); + localState = tagEntity.getReplacement().blockState(); + tileEntityData = tagEntity.getReplacement().serializedBE().orElseGet(CompoundTag::new); } else { @@ -644,8 +644,8 @@ public BlockPlacementResult getResourceRequirements( { if (tileEntityData != null && BlockEntity.loadStatic(localPos, localState, tileEntityData, world.registryAccess()) instanceof BlockEntityTagSubstitution tagEntity) { - localState = tagEntity.getReplacement().getBlockState(); - tileEntityData = tagEntity.getReplacement().getBlockEntityTag(); + localState = tagEntity.getReplacement().blockState(); + tileEntityData = tagEntity.getReplacement().serializedBE().orElseGet(CompoundTag::new); } else {