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 extends Registry> 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 extends Registry> 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);
- }
-}