diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/inventory_controls/GroupGenerator.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/inventory_controls/GroupGenerator.java index dfca35ff..156e8c82 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/inventory_controls/GroupGenerator.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/inventory_controls/GroupGenerator.java @@ -1,6 +1,7 @@ package com.github.khanshoaib3.minecraft_access.features.inventory_controls; import com.github.khanshoaib3.minecraft_access.mixin.*; +import com.google.common.base.CaseFormat; import com.google.common.collect.Lists; import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; import net.minecraft.block.entity.BannerPattern; @@ -26,10 +27,9 @@ import net.minecraft.util.Formatting; import net.minecraft.village.TradeOfferList; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; +import java.util.*; public class GroupGenerator { @@ -73,7 +73,7 @@ public static List generateGroupsFromSlots(HandledScreenAccessor scr SlotsGroup secondaryBeaconPowersButtonsGroup = new SlotsGroup("secondary_beacon_powers_buttons", null); SlotsGroup lapisLazuliInputGroup = new SlotsGroup("lapis_lazuli_input", null); SlotsGroup enchantsGroup = new SlotsGroup("enchants", null); - SlotsGroup unknownGroup = new SlotsGroup("unknown", null); + List unknownSlots = new ArrayList<>(slots.size()); for (Slot s : slots) { int index = ((SlotAccessor) s).getIndex(); @@ -263,7 +263,7 @@ public static List generateGroupsFromSlots(HandledScreenAccessor scr } // - unknownGroup.slotItems.add(new SlotItem(s)); + unknownSlots.add(new SlotItem(s)); } // @@ -467,9 +467,10 @@ else if (screen.getHandler() instanceof HopperScreenHandler) foundGroups.add(itemOutputGroup); } - if (unknownGroup.slotItems.size() > 0) { - foundGroups.add(unknownGroup); - } + // Unknown slots come from screens in other mods (or unsupported original screens) + // that use original SlotItem class to represent item slots. + // One SlotItem represents one slot. + foundGroups.addAll(separateUnknownSlotsIntoGroups(unknownSlots)); // // Then the non-item-related groups you want to interact with (after you put items into input slots, enchant for example). @@ -522,6 +523,86 @@ else if (screen.getHandler() instanceof HopperScreenHandler) return foundGroups; } + /** + * Separate unknown slots into groups according to their position on the screen, + * and give these group names according to slot instances' types (classes). + * + * @return Separation result + */ + @NotNull + private static List separateUnknownSlotsIntoGroups(List slots) { + // An unsorted list may result in many groups being created where there should only be one + slots.sort(Comparator.comparing(slot -> ((SlotItem) slot).y).thenComparing(slot -> ((SlotItem) slot).x)); + + // Separate unknown slots into groups. + // Coordinates adjacency is used instead of slot.inventory to calculate grouping + // because some mods have: 1. non-adjacent slots in the same inventory 2. adjacent slots in different inventories. + List> separatedSlots = new ArrayList<>(slots.size()); + for (SlotItem current : slots) { + // Search for a group which already has one slot that is adjacent to the current slot, + // or create a new group if there is no such group. + List targetGroup = separatedSlots.stream() + .filter(group -> group.stream().anyMatch(groupSlot -> twoSlotsAreAdjacent(current, groupSlot))) + .findFirst() + .orElseGet(() -> { + List group = new ArrayList<>(slots.size()); + separatedSlots.add(group); + return group; + }); + // Add the current slot to this group + targetGroup.add(current); + } + + List result = new ArrayList<>(); + + // Naming groups + Map duplicateCounters = new HashMap<>(separatedSlots.size()); + for (List group : separatedSlots) { + String groupKey; + @Nullable String groupName; + @Nullable Byte index; + boolean allSlotsHaveSameType = group.stream() + .map(slot -> slot.slot.getClass()) + .distinct() + .limit(2) + .count() == 1; + if (allSlotsHaveSameType) { + // Set group name to unique slot class name + // e.g. ModAbcSlot -> mod_abc_slot + groupName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, group.getFirst().slot.getClass().getSimpleName()); + groupKey = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, group.getFirst().slot.getClass().getCanonicalName()); + // Don't use vanilla obfuscated class names + if (groupName.startsWith("class_")) { + groupKey = "unknown"; + groupName = null; + } + } else { + groupKey = "unknown"; + groupName = null; + } + + // Already have a group with the same name + if (duplicateCounters.containsKey(groupKey)) { + byte n = (byte) (duplicateCounters.get(groupKey) + 1); + duplicateCounters.put(groupKey, n); + index = n; + } else { + duplicateCounters.put(groupName, (byte) 1); + index = null; + } + + result.add(new SlotsGroup(groupKey, groupName, index, group)); + } + + return result; + } + + private static boolean twoSlotsAreAdjacent(SlotItem currSlot, SlotItem groupSlot) { + boolean adjacentOnX = groupSlot.x == currSlot.x && (groupSlot.y == currSlot.y + 18 || groupSlot.y == currSlot.y - 18); + boolean adjacentOnY = groupSlot.y == currSlot.y && (groupSlot.x == currSlot.x + 18 || groupSlot.x == currSlot.x - 18); + return adjacentOnX || adjacentOnY; + } + private static @NotNull List inventoryAndCraftingScreensGroups(@NotNull HandledScreenAccessor screen) { List foundGroups = commonGroups(screen); RecipeBookWidget recipeBookWidget = null; diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/inventory_controls/SlotsGroup.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/inventory_controls/SlotsGroup.java index a3eea523..888481ce 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/inventory_controls/SlotsGroup.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/inventory_controls/SlotsGroup.java @@ -4,6 +4,7 @@ import net.minecraft.client.resource.language.I18n; import net.minecraft.screen.slot.Slot; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.HashMap; @@ -11,18 +12,30 @@ import java.util.Objects; public class SlotsGroup { - private final String groupName; + private final @NotNull String groupKey; + private final @Nullable String groupName; + private final @Nullable Byte index; public List slotItems; public boolean isScrollable = false; private final HashMap slotNamePrefixMap; - public SlotsGroup(String groupName, List slotItems) { + public SlotsGroup(@NotNull String groupKey, @Nullable String groupName, @Nullable Byte index, @Nullable List slotItems) { this.slotNamePrefixMap = new HashMap<>(); + this.groupKey = groupKey; this.groupName = groupName; + this.index = index; this.slotItems = Objects.requireNonNullElseGet(slotItems, ArrayList::new); } + public SlotsGroup(@NotNull String groupKey, @Nullable List slotItems) { + this(groupKey, null, null, slotItems); + } + + public SlotsGroup(@NotNull String groupKey) { + this(groupKey, null, null, null); + } + public void setSlotPrefix(Slot slot, String prefix) { this.slotNamePrefixMap.put(slot, prefix); } @@ -112,6 +125,8 @@ void setRowColumnPrefixForSlots() { } public String getGroupName() { - return I18n.translate("minecraft_access.slot_group." + this.groupName); + String key = String.format("minecraft_access.slot_group.%s", groupKey); + String translation = groupName == null || I18n.hasTranslation(key) ? I18n.translate(key) : groupName; + return index == null ? translation : String.format("%s %d", translation, index); } }