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",