From 50352219e52838b088b980fcf2ba9292d2ae5550 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Sat, 16 Jan 2021 01:39:03 +0800 Subject: [PATCH] ArchitecturyBlockEntity to sync data to client (#10) * ArchitecturyBlockEntity to sync data to client * Add remap = false to @Shadow * Upgrade gradle plugins * Fix ArchitecturyBlockEntity on Forge * Rename ArchitecturyBlockEntity to BlockEntityExtension --- .../extensions/BlockEntityExtension.java | 54 +++++++++++++++++ .../architectury/hooks/BlockEntityHooks.java | 35 +++++++++++ .../hooks/fabric/BlockEntityHooksImpl.java | 58 +++++++++++++++++++ .../fabric/MixinBlockEntityExtension.java | 54 +++++++++++++++++ .../main/resources/architectury.mixins.json | 6 +- .../hooks/forge/BlockEntityHooksImpl.java | 52 +++++++++++++++++ .../mixin/forge/MixinBlockEntity.java | 52 +++++++++++++++++ .../forge/MixinBlockEntityExtension.java | 39 +++++++++++++ .../main/resources/architectury.mixins.json | 2 +- 9 files changed, 348 insertions(+), 4 deletions(-) create mode 100644 common/src/main/java/me/shedaniel/architectury/extensions/BlockEntityExtension.java create mode 100644 common/src/main/java/me/shedaniel/architectury/hooks/BlockEntityHooks.java create mode 100644 fabric/src/main/java/me/shedaniel/architectury/hooks/fabric/BlockEntityHooksImpl.java create mode 100644 fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBlockEntityExtension.java create mode 100644 forge/src/main/java/me/shedaniel/architectury/hooks/forge/BlockEntityHooksImpl.java create mode 100644 forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinBlockEntity.java create mode 100644 forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinBlockEntityExtension.java diff --git a/common/src/main/java/me/shedaniel/architectury/extensions/BlockEntityExtension.java b/common/src/main/java/me/shedaniel/architectury/extensions/BlockEntityExtension.java new file mode 100644 index 00000000..0add5bb1 --- /dev/null +++ b/common/src/main/java/me/shedaniel/architectury/extensions/BlockEntityExtension.java @@ -0,0 +1,54 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 shedaniel + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.extensions; + +import me.shedaniel.architectury.hooks.BlockEntityHooks; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * Extensions to {@link net.minecraft.world.level.block.entity.BlockEntity}, implement this on to your class. + */ +public interface BlockEntityExtension { + /** + * Handles data sent by {@link BlockEntityExtension#saveClientData(CompoundTag)} on the server. + */ + @Environment(EnvType.CLIENT) + void loadClientData(@NotNull BlockState pos, @NotNull CompoundTag tag); + + /** + * Writes data to sync to the client. + */ + @NotNull + CompoundTag saveClientData(@NotNull CompoundTag tag); + + /** + * Sync data to the clients by {@link BlockEntityExtension#saveClientData(CompoundTag)} and {@link BlockEntityExtension#loadClientData(BlockState, CompoundTag)}. + */ + @ApiStatus.NonExtendable + default void syncData() { + BlockEntityHooks.syncData((BlockEntity) this); + } +} diff --git a/common/src/main/java/me/shedaniel/architectury/hooks/BlockEntityHooks.java b/common/src/main/java/me/shedaniel/architectury/hooks/BlockEntityHooks.java new file mode 100644 index 00000000..c471c9e9 --- /dev/null +++ b/common/src/main/java/me/shedaniel/architectury/hooks/BlockEntityHooks.java @@ -0,0 +1,35 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 shedaniel + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.hooks; + +import me.shedaniel.architectury.ExpectPlatform; +import net.minecraft.world.level.block.entity.BlockEntity; + +public class BlockEntityHooks { + private BlockEntityHooks() {} + + /** + * Sync data to the clients. + */ + @ExpectPlatform + public static void syncData(BlockEntity entity) { + throw new AssertionError(); + } +} diff --git a/fabric/src/main/java/me/shedaniel/architectury/hooks/fabric/BlockEntityHooksImpl.java b/fabric/src/main/java/me/shedaniel/architectury/hooks/fabric/BlockEntityHooksImpl.java new file mode 100644 index 00000000..e2c8c284 --- /dev/null +++ b/fabric/src/main/java/me/shedaniel/architectury/hooks/fabric/BlockEntityHooksImpl.java @@ -0,0 +1,58 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 shedaniel + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.hooks.fabric; + +import com.google.common.base.Preconditions; +import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; + +import java.util.Objects; + +public class BlockEntityHooksImpl { + /* + * Copyright (c) 2016, 2017, 2018, 2019 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. + */ + public static void syncData(BlockEntity entity) { + if (entity instanceof BlockEntityClientSerializable) { + ((BlockEntityClientSerializable) entity).sync(); + } else { + Level world = Objects.requireNonNull(entity.getLevel()); + if (!(world instanceof ServerLevel)) { + throw new IllegalStateException("Cannot call sync() on the logical client! Did you check world.isClient first?"); + } else { + ((ServerLevel) world).getChunkSource().blockChanged(entity.getBlockPos()); + } + } + } +} diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBlockEntityExtension.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBlockEntityExtension.java new file mode 100644 index 00000000..0fb45747 --- /dev/null +++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBlockEntityExtension.java @@ -0,0 +1,54 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 shedaniel + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.mixin.fabric; + +import me.shedaniel.architectury.extensions.BlockEntityExtension; +import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(BlockEntityExtension.class) +public interface MixinBlockEntityExtension extends BlockEntityClientSerializable { + @Shadow(remap = false) + @NotNull + CompoundTag saveClientData(@NotNull CompoundTag tag); + + @Shadow(remap = false) + void loadClientData(@NotNull BlockState pos, @NotNull CompoundTag tag); + + @Override + default void fromClientTag(CompoundTag tag) { + BlockEntity entity = (BlockEntity) this; + if (entity.hasLevel()) { + entity.setLevelAndPosition(entity.getLevel(), new BlockPos(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"))); + loadClientData(entity.getBlockState(), tag); + } + } + + @Override + default CompoundTag toClientTag(CompoundTag tag) { + return saveClientData(tag); + } +} diff --git a/fabric/src/main/resources/architectury.mixins.json b/fabric/src/main/resources/architectury.mixins.json index 39fd628e..33df1a24 100644 --- a/fabric/src/main/resources/architectury.mixins.json +++ b/fabric/src/main/resources/architectury.mixins.json @@ -9,9 +9,9 @@ "client.MixinTextureAtlas" ], "mixins": [ - "ExplosionPreInvoker", "LivingDeathInvoker", "MixinBlockItem", "MixinCommands", "MixinDedicatedServer", "MixinExplosion", "MixinFurnaceResultSlot", - "MixinItemEntity", "MixinLivingEntity", "MixinPlayer", "MixinPlayerAdvancements", "MixinPlayerList", "MixinResultSlot", "MixinServerGamePacketListenerImpl", - "MixinServerLevel", "MixinServerPlayer", "MixinServerPlayerGameMode", "PlayerAttackInvoker" + "ExplosionPreInvoker", "LivingDeathInvoker", "MixinBlockEntityExtension", "MixinBlockItem", "MixinCommands", "MixinDedicatedServer", "MixinExplosion", + "MixinFurnaceResultSlot", "MixinItemEntity", "MixinLivingEntity", "MixinPlayer", "MixinPlayerAdvancements", "MixinPlayerList", "MixinResultSlot", + "MixinServerGamePacketListenerImpl", "MixinServerLevel", "MixinServerPlayer", "MixinServerPlayerGameMode", "PlayerAttackInvoker" ], "injectors": { "defaultRequire": 1 diff --git a/forge/src/main/java/me/shedaniel/architectury/hooks/forge/BlockEntityHooksImpl.java b/forge/src/main/java/me/shedaniel/architectury/hooks/forge/BlockEntityHooksImpl.java new file mode 100644 index 00000000..3135ff24 --- /dev/null +++ b/forge/src/main/java/me/shedaniel/architectury/hooks/forge/BlockEntityHooksImpl.java @@ -0,0 +1,52 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 shedaniel + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.hooks.forge; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; + +import java.util.Objects; + +public class BlockEntityHooksImpl { + /* + * Copyright (c) 2016, 2017, 2018, 2019 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. + */ + public static void syncData(BlockEntity entity) { + Level world = Objects.requireNonNull(entity.getLevel()); + if (!(world instanceof ServerLevel)) { + throw new IllegalStateException("Cannot call sync() on the logical client! Did you check world.isClient first?"); + } else { + ((ServerLevel) world).getChunkSource().blockChanged(entity.getBlockPos()); + } + } +} diff --git a/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinBlockEntity.java b/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinBlockEntity.java new file mode 100644 index 00000000..ad0de1c0 --- /dev/null +++ b/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinBlockEntity.java @@ -0,0 +1,52 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 shedaniel + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.mixin.forge; + +import me.shedaniel.architectury.extensions.BlockEntityExtension; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.world.level.block.entity.BlockEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(BlockEntity.class) +public abstract class MixinBlockEntity { + @Inject(method = "getUpdatePacket", at = @At("HEAD"), cancellable = true) + public void getUpdatePacket(CallbackInfoReturnable cir) { + if (this instanceof BlockEntityExtension) { + BlockEntityExtension entity = (BlockEntityExtension) this; + BlockEntity be = (BlockEntity) entity; + cir.setReturnValue(new ClientboundBlockEntityDataPacket(be.getBlockPos(), 10, be.getUpdateTag())); + cir.cancel(); + } + } + + @Inject(method = "getUpdateTag", at = @At("HEAD"), cancellable = true) + public void getUpdateTag(CallbackInfoReturnable cir) { + if (this instanceof BlockEntityExtension) { + BlockEntityExtension entity = (BlockEntityExtension) this; + BlockEntity be = (BlockEntity) entity; + cir.setReturnValue(entity.saveClientData(new CompoundTag())); + cir.cancel(); + } + } +} diff --git a/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinBlockEntityExtension.java b/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinBlockEntityExtension.java new file mode 100644 index 00000000..fea8725e --- /dev/null +++ b/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinBlockEntityExtension.java @@ -0,0 +1,39 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 shedaniel + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.mixin.forge; + +import me.shedaniel.architectury.extensions.BlockEntityExtension; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.common.extensions.IForgeTileEntity; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(BlockEntityExtension.class) +public interface MixinBlockEntityExtension extends IForgeTileEntity { + @Shadow(remap = false) + void loadClientData(@NotNull BlockState pos, @NotNull CompoundTag tag); + + @Override + default void handleUpdateTag(BlockState state, CompoundTag tag) { + loadClientData(state, tag); + } +} diff --git a/forge/src/main/resources/architectury.mixins.json b/forge/src/main/resources/architectury.mixins.json index c3391c94..f3eddc58 100644 --- a/forge/src/main/resources/architectury.mixins.json +++ b/forge/src/main/resources/architectury.mixins.json @@ -5,7 +5,7 @@ "minVersion": "0.8", "client": [ ], - "mixins": ["BiomeGenerationSettingsBuilderAccessor", "MobSpawnSettingsBuilderAccessor"], + "mixins": ["BiomeGenerationSettingsBuilderAccessor", "MixinBlockEntity", "MixinBlockEntityExtension", "MobSpawnSettingsBuilderAccessor"], "injectors": { "defaultRequire": 1 }