From c980f89df7d7881682e2d278712d54febaf49fb6 Mon Sep 17 00:00:00 2001 From: Liyan Zhao Date: Thu, 20 Jun 2024 14:25:54 +0800 Subject: [PATCH] feat: support inventory preview on multi player server (#1) * 23w51b-SNAP 0.20.0-snap * fix Block/Entity Reach overrides * 1.20.x revert * IntelliJ Changes * IntelliJ Changes * IntelliJ Changes * IntelliJ Changes * Update Configs.java * IntelliJ Changes * 24w03a - No changes * 24w03b - Adds debugLogger() and attempts to replace the deprecated calls to isLiquid() -- Perhaps move to malilib if working. * 24w03b - Adds debugLogger() and attempts to replace the deprecated calls to isLiquid() -- Perhaps move to malilib if working. * 24w04a - Removed some non-working dev code * 24w04a - gradle 8.6 * 24w05b - half works and runs, but some features are broken due to renderer issues. * 24w06a - renderer issues nearly fixed. Still need to check visual rotation values for flexibleBlockPlacement Grid under MaLiLib * 24w06a - Matrix4f Renderer methods fixed & fully implemented. * 24w06a - merge from 0.19.1 & 0.19.2, elytra swap fix & map preview required shift * Update gradle.properties * 24w06a - added option to disable Trial Vault particles * 24w06a - some code cleanups * 24w06a - remove lame Trial spawner / vault particle rules and cleaned up some wording for the Block / Entity reach overrides with the Single Player warnings. * 24w07a - some mixin target fixes * 24w07a - some debug loggers enabled to debug clicking issues for issue # 485 -- Mac Mouse / Trackpad issue from 0.16.1/3 + ? * 24w07a - added debuggers for tweakFastBlockPlacement workflow * 24w07a - api / loom version bump * 24w09a basic * 24w09a - mostly works, check the Shulker Box Preview code yet. * 24w09a -- loom build 5 * 24w09a - loom build 6 * 24w09a -- loom build 8 * Update gradle.properties * 24w09a - fabric api 0.96.6 * 24w09a - add Bundle Preview * 24w10a - basic item attribute modifier getValue() -> value() rename * 24w10a ghost bug hunting * 24w10a - some cleanups and fixing TWEAK_GAMMA_OVERRIDE correctly this time. * 24w10a - ModMenu 10.0.0-alpha.3 update * 24w10a - fabric 0.96.9 yarn build 8 * 24w11a - yarn build 2 * 24w11a - fabric 0.96.10 and shulker box preview fix * 24w11a - chasing ghost bugs for ClickSlot packets -- Not finding any issues. * 24w12a - fix itemStack.getItem().isDamagable() --> not yet working * 24w12a - fix itemStack.getItem().isDamagable() -> itemStack.isDamageable() * 24w12a - loom 1.6, yarn build 3, api 0.96.12 * 24w13a - chat Hud fix * 24w13a - yarn build 7, api 0.96.13 * 24w13a - add ModMenu parent for malilib * 24w13a - update ModMenu Dependency to a custom SNAPSHOT build, because 10.0.0-alpha.3 does not support 24w13a correctly. * 24w14a - java 21, shulker box tooltip slight Mixin change * 24w14a - some code cleanups * 24w14a - some code cleanups * 24w14a - Lava visibility math corrections * 24w14a - gradle 8.7, yarn build 6 * 1.20.5-pre1 support -- rename mod_version to 0.20.0-beta.1 * 1.20.5-pre.1 - yarn build 4 * 1.20.5-pre.1 - yarn build 4 * merge from UPSTREAM: [QOL] Rehash Angel Block tweak using "floatato technology" (maruohon#496) * some import cleanups * 1.20.5-pre1-- code cleanups * 1.20.5-pre.1 - code cleanups * 1.20.5-pre.1 - code cleanups * 1.20.5-pre.1 - code cleanups * 1.20.5-pre.1 - code cleanups * 1.20.5-pre.2 support * 1.20.5-pre.3 support * 1.20.5-pre.3 - Update lava visibility values to closer resemble the values under 1.20.4, with only a slight boost around maybe ~1.0-2.0f * 1.20.5-pre.3 - Update lava visibility values to closer resemble the values under 1.20.4, with only a slight boost around maybe ~1.0-2.0f * 1.20.5-pre.4 support * 1.20.5-rc1 support * 1.20.5-rc1 - code cleanups * 1.20.5-rc1 - reach distance fix * 1.20.5-rc1 - reach distance fix * remove Network Reference call * Fix code * 1.20.5-rc.3 * code cleanups * 1.20.5-DEV * 1.20.5-DEV * merge from PR * "sakura.1" branding until official release * "sakura.1" branding until official release * DISABLE_WORLD_VIEW_BOB Mixin Conflict with Iris, lower priority fixes it. * DISABLE_WORLD_VIEW_BOB Mixin Conflict with Iris, lower priority fixes it. * 1.20.6 versioning * 24w18a -- new Item Enchantments Components --> Does not yet compile, no time to fix today. * fixed enchantment level lookups, build 3, api 0.97.9 * fixed enchantment level lookups, build 3, api 0.97.9 * Update gradle.properties * version bump * fix shulker box / item container display. * Update RenderHandler.java * fix render call, add tickDelta fixes * yarn build 3, waiting on FAPI * FAPI / modmenu update * Update fabric.mod.json * Upgrade Shulker Box stacking code -- this allows non-modded players and non-modded servers to work with Tweakeroo stacked shulker boxes. * Revert last Commit * fix config minorly * 24w21b - Waiting on FAPI * yarn build 3 * FAPI update, and testing. All seems to be working. * getItemStackFromString check for null * yard build 4 * yarn build 5 * yarn build 8 * 1.21-pre1 * 1.21-pre2 --> added tool / weapon swap parameters to check the item 'weight' with Rarity and enchantments versus the previous Item. i.e.; a tool with more enchantments, or higher enchantment values is picked over the same item with lesser rarity. * Merge from 1.20.6 for Shulker Boxes not dropping fix * Create jitpack.yml * Delete jitpack.yml * ModMenu update * 1.21-pre3 (Was toying with the Flat Presets code) * ItemStack.getMaxCount() fix for mod compatibility if max_stack_size is configured. * add jitpack * add jitpack * 1.21-pre4 * Create build.yml * 1.21-rc1 * 1.21-rc1 * 1.21-rc1 * 1.21-rc1 * resync * 1.21 * Disable Experimental Weapon / Tool swapping * yarn build 2, modmenu beta2, version # bump * feat: support inventory preview on multi player server * fix --------- Co-authored-by: Sakura Ryoko <116967773+sakura-ryoko@users.noreply.github.com> Co-authored-by: Sakura Ryoko --- .../masa/tweakeroo/data/ServerDataSyncer.java | 167 ++++++++++++++++++ .../mixin/IMixinDataQueryHandler.java | 11 ++ .../mixin/MixinDataQueryHandler.java | 20 +++ .../tweakeroo/mixin/MixinMinecraftClient.java | 14 ++ .../masa/tweakeroo/renderer/RenderUtils.java | 43 +++-- .../masa/tweakeroo/util/InventoryUtils.java | 3 + src/main/resources/mixins.tweakeroo.json | 2 + 7 files changed, 242 insertions(+), 18 deletions(-) create mode 100644 src/main/java/fi/dy/masa/tweakeroo/data/ServerDataSyncer.java create mode 100644 src/main/java/fi/dy/masa/tweakeroo/mixin/IMixinDataQueryHandler.java create mode 100644 src/main/java/fi/dy/masa/tweakeroo/mixin/MixinDataQueryHandler.java 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",