From 28aa0cb86b48d20640a6c41e550ee03c01911243 Mon Sep 17 00:00:00 2001 From: seb Date: Tue, 28 Apr 2026 04:56:33 +0200 Subject: [PATCH] 0.0.14 village glow , container glow, activity messages. --- build.gradle | 2 + gradle.properties | 3 +- .../github/sebseb7/autotrade/InitHandler.java | 3 + .../autotrade/event/AutoTradeClientTick.java | 203 +++++++++++++++--- .../autotrade/event/ContainerIoHelper.java | 53 +++-- .../autotrade/event/KeybindCallbacks.java | 13 ++ .../render/TraderHighlightRenderer.java | 92 ++++++++ src/main/resources/fabric.mod.json | 3 +- 8 files changed, 328 insertions(+), 44 deletions(-) create mode 100644 src/main/java/com/github/sebseb7/autotrade/render/TraderHighlightRenderer.java diff --git a/build.gradle b/build.gradle index 7116e11..2e009f9 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,8 @@ repositories { dependencies { minecraft "com.mojang:minecraft:${project.minecraft_version}" implementation "net.fabricmc:fabric-loader:${project.fabric_loader_version}" + // Official 26.1 template uses `implementation` for fabric-api (not modImplementation). + implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}" implementation "maven.modrinth:malilib:${project.malilib_version}" compileOnly "com.terraformersmc:modmenu:${project.mod_menu_version}" } diff --git a/gradle.properties b/gradle.properties index c6079fc..2842e19 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ mod_name = AutoTrade author = sebseb7 mod_file_name = autotrade-fabric -mod_version = 0.0.13 +mod_version = 0.0.14 malilib_version = 0.28.2 minecraft_version_min = 26.1.2 @@ -18,4 +18,5 @@ minecraft_version_out = 26.1.2 minecraft_version = 26.1.2 fabric_loader_version = 0.19.2 +fabric_api_version = 0.145.4+26.1.2 mod_menu_version = 18.0.0-alpha.8 diff --git a/src/main/java/com/github/sebseb7/autotrade/InitHandler.java b/src/main/java/com/github/sebseb7/autotrade/InitHandler.java index 840582a..223b69e 100644 --- a/src/main/java/com/github/sebseb7/autotrade/InitHandler.java +++ b/src/main/java/com/github/sebseb7/autotrade/InitHandler.java @@ -3,6 +3,7 @@ package com.github.sebseb7.autotrade; import com.github.sebseb7.autotrade.config.Configs; import com.github.sebseb7.autotrade.event.InputHandler; import com.github.sebseb7.autotrade.event.KeybindCallbacks; +import com.github.sebseb7.autotrade.render.TraderHighlightRenderer; import fi.dy.masa.malilib.config.ConfigManager; import fi.dy.masa.malilib.config.options.ConfigString; import fi.dy.masa.malilib.event.InputEventHandler; @@ -15,6 +16,8 @@ public class InitHandler implements IInitializationHandler { public void registerModHandlers() { ConfigManager.getInstance().registerConfigHandler(Reference.MOD_ID, new Configs()); + TraderHighlightRenderer.register(); + InputHandler handler = new InputHandler(); InputEventHandler.getKeybindManager().registerKeybindProvider(handler); 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 8e397c2..c54e310 100644 --- a/src/main/java/com/github/sebseb7/autotrade/event/AutoTradeClientTick.java +++ b/src/main/java/com/github/sebseb7/autotrade/event/AutoTradeClientTick.java @@ -21,6 +21,7 @@ import net.minecraft.world.entity.decoration.ItemFrame; import net.minecraft.world.entity.npc.villager.Villager; import net.minecraft.world.entity.npc.wanderingtrader.WanderingTrader; import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.MerchantMenu; import net.minecraft.world.inventory.ShulkerBoxMenu; @@ -47,7 +48,32 @@ final class AutoTradeClientTick { private int voidDelay = 0; private int containerDelay = 0; + /** + * 1 second at 20 TPS — client wireframe highlight (see + * {@code TraderHighlightRenderer}). + */ + private static final int TRADER_HIGHLIGHT_TICKS = 20; + + private int traderGlowTicksRemaining = 0; + private int traderGlowEntityId = -1; + + private int inputContainerHighlightTicks = 0; + private int outputContainerHighlightTicks = 0; + + /** + * Entity to draw in-world highlight for; {@code null} when inactive or unknown + * id. + */ + Entity getTraderGlowEntityForRender(Minecraft mc) { + if (traderGlowTicksRemaining <= 0 || traderGlowEntityId < 0 || mc.level == null) { + return null; + } + return findEntityById(mc, traderGlowEntityId); + } + void tick(Minecraft mc) { + tickTraderGlow(mc); + tickContainerHighlights(mc); if (voidDelay > 0) { if (Configs.Generic.VOID_TRADING_DELAY_AFTER_TELEPORT.getBooleanValue()) { boolean found = false; @@ -152,6 +178,7 @@ final class AutoTradeClientTick { new BlockHitResult(ic, Direction.UP, input, false)); containerDelay = Configs.Generic.CONTAINER_CLOSE_DELAY.getIntegerValue(); inputOpened = true; + inputContainerHighlightTicks = TRADER_HIGHLIGHT_TICKS; return; } if ((mc.player.distanceToSqr(oc) < 16.0D) && (outputInRange == false)) { @@ -160,6 +187,7 @@ final class AutoTradeClientTick { new BlockHitResult(oc, Direction.UP, output, false)); containerDelay = Configs.Generic.CONTAINER_CLOSE_DELAY.getIntegerValue(); outputOpened = true; + outputContainerHighlightTicks = TRADER_HIGHLIGHT_TICKS; return; } if (mc.player.distanceToSqr(ic) > 25.0D) { @@ -173,7 +201,6 @@ final class AutoTradeClientTick { tickCount++; if (tickCount > 200) { tickCount = 0; - villagersInRange.clear(); inputInRange = false; outputInRange = false; var cur = GuiUtils.getCurrentScreen(); @@ -264,61 +291,177 @@ final class AutoTradeClientTick { MerchantOffers offers = menu.getOffers(); 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()) { - 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", - formatItemCountAndName(offer.getResult()), formatOfferPrice(offer)); - try { - ContainerIoHelper.quickMoveResultSlot(mc, menu, slot.index); - } catch (Exception e) { - System.out.println("err " + e); + 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); + } } } if (TradeItemSpec.matches(offer.getCostA(), sellItemStr) && Configs.Generic.ENABLE_SELL.getBooleanValue() && offer.getCostA().getCount() <= Configs.Generic.SELL_LIMIT.getIntegerValue()) { - 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", - formatItemCountAndName(offer.getCostA()) + formatOptionalSecondCost(offer), - formatItemCountAndName(offer.getResult())); - try { - ContainerIoHelper.quickMoveResultSlot(mc, menu, slot.index); - } catch (Exception e) { - System.out.println("err " + e); + 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); + } } } } } screen.onClose(); + startTraderGlow(mc, villagerActive); } - /** e.g. "3× Book" */ + private void tickTraderGlow(Minecraft mc) { + if (mc.level == null || traderGlowTicksRemaining <= 0) { + return; + } + traderGlowTicksRemaining--; + if (traderGlowTicksRemaining == 0) { + traderGlowEntityId = -1; + } + } + + private void tickContainerHighlights(Minecraft mc) { + if (mc.level == null) { + return; + } + if (inputContainerHighlightTicks > 0) { + inputContainerHighlightTicks--; + } + if (outputContainerHighlightTicks > 0) { + outputContainerHighlightTicks--; + } + } + + int getInputContainerHighlightTicks() { + return inputContainerHighlightTicks; + } + + int getOutputContainerHighlightTicks() { + return outputContainerHighlightTicks; + } + + private void startTraderGlow(Minecraft mc, int entityId) { + if (mc.level == null) { + return; + } + if (findEntityById(mc, entityId) == null) { + traderGlowTicksRemaining = 0; + traderGlowEntityId = -1; + return; + } + traderGlowEntityId = entityId; + traderGlowTicksRemaining = TRADER_HIGHLIGHT_TICKS; + } + + private static Entity findEntityById(Minecraft mc, int entityId) { + for (Entity e : mc.level.entitiesForRendering()) { + if (e.getId() == entityId) { + return e; + } + } + return null; + } + + /** + * Same stack rules as the merchant menu: player must have enough of each + * non-empty cost before we fire packets or show a trade toast. + */ + private static boolean playerHasMerchantCosts(Player player, MerchantOffer offer) { + if (!costRequirementMet(player.getInventory(), offer.getCostA())) { + return false; + } + return costRequirementMet(player.getInventory(), offer.getCostB()); + } + + private static boolean costRequirementMet(Inventory inv, ItemStack required) { + if (required.isEmpty()) { + return true; + } + int need = required.getCount(); + int have = 0; + for (int s = 0; s < inv.getContainerSize(); s++) { + ItemStack st = inv.getItem(s); + if (ItemStack.isSameItemSameComponents(st, required)) { + have += st.getCount(); + if (have >= need) { + return true; + } + } + } + return false; + } + + /** e.g. "3× Book" (one villager use). */ private static String formatItemCountAndName(ItemStack stack) { return stack.getCount() + "× " + stack.getHoverName().getString(); } - /** For buying: the stacks you pay (first + optional second slot). */ - private static String formatOfferPrice(MerchantOffer offer) { - String a = offer.getCostA().isEmpty() ? null : formatItemCountAndName(offer.getCostA()); + /** + * Per-trade count × how many of this offer remain before the trade, e.g. 1 + * iron/trade × 12 runs → "12× …". + */ + private static String formatItemCountNameForTrades(ItemStack perTrade, int remainingOfferUses) { + if (remainingOfferUses <= 0) { + return formatItemCountAndName(perTrade); + } + return (perTrade.getCount() * remainingOfferUses) + "× " + perTrade.getHoverName().getString(); + } + + /** For buying: the stacks you pay, scaled to how many of this offer remain. */ + private static String formatOfferPriceForTrades(MerchantOffer offer, int t) { + if (t <= 0) { + String a = offer.getCostA().isEmpty() ? null : formatItemCountAndName(offer.getCostA()); + if (offer.getCostB().isEmpty()) { + return a != null ? a : "—"; + } + String b = formatItemCountAndName(offer.getCostB()); + return a == null ? b : a + " + " + b; + } + String a = offer.getCostA().isEmpty() + ? null + : (offer.getCostA().getCount() * t) + "× " + offer.getCostA().getHoverName().getString(); if (offer.getCostB().isEmpty()) { return a != null ? a : "—"; } - String b = formatItemCountAndName(offer.getCostB()); + String b = (offer.getCostB().getCount() * t) + "× " + offer.getCostB().getHoverName().getString(); return a == null ? b : a + " + " + b; } - /** If the trade has a second input item, show it with " + " */ - private static String formatOptionalSecondCost(MerchantOffer offer) { + /** + * If the trade has a second cost item, " + 2× …" scaled to remaining offer + * uses. + */ + private static String formatOptionalSecondCostForTrades(MerchantOffer offer, int t) { if (offer.getCostB().isEmpty()) { return ""; } - return " + " + formatItemCountAndName(offer.getCostB()); + if (t <= 0) { + return " + " + formatItemCountAndName(offer.getCostB()); + } + return " + " + (offer.getCostB().getCount() * t) + "× " + offer.getCostB().getHoverName().getString(); } } diff --git a/src/main/java/com/github/sebseb7/autotrade/event/ContainerIoHelper.java b/src/main/java/com/github/sebseb7/autotrade/event/ContainerIoHelper.java index ebb7398..bd7c27a 100644 --- a/src/main/java/com/github/sebseb7/autotrade/event/ContainerIoHelper.java +++ b/src/main/java/com/github/sebseb7/autotrade/event/ContainerIoHelper.java @@ -4,11 +4,14 @@ import com.github.sebseb7.autotrade.config.Configs; import com.github.sebseb7.autotrade.util.TradeItemSpec; import fi.dy.masa.malilib.gui.Message; import fi.dy.masa.malilib.util.InfoUtils; +import java.util.HashMap; +import java.util.Map; import net.minecraft.client.Minecraft; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.ContainerInput; import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; final class ContainerIoHelper { @@ -27,22 +30,23 @@ final class ContainerIoHelper { int maxKeep = Configs.Generic.MAX_INPUT_ITEMS.getIntegerValue() * 64; if (Configs.Generic.ENABLE_BUY.getBooleanValue()) { String buySpec = Configs.Generic.BUY_ITEM.getStringValue(); + MoveTotals movedBought = new MoveTotals(); for (int i = 0; i < menu.slots.size(); i++) { Slot s = menu.getSlot(i); if (s.container == playerInv && TradeItemSpec.matches(s.getItem(), buySpec)) { - ItemStack stack = s.getItem(); - if (stack.isEmpty()) { + if (s.getItem().isEmpty()) { continue; } + ItemStack beforeMove = s.getItem().copy(); try { quickMoveResultSlot(mc, menu, i); - InfoUtils.showGuiOrInGameMessage(Message.MessageType.INFO, - "autotrade.message.moved_bought_to_output", formatStackForMessage(stack)); + movedBought.add(beforeMove); } catch (Exception e) { System.out.println("err " + e); } } } + movedBought.flush("autotrade.message.moved_bought_to_output"); } quickMovePlayerExcessOverCap(mc, menu, playerInv, EMERALD_SPEC, maxKeep); if (Configs.Generic.ENABLE_SELL.getBooleanValue()) { @@ -63,6 +67,7 @@ final class ContainerIoHelper { } } Minecraft mc = Minecraft.getInstance(); + MoveTotals movedFromInput = new MoveTotals(); for (int i = 0; i < menu.slots.size(); i++) { Slot s = menu.getSlot(i); if (s.container == playerInv) { @@ -71,20 +76,20 @@ final class ContainerIoHelper { if (TradeItemSpec.matches(s.getItem(), itemToTake)) { if (inputCount < (Configs.Generic.MAX_INPUT_ITEMS.getIntegerValue() * 64)) { inputCount += s.getItem().getCount(); - ItemStack stack = s.getItem(); - if (stack.isEmpty()) { + if (s.getItem().isEmpty()) { continue; } + ItemStack beforeMove = s.getItem().copy(); try { quickMoveResultSlot(mc, menu, i); - InfoUtils.showGuiOrInGameMessage(Message.MessageType.INFO, "autotrade.message.moved_from_input", - formatStackForMessage(stack)); + movedFromInput.add(beforeMove); } catch (Exception e) { System.out.println("err " + e); } } } } + movedFromInput.flush("autotrade.message.moved_from_input"); } private static int countMatchingOnPlayer(AbstractContainerMenu menu, Inventory playerInv, String spec) { @@ -104,6 +109,7 @@ final class ContainerIoHelper { */ private static void quickMovePlayerExcessOverCap(Minecraft mc, AbstractContainerMenu menu, Inventory playerInv, String spec, int maxKeep) { + MoveTotals movedExcess = new MoveTotals(); while (true) { int before = countMatchingOnPlayer(menu, playerInv, spec); if (before <= maxKeep) { @@ -115,14 +121,13 @@ final class ContainerIoHelper { if (s.container != playerInv || !TradeItemSpec.matches(s.getItem(), spec)) { continue; } - ItemStack stack = s.getItem(); - if (stack.isEmpty()) { + if (s.getItem().isEmpty()) { continue; } + ItemStack beforeMove = s.getItem().copy(); try { quickMoveResultSlot(mc, menu, i); - InfoUtils.showGuiOrInGameMessage(Message.MessageType.INFO, - "autotrade.message.moved_excess_to_output", formatStackForMessage(stack)); + movedExcess.add(beforeMove); } catch (Exception e) { System.out.println("err " + e); } @@ -137,9 +142,33 @@ final class ContainerIoHelper { break; } } + movedExcess.flush("autotrade.message.moved_excess_to_output"); } private static String formatStackForMessage(ItemStack stack) { return stack.getCount() + "× " + stack.getHoverName().getString(); } + + /** Merges moved amounts by {@link Item} for one batched toast line per type. */ + private static final class MoveTotals { + private final Map counts = new HashMap<>(); + + void add(ItemStack stack) { + if (stack.isEmpty()) { + return; + } + counts.merge(stack.getItem(), stack.getCount(), Integer::sum); + } + + void flush(String translationKey) { + if (counts.isEmpty()) { + return; + } + for (Map.Entry e : counts.entrySet()) { + ItemStack line = new ItemStack(e.getKey(), e.getValue()); + InfoUtils.showGuiOrInGameMessage(Message.MessageType.INFO, translationKey, formatStackForMessage(line)); + } + counts.clear(); + } + } } diff --git a/src/main/java/com/github/sebseb7/autotrade/event/KeybindCallbacks.java b/src/main/java/com/github/sebseb7/autotrade/event/KeybindCallbacks.java index 501a8fb..a34886d 100644 --- a/src/main/java/com/github/sebseb7/autotrade/event/KeybindCallbacks.java +++ b/src/main/java/com/github/sebseb7/autotrade/event/KeybindCallbacks.java @@ -8,6 +8,7 @@ import fi.dy.masa.malilib.hotkeys.IKeybind; import fi.dy.masa.malilib.hotkeys.KeyAction; import fi.dy.masa.malilib.interfaces.IClientTickHandler; import net.minecraft.client.Minecraft; +import net.minecraft.world.entity.Entity; public class KeybindCallbacks implements IHotkeyCallback, IClientTickHandler { private static final KeybindCallbacks INSTANCE = new KeybindCallbacks(); @@ -31,6 +32,18 @@ public class KeybindCallbacks implements IHotkeyCallback, IClientTickHandler { return Configs.Generic.ENABLED.getBooleanValue(); } + public Entity getTraderHighlightEntity(Minecraft mc) { + return clientTick.getTraderGlowEntityForRender(mc); + } + + public int getInputContainerHighlightTicks() { + return clientTick.getInputContainerHighlightTicks(); + } + + public int getOutputContainerHighlightTicks() { + return clientTick.getOutputContainerHighlightTicks(); + } + @Override public boolean onKeyAction(KeyAction action, IKeybind key) { return HotkeyActions.handle(Minecraft.getInstance(), key); diff --git a/src/main/java/com/github/sebseb7/autotrade/render/TraderHighlightRenderer.java b/src/main/java/com/github/sebseb7/autotrade/render/TraderHighlightRenderer.java new file mode 100644 index 0000000..6d13e3c --- /dev/null +++ b/src/main/java/com/github/sebseb7/autotrade/render/TraderHighlightRenderer.java @@ -0,0 +1,92 @@ +package com.github.sebseb7.autotrade.render; + +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; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.Shapes; + +/** + * Client wireframe highlights: last-traded villager, and input/output container + * blocks for one second after the mod opens them (same idea as Meteor-style ESP + * boxes). + */ +public final class TraderHighlightRenderer { + 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; + + private static final float LINE_WIDTH = 2.5F; + + private TraderHighlightRenderer() { + } + + public static void register() { + LevelRenderEvents.AFTER_SOLID_FEATURES.register(TraderHighlightRenderer::render); + } + + private static void render(LevelRenderContext 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; + } + + MultiBufferSource.BufferSource bufferSource = context.bufferSource(); + VertexConsumer consumer = bufferSource.getBuffer(RenderTypes.lines()); + PoseStack drawPose = new PoseStack(); + Vec3 camera = mc.gameRenderer.getMainCamera().position(); + float tickDelta = mc.getDeltaTracker().getGameTimeDeltaPartialTick(true); + + if (trader != null) { + double offX = Mth.lerp(tickDelta, trader.xOld, trader.getX()) - trader.getX(); + double offY = Mth.lerp(tickDelta, trader.yOld, trader.getY()) - trader.getY(); + double offZ = Mth.lerp(tickDelta, trader.zOld, trader.getZ()) - trader.getZ(); + AABB worldBox = trader.getBoundingBox().move(offX, offY, offZ); + AABB cameraRelative = worldBox.move(-camera.x, -camera.y, -camera.z); + SHAPE_RENDERER.renderShape(drawPose, consumer, Shapes.create(cameraRelative), 0.0D, 0.0D, 0.0D, + TRADER_OUTLINE_COLOR, LINE_WIDTH); + } + + if (inTicks > 0) { + BlockPos in = new BlockPos(Configs.Generic.INPUT_CONTAINER_X.getIntegerValue(), + Configs.Generic.INPUT_CONTAINER_Y.getIntegerValue(), + Configs.Generic.INPUT_CONTAINER_Z.getIntegerValue()); + drawBlockBox(drawPose, consumer, camera, in, INPUT_CONTAINER_COLOR); + } + + if (outTicks > 0) { + BlockPos out = new BlockPos(Configs.Generic.OUTPUT_CONTAINER_X.getIntegerValue(), + Configs.Generic.OUTPUT_CONTAINER_Y.getIntegerValue(), + Configs.Generic.OUTPUT_CONTAINER_Z.getIntegerValue()); + drawBlockBox(drawPose, consumer, camera, out, OUTPUT_CONTAINER_COLOR); + } + } + + private static void drawBlockBox(PoseStack drawPose, VertexConsumer consumer, Vec3 camera, BlockPos pos, + int color) { + AABB world = AABB.encapsulatingFullBlocks(pos, pos); + AABB cameraRelative = world.move(-camera.x, -camera.y, -camera.z); + SHAPE_RENDERER.renderShape(drawPose, consumer, Shapes.create(cameraRelative), 0.0D, 0.0D, 0.0D, color, + LINE_WIDTH); + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 3837adc..5751538 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -31,6 +31,7 @@ "depends": { "minecraft": ">=${minecraft_version_min}", - "malilib": ">=${malilib_version}" + "malilib": ">=${malilib_version}", + "fabric-api": ">=0.145.0" } }