From ecd254cb78ac971194d15527e49abc10aa52165d Mon Sep 17 00:00:00 2001 From: tkrmagid Date: Sun, 17 May 2026 03:21:25 +0900 Subject: [PATCH] v0.4.24: sync anchor config in vanilla BE update packet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: /videoPlace (and any path that places an anchor) reported success on the server but the panel was invisible on the client — no quad, no video. Walking around did not help. Root cause: VideoAnchorBlockEntity did not override getUpdateTag() or getUpdatePacket(). The base implementations return an empty CompoundTag and null respectively, so url/facing/width/height were never carried in vanilla BE sync. There is also a packet-ordering race between level.setBlock() (queues a deferred chunk broadcast) and ServerPlayNetworking.send(SyncAnchorPayload) (writes immediately): if the payload arrives first, the client drops it because the BE does not exist yet, then the chunk packet creates the BE with defaults (url="") and the renderer silently no-ops. Fix: override getUpdateTag(HolderLookup.Provider) → toNbt(), and getUpdatePacket() → ClientboundBlockEntityDataPacket.create(this). NBT key names already line up between toNbt() and loadAdditional(), so vanilla wraps the CompoundTag in TagValueInput and existing load logic reads it. Also fixes the 'walk far away, come back' case — that path has no SyncAnchorPayload, just vanilla chunk re-sync. --- gradle.properties | 2 +- .../block/VideoAnchorBlockEntity.java | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 7d7815a..c2bf664 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.configuration-cache=false # Mod mod_id=video_player -mod_version=0.4.23 +mod_version=0.4.24 maven_group=com.ejclaw.videoplayer archives_base_name=video_player diff --git a/src/main/java/com/ejclaw/videoplayer/block/VideoAnchorBlockEntity.java b/src/main/java/com/ejclaw/videoplayer/block/VideoAnchorBlockEntity.java index 720d336..e734eef 100644 --- a/src/main/java/com/ejclaw/videoplayer/block/VideoAnchorBlockEntity.java +++ b/src/main/java/com/ejclaw/videoplayer/block/VideoAnchorBlockEntity.java @@ -3,7 +3,11 @@ package com.ejclaw.videoplayer.block; import com.ejclaw.videoplayer.registry.VideoPlayerBlockEntities; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.storage.ValueInput; @@ -123,6 +127,35 @@ public class VideoAnchorBlockEntity extends BlockEntity { out.putBoolean("autoplay", autoplay); } + /** + * Vanilla chunk-load BE sync. The base implementation returns an empty tag, which means + * when a client first sees this BE (chunk loads or player walks into range) it gets default + * values — url="" in particular makes the renderer no-op and the panel appears invisible. + * + *

Returning {@link #toNbt()} here carries the custom fields in the vanilla packet, so + * we don't depend on the {@code SyncAnchorPayload} arriving before the chunk's block-update + * packet (there's a race: {@code level.setBlock} queues a deferred chunk broadcast while + * {@code ServerPlayNetworking.send} writes immediately; if the payload wins, the client + * drops it because the BE doesn't exist yet, then the chunk packet creates the BE with + * defaults). It also fixes "player walks far away and comes back" — that path has no + * SyncAnchorPayload at all, just vanilla chunk re-sync. + */ + @Override + public CompoundTag getUpdateTag(HolderLookup.Provider provider) { + return toNbt(); + } + + /** + * Triggers a {@link ClientboundBlockEntityDataPacket} whenever the chunk tracker decides + * this BE needs to push an update. Default implementation returns {@code null} (no packet + * sent on BE change). Combined with {@link #getUpdateTag} above, every BE-state change a + * client sees carries the full config. + */ + @Override + public Packet getUpdatePacket() { + return ClientboundBlockEntityDataPacket.create(this); + } + @Override protected void loadAdditional(ValueInput in) { super.loadAdditional(in);