Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed detection of modded item groups in GUIs #295

Merged
merged 8 commits into from
Jun 24, 2024
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {

Expand Down Expand Up @@ -73,7 +73,7 @@ public static List<SlotsGroup> 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<SlotItem> unknownSlots = new ArrayList<>(slots.size());

for (Slot s : slots) {
int index = ((SlotAccessor) s).getIndex();
Expand Down Expand Up @@ -263,7 +263,7 @@ public static List<SlotsGroup> generateGroupsFromSlots(HandledScreenAccessor scr
}
//</editor-fold>

unknownGroup.slotItems.add(new SlotItem(s));
unknownSlots.add(new SlotItem(s));
}

//<editor-fold desc="Group recipe group slot items if any">
Expand Down Expand Up @@ -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));
//</editor-fold>

// Then the non-item-related groups you want to interact with (after you put items into input slots, enchant for example).
Expand Down Expand Up @@ -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<SlotsGroup> separateUnknownSlotsIntoGroups(List<SlotItem> 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<List<SlotItem>> 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<SlotItem> targetGroup = separatedSlots.stream()
.filter(group -> group.stream().anyMatch(groupSlot -> twoSlotsAreAdjacent(current, groupSlot)))
.findFirst()
.orElseGet(() -> {
List<SlotItem> group = new ArrayList<>(slots.size());
separatedSlots.add(group);
return group;
});
// Add the current slot to this group
targetGroup.add(current);
}

List<SlotsGroup> result = new ArrayList<>();

// Naming groups
Map<String, Byte> duplicateCounters = new HashMap<>(separatedSlots.size());
for (List<SlotItem> 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<SlotsGroup> inventoryAndCraftingScreensGroups(@NotNull HandledScreenAccessor screen) {
List<SlotsGroup> foundGroups = commonGroups(screen);
RecipeBookWidget recipeBookWidget = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,38 @@
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;
import java.util.List;
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<SlotItem> slotItems;
public boolean isScrollable = false;

private final HashMap<Slot, String> slotNamePrefixMap;

public SlotsGroup(String groupName, List<SlotItem> slotItems) {
public SlotsGroup(@NotNull String groupKey, @Nullable String groupName, @Nullable Byte index, @Nullable List<SlotItem> 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<SlotItem> 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);
}
Expand Down Expand Up @@ -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);
}
}