diff --git a/README.md b/README.md index de3e716..540632c 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ In fact, some fully non-vanilla actions are possible for convenience, such as: Inventory Tabs 4 is designed from the ground up to be friendlier to modpacks.
It's configured via `config/inventory_tabs.toml`, which includes comments providing extra context - like what each tab provider does. -You can enable the `configLogging` option to log helpful information for setting up the mod for your modpack when loading into a world. +By default, helpful information for setting up the mod for your modpack is logged when loading into a world. Toggle `configLogging` to disable this. If tabs are appearing on a screen they don't fit well with, the screen can be blacklisted: @@ -58,6 +58,7 @@ If tabs are being made for an inappropriate block, you can manually disable thei ``` [blockProviderOverrides] "cool_mod:incompatible_block" = "" + "really_cool_mod:*" = "" ``` Or manually override it to a relevant one: @@ -66,6 +67,7 @@ Or manually override it to a relevant one: [blockProviderOverrides] "#cool_mod:crafting_stations" = "inventory_tabs:block_unique" "cool_mod:single_chest" = "inventory_tabs:block_simple" + "cool_mod:*_cabinet" = "inventory_tabs:block_simple_storage" "cool_mod:doubleable_chest" = "inventory_tabs:block_chest" ``` @@ -80,7 +82,7 @@ If too many inappropriate blocks are being matched, you may want to disable the "inventory_tabs:block_simple" = false ``` -The `block_simple` provider uses a blacklist instead of a whitelist, so it generates a lot of false-positive tabs. It's enabled by default to help with finding good/bad tabs - but if you're making a modpack, you'll probably turn it off! +The `block_simple` provider uses a blacklist instead of a whitelist, so it generates a lot of false-positive tabs. It's enabled by default to help with finding good/bad tabs. --- @@ -100,7 +102,7 @@ repositories { maven { url "https://repo.sleeping.town/" } } dependencies { - modImplementation "folk.sisby:inventory-tabs:1.1.1" + modImplementation "folk.sisby:inventory-tabs:1.2.0" } ``` diff --git a/gradle.properties b/gradle.properties index c71cca4..91e7379 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,6 @@ org.gradle.configureondemand=true # Enable advanced multi-module optimizations (share tiny-remaper instance between projects) fabric.loom.multiProjectOptimisation=true # Mod Properties -baseVersion = 1.1.8 +baseVersion = 1.2.0 defaultBranch = 1.20 branch = 1.20 diff --git a/src/main/java/folk/sisby/inventory_tabs/InventoryTabsConfig.java b/src/main/java/folk/sisby/inventory_tabs/InventoryTabsConfig.java index a479d11..018f3e1 100644 --- a/src/main/java/folk/sisby/inventory_tabs/InventoryTabsConfig.java +++ b/src/main/java/folk/sisby/inventory_tabs/InventoryTabsConfig.java @@ -30,7 +30,11 @@ public class InventoryTabsConfig extends WrappedConfig { @Comment("Emits helpful information for setting up this config when joining a world") @Comment("Logs all registered screen handler IDs for use in screen overrides") @Comment("Logs all registry tab provider contents (blocks etc) to help find bad tabs") - public final Boolean configLogging = false; + public final Boolean configLogging = true; + + @Comment("Whether to log vanilla tab provider contents") + @Comment("For development purposes, not modpack configuration") + public final Boolean configLoggingVanilla = false; @Comment("Whether to show tabs on screens that aren't specified below") public final Boolean allowScreensByDefault = true; @@ -77,6 +81,7 @@ public class InventoryTabsConfig extends WrappedConfig { @Comment("") public final Map blockProviderOverrides = ValueMap.builder("") .put("minecraft:crafting_table", "inventory_tabs:block_unique") + .put("#minecraft:doors", "") .put("minecraft:fletching_table", "") .build(); diff --git a/src/main/java/folk/sisby/inventory_tabs/ScreenSupport.java b/src/main/java/folk/sisby/inventory_tabs/ScreenSupport.java index 67e198d..7f52fe3 100644 --- a/src/main/java/folk/sisby/inventory_tabs/ScreenSupport.java +++ b/src/main/java/folk/sisby/inventory_tabs/ScreenSupport.java @@ -5,6 +5,8 @@ import net.minecraft.client.gui.screen.ingame.HandledScreen; import net.minecraft.client.gui.screen.ingame.HorseScreen; import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryKey; +import net.minecraft.screen.ScreenHandlerType; import net.minecraft.util.Identifier; import net.minecraft.util.Pair; @@ -18,16 +20,19 @@ public class ScreenSupport { public static Map>> ALLOW = new HashMap<>(); public static Map> SCREEN_BOUND_OFFSETS = new HashMap<>(); + public static Boolean allowTabs(RegistryKey> type) { + if (InventoryTabs.CONFIG.screenOverrides.entrySet().stream().filter(e -> !e.getValue()).anyMatch(e -> Objects.equals(e.getKey(), type.getValue().toString()))) return false; + if (InventoryTabs.CONFIG.screenOverrides.entrySet().stream().filter(Map.Entry::getValue).anyMatch(e -> Objects.equals(e.getKey(), type.getValue().toString()))) return true; + return null; + } + public static boolean allowTabs(Screen screen) { if (screen instanceof HandledScreen hs && hs.getScreenHandler() != null) { if (DENY.values().stream().anyMatch(p -> p.test(hs))) return false; if (ALLOW.values().stream().anyMatch(p -> p.test(hs))) return true; try { - Identifier handlerId = Registries.SCREEN_HANDLER.getId(hs.getScreenHandler().getType()); - if (InventoryTabs.CONFIG.screenOverrides.entrySet().stream().filter(e -> !e.getValue()).anyMatch(e -> Objects.equals(e.getKey(), handlerId.toString()))) - return false; - if (InventoryTabs.CONFIG.screenOverrides.entrySet().stream().filter(Map.Entry::getValue).anyMatch(e -> Objects.equals(e.getKey(), handlerId.toString()))) - return true; + Boolean override = allowTabs(Registries.SCREEN_HANDLER.getKey(hs.getScreenHandler().getType()).orElseThrow()); + if (override != null) return override; } catch (UnsupportedOperationException ignored) { } return InventoryTabs.CONFIG.allowScreensByDefault; diff --git a/src/main/java/folk/sisby/inventory_tabs/TabProviders.java b/src/main/java/folk/sisby/inventory_tabs/TabProviders.java index 30933af..758f6b9 100644 --- a/src/main/java/folk/sisby/inventory_tabs/TabProviders.java +++ b/src/main/java/folk/sisby/inventory_tabs/TabProviders.java @@ -2,9 +2,12 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multiset; import folk.sisby.inventory_tabs.providers.BlockTabProvider; import folk.sisby.inventory_tabs.providers.ChestBlockTabProvider; -import folk.sisby.inventory_tabs.providers.SimpleStorageBlockTabProvider; import folk.sisby.inventory_tabs.providers.EnderChestTabProvider; import folk.sisby.inventory_tabs.providers.EntityTabProvider; import folk.sisby.inventory_tabs.providers.ItemTabProvider; @@ -14,24 +17,29 @@ import folk.sisby.inventory_tabs.providers.SimpleBlockTabProvider; import folk.sisby.inventory_tabs.providers.SimpleEntityTabProvider; import folk.sisby.inventory_tabs.providers.SimpleItemTabProvider; +import folk.sisby.inventory_tabs.providers.SimpleStorageBlockTabProvider; import folk.sisby.inventory_tabs.providers.SneakEntityTabProvider; import folk.sisby.inventory_tabs.providers.TabProvider; import folk.sisby.inventory_tabs.providers.UniqueBlockTabProvider; import folk.sisby.inventory_tabs.providers.UniqueItemTabProvider; import folk.sisby.inventory_tabs.providers.VehicleInventoryTabProvider; -import folk.sisby.inventory_tabs.util.RegistryValue; +import folk.sisby.inventory_tabs.util.RegistryMatcher; import net.minecraft.entity.EntityType; import net.minecraft.registry.DynamicRegistryManager; import net.minecraft.registry.Registry; import net.minecraft.registry.RegistryKey; import net.minecraft.registry.RegistryKeys; import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.screen.ScreenHandlerType; import net.minecraft.util.Identifier; +import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -65,8 +73,11 @@ public static void reload(DynamicRegistryManager manager) { InventoryTabs.LOGGER.info("[InventoryTabs] Reloading tab providers."); refreshConfigPlaceholders(); if (InventoryTabs.CONFIG.configLogging) { - InventoryTabs.LOGGER.info("[Inventory Tabs] Registered Screen Handlers:"); - manager.get(RegistryKeys.SCREEN_HANDLER).getKeys().forEach(id -> InventoryTabs.LOGGER.info("[Inventory Tabs] {}", id.getValue().toString())); + Map>>> types = manager.get(RegistryKeys.SCREEN_HANDLER).getKeys().stream().filter(k -> ScreenSupport.allowTabs(k) == null && InventoryTabs.CONFIG.allowScreensByDefault).collect(Collectors.groupingBy(k -> k.getValue().getNamespace())); + if (!types.isEmpty()) { + InventoryTabs.LOGGER.warn("[Inventory Tabs] {} Automatically tabbed screen handlers:", types.values().stream().mapToInt(Collection::size).sum()); + types.forEach((namespace, ids) -> InventoryTabs.LOGGER.info(" | {}: {}", namespace, ids.stream().map(RegistryKey::getValue).map(Identifier::getPath).collect(Collectors.joining(", ")))); + } } reloadRegistryProviders(manager, RegistryKeys.BLOCK, getProviders(BlockTabProvider.class), InventoryTabs.CONFIG.blockProviderOverrides); warmEntities = reloadRegistryProviders(manager, RegistryKeys.ENTITY_TYPE, getProviders(EntityTabProvider.class), InventoryTabs.CONFIG.entityProviderOverrides); @@ -81,48 +92,108 @@ public static > Map getProviders public static Set reloadRegistryProviders(DynamicRegistryManager manager, RegistryKey> registryKey, Map> providers, Map overrideConfig) { Set warmValues = new HashSet<>(); + Set valueNamespaces = new HashSet<>(); + Multiset> tagSizes = HashMultiset.create(); + Map, Identifier> tagProviders = new HashMap<>(); + Multimap> providerTags = HashMultimap.create(); + Multimap providerValues = HashMultimap.create(); providers.values().forEach(p -> p.values.clear()); // Construct override map - Map, RegistryTabProvider> overrides = new HashMap<>(); + Map, RegistryTabProvider> unsortedOverrides = new HashMap<>(); for (Map.Entry override : overrideConfig.entrySet()) { - RegistryValue registryValue = RegistryValue.fromRegistryString(manager, registryKey, override.getKey()); - if (registryValue == null) { + RegistryMatcher registryMatcher = RegistryMatcher.fromRegistryString(manager, registryKey, override.getKey()); + if (registryMatcher == null) { InventoryTabs.LOGGER.warn("[Inventory Tabs] Unknown override registry value ID {}, skipping...", override.getKey()); continue; } if (override.getValue().isEmpty()) { - overrides.put(registryValue, null); + unsortedOverrides.put(registryMatcher, null); continue; } if (Identifier.tryParse(override.getValue()) == null || providers.get(Identifier.tryParse(override.getValue())) == null) { InventoryTabs.LOGGER.warn("[Inventory Tabs] Unknown override tab provider ID {}, skipping...", override.getValue()); continue; } - overrides.put(registryValue, providers.get(new Identifier(override.getValue()))); + unsortedOverrides.put(registryMatcher, providers.get(new Identifier(override.getValue()))); } + Map, RegistryTabProvider> overrides = new LinkedHashMap<>(); + unsortedOverrides.entrySet().stream().sorted(Comparator.comparingInt(e -> e.getKey().priority())).forEach(e -> overrides.put(e.getKey(), e.getValue())); + // Add values to providers - if (InventoryTabs.CONFIG.configLogging) InventoryTabs.LOGGER.info("[Inventory Tabs] Starting provider freeze for {}", registryKey.getValue()); for (Map.Entry, T> entry : manager.get(registryKey).getEntrySet()) { RegistryEntry holder = manager.createRegistryLookup().getOptional(registryKey).orElseThrow().getOrThrow(entry.getKey()); - Optional, RegistryTabProvider>> override = overrides.entrySet().stream().filter(e -> e.getKey().is(holder)).findFirst(); + Optional, RegistryTabProvider>> override = overrides.entrySet().stream().filter(e -> e.getKey().is(holder)).findFirst(); if (override.isPresent()) { if (override.get().getValue() != null) { - if (InventoryTabs.CONFIG.configLogging) InventoryTabs.LOGGER.info("[Inventory Tabs] {} -> {}", entry.getKey().getValue(), REGISTRY.inverse().get(override.get().getValue())); + Identifier providerId = REGISTRY.inverse().get(override.get().getValue()); + if (InventoryTabs.CONFIG.configLogging && (InventoryTabs.CONFIG.configLoggingVanilla || !entry.getKey().getValue().getNamespace().equals("minecraft"))) { + holder.streamTags().forEach(tag -> { + if (tagProviders.containsKey(tag) && !providerId.equals(tagProviders.get(tag))) { + if (tagProviders.get(tag) != null) { + providerTags.remove(tagProviders.get(tag), tag); + tagProviders.put(tag, null); + } + } else { + tagProviders.put(tag, providerId); + } + }); + } override.get().getValue().values.add(entry.getValue()); } continue; } for (Map.Entry> provider : providers.entrySet()) { - if (!InventoryTabs.CONFIG.registryProviderDefaults.getOrDefault(provider.getKey().toString(), true)) - continue; + if (!InventoryTabs.CONFIG.registryProviderDefaults.getOrDefault(provider.getKey().toString(), true)) continue; if (provider.getValue().consumes(entry.getValue())) { - if (InventoryTabs.CONFIG.configLogging) InventoryTabs.LOGGER.info("[Inventory Tabs] {} -> {}", entry.getKey().getValue(), provider.getKey()); + if (InventoryTabs.CONFIG.configLogging && (InventoryTabs.CONFIG.configLoggingVanilla || !entry.getKey().getValue().getNamespace().equals("minecraft"))) { + Identifier providerId = provider.getKey(); + holder.streamTags().forEach(tag -> { + if (tagProviders.containsKey(tag) && !providerId.equals(tagProviders.get(tag))) { + if (tagProviders.get(tag) != null) { + tagSizes.setCount(tag, 0); + providerTags.remove(tagProviders.get(tag), tag); + tagProviders.put(tag, null); + } + } else { + tagSizes.add(tag); + providerTags.put(providerId, tag); + tagProviders.put(tag, providerId); + } + }); + providerValues.put(providerId, entry.getKey().getValue()); + valueNamespaces.add(entry.getKey().getValue().getNamespace()); + } break; } } warmValues.add(entry.getValue()); } + if (InventoryTabs.CONFIG.configLogging) { + providerTags.asMap().values().forEach(tags -> tags.removeIf(tag -> !"c".equals(tag.id().getNamespace()) && !valueNamespaces.contains(tag.id().getNamespace()))); + if (!providerTags.isEmpty()) { + InventoryTabs.LOGGER.warn("[Inventory Tabs] {} Re-assignable provider tags for {}:", providerTags.size(), registryKey.getValue()); + providerTags.asMap().forEach((provider, tags) -> { + if (!tags.isEmpty()) { + InventoryTabs.LOGGER.info(" | {}", provider); + tags.stream().collect(Collectors.groupingBy(t -> t.id().getNamespace())).entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(e -> { + InventoryTabs.LOGGER.info(" | | #{} - {}", e.getKey(), e.getValue().stream().sorted(Comparator.>comparingInt(tagSizes::count).reversed()).map(s -> "%s (%s)".formatted(s.id().getPath(), tagSizes.count(s))).collect(Collectors.joining(", "))); + }); + } + }); + } + if (!providerValues.isEmpty()) { + InventoryTabs.LOGGER.warn("[Inventory Tabs] {} Re-assignable provider values for {}:", providerValues.size(), registryKey.getValue()); + providerValues.asMap().forEach((provider, values) -> { + if (!values.isEmpty()) { + InventoryTabs.LOGGER.info(" | {}", provider); + values.stream().collect(Collectors.groupingBy(Identifier::getNamespace)).entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(e -> { + InventoryTabs.LOGGER.info(" | | {} - {}", e.getKey(), e.getValue().stream().map(Identifier::getPath).sorted().collect(Collectors.joining(", "))); + }); + } + }); + } + } return warmValues; } diff --git a/src/main/java/folk/sisby/inventory_tabs/util/RegistryMatcher.java b/src/main/java/folk/sisby/inventory_tabs/util/RegistryMatcher.java new file mode 100644 index 0000000..f661146 --- /dev/null +++ b/src/main/java/folk/sisby/inventory_tabs/util/RegistryMatcher.java @@ -0,0 +1,44 @@ +package folk.sisby.inventory_tabs.util; + +import com.mojang.datafixers.util.Either; +import net.minecraft.registry.DynamicRegistryManager; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; +import net.minecraft.util.Pair; +import org.jetbrains.annotations.Nullable; + +public record RegistryMatcher(Either, TagKey>, Pair> value) { + public int priority() { + return value.left().isPresent() ? (value.left().orElseThrow().left().isPresent() ? 0 : 1) : 2; + } + + public static @Nullable RegistryMatcher fromRegistryString(DynamicRegistryManager manager, RegistryKey> registry, String value) { + if (value.contains("*")) { // Prefix/Suffix + String[] split = value.split("\\*"); + if (split.length == 1) { + if (value.startsWith("*")) { // Suffix + return new RegistryMatcher<>(Either.right(new Pair<>("", split[0]))); + } else if (value.endsWith("*")) { // Prefix + return new RegistryMatcher<>(Either.right(new Pair<>(split[0], ""))); + } + } else if (split.length == 2) { + return new RegistryMatcher<>(Either.right(new Pair<>(split[0], split[1]))); + } + return null; + } else if (value.startsWith("#")) { + Identifier tagId = Identifier.tryParse(value.substring(1)); + return tagId != null ? new RegistryMatcher<>(Either.left(Either.right(TagKey.of(registry, tagId)))) : null; + } else { + Identifier id = Identifier.tryParse(value); + if (id == null) return null; + return manager.createRegistryLookup().getOptional(registry).orElseThrow().getOptional(RegistryKey.of(registry, id)).map(h -> new RegistryMatcher<>(Either.left(Either.left(h)))).orElse(null); + } + } + + public boolean is(RegistryEntry value) { + return this.value.map(e -> e.map((v) -> v.equals(value), value::isIn), pair -> value.getKey().orElseThrow().getValue().toString().startsWith(pair.getLeft()) && value.getKey().orElseThrow().getValue().toString().endsWith(pair.getRight())); + } +} diff --git a/src/main/java/folk/sisby/inventory_tabs/util/RegistryValue.java b/src/main/java/folk/sisby/inventory_tabs/util/RegistryValue.java deleted file mode 100644 index 25efe14..0000000 --- a/src/main/java/folk/sisby/inventory_tabs/util/RegistryValue.java +++ /dev/null @@ -1,46 +0,0 @@ -package folk.sisby.inventory_tabs.util; - -import com.mojang.datafixers.util.Either; -import net.minecraft.registry.DynamicRegistryManager; -import net.minecraft.registry.Registry; -import net.minecraft.registry.RegistryKey; -import net.minecraft.registry.entry.RegistryEntry; -import net.minecraft.registry.tag.TagKey; -import net.minecraft.util.Identifier; -import org.jetbrains.annotations.Nullable; - -public record RegistryValue(Either, TagKey> value) { - public Either> getValue() { - return this.value.mapLeft(RegistryEntry::value); - } - - public boolean isTag() { - return this.value.right().isPresent(); - } - - public static @Nullable RegistryValue fromRegistryString(DynamicRegistryManager manager, RegistryKey> registry, String value) { - if (value.startsWith("#")) { - Identifier tagId = Identifier.tryParse(value.substring(1)); - return tagId != null ? new RegistryValue<>(Either.right(TagKey.of(registry, tagId))) : null; - } else { - Identifier id = Identifier.tryParse(value); - if (id == null) return null; - return manager.createRegistryLookup().getOptional(registry).orElseThrow().getOptional(RegistryKey.of(registry, id)).map(h -> new RegistryValue<>(Either.left(h))).orElse(null); - } - } - - public String toRegistryString() { - return value - .map( - holder -> holder.getKey() - .map(RegistryKey::getValue) - .map(Identifier::toString) - .orElse(null), - tag -> "#" + tag.id() - ); - } - - public boolean is(RegistryEntry value) { - return this.value.map((v) -> v.equals(value), value::isIn); - } -}