From c5915afee77dc0d873ae05b7f0d49174d4b2ab66 Mon Sep 17 00:00:00 2001 From: asie <kontakt@asie.pl> Date: Wed, 5 Dec 2018 11:57:23 +0100 Subject: [PATCH] ClientSerializable for block entities --- .../block/entity/ClientSerializable.java | 29 +++++++ .../mixin/block/entity/MixinBlockEntity.java | 75 +++++++++++++++++++ .../entity/MixinClientPlayNetworkHandler.java | 64 ++++++++++++++++ .../net.fabricmc.fabric.mixins.client.json | 1 + .../net.fabricmc.fabric.mixins.common.json | 1 + 5 files changed, 170 insertions(+) create mode 100644 src/main/java/net/fabricmc/fabric/block/entity/ClientSerializable.java create mode 100644 src/main/java/net/fabricmc/fabric/mixin/block/entity/MixinBlockEntity.java create mode 100644 src/main/java/net/fabricmc/fabric/mixin/block/entity/MixinClientPlayNetworkHandler.java diff --git a/src/main/java/net/fabricmc/fabric/block/entity/ClientSerializable.java b/src/main/java/net/fabricmc/fabric/block/entity/ClientSerializable.java new file mode 100644 index 000000000..6cf29ae2d --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/block/entity/ClientSerializable.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.block.entity; + +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.nbt.CompoundTag; + +/** + * Implement this interace on a BlockEntity which you would like to be + * synchronized with the client side using the built-in engine methods. + */ +public interface ClientSerializable { + void fromClientTag(CompoundTag tag); + CompoundTag toClientTag(CompoundTag tag); +} diff --git a/src/main/java/net/fabricmc/fabric/mixin/block/entity/MixinBlockEntity.java b/src/main/java/net/fabricmc/fabric/mixin/block/entity/MixinBlockEntity.java new file mode 100644 index 000000000..9454decad --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/mixin/block/entity/MixinBlockEntity.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.block.entity; + +import net.fabricmc.fabric.block.entity.ClientSerializable; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.network.packet.BlockEntityUpdateClientPacket; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(BlockEntity.class) +public abstract class MixinBlockEntity { + @Shadow + public abstract BlockEntityType<?> getType(); + + @Shadow + public abstract BlockPos getPos(); + + @Inject(at = @At("HEAD"), method = "getUpdatePacket", cancellable = true) + public void getUpdatePacket(CallbackInfoReturnable<BlockEntityUpdateClientPacket> info) { + Object self = (Object) this; + + if (self instanceof ClientSerializable) { + // Mojang's serialization of x/y/z into the update packet is redundant, + // as we have a separate fromClientTag() we don't do it. + // However, we use the "id" field for type discernment, as actionId + // is capped at 8 bits of size with the values presumably reserved + // by Mojang. + + CompoundTag tag = new CompoundTag(); + Identifier entityId = BlockEntityType.getId(getType()); + if (entityId == null) { + throw new RuntimeException(this.getClass() + " is missing a mapping! This is a bug!"); + } + + tag.putString("id", entityId.toString()); + ((ClientSerializable) self).toClientTag(tag); + info.setReturnValue(new BlockEntityUpdateClientPacket(getPos(), 127, tag)); + info.cancel(); + } + } + + @Inject(at = @At("RETURN"), method = "serializeInitialChunkData") + public void serializeInitialChunkData(CallbackInfoReturnable<CompoundTag> info) { + Object self = (Object) this; + + if (self instanceof ClientSerializable && info.getReturnValue() != null) { + info.setReturnValue(((ClientSerializable) self).toClientTag(info.getReturnValue())); + } + } +} diff --git a/src/main/java/net/fabricmc/fabric/mixin/block/entity/MixinClientPlayNetworkHandler.java b/src/main/java/net/fabricmc/fabric/mixin/block/entity/MixinClientPlayNetworkHandler.java new file mode 100644 index 000000000..e9409bd5a --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/mixin/block/entity/MixinClientPlayNetworkHandler.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.block.entity; + +import net.fabricmc.fabric.block.entity.ClientSerializable; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.network.packet.BlockEntityUpdateClientPacket; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; +import org.apache.logging.log4j.Logger; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(ClientPlayNetworkHandler.class) +public class MixinClientPlayNetworkHandler { + @Shadow + private static Logger LOGGER; + + @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/packet/BlockEntityUpdateClientPacket;getActionId()I", ordinal = 0), method = "onBlockEntityUpdate", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD) + public void onBlockEntityUpdate(BlockEntityUpdateClientPacket packet, CallbackInfo info, BlockEntity entity) { + if (entity instanceof ClientSerializable) { + if (packet.getActionId() == 127) { + ClientSerializable serializable = (ClientSerializable) entity; + String id = packet.getCompoundTag().getString("id"); + if (id != null) { + Identifier otherIdObj = BlockEntityType.getId(entity.getType()); + ; + if (otherIdObj == null) { + LOGGER.error(entity.getClass() + " is missing a mapping! This is a bug!"); + info.cancel(); + return; + } + String otherId = otherIdObj.toString(); + + if (otherId.equals(id)) { + serializable.fromClientTag(packet.getCompoundTag()); + } + } + } + + info.cancel(); + } + } +} diff --git a/src/main/resources/net.fabricmc.fabric.mixins.client.json b/src/main/resources/net.fabricmc.fabric.mixins.client.json index 628b0607b..89f2ff4e5 100644 --- a/src/main/resources/net.fabricmc.fabric.mixins.client.json +++ b/src/main/resources/net.fabricmc.fabric.mixins.client.json @@ -3,6 +3,7 @@ "package": "net.fabricmc.fabric.mixin", "compatibilityLevel": "JAVA_8", "mixins": [ + "block.entity.MixinClientPlayNetworkHandler", "events.MixinClientPlayerInteractionManager", "events.MixinMinecraftClient", "networking.MixinClientPlayNetworkHandler", diff --git a/src/main/resources/net.fabricmc.fabric.mixins.common.json b/src/main/resources/net.fabricmc.fabric.mixins.common.json index 78b242835..29599b116 100644 --- a/src/main/resources/net.fabricmc.fabric.mixins.common.json +++ b/src/main/resources/net.fabricmc.fabric.mixins.common.json @@ -3,6 +3,7 @@ "package": "net.fabricmc.fabric.mixin", "compatibilityLevel": "JAVA_8", "mixins": [ + "block.entity.MixinBlockEntity", "commands.MixinServerCommandManager", "events.MixinMinecraftServer", "events.MixinServerPlayNetworkHandler",