diff --git a/build.gradle.kts b/build.gradle.kts index 23c2caf..2526b41 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ import java.time.format.DateTimeFormatter import org.gradle.jvm.toolchain.JavaLanguageVersion plugins { - id("dev.isxander.modstitch.base") version "0.8.4" + id("dev.isxander.modstitch.base") version "0.8.5" id("com.modrinth.minotaur") version "2.+" } @@ -84,6 +84,12 @@ stonecutter { "forge" to loader.equals("forge", ignoreCase = true), "vanilla" to loader.equals("vanilla", ignoreCase = true), "mc26" to (minecraft == "26.1.2"), + // TraderHighlightRenderer: MC 26 uses Fabric LevelRenderEvents; 1.21.x uses WorldRenderEvents (v1.world). + "traderWireframeRender" to (minecraft == "26.1.2" || minecraft == "1.21.11" || minecraft == "1.21.10"), + "traderWireframeMc26" to (minecraft == "26.1.2"), + "traderWireframe121" to (minecraft == "1.21.10" || minecraft == "1.21.11"), + "traderWireframe12110" to (minecraft == "1.21.10"), + "traderWireframe12111" to (minecraft == "1.21.11"), "npcSplit" to (minecraft == "26.1.2" || minecraft == "1.21.11"), "npcFlat" to (minecraft == "1.21.10"), ) diff --git a/src/main/java/com/github/sebseb7/autotrade/event/AutoTradeClientTick.java b/src/main/java/com/github/sebseb7/autotrade/event/AutoTradeClientTick.java index 9d9a01c..442ea89 100644 --- a/src/main/java/com/github/sebseb7/autotrade/event/AutoTradeClientTick.java +++ b/src/main/java/com/github/sebseb7/autotrade/event/AutoTradeClientTick.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Vector; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.inventory.ContainerScreen; +import net.minecraft.network.chat.Component; import net.minecraft.client.gui.screens.inventory.MerchantScreen; import net.minecraft.client.gui.screens.inventory.ShulkerBoxScreen; import net.minecraft.core.BlockPos; @@ -48,7 +49,6 @@ final class AutoTradeClientTick { private final Vector villagersInRange = new Vector<>(); private int villagerActive = 0; - private boolean state = false; private boolean inputInRange = false; private boolean inputOpened = false; private boolean outputInRange = false; @@ -68,6 +68,7 @@ final class AutoTradeClientTick { private int inputContainerHighlightTicks = 0; private int outputContainerHighlightTicks = 0; + private int postMerchantInventorySyncTicks = 0; /** * Entity to draw in-world highlight for; {@code null} when inactive or unknown @@ -102,7 +103,14 @@ final class AutoTradeClientTick { if (containerDelay > 0) { containerDelay--; } - if (!Configs.Generic.ENABLED.getBooleanValue() || mc.player == null) { + if (mc.player == null) { + return; + } + if (postMerchantInventorySyncTicks > 0) { + postMerchantInventorySyncTicks--; + ContainerIoHelper.syncPlayerInventoryAfterMerchant(mc); + } + if (!Configs.Generic.ENABLED.getBooleanValue()) { return; } Inventory plInv = mc.player.getInventory(); @@ -158,9 +166,9 @@ final class AutoTradeClientTick { //?} else { mc.gameMode.interact(mc.player, entity, InteractionHand.MAIN_HAND); //?} + postMerchantInventorySyncTicks = 0; voidDelay = Configs.Generic.VOID_TRADING_DELAY.getIntegerValue(); villagerActive = entity.getId(); - state = false; break; } } @@ -296,64 +304,101 @@ final class AutoTradeClientTick { } private void tickMerchantScreen(Minecraft mc, MerchantScreen screen) { - if (!state) { - String sellItemStr = Configs.Generic.SELL_ITEM.getStringValue(); - String buyItemStr = Configs.Generic.BUY_ITEM.getStringValue(); - state = true; - MerchantMenu menu = screen.getMenu(); - MerchantOffers offers = menu.getOffers(); + MerchantMenu menu = screen.getMenu(); + MerchantOffers offers = menu.getOffers(); - // Cache offers for the in-world trade overlay. - Entity activeEntity = findEntityById(mc, villagerActive); - if (activeEntity != null && offers != null && !offers.isEmpty()) { - VillagerTradeCache.put(activeEntity.getUUID(), offers); - } - for (int i = 0; i < offers.size(); i++) { - MerchantOffer offer = offers.get(i); - int tradesLeft = offer.getMaxUses() - offer.getUses(); - if (TradeItemSpec.matches(offer.getResult(), buyItemStr) && Configs.Generic.ENABLE_BUY.getBooleanValue() - && offer.getResult().getCount() <= Configs.Generic.BUY_LIMIT.getIntegerValue()) { - if (tradesLeft > 0 && playerHasMerchantCosts(mc.player, offer)) { - Slot slot = menu.getSlot(2); - menu.setSelectionHint(i); - mc.player.connection.send(new ServerboundSelectTradePacket(i)); - AutoTrade.bought += offer.getMaxUses(); - InfoUtils.showGuiOrInGameMessage(Message.MessageType.INFO, "autotrade.message.trade_bought", - formatItemCountNameForTrades(offer.getResult(), tradesLeft), - formatOfferPriceForTrades(offer, tradesLeft)); - try { - ContainerIoHelper.quickMoveResultSlot(mc, menu, slot.index); - } catch (Exception e) { - System.out.println("err " + e); - } + Entity activeEntity = findEntityById(mc, villagerActive); + if (activeEntity != null && offers != null && !offers.isEmpty()) { + VillagerTradeCache.put(activeEntity.getUUID(), offers); + } + + if (tryExecuteOneMerchantTrade(mc, screen)) { + ContainerIoHelper.syncPlayerInventoryAfterMerchant(mc); + return; + } + + finishMerchantSession(mc, screen); + } + + /** + * Runs at most one trade per tick so the server can answer before the next + * packet — avoids inventory slot ghosts from batched select-trade + shift-clicks. + */ + private boolean tryExecuteOneMerchantTrade(Minecraft mc, MerchantScreen screen) { + MerchantMenu menu = screen.getMenu(); + MerchantOffers offers = menu.getOffers(); + if (offers == null || offers.isEmpty()) { + return false; + } + String sellItemStr = Configs.Generic.SELL_ITEM.getStringValue(); + String buyItemStr = Configs.Generic.BUY_ITEM.getStringValue(); + + for (int i = 0; i < offers.size(); i++) { + MerchantOffer offer = offers.get(i); + int tradesLeft = offer.getMaxUses() - offer.getUses(); + + if (TradeItemSpec.matches(offer.getResult(), buyItemStr) && Configs.Generic.ENABLE_BUY.getBooleanValue() + && offer.getResult().getCount() <= Configs.Generic.BUY_LIMIT.getIntegerValue()) { + if (tradesLeft > 0 && playerHasMerchantCosts(mc.player, offer)) { + Slot slot = menu.getSlot(2); + menu.setSelectionHint(i); + mc.player.connection.send(new ServerboundSelectTradePacket(i)); + AutoTrade.bought += offer.getMaxUses(); + showTradeNotice(mc, "autotrade.message.trade_bought", + Component.literal(formatItemCountNameForTrades(offer.getResult(), tradesLeft)), + Component.literal(formatOfferPriceForTrades(offer, tradesLeft))); + try { + ContainerIoHelper.quickMoveResultSlot(mc, menu, slot.index); + } catch (Exception e) { + System.out.println("err " + e); } + return true; } - if (TradeItemSpec.matches(offer.getCostA(), sellItemStr) - && Configs.Generic.ENABLE_SELL.getBooleanValue() - && offer.getCostA().getCount() <= Configs.Generic.SELL_LIMIT.getIntegerValue()) { - if (tradesLeft > 0 && playerHasMerchantCosts(mc.player, offer)) { - Slot slot = menu.getSlot(2); - menu.setSelectionHint(i); - AutoTrade.sold += offer.getMaxUses(); - mc.player.connection.send(new ServerboundSelectTradePacket(i)); - InfoUtils.showGuiOrInGameMessage(Message.MessageType.INFO, "autotrade.message.trade_sold", - formatItemCountNameForTrades(offer.getCostA(), tradesLeft) - + formatOptionalSecondCostForTrades(offer, tradesLeft), - formatItemCountNameForTrades(offer.getResult(), tradesLeft)); - try { - ContainerIoHelper.quickMoveResultSlot(mc, menu, slot.index); - } catch (Exception e) { - System.out.println("err " + e); - } + } + + if (TradeItemSpec.matches(offer.getCostA(), sellItemStr) + && Configs.Generic.ENABLE_SELL.getBooleanValue() + && offer.getCostA().getCount() <= Configs.Generic.SELL_LIMIT.getIntegerValue()) { + if (tradesLeft > 0 && playerHasMerchantCosts(mc.player, offer)) { + Slot slot = menu.getSlot(2); + menu.setSelectionHint(i); + mc.player.connection.send(new ServerboundSelectTradePacket(i)); + AutoTrade.sold += offer.getMaxUses(); + showTradeNotice(mc, "autotrade.message.trade_sold", + Component.literal(formatItemCountNameForTrades(offer.getCostA(), tradesLeft) + + formatOptionalSecondCostForTrades(offer, tradesLeft)), + Component.literal(formatItemCountNameForTrades(offer.getResult(), tradesLeft))); + try { + ContainerIoHelper.quickMoveResultSlot(mc, menu, slot.index); + } catch (Exception e) { + System.out.println("err " + e); } + return true; } } } + return false; + } + + private void finishMerchantSession(Minecraft mc, MerchantScreen screen) { + ContainerIoHelper.syncPlayerInventoryAfterMerchant(mc); screen.onClose(); ContainerIoHelper.syncPlayerInventoryAfterMerchant(mc); + postMerchantInventorySyncTicks = 15; startTraderGlow(mc, villagerActive); } + /** + * Malilib's {@code showGuiOrInGameMessage} routes to multiple HUD targets; trade spam looked like 3× duplication. + * Vanilla overlay is a single on-screen line (same idea as vanilla toast-adjacent hints). + */ + private static void showTradeNotice(Minecraft mc, String translationKey, Component arg1, Component arg2) { + if (mc.gui == null) { + return; + } + mc.gui.setOverlayMessage(Component.translatable(translationKey, arg1, arg2), false); + } + private void tickTraderGlow(Minecraft mc) { if (mc.level == null || traderGlowTicksRemaining <= 0) { return; diff --git a/src/main/java/com/github/sebseb7/autotrade/render/TraderHighlightRenderer.java b/src/main/java/com/github/sebseb7/autotrade/render/TraderHighlightRenderer.java index e59b92b..e48d054 100644 --- a/src/main/java/com/github/sebseb7/autotrade/render/TraderHighlightRenderer.java +++ b/src/main/java/com/github/sebseb7/autotrade/render/TraderHighlightRenderer.java @@ -1,16 +1,13 @@ package com.github.sebseb7.autotrade.render; -//? if mc26 { +//? if traderWireframeRender { import com.github.sebseb7.autotrade.config.Configs; import com.github.sebseb7.autotrade.event.KeybindCallbacks; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; -import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderContext; -import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderEvents; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.ShapeRenderer; -import net.minecraft.client.renderer.rendertype.RenderTypes; import net.minecraft.core.BlockPos; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; @@ -18,6 +15,20 @@ import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.Shapes; //?} +//? if traderWireframeMc26 { +import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderEvents; +//?} +//? if traderWireframe121 { +import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents; +//?} +//? if traderWireframe12110 { +import net.minecraft.client.renderer.RenderType; +//?} +//? if traderWireframeMc26 || traderWireframe12111 { +import net.minecraft.client.renderer.rendertype.RenderTypes; +//?} /** * Client wireframe highlights: last-traded villager, and input/output container @@ -30,21 +41,27 @@ public final class TraderHighlightRenderer { } public static void register() { - //? if mc26 { - LevelRenderEvents.AFTER_SOLID_FEATURES.register(TraderHighlightRenderer::renderLevel); + //? if traderWireframeMc26 { + LevelRenderEvents.AFTER_SOLID_FEATURES.register(TraderHighlightRenderer::renderLevelMc26); + //?} + //? if traderWireframe121 { + WorldRenderEvents.END_MAIN.register(TraderHighlightRenderer::renderWorld121); //?} } - //? if mc26 { + //? if traderWireframeRender { private static final ShapeRenderer SHAPE_RENDERER = new ShapeRenderer(); private static final int TRADER_OUTLINE_COLOR = 0xFF66FF66; private static final int INPUT_CONTAINER_COLOR = 0xFFFF6666; private static final int OUTPUT_CONTAINER_COLOR = 0xFF6666FF; + //? if traderWireframeMc26 || traderWireframe12111 { private static final float LINE_WIDTH = 2.5F; + //?} - private static void renderLevel(LevelRenderContext context) { + //? if traderWireframeMc26 { + private static void renderLevelMc26(LevelRenderContext context) { Minecraft mc = Minecraft.getInstance(); if (mc.level == null) { return; @@ -65,6 +82,38 @@ public final class TraderHighlightRenderer { renderBoxes(mc, drawPose, consumer, camera, tickDelta, trader, inTicks, outTicks); } + //?} + + //? if traderWireframe121 { + private static void renderWorld121(WorldRenderContext context) { + Minecraft mc = Minecraft.getInstance(); + if (mc.level == null) { + return; + } + KeybindCallbacks kb = KeybindCallbacks.getInstance(); + Entity trader = kb.getTraderHighlightEntity(mc); + int inTicks = kb.getInputContainerHighlightTicks(); + int outTicks = kb.getOutputContainerHighlightTicks(); + if (trader == null && inTicks <= 0 && outTicks <= 0) { + return; + } + + var vertexConsumers = context.consumers(); + PoseStack drawPose = new PoseStack(); + Vec3 camera = mc.gameRenderer.getMainCamera().position(); + float tickDelta = mc.getDeltaTracker().getGameTimeDeltaPartialTick(true); + + final VertexConsumer consumer; + //? if traderWireframe12110 { + consumer = vertexConsumers.getBuffer(RenderType.lines()); + //?} + //? if traderWireframe12111 { + consumer = vertexConsumers.getBuffer(RenderTypes.lines()); + //?} + + renderBoxes(mc, drawPose, consumer, camera, tickDelta, trader, inTicks, outTicks); + } + //?} private static void renderBoxes(Minecraft mc, PoseStack drawPose, VertexConsumer consumer, Vec3 camera, float tickDelta, Entity trader, int inTicks, int outTicks) { @@ -100,8 +149,13 @@ public final class TraderHighlightRenderer { } private static void renderShape(PoseStack drawPose, VertexConsumer consumer, AABB cameraRelative, int color) { + //? if traderWireframe12110 { + SHAPE_RENDERER.renderShape(drawPose, consumer, Shapes.create(cameraRelative), 0.0D, 0.0D, 0.0D, color); + //?} + //? if traderWireframeMc26 || traderWireframe12111 { SHAPE_RENDERER.renderShape(drawPose, consumer, Shapes.create(cameraRelative), 0.0D, 0.0D, 0.0D, color, LINE_WIDTH); + //?} } //?} } diff --git a/versions/1.21.10-fabric/gradle.properties b/versions/1.21.10-fabric/gradle.properties index 747077f..aa0cd52 100644 --- a/versions/1.21.10-fabric/gradle.properties +++ b/versions/1.21.10-fabric/gradle.properties @@ -1,7 +1,7 @@ modstitch.platform=fabric-loom-remap deps.minecraft=1.21.10 -fabric_loader_version=0.19.2 +fabric_loader_version=0.18.3 fabric_api_version=0.138.4+1.21.10 malilib_version=0.26.8 mod_menu_version=16.0.1 diff --git a/versions/1.21.11-fabric/gradle.properties b/versions/1.21.11-fabric/gradle.properties index 613f2f5..00a9923 100644 --- a/versions/1.21.11-fabric/gradle.properties +++ b/versions/1.21.11-fabric/gradle.properties @@ -2,7 +2,7 @@ modstitch.platform=fabric-loom-remap deps.minecraft=1.21.11 fabric_loader_version=0.19.2 -fabric_api_version=0.136.0+1.21.11 -malilib_version=0.27.3 +fabric_api_version=0.141.3+1.21.11 +malilib_version=0.27.9 mod_menu_version=17.0.0 minecraft_version_min=1.21.11