0.0.14
village glow , container glow, activity messages.
This commit is contained in:
@@ -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}"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Item, Integer> 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<Item, Integer> e : counts.entrySet()) {
|
||||
ItemStack line = new ItemStack(e.getKey(), e.getValue());
|
||||
InfoUtils.showGuiOrInGameMessage(Message.MessageType.INFO, translationKey, formatStackForMessage(line));
|
||||
}
|
||||
counts.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
"depends": {
|
||||
"minecraft": ">=${minecraft_version_min}",
|
||||
"malilib": ">=${malilib_version}"
|
||||
"malilib": ">=${malilib_version}",
|
||||
"fabric-api": ">=0.145.0"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user