diff --git a/src/main/java/fi/dy/masa/tweakeroo/data/ServerDataSyncer.java b/src/main/java/fi/dy/masa/tweakeroo/data/ServerDataSyncer.java new file mode 100644 index 000000000..57e0766da --- /dev/null +++ b/src/main/java/fi/dy/masa/tweakeroo/data/ServerDataSyncer.java @@ -0,0 +1,167 @@ +package fi.dy.masa.tweakeroo.data; + +import com.mojang.datafixers.util.Either; +import fi.dy.masa.tweakeroo.mixin.IMixinDataQueryHandler; +import net.minecraft.block.BlockEntityProvider; +import net.minecraft.block.BlockState; +import net.minecraft.block.ChestBlock; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.ChestBlockEntity; +import net.minecraft.block.enums.ChestType; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.DataQueryHandler; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; +import net.minecraft.inventory.DoubleInventory; +import net.minecraft.inventory.Inventory; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.util.Pair; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.WorldChunk; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +@SuppressWarnings("deprecation") +public class ServerDataSyncer { + public static ServerDataSyncer INSTANCE; + + /** + * key: BlockPos + * value: data, timestamp + */ + private final Map> blockCache = new HashMap<>(); + private final Map> entityCache = new HashMap<>(); + private final Map> pendingQueries = new HashMap<>(); + private final ClientWorld clientWorld; + + public ServerDataSyncer(ClientWorld world) { + this.clientWorld = Objects.requireNonNull(world); + } + + private @Nullable BlockEntity getCache(BlockPos pos) { + var data = blockCache.get(pos); + if (data != null && System.currentTimeMillis() - data.getRight() <= 1000) { + if (System.currentTimeMillis() - data.getRight() > 500) { + syncBlockEntity(clientWorld, pos); + } + return data.getLeft(); + } + + return null; + } + + private @Nullable Entity getCache(int networkId) { + var data = entityCache.get(networkId); + if (data != null && System.currentTimeMillis() - data.getRight() <= 1000) { + if (System.currentTimeMillis() - data.getRight() > 500) { + syncEntity(networkId); + } + return data.getLeft(); + } + + return null; + } + + public void handleQueryResponse(int transactionId, NbtCompound nbt) { + if (nbt == null) return; + if (pendingQueries.containsKey(transactionId)) { + Either either = pendingQueries.remove(transactionId); + either.ifLeft(pos -> { + if (!clientWorld.isChunkLoaded(pos)) return; + BlockState state = clientWorld.getBlockState(pos); + if (state.getBlock() instanceof BlockEntityProvider provider) { + var be = provider.createBlockEntity(pos, state); + if (be != null) { + be.read(nbt, clientWorld.getRegistryManager()); + blockCache.put(pos, new Pair<>(be, System.currentTimeMillis())); + } + } + }).ifRight(id -> { + Entity entity = clientWorld.getEntityById(id).getType().create(clientWorld); + if (entity != null) { + entity.readNbt(nbt); + entityCache.put(id, new Pair<>(entity, System.currentTimeMillis())); + } + }); + } + if (blockCache.size() > 30) { + blockCache.entrySet().removeIf(entry -> System.currentTimeMillis() - entry.getValue().getRight() > 1000); + } + if (entityCache.size() > 30) { + entityCache.entrySet().removeIf(entry -> System.currentTimeMillis() - entry.getValue().getRight() > 1000); + } + } + + public Inventory getBlockInventory(World world, BlockPos pos) { + if (!world.isChunkLoaded(pos)) return null; + var data = getCache(pos); + if (data instanceof Inventory inv) { + BlockState state = world.getBlockState(pos); + if (state.getBlock() instanceof ChestBlock && data instanceof ChestBlockEntity) { + ChestType type = state.get(ChestBlock.CHEST_TYPE); + + if (type != ChestType.SINGLE) { + BlockPos posAdj = pos.offset(ChestBlock.getFacing(state)); + if (!world.isChunkLoaded(posAdj)) return null; + BlockState stateAdj = world.getBlockState(posAdj); + + var dataAdj = getCache(posAdj); + if (dataAdj == null) { + syncBlockEntity(world, posAdj); + } + + if (stateAdj.getBlock() == state.getBlock() && + dataAdj instanceof ChestBlockEntity inv2 && + stateAdj.get(ChestBlock.CHEST_TYPE) != ChestType.SINGLE && + stateAdj.get(ChestBlock.FACING) == state.get(ChestBlock.FACING)) { + Inventory invRight = type == ChestType.RIGHT ? inv : inv2; + Inventory invLeft = type == ChestType.RIGHT ? inv2 : inv; + inv = new DoubleInventory(invRight, invLeft); + } + } + } + return inv; + } + + syncBlockEntity(world, pos); + return null; + } + + public void syncBlockEntity(World world, BlockPos pos) { + if (MinecraftClient.getInstance().isIntegratedServerRunning()) { + BlockEntity blockEntity = MinecraftClient.getInstance().getServer().getWorld(world.getRegistryKey()).getWorldChunk(pos).getBlockEntity(pos, WorldChunk.CreationType.CHECK); + if (blockEntity != null) { + blockCache.put(pos, new Pair<>(blockEntity, System.currentTimeMillis())); + return; + } + } + Either posEither = Either.left(pos); + if (MinecraftClient.getInstance().getNetworkHandler() != null && !pendingQueries.containsValue(posEither)) { + DataQueryHandler handler = MinecraftClient.getInstance().getNetworkHandler().getDataQueryHandler(); + handler.queryBlockNbt(pos, it -> {}); + pendingQueries.put(((IMixinDataQueryHandler) handler).currentTransactionId(), posEither); + } + } + + public void syncEntity(int networkId) { + Either idEither = Either.right(networkId); + if (MinecraftClient.getInstance().getNetworkHandler() != null && !pendingQueries.containsValue(idEither)) { + DataQueryHandler handler = MinecraftClient.getInstance().getNetworkHandler().getDataQueryHandler(); + handler.queryEntityNbt(networkId, it -> {}); + pendingQueries.put(((IMixinDataQueryHandler) handler).currentTransactionId(), idEither); + } + } + + public @Nullable Entity getServerEntity(Entity entity) { + Entity serverEntity = getCache(entity.getId()); + if (serverEntity == null) { + syncEntity(entity.getId()); + return null; + } + return serverEntity; + } +} diff --git a/src/main/java/fi/dy/masa/tweakeroo/mixin/IMixinDataQueryHandler.java b/src/main/java/fi/dy/masa/tweakeroo/mixin/IMixinDataQueryHandler.java new file mode 100644 index 000000000..41c6e2f05 --- /dev/null +++ b/src/main/java/fi/dy/masa/tweakeroo/mixin/IMixinDataQueryHandler.java @@ -0,0 +1,11 @@ +package fi.dy.masa.tweakeroo.mixin; + +import net.minecraft.client.network.DataQueryHandler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(DataQueryHandler.class) +public interface IMixinDataQueryHandler { + @Accessor("expectedTransactionId") + int currentTransactionId(); +} diff --git a/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinDataQueryHandler.java b/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinDataQueryHandler.java new file mode 100644 index 000000000..db1608dd4 --- /dev/null +++ b/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinDataQueryHandler.java @@ -0,0 +1,20 @@ +package fi.dy.masa.tweakeroo.mixin; + +import fi.dy.masa.tweakeroo.data.ServerDataSyncer; +import net.minecraft.client.network.DataQueryHandler; +import net.minecraft.nbt.NbtCompound; +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(DataQueryHandler.class) +public class MixinDataQueryHandler { + @Inject( + method = "handleQueryResponse", + at = @At("HEAD") + ) + private void queryResponse(int transactionId, NbtCompound nbt, CallbackInfoReturnable cir) { + ServerDataSyncer.INSTANCE.handleQueryResponse(transactionId, nbt); + } +} diff --git a/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinMinecraftClient.java b/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinMinecraftClient.java index 4bbdee519..d15e443b7 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinMinecraftClient.java +++ b/src/main/java/fi/dy/masa/tweakeroo/mixin/MixinMinecraftClient.java @@ -1,5 +1,6 @@ package fi.dy.masa.tweakeroo.mixin; +import fi.dy.masa.tweakeroo.data.ServerDataSyncer; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -130,4 +131,17 @@ private void onProcessKeybindsPre(CallbackInfo ci) } } } + + @Inject( + method = "setWorld", + at = @At("HEAD") + ) + private void onWorldChanged(ClientWorld world, CallbackInfo ci) + { + if (world == null) { + ServerDataSyncer.INSTANCE = null; + } else { + ServerDataSyncer.INSTANCE = new ServerDataSyncer(world); + } + } } diff --git a/src/main/java/fi/dy/masa/tweakeroo/renderer/RenderUtils.java b/src/main/java/fi/dy/masa/tweakeroo/renderer/RenderUtils.java index 6d975718c..3b7fe5fcb 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/renderer/RenderUtils.java +++ b/src/main/java/fi/dy/masa/tweakeroo/renderer/RenderUtils.java @@ -2,8 +2,14 @@ import java.util.Set; import com.mojang.blaze3d.systems.RenderSystem; -import org.joml.Matrix4f; -import org.joml.Matrix4fStack; +import fi.dy.masa.malilib.util.EntityUtils; +import fi.dy.masa.malilib.util.GuiUtils; +import fi.dy.masa.tweakeroo.config.Configs; +import fi.dy.masa.tweakeroo.data.ServerDataSyncer; +import fi.dy.masa.tweakeroo.mixin.IMixinAbstractHorseEntity; +import fi.dy.masa.tweakeroo.util.MiscUtils; +import fi.dy.masa.tweakeroo.util.RayTraceUtils; +import fi.dy.masa.tweakeroo.util.SnapAimMode; import net.minecraft.block.Block; import net.minecraft.block.ShulkerBoxBlock; @@ -31,14 +37,10 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; +import org.joml.Matrix4f; +import org.joml.Matrix4fStack; -import fi.dy.masa.malilib.util.EntityUtils; -import fi.dy.masa.malilib.util.GuiUtils; -import fi.dy.masa.tweakeroo.config.Configs; -import fi.dy.masa.tweakeroo.mixin.IMixinAbstractHorseEntity; -import fi.dy.masa.tweakeroo.util.MiscUtils; -import fi.dy.masa.tweakeroo.util.RayTraceUtils; -import fi.dy.masa.tweakeroo.util.SnapAimMode; +import java.util.Set; public class RenderUtils { @@ -133,13 +135,8 @@ public static void renderInventoryOverlay(MinecraftClient mc, DrawContext drawCo HitResult trace = RayTraceUtils.getRayTraceFromEntity(world, cameraEntity, false); - if (trace == null) - { - return; - } - Inventory inv = null; - ShulkerBoxBlock block = null; + ShulkerBoxBlock shulkerBoxBlock = null; LivingEntity entityLivingBase = null; if (trace.getType() == HitResult.Type.BLOCK) @@ -149,14 +146,24 @@ public static void renderInventoryOverlay(MinecraftClient mc, DrawContext drawCo if (blockTmp instanceof ShulkerBoxBlock) { - block = (ShulkerBoxBlock) blockTmp; + shulkerBoxBlock = (ShulkerBoxBlock) blockTmp; } - inv = fi.dy.masa.malilib.util.InventoryUtils.getInventory(world, pos); + if (world instanceof ServerWorld realWorld) { + inv = fi.dy.masa.malilib.util.InventoryUtils.getInventory(realWorld, pos); + } else { + inv = ServerDataSyncer.INSTANCE.getBlockInventory(world, pos); + } } else if (trace.getType() == HitResult.Type.ENTITY) { Entity entity = ((EntityHitResult) trace).getEntity(); + if (entity.getWorld().isClient) { + Entity serverEntity = ServerDataSyncer.INSTANCE.getServerEntity(entity); + if (serverEntity != null) { + entity = serverEntity; + } + } if (entity instanceof LivingEntity) { @@ -207,7 +214,7 @@ else if (entity instanceof AbstractHorseEntity) yInv = Math.min(yInv, yCenter - 92); } - fi.dy.masa.malilib.render.RenderUtils.setShulkerboxBackgroundTintColor(block, Configs.Generic.SHULKER_DISPLAY_BACKGROUND_COLOR.getBooleanValue()); + fi.dy.masa.malilib.render.RenderUtils.setShulkerboxBackgroundTintColor(shulkerBoxBlock, Configs.Generic.SHULKER_DISPLAY_BACKGROUND_COLOR.getBooleanValue()); if (isHorse) { diff --git a/src/main/java/fi/dy/masa/tweakeroo/util/InventoryUtils.java b/src/main/java/fi/dy/masa/tweakeroo/util/InventoryUtils.java index 79704e41d..0ca435336 100644 --- a/src/main/java/fi/dy/masa/tweakeroo/util/InventoryUtils.java +++ b/src/main/java/fi/dy/masa/tweakeroo/util/InventoryUtils.java @@ -824,6 +824,9 @@ private static void repairModeHandleSlot(PlayerEntity player, EquipmentSlot type } } + /** + * Adds the enchantment checks for Tools or Weapons + */ private static int findRepairableItemNotInRepairableSlot(Slot targetSlot, PlayerEntity player) { ScreenHandler containerPlayer = player.currentScreenHandler; diff --git a/src/main/resources/mixins.tweakeroo.json b/src/main/resources/mixins.tweakeroo.json index 7529e18a4..700fe60b7 100644 --- a/src/main/resources/mixins.tweakeroo.json +++ b/src/main/resources/mixins.tweakeroo.json @@ -11,6 +11,7 @@ "IMixinClientWorld", "IMixinCommandBlockExecutor", "IMixinCustomizeFlatLevelScreen", + "IMixinDataQueryHandler", "IMixinShovelItem", "IMixinSimpleOption", "MixinAbstractClientPlayerEntity", @@ -36,6 +37,7 @@ "MixinCloneCommand", "MixinCommandBlockScreen", "MixinCreativeInventoryScreen", + "MixinDataQueryHandler", "MixinDimensionEffects_Nether", "MixinEntity", "MixinEntityRenderDispatcher",