diff --git a/src/main/java/com/github/mkram17/bazaarutils/config/features/gui/InventoryConfig.java b/src/main/java/com/github/mkram17/bazaarutils/config/features/gui/InventoryConfig.java index 55f5254f..8fb33fbb 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/config/features/gui/InventoryConfig.java +++ b/src/main/java/com/github/mkram17/bazaarutils/config/features/gui/InventoryConfig.java @@ -1,21 +1,21 @@ package com.github.mkram17.bazaarutils.config.features.gui; -import com.github.mkram17.bazaarutils.features.gui.inventory.InstantSellHighlight; -import com.github.mkram17.bazaarutils.features.gui.inventory.OrderStatusHighlight; -import com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell.NumericRestrictBy; -import com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell.controls.DoubleSellRestrictionControl; -import com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell.controls.SellRestrictionControl; -import com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell.controls.StringSellRestrictionControl; +import com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.controls.NumericRestrictBy; +import com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.controls.DoubleRestrictionControl; +import com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.controls.RestrictionControl; +import com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.RestrictionTarget; +import com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.controls.StringRestrictionControl; import com.teamresourceful.resourcefulconfig.api.annotations.*; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; @Category( value = "inventory_config", categories = { - InventoryConfig.SellRestrictionsRules.class + InventoryConfig.RestrictionRules.class } ) @ConfigInfo( @@ -26,28 +26,6 @@ icon = "box" ) public final class InventoryConfig { - - @ConfigEntry( - id = "instant_sell_restrictions", - translation = "bazaarutils.config.inventory.instant_sell_restrictions.label" - ) - @Comment( - value = "Enables a locking functionality over the Bazaars' §aInstant Sell§r button, with inventory state as well as action restriction.", - translation = "bazaarutils.config.inventory.instant_sell_restrictions.hint" - ) - @ConfigOption.Separator(value = "bazaarutils.config.inventory.separator.instant_sell_restrictions.label") - public static boolean INSTANT_SELL_RESTRICTIONS_TOGGLE = true; - - @ConfigEntry( - id = "instant_sell_restrictions:clicks_required", - translation = "bazaarutils.config.inventory.instant_sell_restrictions.clicks_required.label" - ) - @Comment( - value = "The amount of clicks required to be pressed on the §aInstant Sell§r item to allow the action.", - translation = "bazaarutils.config.inventory.instant_sell_restrictions.clicks_required.hint" - ) - public static int INSTANT_SELL_RESTRICTIONS_CLICKS_OVERRIDE = 3; - @ConfigEntry( id = "instant_sell_highlight", translation = "bazaarutils.config.inventory.instant_sell_highlight.label" @@ -117,73 +95,75 @@ public final class InventoryConfig { @ConfigOption.Color(alpha = true) public static int ORDER_STATUS_HIGHLIGHT_OUTBID_COLOR = 0xFFFF5555; - @Category(value = "instant_sell_rules") + @Category(value = "restrictions") @ConfigInfo( - title = "Instant Sell Rules", - titleTranslation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.category.label", - description = "Manage the rules to be checked by the Instant Sell Restrictions feature", - descriptionTranslation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.category.hint", - icon = "ruler" + title = "Restrictions Rules", + titleTranslation = "bazaarutils.config.inventory.restrictions.category.label" ) - public static final class SellRestrictionsRules { - - @ConfigEntry(id = "numeric_restrictions_separator") - @ConfigOption.Hidden - @ConfigOption.Separator( - value = "bazaarutils.config.inventory.instant_sell_restrictions.rules.separator.numeric_restrictions.label", - description = "bazaarutils.config.inventory.instant_sell_restrictions.rules.separator.numeric_restrictions.hint" + public static final class RestrictionRules { + @ConfigEntry( + id = "enabled", + translation = "bazaarutils.config.inventory.restrictions.enabled.label" ) - public static boolean NUMERIC_RESTRICTIONS_SEPARATOR = true; - - // translation keys now use numeric index for array nesting in json5 - @ConfigEntry(id = "first_numeric_restriction", translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.numeric_restriction.1.label") - public static final DoubleSellRestrictionControl FIRST_NUMERIC_RESTRICTION = new DoubleSellRestrictionControl(false, NumericRestrictBy.PRICE, 0); - - @ConfigEntry(id = "second_numeric_restriction", translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.numeric_restriction.2.label") - public static final DoubleSellRestrictionControl SECOND_NUMERIC_RESTRICTION = new DoubleSellRestrictionControl(false, NumericRestrictBy.PRICE, 0); - - @ConfigEntry(id = "third_numeric_restriction", translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.numeric_restriction.3.label") - public static final DoubleSellRestrictionControl THIRD_NUMERIC_RESTRICTION = new DoubleSellRestrictionControl(false, NumericRestrictBy.PRICE, 0); + @Comment( + value = "Locks selected Bazaar buttons based on inventory or action criteria to prevent accidental market actions.", + translation = "bazaarutils.config.inventory.restrictions.enabled.hint" + ) + @ConfigOption.Separator(value = "bazaarutils.config.inventory.restrictions.separator.introductory.label") + public static boolean RESTRICTIONS_TOGGLE = true; - @ConfigEntry(id = "fourth_numeric_restriction", translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.numeric_restriction.4.label") - public static final DoubleSellRestrictionControl FOURTH_NUMERIC_RESTRICTION = new DoubleSellRestrictionControl(false, NumericRestrictBy.PRICE, 0); + @ConfigEntry( + id = "features", + translation = "bazaarutils.config.inventory.restrictions.features.label" + ) + @Comment( + value = "The inventory buttons for which restrictions are enabled.", + translation = "bazaarutils.config.inventory.restrictions.features.hint" + ) + public static RestrictionTarget[] RESTRICTIONS_ENABLED_FEATURES = new RestrictionTarget[]{}; - @ConfigEntry(id = "fifth_numeric_restriction", translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.numeric_restriction.5.label") - public static final DoubleSellRestrictionControl FIFTH_NUMERIC_RESTRICTION = new DoubleSellRestrictionControl(false, NumericRestrictBy.PRICE, 0); + @ConfigEntry( + id = "clicks_required", + translation = "bazaarutils.config.inventory.restrictions.clicks_required.label" + ) + @Comment( + value = "The number of clicks required on the feature button to confirm the action.", + translation = "bazaarutils.config.inventory.restrictions.clicks_required.hint" + ) + public static int RESTRICTIONS_CLICKS_OVERRIDE = 3; - @ConfigEntry(id = "string_restrictions_separator") + @ConfigEntry(id = "rules_informational_separator") @ConfigOption.Hidden @ConfigOption.Separator( - value = "bazaarutils.config.inventory.instant_sell_restrictions.rules.separator.string_restrictions.label", - description = "bazaarutils.config.inventory.instant_sell_restrictions.rules.separator.string_restrictions.hint" + value = "bazaarutils.config.inventory.restrictions.separator.rules_informational.label", + description = "bazaarutils.config.inventory.restrictions.separator.rules_informational.hint" ) - public static boolean STRING_RESTRICTIONS_SEPARATOR = true; - - // translation keys now use numeric index for array nesting in json5 - @ConfigEntry(id = "first_string_restriction", translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.string_restriction.1.label") - public static final StringSellRestrictionControl FIRST_STRING_RESTRICTION = new StringSellRestrictionControl(false, ""); + public static boolean RESTRICTIONS_RULES_INFORMATIONAL_SEPARATOR = true; - @ConfigEntry(id = "second_string_restriction", translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.string_restriction.2.label") - public static final StringSellRestrictionControl SECOND_STRING_RESTRICTION = new StringSellRestrictionControl(false, ""); - - @ConfigEntry(id = "third_string_restriction", translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.string_restriction.3.label") - public static final StringSellRestrictionControl THIRD_STRING_RESTRICTION = new StringSellRestrictionControl(false, ""); - - @ConfigEntry(id = "fourth_string_restriction", translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.string_restriction.4.label") - public static final StringSellRestrictionControl FOURTH_STRING_RESTRICTION = new StringSellRestrictionControl(false, ""); - - @ConfigEntry(id = "fifth_string_restriction", translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.string_restriction.5.label") - public static final StringSellRestrictionControl FIFTH_STRING_RESTRICTION = new StringSellRestrictionControl(false, ""); + @ConfigEntry( + id = "numeric_restrictions", + translation = "bazaarutils.config.inventory.restrictions.numeric_restrictions.label" + ) + @Comment( + value = "Rules checking numeric conditions (e.g., total items or coins) to restrict targeted actions.", + translation = "bazaarutils.config.inventory.restrictions.numeric_restrictions.hint" + ) + public static final List RESTRICTIONS_NUMERIC_RULES = new ArrayList<>(List.of((new DoubleRestrictionControl(NumericRestrictBy.PRICE, 0)))); - public static final SellRestrictionControl[] ALL = new SellRestrictionControl[]{ - FIRST_NUMERIC_RESTRICTION, SECOND_NUMERIC_RESTRICTION, THIRD_NUMERIC_RESTRICTION, - FOURTH_NUMERIC_RESTRICTION, FIFTH_NUMERIC_RESTRICTION, - FIRST_STRING_RESTRICTION, SECOND_STRING_RESTRICTION, THIRD_STRING_RESTRICTION, - FOURTH_STRING_RESTRICTION, FIFTH_STRING_RESTRICTION - }; + @ConfigEntry( + id = "string_restrictions", + translation = "bazaarutils.config.inventory.restrictions.string_restrictions.label" + ) + @Comment( + value = "Rules checking specific item names or types to restrict targeted actions.", + translation = "bazaarutils.config.inventory.restrictions.string_restrictions.hint" + ) + public static final List RESTRICTIONS_STRING_RULES = new ArrayList<>(); - public static List> restrictors() { - return Arrays.stream(ALL).collect(Collectors.toList()); + public static List> restrictors(RestrictionTarget target) { + return Stream.concat(RESTRICTIONS_NUMERIC_RULES.stream(), RESTRICTIONS_STRING_RULES.stream()) + .filter(rule -> rule.appliesTo(target)) + .collect(Collectors.toList()); } } } \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/events/ChestLoadedEvent.java b/src/main/java/com/github/mkram17/bazaarutils/events/ChestLoadedEvent.java index b0b5dde1..b0e0bbee 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/events/ChestLoadedEvent.java +++ b/src/main/java/com/github/mkram17/bazaarutils/events/ChestLoadedEvent.java @@ -10,6 +10,7 @@ import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.LoreComponent; import net.minecraft.inventory.Inventory; import net.minecraft.item.ItemStack; import net.minecraft.screen.GenericContainerScreenHandler; @@ -143,11 +144,24 @@ private static boolean isItemLoading(Inventory inventory) { if (item.isEmpty()) continue; Text customName = item.get(DataComponentTypes.CUSTOM_NAME); - if (customName != null) { - String displayName = Util.removeFormatting(customName.getString()); - if (displayName.contains("Loading")) { - PlayerActionUtil.notifyAll("Loading item...", NotificationType.GUI); - return true; + if (customName == null) continue; + + String name = Util.removeFormatting(customName.getString()); + + if (name.contains("Loading")) { + return true; + } + + // Only bottleneck on lore data of items known to have partialized lore + if (name.contains("Sell")) { + LoreComponent lore = item.get(DataComponentTypes.LORE); + + if (lore != null && !lore.lines().isEmpty()) { + for (Text line : lore.lines()) { + if (Util.removeFormatting(line.getString()).contains("Loading")) { + return true; + } + } } } } diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/InstantSellHighlight.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/InstantSellHighlight.java index c7367dd4..d4e89725 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/InstantSellHighlight.java +++ b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/InstantSellHighlight.java @@ -1,113 +1,122 @@ package com.github.mkram17.bazaarutils.features.gui.inventory; +import com.github.mkram17.bazaarutils.BazaarUtils; import com.github.mkram17.bazaarutils.config.features.gui.InventoryConfig; import com.github.mkram17.bazaarutils.events.ChestLoadedEvent; import com.github.mkram17.bazaarutils.events.listener.BUListener; -import com.github.mkram17.bazaarutils.generated.BazaarUtilsModules; -import com.github.mkram17.bazaarutils.misc.SlotHighlightCache; +import com.github.mkram17.bazaarutils.utils.Util; +import com.github.mkram17.bazaarutils.utils.bazaar.gui.BazaarScreenHandler; import com.github.mkram17.bazaarutils.utils.bazaar.gui.BazaarScreens; import com.github.mkram17.bazaarutils.utils.annotations.modules.Module; +import com.github.mkram17.bazaarutils.utils.bazaar.components.InstantSellParser; import com.github.mkram17.bazaarutils.utils.bazaar.market.order.OrderInfo; import com.github.mkram17.bazaarutils.utils.config.BUToggleableFeature; +import com.github.mkram17.bazaarutils.utils.minecraft.ItemInfo; +import com.github.mkram17.bazaarutils.utils.minecraft.SlotHighlight; +import com.github.mkram17.bazaarutils.utils.minecraft.gui.ScreenContext; import com.github.mkram17.bazaarutils.utils.minecraft.gui.ScreenManager; -import com.github.mkram17.bazaarutils.utils.Util; -import com.github.mkram17.bazaarutils.utils.InstaSellUtil; import meteordevelopment.orbit.EventHandler; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.HandledScreen; import net.minecraft.entity.player.PlayerInventory; -import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; @Module -public class InstantSellHighlight extends BUListener implements BUToggleableFeature { - private static final List highlightedSlotIndexes = new ArrayList<>(); +public class InstantSellHighlight extends BUListener implements BUToggleableFeature, SlotHighlight { + public static final Identifier IDENTIFIER = Identifier.tryParse(BazaarUtils.MOD_ID, "highlights/standard_background"); @Override - public boolean isEnabled() { - return InventoryConfig.INSTANT_SELL_HIGHLIGHT_TOGGLE; + public Identifier getIdentifier() { + return IDENTIFIER; } - public InstantSellHighlight() {} + private static final Map colorCache = new ConcurrentHashMap<>(); - @EventHandler - private void onScreenLoad(ChestLoadedEvent e) { - highlightedSlotIndexes.clear(); + private void populateCache(Set names, HandledScreen screen, PlayerInventory playerInventory) { + colorCache.clear(); - if (!isEnabled() || !ScreenManager.getInstance().isCurrent(BazaarScreens.MAIN_PAGE)) { - return; - } + for (Slot slot : screen.getScreenHandler().slots) { + if (!slot.hasStack() || slot.inventory != playerInventory) continue; + + Text customName = slot.getStack().getCustomName(); + + if (customName == null) continue; - Optional optionalInventory = getInventory(); - if (optionalInventory.isEmpty()) { - Util.notifyError("Failed to get player inventory.", new Throwable()); - return; + String itemName = customName.getString(); + + if (names.stream().anyMatch(itemName::equalsIgnoreCase)) { + colorCache.put(slot.getIndex(), InventoryConfig.INSTANT_SELL_HIGHLIGHT_COLOR); + } } - PlayerInventory inventory = optionalInventory.get(); - - List instaSellOrders = InstaSellUtil.getInstaSellOrders(e.getItemStacks()); - List names = instaSellOrders.stream() - .map(OrderInfo::getName) - .distinct() - .toList(); - - List inventoryStacks = getInventoryStacks(names); - - highlightedSlotIndexes.addAll( - inventoryStacks.stream() - .filter(itemStack -> !itemStack.isEmpty()) - .map(ScreenManager::getInventorySlotFromItemStack) - .flatMap(Optional::stream) - .toList() - ); } - private Optional getInventory(){ - ClientPlayerEntity player = MinecraftClient.getInstance().player; - if (player == null) { - Util.notifyError("Player is null, cannot get inventory stacks.", new Throwable()); - return Optional.empty(); - } + @Override + public Integer getHighlightColor(int slotIndex) { + return colorCache.get(slotIndex); + } - return Optional.of(player.getInventory()); + @Override + public boolean isEnabled() { + return InventoryConfig.INSTANT_SELL_HIGHLIGHT_TOGGLE; } + public InstantSellHighlight() { + super(); + } - private List getInventoryStacks(List names){ - List inventoryStacks = new ArrayList<>(); + @Override + protected void registerFabricEvents() { + ScreenEvents.AFTER_INIT.register(this::onScreenInitialized); + } - var inventoryOpt = getInventory(); - if (inventoryOpt.isEmpty()) return Collections.emptyList(); - var inventory = inventoryOpt.get(); + @EventHandler + private void onChestLoaded(ChestLoadedEvent event) { + colorCache.clear(); - var stacks = inventory.getMainStacks(); + if (!isEnabled()) return; - stacks.forEach(itemStack -> { - if(itemStack.isEmpty()) return; - String itemName = itemStack.getName().getString(); - if (names.stream().anyMatch(name -> itemName.toLowerCase().contains(name.toLowerCase()))) { - inventoryStacks.add(itemStack); - } - }); - return inventoryStacks; - } + ScreenManager.getInstance().current().ifPresent(context -> { + HandledScreen screen = ScreenManager.getCurrentlyHandledScreen(HandledScreen.class).orElse(null); + MinecraftClient client = MinecraftClient.getInstance(); - public static void updateHighlightCache() { - if (!BazaarUtilsModules.InstantSellHighlight.isEnabled()) { - return; - } + if (screen == null || client.player == null) return; - for (Integer index : highlightedSlotIndexes) { - getColorFromIndex(index).ifPresent(instaSellHighlightColor -> SlotHighlightCache.instaSellHighlightCache.computeIfAbsent(index, (k) -> instaSellHighlightColor)); - } + List orders = resolveOrders(context); + + if (orders.isEmpty()) return; + + Set names = orders.stream() + .map(OrderInfo::getName) + .collect(Collectors.toSet()); + + populateCache(names, screen, client.player.getInventory()); + }); } - public static OptionalInt getColorFromIndex(int slotIndex) { - if (highlightedSlotIndexes.stream().noneMatch(i -> i.equals(slotIndex))) { - return OptionalInt.empty(); - } + private void onScreenInitialized(MinecraftClient client, Screen screen, int width, int height) { + colorCache.clear(); + } - return OptionalInt.of(InventoryConfig.INSTANT_SELL_HIGHLIGHT_COLOR); + private static List resolveOrders(ScreenContext context) { + if (context.isAnyOf(BazaarScreens.ITEM_PAGE)) + return BazaarScreenHandler.getInstantSellItem(context) + .map(ItemInfo::itemStack) + .flatMap(InstantSellParser::parseItemPageOrder) + .map(InstantSellParser.InstantSellResult::items) + .orElse(List.of()); + + return BazaarScreenHandler.getInstantSellItem(context) + .map(ItemInfo::itemStack) + .map(InstantSellParser::parseOrders) + .map(InstantSellParser.InstantSellResult::items) + .orElse(List.of()); } -} +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/OrderStatusHighlight.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/OrderStatusHighlight.java index 94d2d7e0..dd5d0265 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/OrderStatusHighlight.java +++ b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/OrderStatusHighlight.java @@ -1,17 +1,24 @@ package com.github.mkram17.bazaarutils.features.gui.inventory; +import com.github.mkram17.bazaarutils.BazaarUtils; import com.github.mkram17.bazaarutils.config.features.DeveloperConfig; import com.github.mkram17.bazaarutils.config.features.gui.InventoryConfig; +import com.github.mkram17.bazaarutils.events.ChestLoadedEvent; import com.github.mkram17.bazaarutils.events.listener.BUListener; import com.github.mkram17.bazaarutils.generated.BazaarUtilsModules; +import com.github.mkram17.bazaarutils.utils.bazaar.gui.BazaarScreens; import com.github.mkram17.bazaarutils.utils.config.BUToggleableFeature; -import com.github.mkram17.bazaarutils.misc.SlotHighlightCache; import com.github.mkram17.bazaarutils.utils.annotations.modules.Module; import com.github.mkram17.bazaarutils.utils.bazaar.market.order.*; import com.github.mkram17.bazaarutils.utils.Util; import com.github.mkram17.bazaarutils.utils.bazaar.market.price.PricingPosition; +import com.github.mkram17.bazaarutils.utils.minecraft.SlotHighlight; +import com.github.mkram17.bazaarutils.utils.minecraft.gui.ScreenManager; +import meteordevelopment.orbit.EventHandler; import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.HandledScreen; import net.minecraft.item.ItemStack; import net.minecraft.item.tooltip.TooltipType; @@ -21,155 +28,100 @@ import net.minecraft.text.TextColor; import net.minecraft.util.Identifier; +import java.util.ArrayList; import java.util.List; -import java.util.Optional; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; //drawing done in MixinHandledScreen @Module -public class OrderStatusHighlight extends BUListener implements BUToggleableFeature { - public static final Identifier IDENTIFIER = Identifier.tryParse("bazaarutils", "orderstatushighlight/background"); +public class OrderStatusHighlight extends BUListener implements BUToggleableFeature, SlotHighlight { + public static final Identifier IDENTIFIER = Identifier.tryParse(BazaarUtils.MOD_ID, "highlights/standard_background"); @Override - public boolean isEnabled() { - return InventoryConfig.ORDER_STATUS_HIGHLIGHT_TOGGLE; + public Identifier getIdentifier() { + return IDENTIFIER; } - public OrderStatusHighlight() {} + private static final Map colorCache = new ConcurrentHashMap<>(); + private static final Map> tooltipCache = new ConcurrentHashMap<>(); - public static Order getHighlightedOrder(int slotIndex) { - Optional order = OrderUtil.getUserOrderFromIndex(slotIndex); + private void populateCache(ItemStack stack, HandledScreen screen) { + int index = getSlotIndex(stack, screen); + if (index == -1) return; - return order.filter( - bazaarOrder -> bazaarOrder.getStatus() != null && bazaarOrder.getStatus() == OrderStatus.SET) - .orElse(null); + Order order = getOrderForHighlight(index); + if (order == null) return; + + PricingPosition pos = order.getPricingPosition(); + if (pos == null) return; + + colorCache.put(index, getArgbFromPricingPosition(pos)); + tooltipCache.put(index, buildTooltipLines(order, pos)); } @Override - protected void registerFabricEvents() { - super.subscribeToMeteorEventBus = false; - registerTooltipListener(); + public Integer getHighlightColor(int slotIndex) { + return colorCache.get(slotIndex); } - public static void updateHighlightCache(List itemStacks) { - if (!BazaarUtilsModules.OrderStatusHighlight.isEnabled()) { - return; - } - - for (ItemStack stack : itemStacks) { - MinecraftClient client = MinecraftClient.getInstance(); - - if (client.player == null || !(client.currentScreen instanceof HandledScreen handledScreen)) { - continue; - } - - int index = -1; - - for (Slot slot : handledScreen.getScreenHandler().slots) { - if (!slot.hasStack() || !slot.getStack().equals(stack)) { - continue; - } - - index = slot.getIndex(); - } - - if (index == -1) { - continue; - } + @Override + public boolean isEnabled() { + return InventoryConfig.ORDER_STATUS_HIGHLIGHT_TOGGLE; + } - SlotHighlightCache.orderStatusHighlightCache.computeIfAbsent(index, OrderStatusHighlight::getHighlightColorFromIndex); - } + public OrderStatusHighlight() { + super(); } - private static Integer getHighlightColorFromIndex(int index) { - Order order = getHighlightedOrder(index); + @Override + protected void registerFabricEvents() { + ScreenEvents.AFTER_INIT.register(this::onScreenInitialized); + ItemTooltipCallback.EVENT.register(this::onTooltip); + } - if (order == null) { - return null; + @EventHandler + private void onChestLoaded(ChestLoadedEvent event) { + if (!BazaarUtilsModules.OrderStatusHighlight.isEnabled() || !ScreenManager.getInstance().isCurrent(BazaarScreens.ORDERS_PAGE)) { + return; } - PricingPosition pricingPosition = order.getPricingPosition(); - - if (pricingPosition == null) { - return null; - } + HandledScreen screen = ScreenManager.getCurrentlyHandledScreen(HandledScreen.class).orElse(null); + if (screen == null) return; - return getArgbFromPricingPosition(pricingPosition); + event.getItemStacks().forEach(stack -> populateCache(stack, screen)); } - //maybe could be split into separate methods, but this is fine for now - private void registerTooltipListener() { - ItemTooltipCallback.EVENT.register((ItemStack stack, net.minecraft.item.Item.TooltipContext context, TooltipType type, List lines) -> { - if (!isEnabled()) { - return; - } - - MinecraftClient client = MinecraftClient.getInstance(); - - if (!(client.currentScreen instanceof HandledScreen handledScreen)) { - return; - } - - int index = -1; - - for (Slot slot : handledScreen.getScreenHandler().slots) { - if (!slot.hasStack() || !(slot.getStack().equals(stack))) { - continue; - } - - index = slot.getIndex(); - } + private void onScreenInitialized(MinecraftClient client, Screen screen, int width, int height) { + colorCache.clear(); + tooltipCache.clear(); + } - if (!SlotHighlightCache.orderStatusHighlightCache.containsKey(index)) { - return; - } + private void onTooltip(ItemStack stack, net.minecraft.item.Item.TooltipContext context, TooltipType type, List lines) { + if (!isEnabled() || !ScreenManager.getInstance().isCurrent(BazaarScreens.ORDERS_PAGE)) return; - Order order = getHighlightedOrder(index); + HandledScreen screen = ScreenManager.getCurrentlyHandledScreen(HandledScreen.class).orElse(null); + if (screen == null) return; - if (order == null) { - return; - } + int index = getSlotIndex(stack, screen); + if (index == -1) return; - PricingPosition pricingPosition = order.getPricingPosition(); + List cached = tooltipCache.get(index); + if (cached != null) lines.addAll(1, cached); + } - if (pricingPosition == null) { - return; - } + private static int getSlotIndex(ItemStack stack, HandledScreen screen) { + for (Slot slot : screen.getScreenHandler().slots) { + if (slot.hasStack() && slot.getStack().equals(stack)) return slot.getIndex(); + } - switch (pricingPosition) { - case COMPETITIVE: - lines.add(1, Text.literal("COMPETITIVE") - .setStyle(Style.EMPTY - .withColor(TextColor.fromRgb(InventoryConfig.ORDER_STATUS_HIGHLIGHT_COMPETITIVE_COLOR)) - .withBold(true))); - break; - - case MATCHED: - lines.add(1, Text.literal("MATCHED") - .setStyle(Style.EMPTY - .withColor(TextColor.fromRgb(InventoryConfig.ORDER_STATUS_HIGHLIGHT_MATCHED_COLOR)) - .withBold(true))); - break; - - case OUTBID: - lines.add(1, Text.literal("OUTBID") - .setStyle(Style.EMPTY - .withColor(TextColor.fromRgb(InventoryConfig.ORDER_STATUS_HIGHLIGHT_OUTBID_COLOR)) - .withBold(true))); - - lines.add(2, Text.literal("Market Price: " + - Util.getPrettyString(order.getMarketPrice(order.getTransactionType().getSide()))) - .setStyle(Style.EMPTY - .withColor(TextColor.fromRgb(InventoryConfig.ORDER_STATUS_HIGHLIGHT_OUTBID_COLOR)))); - break; - } - if (DeveloperConfig.DEVELOPER_MODE_TOGGLE) { - var sellPrice = order.getMarketPrice(TransactionType.Side.BUY); - var buyPrice = order.getMarketPrice(TransactionType.Side.SELL); + return -1; + } - lines.add(Text.literal("[BU] Buy: " + Util.getPrettyString(sellPrice) + " coins")); - lines.add(Text.literal("[BU] Sell: " + Util.getPrettyString(buyPrice) + " coins")); - } - }); + private static Order getOrderForHighlight(int slotIndex) { + return OrderUtil.getUserOrderFromIndex(slotIndex) + .filter(o -> o.getStatus() != null && o.getStatus() == OrderStatus.SET) + .orElse(null); } private static int getArgbFromPricingPosition(PricingPosition pricingPosition) { @@ -179,4 +131,28 @@ private static int getArgbFromPricingPosition(PricingPosition pricingPosition) { case OUTBID -> InventoryConfig.ORDER_STATUS_HIGHLIGHT_OUTBID_COLOR; }; } -} + + private static List buildTooltipLines(Order order, PricingPosition pos) { + List lines = new ArrayList<>(); + + switch (pos) { + case COMPETITIVE -> lines.add(styledText("COMPETITIVE", InventoryConfig.ORDER_STATUS_HIGHLIGHT_COMPETITIVE_COLOR, true)); + case MATCHED -> lines.add(styledText("MATCHED", InventoryConfig.ORDER_STATUS_HIGHLIGHT_MATCHED_COLOR, true)); + case OUTBID -> { + lines.add(styledText("OUTBID", InventoryConfig.ORDER_STATUS_HIGHLIGHT_OUTBID_COLOR, true)); + lines.add(styledText("Market Price: " + Util.getPrettyString(order.getMarketPrice(order.getTransactionType().getSide())), InventoryConfig.ORDER_STATUS_HIGHLIGHT_OUTBID_COLOR, false)); + } + } + + if (DeveloperConfig.DEVELOPER_MODE_TOGGLE) { + lines.add(Text.literal("[BU] Buy: " + Util.getPrettyString(order.getMarketPrice(TransactionType.Side.BUY)) + " coins")); + lines.add(Text.literal("[BU] Sell: " + Util.getPrettyString(order.getMarketPrice(TransactionType.Side.SELL)) + " coins")); + } + + return lines; + } + + private static Text styledText(String content, int rgb, boolean bold) { + return Text.literal(content).setStyle(Style.EMPTY.withColor(TextColor.fromRgb(rgb)).withBold(bold)); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/InstantSellRestrictions.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/InstantSellRestrictions.java new file mode 100644 index 00000000..cb268ddd --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/InstantSellRestrictions.java @@ -0,0 +1,83 @@ +package com.github.mkram17.bazaarutils.features.gui.inventory.restrictions; + +import com.github.mkram17.bazaarutils.config.features.gui.InventoryConfig; +import com.github.mkram17.bazaarutils.events.ChestLoadedEvent; +import com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.controls.RestrictionControl; +import com.github.mkram17.bazaarutils.utils.annotations.modules.Module; +import com.github.mkram17.bazaarutils.utils.bazaar.RestrictionHelper; +import com.github.mkram17.bazaarutils.utils.bazaar.gui.BazaarScreenHandler; +import com.github.mkram17.bazaarutils.utils.bazaar.gui.BazaarScreens; +import com.github.mkram17.bazaarutils.utils.bazaar.components.InstantSellParser; +import com.github.mkram17.bazaarutils.utils.bazaar.market.order.OrderInfo; +import com.github.mkram17.bazaarutils.utils.minecraft.ItemInfo; +import com.github.mkram17.bazaarutils.utils.minecraft.gui.ScreenContext; +import com.github.mkram17.bazaarutils.utils.minecraft.gui.ScreenManager; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Optional; + +//TODO maybe color chest if it is locked +@Module +public class InstantSellRestrictions extends RestrictionHelper { + public record InstantSellState( + @NotNull + ItemInfo targetItem, + + @NotNull + List> triggeredRestrictors + ) implements RestrictionHelper.RestrictionState {} + + @Override + public boolean isEnabled() { + return InventoryConfig.RestrictionRules.RESTRICTIONS_TOGGLE && RestrictionTarget.isRestrictorFeatureEnabled(RestrictionTarget.INSTANT_SELL); + } + + @Override + protected int getClicksOverride() { + return InventoryConfig.RestrictionRules.RESTRICTIONS_CLICKS_OVERRIDE; + } + + @Override + protected String getMessagePrefix() { + return "Sell protected by rules:"; + } + + @Override + protected List> getRestrictors() { + return InventoryConfig.RestrictionRules.restrictors(RestrictionTarget.INSTANT_SELL); + } + + public InstantSellRestrictions() { + super("Instant Sell Restrictions"); + } + + @Override + public boolean inCorrectScreen() { + return ScreenManager.getInstance().isCurrent(BazaarScreens.MAIN_PAGE, BazaarScreens.ITEM_PAGE, BazaarScreens.ITEMS_GROUP_PAGE); + } + + @Override + protected Optional makeState(ChestLoadedEvent event) { + Optional context = ScreenManager.getInstance().current(); + + if (context.isEmpty()) return Optional.empty(); + + Optional instantSellItem = BazaarScreenHandler.getInstantSellItem(context.get()); + + if (instantSellItem.isEmpty()) return Optional.empty(); + + List orders = context.get().isAnyOf(BazaarScreens.ITEM_PAGE) + ? InstantSellParser.parseItemPageOrder(instantSellItem.get().itemStack()) + .map(InstantSellParser.InstantSellResult::items) + .orElse(List.of()) + : InstantSellParser.parseOrders(instantSellItem.get().itemStack()) + .items(); + + List> triggered = getRestrictors().stream() + .filter(control -> control.anyMatch(orders)) + .toList(); + + return Optional.of(new InstantSellState(instantSellItem.get(), triggered)); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/RestrictionTarget.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/RestrictionTarget.java new file mode 100644 index 00000000..c24bf9c5 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/RestrictionTarget.java @@ -0,0 +1,41 @@ +package com.github.mkram17.bazaarutils.features.gui.inventory.restrictions; + +import com.github.mkram17.bazaarutils.config.features.gui.InventoryConfig; +import com.teamresourceful.resourcefulconfig.api.types.info.TooltipProvider; +import com.teamresourceful.resourcefulconfig.api.types.info.Translatable; +import net.minecraft.text.Text; + +public enum RestrictionTarget implements TooltipProvider, Translatable { + INSTANT_SELL { + @Override + public String getTranslationKey() { + return "bazaarutils.config.inventory.restrictions.features.target.instant_sell.label"; + } + + @Override + public Text getTooltip() { + return Text.translatable("bazaarutils.config.inventory.restrictions.features.target.instant_sell.label"); + } + }, + SELL_SACKS { + @Override + public String getTranslationKey() { + return "bazaarutils.config.inventory.restrictions.features.target.sell_sacks.label"; + } + + @Override + public Text getTooltip() { + return Text.translatable("bazaarutils.config.inventory.restrictions.features.target.sell_sacks.label"); + } + }; + + public abstract String getTranslationKey(); + + public static boolean isRestrictorFeatureEnabled(RestrictionTarget target) { + for (RestrictionTarget scoped : InventoryConfig.RestrictionRules.RESTRICTIONS_ENABLED_FEATURES) { + if (scoped == target) return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/SellSacksRestrictions.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/SellSacksRestrictions.java new file mode 100644 index 00000000..353bd1a2 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/SellSacksRestrictions.java @@ -0,0 +1,91 @@ +package com.github.mkram17.bazaarutils.features.gui.inventory.restrictions; + +import com.github.mkram17.bazaarutils.config.features.gui.InventoryConfig; +import com.github.mkram17.bazaarutils.events.ChestLoadedEvent; +import com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.controls.DoubleRestrictionControl; +import com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.controls.RestrictionControl; +import com.github.mkram17.bazaarutils.utils.annotations.modules.Module; +import com.github.mkram17.bazaarutils.utils.bazaar.RestrictionHelper; +import com.github.mkram17.bazaarutils.utils.bazaar.components.SellSacksParser; +import com.github.mkram17.bazaarutils.utils.bazaar.gui.BazaarScreenHandler; +import com.github.mkram17.bazaarutils.utils.bazaar.gui.BazaarScreens; +import com.github.mkram17.bazaarutils.utils.minecraft.ItemInfo; +import com.github.mkram17.bazaarutils.utils.minecraft.gui.ScreenContext; +import com.github.mkram17.bazaarutils.utils.minecraft.gui.ScreenManager; +import org.jetbrains.annotations.NotNull; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +@Module +public class SellSacksRestrictions extends RestrictionHelper { + public record SellSacksState( + @NotNull + ItemInfo targetItem, + + @NotNull + List> triggeredRestrictors + ) implements RestrictionHelper.RestrictionState {} + + @Override + public boolean isEnabled() { + return InventoryConfig.RestrictionRules.RESTRICTIONS_TOGGLE && RestrictionTarget.isRestrictorFeatureEnabled(RestrictionTarget.SELL_SACKS); + } + + @Override + protected int getClicksOverride() { + return InventoryConfig.RestrictionRules.RESTRICTIONS_CLICKS_OVERRIDE; + } + + @Override + protected String getMessagePrefix() { + return "Sell sacks protected by rules:"; + } + + @Override + protected List> getRestrictors() { + return InventoryConfig.RestrictionRules.restrictors(RestrictionTarget.SELL_SACKS); + } + + + public SellSacksRestrictions() { + super("Sell Sacks Restrictions"); + } + + @Override + public boolean inCorrectScreen() { + return ScreenManager.getInstance().isCurrent(BazaarScreens.MAIN_PAGE, BazaarScreens.ITEM_PAGE, BazaarScreens.ITEMS_GROUP_PAGE); + } + + @Override + protected Optional makeState(ChestLoadedEvent event) { + Optional context = ScreenManager.getInstance().current(); + + if (context.isEmpty()) return Optional.empty(); + + Optional sellSacksItem = BazaarScreenHandler.getSellSacksItem(context.get()); + + if (sellSacksItem.isEmpty()) return Optional.empty(); + + SellSacksParser.SellSacksResult result = SellSacksParser.parseOrders(sellSacksItem.get().itemStack()); + + Set> triggered = new LinkedHashSet<>(getRestrictors().stream() + .filter(control -> control.anyMatch(result.items())) + .toList()); + + result.otherItems().ifPresent(other -> triggered.addAll(collectOtherItemsTriggered(other))); + + return Optional.of(new SellSacksState(sellSacksItem.get(), List.copyOf(triggered))); + } + + private List> collectOtherItemsTriggered(SellSacksParser.SellSacksResult.OtherItems otherItems) { + return getRestrictors().stream() + .filter(control -> control instanceof DoubleRestrictionControl doubleControl && switch (doubleControl.getRule()) { + case PRICE -> otherItems.totalValue() > doubleControl.getAmount(); + case VOLUME -> otherItems.volume() > doubleControl.getAmount(); + }) + .toList(); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/DoubleRestrictionControl.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/DoubleRestrictionControl.java new file mode 100644 index 00000000..ca5d7b93 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/DoubleRestrictionControl.java @@ -0,0 +1,90 @@ +package com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.controls; + +import com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.RestrictionTarget; +import com.github.mkram17.bazaarutils.utils.bazaar.market.order.OrderInfo; +import com.teamresourceful.resourcefulconfig.api.annotations.Comment; +import com.teamresourceful.resourcefulconfig.api.annotations.ConfigEntry; +import com.teamresourceful.resourcefulconfig.api.annotations.ConfigObject; +import com.teamresourceful.resourcefulconfig.api.types.info.ListEntryInfoProvider; +import lombok.Getter; +import lombok.Setter; +import net.minecraft.text.Text; + +import java.util.Arrays; +import java.util.Locale; + +@Getter +@Setter +@ConfigObject +public final class DoubleRestrictionControl implements RestrictionControl, ListEntryInfoProvider { + @ConfigEntry( + id = "rule", + translation = "bazaarutils.config.inventory.restrictions.control.numeric.type.label" + ) + @Comment( + value = "Whether the restriction triggers on total coin value or item quantity", + translation = "bazaarutils.config.inventory.restrictions.control.numeric.type.hint" + ) + public NumericRestrictBy rule; + + @ConfigEntry( + id = "amount", + translation = "bazaarutils.config.inventory.restrictions.control.numeric.threshold.label" + ) + @Comment( + value = "The threshold value above which the restriction will trigger", + translation = "bazaarutils.config.inventory.restrictions.control.numeric.threshold.hint" + ) + public double amount; + + @ConfigEntry( + id = "targets", + translation = "bazaarutils.config.inventory.restrictions.control.targets.label" + ) + @Comment( + value = "The features for which this rule is enabled", + translation = "bazaarutils.config.inventory.restrictions.control.targets.hint" + ) + public RestrictionTarget[] targets = new RestrictionTarget[] { + RestrictionTarget.INSTANT_SELL, + RestrictionTarget.SELL_SACKS + }; + + public DoubleRestrictionControl(NumericRestrictBy rule, double amount) { + this.rule = rule; + this.amount = amount; + } + + public DoubleRestrictionControl() { + this(NumericRestrictBy.PRICE, 0); + } + + @Override + public boolean shouldRestrict(OrderInfo item) { + return switch (rule) { + case PRICE -> item.getPricePerItem() * item.getVolume() > amount; + case VOLUME -> item.getVolume() > amount; + }; + } + + @Override + public String describeRule() { + return switch (rule) { + case PRICE -> "PRICE > " + amount; + case VOLUME -> "VOLUME > " + amount; + }; + } + + @Override + public Text getTitle(int index) { + return Text.literal(switch (rule) { + case PRICE -> "Blocks if total price > " + amount; + case VOLUME -> "Blocks if volume held > " + amount; + }); + } + + @Override + public Text getDescription(int index) { + return Text.literal("Applies to: " + formatTargets()); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/NumericRestrictBy.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/NumericRestrictBy.java similarity index 84% rename from src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/NumericRestrictBy.java rename to src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/NumericRestrictBy.java index 991e6929..03d23f98 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/NumericRestrictBy.java +++ b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/NumericRestrictBy.java @@ -1,3 +1,3 @@ -package com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell; +package com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.controls; public enum NumericRestrictBy { PRICE, VOLUME } diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/RestrictionControl.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/RestrictionControl.java new file mode 100644 index 00000000..acf81289 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/RestrictionControl.java @@ -0,0 +1,40 @@ +package com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.controls; + +import com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.RestrictionTarget; + +public sealed interface RestrictionControl> extends Restrictor permits DoubleRestrictionControl, StringRestrictionControl { + RestrictionTarget[] getTargets(); + T getRule(); + String describeRule(); + + default boolean appliesTo(RestrictionTarget target) { + for (RestrictionTarget scoped : getTargets()) { + if (scoped == target) return true; + } + + return false; + } + + default String formatTargets() { + RestrictionTarget[] targets = getTargets(); + + if (targets == null || targets.length == 0) return "None"; + + StringBuilder builder = new StringBuilder(); + + for (int i = 0; i < targets.length; i++) { + String pretty = switch (targets[i]) { + case INSTANT_SELL -> "Instant Sell"; + case SELL_SACKS -> "Sell Sacks"; + }; + + builder.append(pretty); + + if (i < targets.length - 1) { + builder.append(", "); + } + } + + return builder.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/Restrictor.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/Restrictor.java new file mode 100644 index 00000000..8ca3f444 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/Restrictor.java @@ -0,0 +1,13 @@ +package com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.controls; + +import com.github.mkram17.bazaarutils.utils.bazaar.market.order.OrderInfo; + +import java.util.List; + +public interface Restrictor { + boolean shouldRestrict(OrderInfo item); + + default boolean anyMatch(List items) { + return items.stream().anyMatch(this::shouldRestrict); + } +} diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/StringRestrictBy.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/StringRestrictBy.java similarity index 83% rename from src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/StringRestrictBy.java rename to src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/StringRestrictBy.java index c077dce6..beffeb9d 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/StringRestrictBy.java +++ b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/StringRestrictBy.java @@ -1,3 +1,3 @@ -package com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell; +package com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.controls; public enum StringRestrictBy { NAME } diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/StringRestrictionControl.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/StringRestrictionControl.java new file mode 100644 index 00000000..8d669a47 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictions/controls/StringRestrictionControl.java @@ -0,0 +1,69 @@ +package com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.controls; + +import com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.RestrictionTarget; +import com.github.mkram17.bazaarutils.utils.bazaar.market.order.OrderInfo; +import com.teamresourceful.resourcefulconfig.api.annotations.Comment; +import com.teamresourceful.resourcefulconfig.api.annotations.ConfigEntry; +import com.teamresourceful.resourcefulconfig.api.annotations.ConfigObject; +import com.teamresourceful.resourcefulconfig.api.types.info.ListEntryInfoProvider; +import lombok.Getter; +import lombok.Setter; +import net.minecraft.text.Text; + +@Getter +@Setter +@ConfigObject +public final class StringRestrictionControl implements RestrictionControl, ListEntryInfoProvider { + @ConfigEntry( + id = "name", + translation = "bazaarutils.config.inventory.restrictions.control.name.value.label" + ) + @Comment( + value = "The item name that, if present, will trigger the restriction", + translation = "bazaarutils.config.inventory.restrictions.control.name.value.hint" + ) + public String name; + + @ConfigEntry( + id = "targets", + translation = "bazaarutils.config.inventory.restrictions.control.targets.label" + ) + @Comment( + value = "The features for which this rule is active", + translation = "bazaarutils.config.inventory.restrictions.control.targets.hint" + ) + public RestrictionTarget[] targets = new RestrictionTarget[] { + RestrictionTarget.INSTANT_SELL, + RestrictionTarget.SELL_SACKS + }; + + private StringRestrictBy rule = StringRestrictBy.NAME; + + public StringRestrictionControl(String name) { + this.name = name; + } + + public StringRestrictionControl() { + this(""); + } + + @Override + public boolean shouldRestrict(OrderInfo container) { + return container.getName().equalsIgnoreCase(name); + } + + @Override + public String describeRule() { + return "NAME: " + name; + } + + @Override + public Text getTitle(int index) { + return Text.literal("Blocks items matching \"" + name + "\""); + } + + @Override + public Text getDescription(int index) { + return Text.literal("Applies to: " + formatTargets()); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/InstantSellRestrictions.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/InstantSellRestrictions.java deleted file mode 100644 index 378c4033..00000000 --- a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/InstantSellRestrictions.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell; - -import com.github.mkram17.bazaarutils.config.features.gui.InventoryConfig; -import com.github.mkram17.bazaarutils.events.ChestLoadedEvent; -import com.github.mkram17.bazaarutils.events.SlotClickEvent; -import com.github.mkram17.bazaarutils.events.listener.BUListener; -import com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell.controls.DoubleSellRestrictionControl; -import com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell.controls.SellRestrictionControl; -import com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell.controls.StringSellRestrictionControl; -import com.github.mkram17.bazaarutils.utils.annotations.modules.Module; -import com.github.mkram17.bazaarutils.utils.bazaar.gui.BazaarScreens; -import com.github.mkram17.bazaarutils.utils.bazaar.market.order.OrderInfo; -import com.github.mkram17.bazaarutils.utils.PlayerActionUtil; -import com.github.mkram17.bazaarutils.utils.InstaSellUtil; -import com.github.mkram17.bazaarutils.utils.config.BUToggleableFeature; -import com.github.mkram17.bazaarutils.utils.minecraft.gui.ScreenManager; -import lombok.Getter; -import meteordevelopment.orbit.EventHandler; -import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; - -import java.util.List; - -//TODO maybe color chest if it is locked -@Module -public class InstantSellRestrictions extends BUListener implements BUToggleableFeature { - private static final int INSTA_SELL_SLOT_INDEX = 47; - - public boolean isEnabled() { - return InventoryConfig.INSTANT_SELL_RESTRICTIONS_TOGGLE; - } - - public InstantSellRestrictions() {} - - @Getter - private transient int clicks = 0; - - private transient boolean isInstantSellRestricted; - - @Override - protected void registerFabricEvents() { - registerScreenEvent(); - } - - private void registerScreenEvent() { - ScreenEvents.AFTER_INIT.register((client, screen, width, height) -> { - clicks = 0; - isInstantSellRestricted = true; - }); - } - - @EventHandler - private void onChestLoaded(ChestLoadedEvent e) { - if (!isEnabled() || !ScreenManager.getInstance().isCurrent(BazaarScreens.MAIN_PAGE)) { - return; - } - - List items = InstaSellUtil.getInstaSellOrders(e.getItemStacks()); - isInstantSellRestricted = shouldRestrictInstantSell(items); - } - - @EventHandler - private void onClick(SlotClickEvent clickEvent){ - if (!isEnabled() || clickEvent.slot.getIndex() != INSTA_SELL_SLOT_INDEX) { - return; - } - - if (isInstantSellRestricted && clicks < InventoryConfig.INSTANT_SELL_RESTRICTIONS_CLICKS_OVERRIDE) { - clicks++; - PlayerActionUtil.notifyAll(getMessage()); - clickEvent.cancel(); - } - } - - private boolean shouldRestrictInstantSell(List items){ - return items.stream().anyMatch(this::isItemRestricted); - } - - public static void addRule(SellRestrictionControl control){ -// TODO: deprecate this -// controls.add(control); - } - - private boolean isItemRestricted(OrderInfo item) { - for (SellRestrictionControl control : InventoryConfig.SellRestrictionsRules.restrictors()) { - if (!control.isEnabled()) { - continue; - } - - if (control.shouldRestrict(item)) { - return true; - } - } - return false; - } - - public String getMessage() { - StringBuilder message = new StringBuilder("Sell protected by rules:"); - - for (SellRestrictionControl control : InventoryConfig.SellRestrictionsRules.restrictors()) { - if (!control.isEnabled()) { - continue; - } - - if (control instanceof DoubleSellRestrictionControl doubleControl) { - switch (doubleControl.getRule()) { - case NumericRestrictBy.PRICE -> message.append(" PRICE: "); - case NumericRestrictBy.VOLUME -> message.append(" VOLUME: "); - } - - message.append(doubleControl.getAmount()); - } else { - StringSellRestrictionControl stringControl = (StringSellRestrictionControl) control; - - message.append(" NAME: "); - message.append(stringControl.getName()); - } - } - - message.append(" (") - .append("Safety Clicks Left: ") - .append(InventoryConfig.INSTANT_SELL_RESTRICTIONS_CLICKS_OVERRIDE - clicks) - .append(")"); - - return message.toString(); - } -} diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/controls/DoubleSellRestrictionControl.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/controls/DoubleSellRestrictionControl.java deleted file mode 100644 index dc461670..00000000 --- a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/controls/DoubleSellRestrictionControl.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell.controls; - -import com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell.NumericRestrictBy; -import com.github.mkram17.bazaarutils.utils.bazaar.market.order.OrderInfo; -import com.teamresourceful.resourcefulconfig.api.annotations.Comment; -import com.teamresourceful.resourcefulconfig.api.annotations.ConfigEntry; -import com.teamresourceful.resourcefulconfig.api.annotations.ConfigObject; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@ConfigObject -public final class DoubleSellRestrictionControl implements SellRestrictionControl { - - @ConfigEntry( - id = "enabled", - translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.restriction.enabled.label" - ) - public boolean enabled; - - @ConfigEntry( - id = "rule", - translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.restriction.numeric.label" - ) - @Comment( - value = "Whether the restriction will be on total coins worth or items held", - translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.restriction.numeric.hint" - ) - public NumericRestrictBy rule; - - @ConfigEntry( - id = "amount", - translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.restriction.amount.label" - ) - @Comment( - value = "The amount of coins or items held to lock upon", - translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.restriction.amount.hint" - ) - public double amount; - - public DoubleSellRestrictionControl(boolean enabled, NumericRestrictBy rule, double amount) { - this.enabled = enabled; - this.rule = rule; - this.amount = amount; - } - - @Override - public boolean shouldRestrict(OrderInfo container) { - return container.getPricePerItem() > amount; - } -} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/controls/SellRestrictionControl.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/controls/SellRestrictionControl.java deleted file mode 100644 index ba1d845f..00000000 --- a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/controls/SellRestrictionControl.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell.controls; - -public sealed interface SellRestrictionControl> extends SellRestrictor permits DoubleSellRestrictionControl, StringSellRestrictionControl { - boolean isEnabled(); - - T getRule(); -} diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/controls/SellRestrictor.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/controls/SellRestrictor.java deleted file mode 100644 index fd334894..00000000 --- a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/controls/SellRestrictor.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell.controls; - -import com.github.mkram17.bazaarutils.utils.bazaar.market.order.OrderInfo; - -public interface SellRestrictor { - boolean shouldRestrict(OrderInfo container); -} diff --git a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/controls/StringSellRestrictionControl.java b/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/controls/StringSellRestrictionControl.java deleted file mode 100644 index 0b6ab904..00000000 --- a/src/main/java/com/github/mkram17/bazaarutils/features/gui/inventory/restrictsell/controls/StringSellRestrictionControl.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell.controls; - -import com.github.mkram17.bazaarutils.features.gui.inventory.restrictsell.StringRestrictBy; -import com.github.mkram17.bazaarutils.utils.bazaar.market.order.OrderInfo; -import com.teamresourceful.resourcefulconfig.api.annotations.Comment; -import com.teamresourceful.resourcefulconfig.api.annotations.ConfigEntry; -import com.teamresourceful.resourcefulconfig.api.annotations.ConfigObject; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@ConfigObject -public final class StringSellRestrictionControl implements SellRestrictionControl { - - @ConfigEntry( - id = "enabled", - translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.restriction.enabled.label" - ) - public boolean enabled; - - @ConfigEntry( - id = "name", - translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.restriction.name.label" - ) - @Comment( - value = "The items' name which if held will lock", - translation = "bazaarutils.config.inventory.instant_sell_restrictions.rules.restriction.name.hint" - ) - public String name; - - private StringRestrictBy rule = StringRestrictBy.NAME; - - public StringSellRestrictionControl(boolean enabled, String name) { - this.enabled = enabled; - this.name = name; - } - - @Override - public boolean shouldRestrict(OrderInfo container) { - return container.getName().equalsIgnoreCase(name); - } -} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/misc/SlotHighlightCache.java b/src/main/java/com/github/mkram17/bazaarutils/misc/SlotHighlightCache.java deleted file mode 100644 index 9f8c2d24..00000000 --- a/src/main/java/com/github/mkram17/bazaarutils/misc/SlotHighlightCache.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.github.mkram17.bazaarutils.misc; - -import com.github.mkram17.bazaarutils.BazaarUtils; -import com.github.mkram17.bazaarutils.events.ChestLoadedEvent; -import com.github.mkram17.bazaarutils.events.listener.BUListener; -import com.github.mkram17.bazaarutils.utils.bazaar.gui.BazaarScreens; -import com.github.mkram17.bazaarutils.utils.minecraft.gui.ScreenManager; -import com.github.mkram17.bazaarutils.features.gui.inventory.InstantSellHighlight; -import com.github.mkram17.bazaarutils.features.gui.inventory.OrderStatusHighlight; -import meteordevelopment.orbit.EventHandler; -import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class SlotHighlightCache extends BUListener { - - // key: slotIndex, value: highlightColor - public static final Map orderStatusHighlightCache = new ConcurrentHashMap<>(); - public static final Map instaSellHighlightCache = new ConcurrentHashMap<>(); - - public SlotHighlightCache() { - super(); - } - - public void registerFabricEvents(){ - ScreenEvents.AFTER_INIT.register((client, screen, width, height) -> { - orderStatusHighlightCache.clear(); - instaSellHighlightCache.clear(); - }); - } - - @EventHandler - public static void updateCaches(ChestLoadedEvent event) { - if (!ScreenManager.getInstance().isCurrent(BazaarScreens.ORDERS_PAGE, BazaarScreens.MAIN_PAGE)) { - return; - } - - OrderStatusHighlight.updateHighlightCache(event.getItemStacks()); - InstantSellHighlight.updateHighlightCache(); - } -} diff --git a/src/main/java/com/github/mkram17/bazaarutils/mixin/MixinHandledScreen.java b/src/main/java/com/github/mkram17/bazaarutils/mixin/MixinHandledScreen.java index 4b0aa49e..0cdf7aa4 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/mixin/MixinHandledScreen.java +++ b/src/main/java/com/github/mkram17/bazaarutils/mixin/MixinHandledScreen.java @@ -2,11 +2,8 @@ package com.github.mkram17.bazaarutils.mixin; import com.github.mkram17.bazaarutils.BazaarUtils; -import com.github.mkram17.bazaarutils.config.util.ConfigUtil; import com.github.mkram17.bazaarutils.events.SlotClickEvent; -import com.github.mkram17.bazaarutils.features.gui.inventory.OrderStatusHighlight; import com.github.mkram17.bazaarutils.generated.BazaarUtilsModules; -import com.github.mkram17.bazaarutils.misc.SlotHighlightCache; import com.github.mkram17.bazaarutils.utils.bazaar.gui.BazaarScreens; import com.github.mkram17.bazaarutils.utils.minecraft.gui.ScreenManager; import net.minecraft.client.MinecraftClient; @@ -14,11 +11,11 @@ import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.HandledScreen; -import net.minecraft.client.gui.widget.ClickableWidget; import net.minecraft.screen.slot.Slot; import net.minecraft.screen.slot.SlotActionType; import net.minecraft.text.Text; import net.minecraft.util.Atlases; +import net.minecraft.util.Identifier; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -34,9 +31,7 @@ protected MixinHandledScreen(Text title) { @Inject(method = "onMouseClick(Lnet/minecraft/screen/slot/Slot;IILnet/minecraft/screen/slot/SlotActionType;)V", at = @At("HEAD"), cancellable = true) private void onHandleMouseClick(Slot slot, int slotId, int button, SlotActionType actionType, CallbackInfo ci) { - if (slot == null) { - return; - } + if (slot == null) return; HandledScreen screen = (HandledScreen) (Object) this; SlotClickEvent event = new SlotClickEvent(screen, slot, slotId, button, actionType); @@ -64,7 +59,8 @@ private void onHandleMouseClick(Slot slot, int slotId, int button, SlotActionTyp @Inject(method = "drawSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/item/ItemStack;III)V")) private void drawOnItem_OrderStatusHighlight(DrawContext context, Slot slot, int x, int y, CallbackInfo ci) { - if (slot == null || !slot.hasStack() || !ScreenManager.getInstance().isCurrent(BazaarScreens.ORDERS_PAGE)) { + if (slot == null || !slot.hasStack() || !BazaarUtilsModules.OrderStatusHighlight.isEnabled() + || !ScreenManager.getInstance().isCurrent(BazaarScreens.ORDERS_PAGE)) { return; } @@ -72,34 +68,31 @@ private void drawOnItem_OrderStatusHighlight(DrawContext context, Slot slot, int return; } - if (BazaarUtilsModules.OrderStatusHighlight.isEnabled() && SlotHighlightCache.orderStatusHighlightCache.containsKey(slot.getIndex())) { - draw(context, x, y, SlotHighlightCache.orderStatusHighlightCache.get(slot.getIndex())); - } + Integer color = BazaarUtilsModules.OrderStatusHighlight.getHighlightColor(slot.getIndex()); + if (color != null) draw(context, slot.x, slot.y, BazaarUtilsModules.OrderStatusHighlight.getIdentifier(), color); } @Inject(method = "drawSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/item/ItemStack;III)V")) - private void drawOnItem_InstaSellHighlight(DrawContext context, Slot slot, int x, int y, CallbackInfo ci) { - if (slot == null || !slot.hasStack() || !ScreenManager.getInstance().isCurrent(BazaarScreens.MAIN_PAGE)) { + private void drawOnItem_InstantSellHighlight(DrawContext context, Slot slot, int x, int y, CallbackInfo ci) { + if (slot == null || !slot.hasStack() || !BazaarUtilsModules.InstantSellHighlight.isEnabled() + || !ScreenManager.getInstance().isCurrent(BazaarScreens.MAIN_PAGE, BazaarScreens.ITEMS_GROUP_PAGE, BazaarScreens.ITEM_PAGE)) { return; } - if (MinecraftClient.getInstance().player != null && !(slot.inventory == MinecraftClient.getInstance().player.getInventory())) { + if (MinecraftClient.getInstance().player != null && slot.inventory != MinecraftClient.getInstance().player.getInventory()) { return; } - if (BazaarUtilsModules.InstantSellHighlight.isEnabled() && SlotHighlightCache.instaSellHighlightCache.containsKey(slot.getIndex())) { - draw(context, x, y, SlotHighlightCache.instaSellHighlightCache.get(slot.getIndex())); - } + Integer color = BazaarUtilsModules.InstantSellHighlight.getHighlightColor(slot.getIndex()); + if (color != null) draw(context, slot.x, slot.y, BazaarUtilsModules.InstantSellHighlight.getIdentifier(), color); } @Unique - protected void draw(DrawContext context, int x, int y, int argb) { - final var sprite = MinecraftClient.getInstance().getAtlasManager().getAtlasTexture(Atlases.GUI) - .getSprite(OrderStatusHighlight.IDENTIFIER); + protected void draw(DrawContext context, int x, int y, Identifier identifier, int argb) { + final var sprite = MinecraftClient.getInstance().getAtlasManager() + .getAtlasTexture(Atlases.GUI) + .getSprite(identifier); - context.drawSpriteStretched(RenderPipelines.GUI_TEXTURED, - sprite, x, y, 16, 16, argb - ); + context.drawSpriteStretched(RenderPipelines.GUI_TEXTURED, sprite, x, y, 16, 16, argb); } - } \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/utils/InstaSellUtil.java b/src/main/java/com/github/mkram17/bazaarutils/utils/InstaSellUtil.java deleted file mode 100644 index 938721e5..00000000 --- a/src/main/java/com/github/mkram17/bazaarutils/utils/InstaSellUtil.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.github.mkram17.bazaarutils.utils; - -import com.github.mkram17.bazaarutils.utils.bazaar.gui.BazaarScreens; -import com.github.mkram17.bazaarutils.utils.bazaar.market.order.OrderInfo; -import com.github.mkram17.bazaarutils.utils.bazaar.market.order.TransactionType; -import com.github.mkram17.bazaarutils.utils.minecraft.components.TextSearch; -import com.github.mkram17.bazaarutils.utils.minecraft.gui.ScreenManager; -import net.minecraft.component.DataComponentTypes; -import net.minecraft.component.type.LoreComponent; -import net.minecraft.item.ItemStack; -import net.minecraft.text.Text; - -import java.util.*; - -//NOTE FROM INSTASELLRESTRICTION: if there are items with no buy orders in inv, you get "Some items can't be sold" and there are 2 extra components - -public class InstaSellUtil { - public static List getInstaSellOrders(List itemStacks) { - if (!ScreenManager.getInstance().isCurrent(BazaarScreens.MAIN_PAGE)) { - return Collections.emptyList(); - } - - Optional instaSellItemStack = getInstaSellItemStack(itemStacks); - - if (instaSellItemStack.isEmpty()) { - Util.notifyError("Could not find insta-sell item stack in Bazaar GUI. Please report this issue.", new Throwable()); - - return Collections.emptyList(); - } - - return getInstaSellOrderData(instaSellItemStack.get()); - } - - public static Optional getInstaSellItemStack(List itemStacks) { - return itemStacks.stream().filter(itemStack -> itemStack.getName().getString().contains("Sell Inventory Now")).findFirst(); - } - - private static List getInstaSellOrderData(ItemStack instaSellItemStack) { - List orderData = new ArrayList<>(); - - LoreComponent loreComponents = instaSellItemStack.get(DataComponentTypes.LORE); - - if (loreComponents == null) { - return Collections.emptyList(); - } - - List loreLines = loreComponents.lines(); - - return findInstaSellOrderData(loreLines); - } - - private static List findInstaSellOrderData(List loreLines) { - List orderData = new ArrayList<>(); - - List itemLoreLines = getItemLoreLines(loreLines); - - for (Text line : itemLoreLines) { - int volume = getVolume(line); - - double totalPrice = getTotalPrice(line); - double pricePerUnit = Math.round(totalPrice / volume * 10)/10.0; - - String name = getName(line); - - OrderInfo buyOrderItem = new OrderInfo(name, TransactionType.Side.BUY, null, volume, pricePerUnit, null); - - orderData.add(buyOrderItem); - } - - return orderData; - } - - private static List getItemLoreLines(List loreLines) { - int firstItemIndex = TextSearch.indexOf(loreLines, "coins"); - int totalCoinIndex = TextSearch.lastIndexOf(loreLines, "coins"); - - if (firstItemIndex == -1 || totalCoinIndex == -1) { - return Collections.emptyList(); - } - - int lastItemIndex = TextSearch.lastIndexOf(loreLines.subList(0, totalCoinIndex-1), "coins"); - - return loreLines.subList(firstItemIndex, lastItemIndex+1); - } - - private static int getVolume(Text text) { - String volumeString = text.getSiblings().get(1).getString(); - - return Util.parseNumber(volumeString); - } - private static String getName(Text text) { - String nameString = text.getSiblings().get(3).getString(); - - return nameString.trim(); - } - private static double getTotalPrice(Text text) { - String priceString = text.getSiblings().get(5).getString(); - - return Util.parseNumber(priceString); - } -} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/RestrictionHelper.java b/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/RestrictionHelper.java new file mode 100644 index 00000000..705df532 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/RestrictionHelper.java @@ -0,0 +1,100 @@ +package com.github.mkram17.bazaarutils.utils.bazaar; + +import com.github.mkram17.bazaarutils.events.ChestLoadedEvent; +import com.github.mkram17.bazaarutils.events.SlotClickEvent; +import com.github.mkram17.bazaarutils.events.listener.BUListener; +import com.github.mkram17.bazaarutils.features.gui.inventory.restrictions.controls.RestrictionControl; +import com.github.mkram17.bazaarutils.utils.PlayerActionUtil; +import com.github.mkram17.bazaarutils.utils.bazaar.market.order.OrderInfo; +import com.github.mkram17.bazaarutils.utils.config.BUToggleableFeature; +import com.github.mkram17.bazaarutils.utils.minecraft.ItemInfo; +import lombok.Getter; +import meteordevelopment.orbit.EventHandler; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Optional; + +public abstract class RestrictionHelper extends BUListener implements BUToggleableFeature { + public interface RestrictionState { + @NotNull + ItemInfo targetItem(); + @NotNull + List> triggeredRestrictors(); + } + + @Getter + protected String name; + + protected abstract int getClicksOverride(); + protected abstract String getMessagePrefix(); + protected abstract List> getRestrictors(); + + @Getter + private transient int clicks = 0; + private transient boolean isRestricted = true; + private transient Optional state = Optional.empty(); + + protected abstract Optional makeState(ChestLoadedEvent event); + + protected void resetState() { + state = Optional.empty(); + } + + public RestrictionHelper(String name) { + super(); + this.name = name; + } + + @Override + protected void registerFabricEvents() { + ScreenEvents.AFTER_INIT.register((client, screen, width, height) -> { + clicks = 0; + isRestricted = true; + state = Optional.empty(); + }); + } + + @EventHandler + public void onChestLoaded(ChestLoadedEvent event) { + if (!(isEnabled() && inCorrectScreen())) { + resetState(); + return; + } + + state = makeState(event); + isRestricted = state.map(state -> !state.triggeredRestrictors().isEmpty()).orElse(true); + clicks = 0; + } + + @EventHandler + public void onSlotClicked(SlotClickEvent event) { + if (!(isEnabled() && inCorrectScreen())) return; + + boolean isRestrictedSlot = state.map(RestrictionState::targetItem) + .map(info -> info.slotIndex() == event.slotId) + .orElse(false); + + if (!isRestrictedSlot) return; + + if (isRestricted && clicks < getClicksOverride()) { + clicks++; + PlayerActionUtil.notifyAll(getMessage(state.get())); + event.cancel(); + } + } + + public abstract boolean inCorrectScreen(); + + protected String getMessage(T state) { + StringBuilder message = new StringBuilder(getMessagePrefix()); + + for (RestrictionControl control : state.triggeredRestrictors()) { + message.append(" ").append(control.describeRule()); + } + + message.append(" (Safety Clicks Left: ").append(getClicksOverride() - getClicks()).append(")"); + return message.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/components/InstantSellParser.java b/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/components/InstantSellParser.java new file mode 100644 index 00000000..a874e627 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/components/InstantSellParser.java @@ -0,0 +1,60 @@ +package com.github.mkram17.bazaarutils.utils.bazaar.components; + +import com.github.mkram17.bazaarutils.utils.Util; +import com.github.mkram17.bazaarutils.utils.bazaar.market.order.OrderInfo; +import com.github.mkram17.bazaarutils.utils.bazaar.market.order.TransactionType; +import com.github.mkram17.bazaarutils.utils.minecraft.components.LoreParser; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; + +import java.util.List; +import java.util.Optional; + +public final class InstantSellParser { + public record InstantSellResult(List items) {} + + private InstantSellParser() {} + + public static InstantSellResult parseOrders(ItemStack instantSellStack) { + List items = LoreParser.lines(instantSellStack).stream() + .filter(line -> line.getSiblings().size() == 6) + .map(InstantSellParser::parseLine) + .flatMap(Optional::stream) + .toList(); + + return new InstantSellResult(items); + } + + public static Optional parseItemPageOrder(ItemStack sellInstantlyStack) { + List lines = LoreParser.lines(sellInstantlyStack); + if (lines.size() < 6) return Optional.empty(); + + try { + String name = lines.get(0).getSiblings().getFirst().getString().trim(); + int volume = Util.parseNumber(lines.get(4).getSiblings().get(1).getString()); + double totalPrice = Double.parseDouble(lines.get(5).getSiblings().get(1).getString().replace(" coins", "").replace(",", "")); + double pricePerUnit = Math.round(totalPrice / volume * 10) / 10.0; + + return Optional.of(new InstantSellResult(List.of(new OrderInfo(name, TransactionType.Side.BUY, null, volume, pricePerUnit, null)))); + } catch (Exception e) { + return Optional.empty(); + } + } + + private static Optional parseLine(Text line) { + List s = line.getSiblings(); + if (s.size() != 6) return Optional.empty(); + + String name = s.get(3).getString().trim(); + + try { + int volume = Util.parseNumber(s.get(1).getString()); + double totalPrice = Double.parseDouble(s.get(5).getString().replace(" coins", "").replace(",", "")); + double pricePerUnit = Math.round(totalPrice / volume * 10) / 10.0; + + return Optional.of(new OrderInfo(name, TransactionType.Side.BUY, null, volume, pricePerUnit, null)); + } catch (Exception e) { + return Optional.empty(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/components/SellSacksParser.java b/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/components/SellSacksParser.java new file mode 100644 index 00000000..e1f1588b --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/components/SellSacksParser.java @@ -0,0 +1,46 @@ +package com.github.mkram17.bazaarutils.utils.bazaar.components; + +import com.github.mkram17.bazaarutils.utils.Util; +import com.github.mkram17.bazaarutils.utils.bazaar.market.order.OrderInfo; +import com.github.mkram17.bazaarutils.utils.bazaar.market.order.TransactionType; +import com.github.mkram17.bazaarutils.utils.minecraft.components.LoreParser; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public final class SellSacksParser { + public record SellSacksResult(List items, Optional otherItems) { + public record OtherItems(int volume, double totalValue) {} + } + + private SellSacksParser() {} + + public static SellSacksResult parseOrders(ItemStack sellSacksStack) { + List items = new ArrayList<>(); + Optional otherItems = Optional.empty(); + + for (Text line : LoreParser.lines(sellSacksStack)) { + List s = line.getSiblings(); + if (s.size() != 6) continue; + + String name = s.get(3).getString().trim(); + + try { + int volume = Util.parseNumber(s.get(1).getString()); + double totalPrice = Double.parseDouble(s.get(5).getString().replace(" coins", "").replace(",", "")); + double pricePerUnit = Math.round(totalPrice / volume * 10) / 10.0; + + if (name.equals("Other items")) { + otherItems = Optional.of(new SellSacksResult.OtherItems(volume, totalPrice)); + } else { + items.add(new OrderInfo(name, TransactionType.Side.BUY, null, volume, pricePerUnit, null)); + } + } catch (Exception ignored) {} + } + + return new SellSacksResult(List.copyOf(items), otherItems); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/gui/BazaarScreenHandler.java b/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/gui/BazaarScreenHandler.java index ddce0ae3..bbd5c1ed 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/gui/BazaarScreenHandler.java +++ b/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/gui/BazaarScreenHandler.java @@ -4,6 +4,8 @@ import com.github.mkram17.bazaarutils.utils.bazaar.market.order.OrderInfo; import com.github.mkram17.bazaarutils.utils.bazaar.market.order.TransactionType; import com.github.mkram17.bazaarutils.utils.minecraft.ItemInfo; +import com.github.mkram17.bazaarutils.utils.minecraft.ItemInfo; +import com.github.mkram17.bazaarutils.utils.minecraft.ItemInfo; import com.github.mkram17.bazaarutils.utils.minecraft.SlotLookup; import com.github.mkram17.bazaarutils.utils.minecraft.components.LoreParser; import com.github.mkram17.bazaarutils.utils.minecraft.gui.ScreenContext; @@ -19,21 +21,44 @@ import java.util.regex.Pattern; public final class BazaarScreenHandler { - public static final Pattern AMOUNT_PATTERN = Pattern.compile("Amount: (?[0-9,.]+)x"); + public static final Pattern AMOUNT_PATTERN = Pattern.compile("Amount: (?[0-9,.]+)x"); public static final Pattern SELL_LIMIT_PATTERN = Pattern.compile("Inventory: (?[0-9,.]+) items"); public static final Pattern PURCHASE_LIMIT_PATTERN = Pattern.compile("Buy up to (?[0-9,.]+)x."); private BazaarScreenHandler() {} + public static Optional getInstantSellItem(@NotNull ScreenContext context) { + if (context.isAnyOf(BazaarScreens.MAIN_PAGE)) + return getItemFromSlot(context, BazaarSlots.OVERVIEW_PAGE.SELL_INVENTORY.slot); + + if (context.isAnyOf(BazaarScreens.ITEM_PAGE)) + return getItemFromSlot(context, BazaarSlots.ITEM_PAGE.SELL_INSTANTLY.slot); + + if (context.isAnyOf(BazaarScreens.ITEMS_GROUP_PAGE)) + return getItemFromSlot(context, BazaarSlots.ITEMS_GROUP_PAGE.SELL_INVENTORY.slot); + + return Optional.empty(); + } + + public static Optional getSellSacksItem(@NotNull ScreenContext context) { + if (context.isAnyOf(BazaarScreens.MAIN_PAGE)) + return getItemFromSlot(context, BazaarSlots.OVERVIEW_PAGE.SELL_SACKS.slot); + + if (context.isAnyOf(BazaarScreens.ITEM_PAGE)) + return getItemFromSlot(context, BazaarSlots.ITEM_PAGE.SELL_SACKS.slot); + + if (context.isAnyOf(BazaarScreens.ITEMS_GROUP_PAGE)) + return getItemFromSlot(context, BazaarSlots.ITEMS_GROUP_PAGE.SELL_SACKS.slot); + + return Optional.empty(); + } + public static Optional getDisplayItem(@NotNull ScreenContext context) { // #isAnyOf rather than #matches — likely to hit computation cache from the // preceding isCurrent call in the same stack. if (!context.isAnyOf(BazaarScreens.ITEM_PAGE)) return Optional.empty(); - return context.as(GenericContainerScreen.class) - .map(screen -> SlotLookup.getInventoryItem( - screen.getScreenHandler().getInventory(), - BazaarSlots.ITEM_PAGE.ITEM_DISPLAY.slot)); + return getItemFromSlot(context, BazaarSlots.ITEM_PAGE.ITEM_DISPLAY.slot); } public static Optional getDisplayItemName(@NotNull ScreenContext context) { @@ -81,6 +106,11 @@ private static String getItemNameFromStacks(List stacks, String nameF return "???"; } + private static Optional getItemFromSlot(@NotNull ScreenContext context, BazaarSlots.BazaarSlot slot) { + return context.as(GenericContainerScreen.class) + .map(screen -> SlotLookup.getInventoryItem(screen.getScreenHandler().getInventory(), slot)); + } + public static Optional findOptionAmount(ItemStack option) { return LoreParser.matchDouble(option, AMOUNT_PATTERN, "amount", "option amount on " + option.getCustomName()); } diff --git a/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/gui/BazaarSlots.java b/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/gui/BazaarSlots.java index 03a690b2..96bdae40 100644 --- a/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/gui/BazaarSlots.java +++ b/src/main/java/com/github/mkram17/bazaarutils/utils/bazaar/gui/BazaarSlots.java @@ -22,6 +22,31 @@ public ContainerQuery query(Inventory container) { } } + @AllArgsConstructor + public enum OVERVIEW_PAGE { + SELL_INVENTORY(new BazaarSlot( + new SlotLookup.IndexReference.FixedIndex(47), + (query) -> query + .itemType(Items.CHEST) + .withCustomName("Sell Inventory Now") + ) + ), + + SELL_SACKS(new BazaarSlot( + new SlotLookup.IndexReference.FixedIndex(48), + (query) -> query + .itemType(Items.CAULDRON) + .withCustomName("Sell Sacks Now") + ) + ); + + public final BazaarSlot slot; + + public ContainerQuery query(Inventory container) { + return slot.query(container); + } + } + @AllArgsConstructor public enum ITEM_PAGE { BUY_INSTANTLY(new BazaarSlot( @@ -86,7 +111,24 @@ public ContainerQuery query(Inventory container) { @AllArgsConstructor public enum ITEMS_GROUP_PAGE { - SWITCH_VIEW_MODE (new BazaarSlot( + SELL_INVENTORY( + new BazaarSlot( + new SlotLookup.IndexReference.ContainerSizeNegativeOffset(6), + (query) -> query + .itemType(Items.CHEST) + .withCustomName("Sell Inventory Now") + ) + ), + + SELL_SACKS(new BazaarSlot( + new SlotLookup.IndexReference.ContainerSizeNegativeOffset(2), + (query) -> query + .itemType(Items.CAULDRON) + .withCustomName("Sell Sacks Now") + ) + ), + + SWITCH_VIEW_MODE(new BazaarSlot( new SlotLookup.IndexReference.ContainerSizeNegativeOffset(1), (query) -> query .itemType(Items.IRON_ORE, Items.GOLD_ORE) diff --git a/src/main/java/com/github/mkram17/bazaarutils/utils/minecraft/SlotHighlight.java b/src/main/java/com/github/mkram17/bazaarutils/utils/minecraft/SlotHighlight.java new file mode 100644 index 00000000..89f7c068 --- /dev/null +++ b/src/main/java/com/github/mkram17/bazaarutils/utils/minecraft/SlotHighlight.java @@ -0,0 +1,9 @@ +package com.github.mkram17.bazaarutils.utils.minecraft; + +import net.minecraft.util.Identifier; + +public interface SlotHighlight { + Identifier getIdentifier(); + + Integer getHighlightColor(int slotIndex); +} \ No newline at end of file diff --git a/src/main/resources/assets/bazaarutils/lang/en_us.json5 b/src/main/resources/assets/bazaarutils/lang/en_us.json5 index c4d66231..a8bd2131 100644 --- a/src/main/resources/assets/bazaarutils/lang/en_us.json5 +++ b/src/main/resources/assets/bazaarutils/lang/en_us.json5 @@ -250,72 +250,6 @@ "category.label": "Inventory", "category.hint": "Configurations for the inventory features of the mod", - "separator.instant_sell_restrictions.label": "Instant Sell Restrictions", - "instant_sell_restrictions.{}": { - "label": "Instant Sell Restrictions", - "hint": [ - "Locks the Bazaar's Instant Sell button based on inventory or action criteria to prevent accidental sales." - ], - - "clicks_required.{}": { - "label": "Clicks Required", - "hint": [ - "The number of clicks required on the Instant Sell button to confirm the action." - ] - }, - - "rules.{}": { - "category.label": "Instant Sell Rules", - "category.hint": "Manage the rules to be checked by the Instant Sell Restrictions feature", - - "separator.numeric_restrictions.{}": { - "label": "Number predicates/restrictions", - "hint": [ - "Rules checking numeric conditions (e.g., total items or coins) to restrict Instant Sell." - ] - }, - - "numeric_restriction.{}.label": [ - "#1 Number Predicate", - "#2 Number Predicate", - "#3 Number Predicate", - "#4 Number Predicate", - "#5 Number Predicate" - ], - - "separator.string_restrictions.{}": { - "label": "String predicates/restrictions", - "hint": [ - "Rules checking specific item names or types to restrict Instant Sell." - ] - }, - - "string_restriction.{}.label": [ - "#1 Name Predicate", - "#2 Name Predicate", - "#3 Name Predicate", - "#4 Name Predicate", - "#5 Name Predicate" - ], - - "restriction.{}": { - "enabled.label": "Enabled", - "numeric.{}": { - "label": "Restriction Type", - "hint": "Determines if the restriction applies to total coin value or item quantity." - }, - "amount.{}": { - "label": "Amount", - "hint": "The quantity of coins or items required to trigger the lock." - }, - "name.{}": { - "label": "Item Name", - "hint": "The name of the item that, if present, will lock Instant Sell." - } - } - } - }, - "separator.instant_sell_highlight.label": "Instant Sell Highlight", "instant_sell_highlight.{}": { "label": "Instant Sell Highlight", @@ -329,18 +263,89 @@ "order_status_highlight.{}": { "label": "Order Status Highlight", "hint": "Highlights the status of your Bazaar Orders by coloring the items with representative colors.", + "competitive_color.{}": { "label": "Competitive Color", "hint": "The highlight color for orders that are currently the best market offer." }, + "matched_color.{}": { "label": "Matched Color", "hint": "The highlight color for orders that match the current market price." }, + "outbid_color.{}": { "label": "Outbid Color", "hint": "The highlight color for orders that have been outbid." } + }, + + "restrictions.{}": { + "separator.introductory.{}": { + "label": "Inventory Restrictions" + }, + + "category.label": "Inventory Restrictions", + + "enabled.{}": { + "label": "Inventory Restrictions", + "hint": "Locks selected Bazaar buttons based on inventory or action criteria to prevent accidental market actions.", + }, + + "features.{}": { + "label": "Restricted Buttons", + "hint": "The inventory buttons for which restrictions are enabled.", + + "target.{}": { + "instant_sell.label": "Instant Sell", + "sell_sacks.label": "Sell Sacks", + } + }, + + "clicks_required.{}": { + "label": "Clicks Required", + "hint": "The number of clicks required on the feature button to confirm the action." + }, + + "separator.rules_informational.{}": { + "label": "Restriction Rules", + "hint": "Manage the rules to be checked by the Restrictions feature" + }, + + "numeric_restrictions.{}": { + "label": "Numeric Predicates", + "hint": "Rules checking numeric conditions (e.g., total items or coins) to restrict targeted actions." + }, + + "string_restrictions.{}": { + "label": "Name Predicates", + "hint": "Rules checking specific item names or types to restrict targeted actions." + }, + + "control.{}": { + "targets.{}": { + "label": "Features", + "hint": "The features for which this rule is active." + }, + + "numeric.{}": { + "type.{}": { + "label": "Restriction Type", + "hint": "Whether the restriction triggers on total coin value or item quantity." + }, + "threshold.{}": { + "label": "Threshold", + "hint": "The value above which the restriction will trigger." + } + }, + + "name.{}": { + "value.{}": { + "label": "Item Name", + "hint": "The item name that, if present in the sell action, will trigger the restriction." + } + } + } } }, diff --git a/src/main/resources/assets/bazaarutils/textures/gui/sprites/orderstatushighlight/background.png b/src/main/resources/assets/bazaarutils/textures/gui/sprites/highlights/standard_background.png similarity index 100% rename from src/main/resources/assets/bazaarutils/textures/gui/sprites/orderstatushighlight/background.png rename to src/main/resources/assets/bazaarutils/textures/gui/sprites/highlights/standard_background.png