diff --git a/Common/src/main/java/mezz/jei/common/config/ClientConfig.java b/Common/src/main/java/mezz/jei/common/config/ClientConfig.java index b6730fbf0..81c2cc87d 100644 --- a/Common/src/main/java/mezz/jei/common/config/ClientConfig.java +++ b/Common/src/main/java/mezz/jei/common/config/ClientConfig.java @@ -1,19 +1,25 @@ package mezz.jei.common.config; import com.google.common.base.Preconditions; + +import mezz.jei.api.runtime.config.IJeiConfigValue; import mezz.jei.common.config.file.IConfigCategoryBuilder; +import mezz.jei.common.config.file.IConfigSchema; import mezz.jei.common.config.file.IConfigSchemaBuilder; -import mezz.jei.common.config.file.serializers.EnumSerializer; +import mezz.jei.common.config.file.serializers.IngredientSortStageSerializer; import mezz.jei.common.config.file.serializers.ListSerializer; import mezz.jei.common.platform.Services; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.Optional; import java.util.function.Supplier; +import java.util.stream.Collectors; public final class ClientConfig implements IClientConfig { @Nullable private static IClientConfig instance; + private Optional configSchema = Optional.empty(); private final Supplier centerSearchBarEnabled; private final Supplier lowMemorySlowSearchEnabled; @@ -90,11 +96,15 @@ public ClientConfig(IConfigSchemaBuilder schema) { ingredientSorterStages = sorting.addList( "IngredientSortStages", IngredientSortStage.defaultStages, - new ListSerializer<>(new EnumSerializer<>(IngredientSortStage.class)), + new ListSerializer(new IngredientSortStageSerializer()), "Sorting order for the ingredient list" ); } + public void setSchema(IConfigSchema schema) { + this.configSchema = Optional.ofNullable(schema); + } + /** * Only use this for hacky stuff like the debug plugin */ @@ -158,4 +168,37 @@ public int getMaxRecipeGuiHeight() { public List getIngredientSorterStages() { return ingredientSorterStages.get(); } + + @Override + public void setIngredientSorterStages(List ingredientSortStages) { + if (configSchema.isEmpty()) { + return; + } + @SuppressWarnings("unchecked") + IJeiConfigValue> stages = (IJeiConfigValue>)configSchema.get().getConfigValue("sorting", "IngredientSortStages").orElseGet(null); + if (stages != null) { + stages.set(ingredientSortStages); + } + + } + + @Override + public String getSerializedIngredientSorterStages() { + return ingredientSorterStages.get().stream() + .map(o -> o.name) + .collect(Collectors.joining(", ")); + } + + @Override + public void setIngredientSorterStages(String ingredientSortStages) { + if (configSchema.isEmpty()) { + return; + } + @SuppressWarnings("unchecked") + IJeiConfigValue> stages = (IJeiConfigValue>)configSchema.get().getConfigValue("sorting", "IngredientSortStages").orElseGet(null); + if (stages != null) { + stages.setUsingSerializedValue(ingredientSortStages); + } + + } } diff --git a/Common/src/main/java/mezz/jei/common/config/IClientConfig.java b/Common/src/main/java/mezz/jei/common/config/IClientConfig.java index 73df9c5af..9442fd77d 100644 --- a/Common/src/main/java/mezz/jei/common/config/IClientConfig.java +++ b/Common/src/main/java/mezz/jei/common/config/IClientConfig.java @@ -28,4 +28,10 @@ public interface IClientConfig { int getMaxRecipeGuiHeight(); List getIngredientSorterStages(); + + void setIngredientSorterStages(List ingredientSortStages); + + String getSerializedIngredientSorterStages(); + + void setIngredientSorterStages(String ingredientSortStages); } diff --git a/Common/src/main/java/mezz/jei/common/config/IngredientSortStage.java b/Common/src/main/java/mezz/jei/common/config/IngredientSortStage.java index 818be063d..c3b91e45a 100644 --- a/Common/src/main/java/mezz/jei/common/config/IngredientSortStage.java +++ b/Common/src/main/java/mezz/jei/common/config/IngredientSortStage.java @@ -1,13 +1,84 @@ package mezz.jei.common.config; +import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; -public enum IngredientSortStage { - MOD_NAME, INGREDIENT_TYPE, ALPHABETICAL, CREATIVE_MENU, TAG, ARMOR, MAX_DURABILITY; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + + +public class IngredientSortStage { + private static final Logger LOGGER = LogManager.getLogger(); + public final String name; + public final Boolean customStage; + + private IngredientSortStage(String name, Boolean customStage) { + this.name = name.toUpperCase().trim(); + this.customStage = customStage; + var existingStage = getStage(name); + if (existingStage == null) { + if (customStage) { + LOGGER.info("Adding Custom Sort Stage: " + name); + } else { + LOGGER.info("Adding Built-in Sort Stage: " + name); + } + allStages.put(name, this); + } else if (existingStage.customStage) { + LOGGER.info("Replacing Sort Stage: " + name); + //Replace the existing one, maybe the new one is an internal comparator. + allStages.put(name, this); + } else { + LOGGER.debug("Ignoring Duplicate Sort Stage: " + name); + } + //Don't replace our built-in stages. + } + + private IngredientSortStage(String name) { + this(name, true); + } + + private static Map allStages = new HashMap(10); + + public static final IngredientSortStage MOD_NAME = new IngredientSortStage("MOD_NAME", false); + public static final IngredientSortStage INGREDIENT_TYPE = new IngredientSortStage("INGREDIENT_TYPE", false); + public static final IngredientSortStage ALPHABETICAL = new IngredientSortStage("ALPHABETICAL", false); + public static final IngredientSortStage CREATIVE_MENU = new IngredientSortStage("CREATIVE_MENU", false); + public static final IngredientSortStage TAG = new IngredientSortStage("TAG", false); + public static final IngredientSortStage ARMOR = new IngredientSortStage("ARMOR", false); + public static final IngredientSortStage MAX_DURABILITY = new IngredientSortStage("MAX_DURABILITY", false); public static final List defaultStages = List.of( IngredientSortStage.MOD_NAME, IngredientSortStage.INGREDIENT_TYPE, IngredientSortStage.CREATIVE_MENU ); + + public static Collection getAllStages() { + return allStages.values(); + } + + public static IngredientSortStage getOrCreateStage(String name) { + var stage = getStage(name); + if (stage == null) { + stage = new IngredientSortStage(name, true); + } + return stage; + } + + public static IngredientSortStage getStage(String needle) { + needle = needle.toUpperCase().trim(); + return allStages.get(needle); + } + + public static final List defaultStageNames = List.of( + IngredientSortStage.MOD_NAME.name, + IngredientSortStage.INGREDIENT_TYPE.name, + IngredientSortStage.CREATIVE_MENU.name + ); + + public static Collection getAllStageNames() { + return allStages.keySet(); + } } diff --git a/Common/src/main/java/mezz/jei/common/config/JeiClientConfigs.java b/Common/src/main/java/mezz/jei/common/config/JeiClientConfigs.java index 790b16786..d430cac16 100644 --- a/Common/src/main/java/mezz/jei/common/config/JeiClientConfigs.java +++ b/Common/src/main/java/mezz/jei/common/config/JeiClientConfigs.java @@ -9,7 +9,7 @@ import java.nio.file.Path; public class JeiClientConfigs implements IJeiClientConfigs { - private final IClientConfig clientConfig; + private final ClientConfig clientConfig; private final IIngredientFilterConfig ingredientFilterConfig; private final IIngredientGridConfig ingredientListConfig; private final IIngredientGridConfig bookmarkListConfig; @@ -25,6 +25,7 @@ public JeiClientConfigs(Path configFile) { bookmarkListConfig = new IngredientGridConfig("BookmarkList", builder, HorizontalAlignment.LEFT); schema = builder.build(); + clientConfig.setSchema(schema); } public void register(FileWatcher fileWatcher, ConfigManager configManager) { diff --git a/Common/src/main/java/mezz/jei/common/config/file/ConfigCategory.java b/Common/src/main/java/mezz/jei/common/config/file/ConfigCategory.java index 7d1896318..f26782bac 100644 --- a/Common/src/main/java/mezz/jei/common/config/file/ConfigCategory.java +++ b/Common/src/main/java/mezz/jei/common/config/file/ConfigCategory.java @@ -30,6 +30,8 @@ public String getName() { return name; } + @Override + @Unmodifiable public Optional> getConfigValue(String configValueName) { ConfigValue configValue = valueMap.get(configValueName); return Optional.ofNullable(configValue); @@ -41,6 +43,7 @@ public Collection> getConfigValues() { return this.valueMap.values(); } + @Override public Set getValueNames() { return this.valueMap.keySet(); } diff --git a/Common/src/main/java/mezz/jei/common/config/file/ConfigSchema.java b/Common/src/main/java/mezz/jei/common/config/file/ConfigSchema.java index a7a3a5e86..c5667351c 100644 --- a/Common/src/main/java/mezz/jei/common/config/file/ConfigSchema.java +++ b/Common/src/main/java/mezz/jei/common/config/file/ConfigSchema.java @@ -1,5 +1,7 @@ package mezz.jei.common.config.file; +import mezz.jei.api.runtime.config.IJeiConfigCategory; +import mezz.jei.api.runtime.config.IJeiConfigValue; import mezz.jei.common.config.ConfigManager; import mezz.jei.common.util.DeduplicatingRunner; import org.apache.logging.log4j.LogManager; @@ -11,6 +13,7 @@ import java.nio.file.Path; import java.time.Duration; import java.util.List; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; public class ConfigSchema implements IConfigSchema { @@ -79,6 +82,30 @@ public List getCategories() { return categories; } + @Override + public Optional getCategory(String categoryName) { + ConfigCategory found = null; + for (ConfigCategory category : categories) { + if (category.getName().equals(categoryName)) { + found = category; + break; + } + } + return Optional.ofNullable(found); + } + + @Override + public Optional> getConfigValue(String categoryName, String valueName) { + var cat = this.getCategory(categoryName); + + if (cat.isEmpty()) { + return Optional.ofNullable(null); + } + + return cat.get().getConfigValue(valueName); + + } + @Override public Path getPath() { return path; diff --git a/Common/src/main/java/mezz/jei/common/config/file/ConfigValue.java b/Common/src/main/java/mezz/jei/common/config/file/ConfigValue.java index b29f92c2b..0338caf2e 100644 --- a/Common/src/main/java/mezz/jei/common/config/file/ConfigValue.java +++ b/Common/src/main/java/mezz/jei/common/config/file/ConfigValue.java @@ -46,6 +46,14 @@ public T getDefaultValue() { return defaultValue; } + /* + * Allows retreiving the default value without having to know the real data type of the value. + */ + @Override + public String getSerializedDefaultValue() { + return serializer.serialize(defaultValue); + } + @Override public T getValue() { if (schema != null) { @@ -54,11 +62,25 @@ public T getValue() { return currentValue; } + /* + * Allows retreiving the value without having to know the real data type of the value. + */ + @Override + public String getSerializedValue() { + if (schema != null) { + schema.loadIfNeeded(); + } + return serializer.serialize(currentValue); + } + @Override public IJeiConfigValueSerializer getSerializer() { return serializer; } + /* + * This one is for internal loading. + */ public List setFromSerializedValue(String value) { IJeiConfigValueSerializer.IDeserializeResult deserializeResult = serializer.deserialize(value); deserializeResult.getResult() @@ -66,6 +88,30 @@ public List setFromSerializedValue(String value) { return deserializeResult.getErrors(); } + /* + * Update the value without knowing the exact underlying type. + */ + @Override + public boolean setUsingSerializedValue(String value) { + IJeiConfigValueSerializer.IDeserializeResult deserializeResult = serializer.deserialize(value); + if (!deserializeResult.getErrors().isEmpty()) { + LOGGER.error("Tried to set invalid value : {}\n{}", value, serializer.getValidValuesDescription()); + return false; + } + if (deserializeResult.getResult().isPresent()) { + T realValue = deserializeResult.getResult().get(); + if (!currentValue.equals(realValue)) { + currentValue = realValue; + if (schema != null) { + schema.markDirty(); + } + return true; + } + } + return false; + } + + @Override public boolean set(T value) { if (!serializer.isValid(value)) { diff --git a/Common/src/main/java/mezz/jei/common/config/file/serializers/IngredientSortStageSerializer.java b/Common/src/main/java/mezz/jei/common/config/file/serializers/IngredientSortStageSerializer.java new file mode 100644 index 000000000..00c2ee273 --- /dev/null +++ b/Common/src/main/java/mezz/jei/common/config/file/serializers/IngredientSortStageSerializer.java @@ -0,0 +1,51 @@ +package mezz.jei.common.config.file.serializers; + +import mezz.jei.api.runtime.config.IJeiConfigValueSerializer; +import mezz.jei.common.config.IngredientSortStage; + +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Collectors; + +public class IngredientSortStageSerializer implements IJeiConfigValueSerializer { + + public IngredientSortStageSerializer() { + } + + @Override + public String serialize(IngredientSortStage value) { + return value.name; + } + + @Override + public DeserializeResult deserialize(String string) { + string = string.trim(); + if (string.startsWith("\"") && string.endsWith("\"")) { + string = string.substring(1, string.length() - 1); + } + //Since valid values could be added after we read the config, we can't validate them yet. + var stage = IngredientSortStage.getOrCreateStage(string); + return new DeserializeResult<>(stage); + } + + @Override + public String getValidValuesDescription() { + String names = IngredientSortStage.getAllStageNames().stream() + .collect(Collectors.joining(", ")); + + return "[%s]".formatted(names); + } + + @Override + public boolean isValid(IngredientSortStage value) { + //TODO: Not sure if this only occurs after addins have a chance to register their sorters. + //If so, we just need to return true. + var stage = IngredientSortStage.getStage(value.name); + return stage != null; + } + + @Override + public Optional> getAllValidValues() { + return Optional.of(IngredientSortStage.getAllStages()); + } +} diff --git a/CommonApi/src/main/java/mezz/jei/api/ingredients/IIngredientHelper.java b/CommonApi/src/main/java/mezz/jei/api/ingredients/IIngredientHelper.java index 358ab12d1..1d065e801 100644 --- a/CommonApi/src/main/java/mezz/jei/api/ingredients/IIngredientHelper.java +++ b/CommonApi/src/main/java/mezz/jei/api/ingredients/IIngredientHelper.java @@ -74,7 +74,7 @@ default Iterable getColors(V ingredient) { * @return an ItemStack for JEI to give the player, or an empty stack if there is nothing that can be given. */ default ItemStack getCheatItemStack(V ingredient) { - return ItemStack.EMPTY; + return null; } /** diff --git a/CommonApi/src/main/java/mezz/jei/api/runtime/IIngredientFilter.java b/CommonApi/src/main/java/mezz/jei/api/runtime/IIngredientFilter.java index 19be97c3d..8ecb724dd 100644 --- a/CommonApi/src/main/java/mezz/jei/api/runtime/IIngredientFilter.java +++ b/CommonApi/src/main/java/mezz/jei/api/runtime/IIngredientFilter.java @@ -4,6 +4,7 @@ import mezz.jei.api.ingredients.IIngredientType; import net.minecraft.world.item.ItemStack; +import java.util.Comparator; import java.util.List; /** @@ -45,4 +46,19 @@ default List getFilteredItemStacks() { * to get all the ingredients known to JEI, not just ones currently shown by the filter */ List getFilteredIngredients(IIngredientType ingredientType); + + /** + * Register your own sorting option here. + * The ItemStack comparator needs to be able to handle isEmpty ItemStack inputs (Never Null.) + * (FluidStacks will be silently converted to bucket ItemStacks). + * + * The Object comparator needs to be able to handle ItemStack, FluidStack, and unknown types + * (Mod specific stacks) inputs. Examples of Mod specific ones are Mekanism's GasStack and + * EnderIO's EnergyIngredient + * + * @since JEI ?.?.? + */ + default void addIngredientListItemStackSorter(String name, Comparator comparator) { + } + } diff --git a/CommonApi/src/main/java/mezz/jei/api/runtime/config/IJeiConfigCategory.java b/CommonApi/src/main/java/mezz/jei/api/runtime/config/IJeiConfigCategory.java index a6fc3a3fc..0cae831e6 100644 --- a/CommonApi/src/main/java/mezz/jei/api/runtime/config/IJeiConfigCategory.java +++ b/CommonApi/src/main/java/mezz/jei/api/runtime/config/IJeiConfigCategory.java @@ -3,6 +3,8 @@ import org.jetbrains.annotations.Unmodifiable; import java.util.Collection; +import java.util.Optional; +import java.util.Set; /** * Categories organize {@link IJeiConfigValue}s into groups. @@ -25,4 +27,19 @@ public interface IJeiConfigCategory { */ @Unmodifiable Collection> getConfigValues(); + + /** + * Get a specific Config Value from this category. + * + * @since ?.?.? + */ + @Unmodifiable + Optional> getConfigValue(String configValueName); + + /** + * Get a list of Config Value names in this category. + * + * @since ?.?.? + */ + Set getValueNames(); } diff --git a/CommonApi/src/main/java/mezz/jei/api/runtime/config/IJeiConfigFile.java b/CommonApi/src/main/java/mezz/jei/api/runtime/config/IJeiConfigFile.java index 333b2277b..a13436ca3 100644 --- a/CommonApi/src/main/java/mezz/jei/api/runtime/config/IJeiConfigFile.java +++ b/CommonApi/src/main/java/mezz/jei/api/runtime/config/IJeiConfigFile.java @@ -4,6 +4,7 @@ import java.nio.file.Path; import java.util.List; +import java.util.Optional; /** * Represents one Config file used by JEI. @@ -34,4 +35,20 @@ public interface IJeiConfigFile { */ @Unmodifiable List getCategories(); + + /** + * Get a specific category from this file. + * + * @since ?.?.? + */ + Optional getCategory(String categoryName); + + + /** + * Get a specific Config Value from a category. + * + * @since ?.?.? + */ + Optional> getConfigValue(String categoryName, String valueName) ; + } diff --git a/CommonApi/src/main/java/mezz/jei/api/runtime/config/IJeiConfigValue.java b/CommonApi/src/main/java/mezz/jei/api/runtime/config/IJeiConfigValue.java index 3f3719077..9dbffb8e8 100644 --- a/CommonApi/src/main/java/mezz/jei/api/runtime/config/IJeiConfigValue.java +++ b/CommonApi/src/main/java/mezz/jei/api/runtime/config/IJeiConfigValue.java @@ -33,6 +33,14 @@ public interface IJeiConfigValue { */ T getValue(); + /** + * Get the serialized current value. + * This will automatically update and load from the config file if there are changes. + * + * @since ?.?.? + */ + String getSerializedValue() ; + /** * Get the default value. * @@ -40,6 +48,14 @@ public interface IJeiConfigValue { */ T getDefaultValue(); + + /** + * Get the serialized default value. + * + * @since ?.?.? + */ + String getSerializedDefaultValue(); + /** * Set the config value to the given value. * This will automatically mark the config file as dirty so that it will save the new values. @@ -48,6 +64,15 @@ public interface IJeiConfigValue { */ boolean set(T value); + + /** + * Set the config value to the given value using the serialized value. + * This will automatically mark the config file as dirty so that it will save the new values. + * + * @since ?.?.? + */ + boolean setUsingSerializedValue(String value); + /** * Get the helper for serializing values to and from Strings, and validating values. * diff --git a/Forge/src/test/java/mezz/jei/test/lib/TestClientConfig.java b/Forge/src/test/java/mezz/jei/test/lib/TestClientConfig.java index 06e4e1172..404d6edce 100644 --- a/Forge/src/test/java/mezz/jei/test/lib/TestClientConfig.java +++ b/Forge/src/test/java/mezz/jei/test/lib/TestClientConfig.java @@ -67,4 +67,18 @@ public int getMaxRecipeGuiHeight() { public List getIngredientSorterStages() { return List.of(); } + + @Override + public void setIngredientSorterStages(List ingredientSortStages) { + } + + @Override + public String getSerializedIngredientSorterStages() { + return ""; + } + + @Override + public void setIngredientSorterStages(String ingredientSortStages) { + } + } diff --git a/Gui/src/main/java/mezz/jei/gui/ingredients/IIngredientSorter.java b/Gui/src/main/java/mezz/jei/gui/ingredients/IIngredientSorter.java index 1cdabadc5..1513867f1 100644 --- a/Gui/src/main/java/mezz/jei/gui/ingredients/IIngredientSorter.java +++ b/Gui/src/main/java/mezz/jei/gui/ingredients/IIngredientSorter.java @@ -13,4 +13,8 @@ default void doPreSort(IngredientFilter ingredientFilter, IIngredientManager ing default void invalidateCache() { } + + default Boolean hasStageOrderChanged() { + return false; + } } diff --git a/Gui/src/main/java/mezz/jei/gui/ingredients/IListElementInfo.java b/Gui/src/main/java/mezz/jei/gui/ingredients/IListElementInfo.java index 0eb89b0e7..5761436a3 100644 --- a/Gui/src/main/java/mezz/jei/gui/ingredients/IListElementInfo.java +++ b/Gui/src/main/java/mezz/jei/gui/ingredients/IListElementInfo.java @@ -9,6 +9,8 @@ import mezz.jei.api.runtime.IIngredientManager; import mezz.jei.common.config.IIngredientFilterConfig; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; + import org.jetbrains.annotations.Unmodifiable; public interface IListElementInfo { @@ -38,4 +40,6 @@ public interface IListElementInfo { int getSortedIndex(); + ItemStack getCheatItemStack(); + } diff --git a/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientFilter.java b/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientFilter.java index 858ff9d8e..232627b34 100644 --- a/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientFilter.java +++ b/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientFilter.java @@ -19,6 +19,7 @@ import mezz.jei.gui.search.ElementSearchLowMem; import mezz.jei.gui.search.IElementSearch; import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; @@ -168,10 +169,13 @@ public void onIngredientVisibilityChanged(ITypedIngredient ingredient, bo @Override public List> getIngredientList() { - String filterText = this.filterTextSource.getFilterText(); - filterText = filterText.toLowerCase(); + if (sorter.hasStageOrderChanged()) { + invalidateCache(); + } if (ingredientListCached == null) { - ingredientListCached = getIngredientListUncached(filterText); + String filterText = this.filterTextSource.getFilterText(); + filterText = filterText.toLowerCase(); + ingredientListCached = getIngredientListUncached(filterText); } return ingredientListCached; } @@ -365,4 +369,17 @@ private void notifyListenersOfChange() { listener.onSourceListChanged(); } } + + public void addIngredientListItemStackSorter(String name, Comparator comparator) { + IngredientSorterComparators.AddCustomItemStackComparator(name, comparator); + invalidateCache(); + notifyListenersOfChange(); + } + + public void addIngredientListElementSorter(String name, Comparator> comparator) { + IngredientSorterComparators.AddCustomListElementComparator(name, comparator); + invalidateCache(); + notifyListenersOfChange(); + } + } diff --git a/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientFilterApi.java b/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientFilterApi.java index cdd936913..20a0670d6 100644 --- a/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientFilterApi.java +++ b/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientFilterApi.java @@ -3,8 +3,10 @@ import mezz.jei.api.ingredients.IIngredientType; import mezz.jei.api.runtime.IIngredientFilter; import mezz.jei.gui.filter.IFilterTextSource; +import net.minecraft.world.item.ItemStack; import mezz.jei.common.util.ErrorUtil; +import java.util.Comparator; import java.util.List; public class IngredientFilterApi implements IIngredientFilter { @@ -31,4 +33,10 @@ public void setFilterText(String filterText) { public List getFilteredIngredients(IIngredientType ingredientType) { return ingredientFilter.getFilteredIngredients(ingredientType); } + + @Override + public void addIngredientListItemStackSorter(String name, Comparator comparator) { + ingredientFilter.addIngredientListItemStackSorter(name, comparator); + } + } diff --git a/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientSorter.java b/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientSorter.java index e9b488e0e..026813ec0 100644 --- a/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientSorter.java +++ b/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientSorter.java @@ -2,6 +2,7 @@ import mezz.jei.api.runtime.IIngredientManager; import mezz.jei.common.config.IngredientSortStage; +import mezz.jei.core.util.LoggedTimer; import mezz.jei.common.config.IClientConfig; import mezz.jei.gui.config.IngredientTypeSortingConfig; import mezz.jei.gui.config.ModNameSortingConfig; @@ -10,6 +11,7 @@ import java.util.List; public final class IngredientSorter implements IIngredientSorter { + //private static final Logger LOGGER = LogManager.getLogger(); private static final Comparator> PRE_SORTED = Comparator.comparing(IListElementInfo::getSortedIndex); @@ -18,6 +20,7 @@ public final class IngredientSorter implements IIngredientSorter { private final IngredientTypeSortingConfig ingredientTypeSortingConfig; private boolean isCacheValid; + private String lastStageOrder = ""; public IngredientSorter(IClientConfig clientConfig, ModNameSortingConfig modNameSortingConfig, IngredientTypeSortingConfig ingredientTypeSortingConfig) { this.clientConfig = clientConfig; @@ -28,10 +31,15 @@ public IngredientSorter(IClientConfig clientConfig, ModNameSortingConfig modName @Override public void doPreSort(IngredientFilter ingredientFilter, IIngredientManager ingredientManager) { + LoggedTimer sortTime = new LoggedTimer(); + sortTime.start("Sorting Items"); IngredientSorterComparators comparators = new IngredientSorterComparators(ingredientFilter, ingredientManager, this.modNameSortingConfig, this.ingredientTypeSortingConfig); List ingredientSorterStages = this.clientConfig.getIngredientSorterStages(); + //Remember the stage order so we can tell if it changed later. + lastStageOrder = this.clientConfig.getSerializedIngredientSorterStages(); + Comparator> completeComparator = comparators.getComparator(ingredientSorterStages); // Get all of the items sorted with our custom comparator. @@ -43,11 +51,13 @@ public void doPreSort(IngredientFilter ingredientFilter, IIngredientManager ingr element.setSortedIndex(i); } this.isCacheValid = true; + sortTime.stop(); } @Override public Comparator> getComparator(IngredientFilter ingredientFilter, IIngredientManager ingredientManager) { - if (!this.isCacheValid) { + if (!this.isCacheValid || hasStageOrderChanged()) { + ingredientFilter.invalidateCache(); doPreSort(ingredientFilter, ingredientManager); } //Now the comparator just uses that index value to order everything. @@ -59,4 +69,10 @@ public void invalidateCache() { this.isCacheValid = false; } + @Override + public Boolean hasStageOrderChanged() { + String ingredientSorterStages = this.clientConfig.getSerializedIngredientSorterStages(); + return !lastStageOrder.equals(ingredientSorterStages); + } + } diff --git a/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientSorterComparators.java b/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientSorterComparators.java index c9fc3b64d..60f0aee8a 100644 --- a/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientSorterComparators.java +++ b/Gui/src/main/java/mezz/jei/gui/ingredients/IngredientSorterComparators.java @@ -3,6 +3,7 @@ import mezz.jei.api.ingredients.IIngredientType; import mezz.jei.api.ingredients.ITypedIngredient; import mezz.jei.api.runtime.IIngredientManager; +//import mezz.jei.common.Internal; import mezz.jei.common.config.IngredientSortStage; import mezz.jei.gui.config.IngredientTypeSortingConfig; import mezz.jei.gui.config.ModNameSortingConfig; @@ -17,15 +18,22 @@ import java.util.Collection; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + + public class IngredientSorterComparators { + private static final Logger LOGGER = LogManager.getLogger(); private final IngredientFilter ingredientFilter; private final IIngredientManager ingredientManager; private final ModNameSortingConfig modNameSortingConfig; private final IngredientTypeSortingConfig ingredientTypeSortingConfig; + private static HashMap>> customComparators = new HashMap>>(); public IngredientSorterComparators( IngredientFilter ingredientFilter, @@ -47,15 +55,33 @@ public Comparator> getComparator(List i } public Comparator> getComparator(IngredientSortStage ingredientSortStage) { - return switch (ingredientSortStage) { - case ALPHABETICAL -> getAlphabeticalComparator(); - case CREATIVE_MENU -> getCreativeMenuComparator(); - case INGREDIENT_TYPE -> getIngredientTypeComparator(); - case MOD_NAME -> getModNameComparator(); - case TAG -> getTagComparator(); - case ARMOR -> getArmorComparator(); - case MAX_DURABILITY -> getMaxDurabilityComparator(); + //Just return one of the built-in sorts. + switch (ingredientSortStage.name) { + case "ALPHABETICAL": + return getAlphabeticalComparator(); + case "CREATIVE_MENU": + return getCreativeMenuComparator(); + case "INGREDIENT_TYPE": + return getIngredientTypeComparator(); + case "MOD_NAME": + return getModNameComparator(); + case "TAG": + return getTagComparator(); + case "ARMOR": + return getArmorComparator(); + case "MAX_DURABILITY": + return getMaxDurabilityComparator(); }; + + //Find and use a custom sort. + var custom = customComparators.get(ingredientSortStage.name); + if (custom != null) { + return custom; + } + + //Accept and ignore an unknown sort. Mod that added it removed, bad spelling, tried to use it before it was registered, etc. + LOGGER.warn("Sorting option '" + ingredientSortStage.name + "' does not exist, skipping."); + return getNullComparator(); } public Comparator> getDefault() { @@ -64,6 +90,12 @@ public Comparator> getDefault() { .thenComparing(getCreativeMenuComparator()); } + public Comparator> getNullComparator() { + Comparator> nullComparator = + Comparator.comparing(o -> 0); + return nullComparator; + } + private static Comparator> getCreativeMenuComparator() { return Comparator.comparingInt(o -> { IListElement element = o.getElement(); @@ -184,6 +216,42 @@ public static ItemStack getItemStack(IListElementInfo ingredientInfo) { if (ingredient.getIngredient() instanceof ItemStack itemStack) { return itemStack; } - return ItemStack.EMPTY; + ItemStack aStack = ingredientInfo.getCheatItemStack(); + if (aStack == null) { + aStack = ItemStack.EMPTY; + } + return aStack; + } + + public static class GenericComparator implements Comparator> { + final private Comparator _itemStackComparator; + public GenericComparator(Comparator comparator) { + this._itemStackComparator = comparator; + } + public int compare(IListElementInfo left, IListElementInfo right) { + return this._itemStackComparator.compare(getItemStack(left), getItemStack(right)); + } + } + + public static IngredientSortStage AddCustomListElementComparator(String comparatorName, Comparator> complexComparator) { + comparatorName = comparatorName.toUpperCase().trim(); + var stage = IngredientSortStage.getOrCreateStage(comparatorName); + //Trying to decide if I want to do this automatically, it would keep coming + //back if the user removed it. My current position is to let the addin do it. + // var stage = IngredientSortStage.getStage(comparatorName); + // if (stage == null) { + // var configs = Internal.getJeiClientConfigs(); + // var stages = configs.getClientConfig().getIngredientSorterStages(); + // stage = IngredientSortStage.getOrCreateStage(comparatorName); + // stages.add(stage); + // configs.getClientConfig().setIngredientSorterStages(stages); + // } + customComparators.put(comparatorName, complexComparator); + return stage; + } + + public static IngredientSortStage AddCustomItemStackComparator(String comparatorName, Comparator itemStackComparator) { + var complexComparator = new GenericComparator(itemStackComparator); + return AddCustomListElementComparator(comparatorName, complexComparator); } } diff --git a/Gui/src/main/java/mezz/jei/gui/ingredients/ListElementInfo.java b/Gui/src/main/java/mezz/jei/gui/ingredients/ListElementInfo.java index cb93a20d9..33374d205 100644 --- a/Gui/src/main/java/mezz/jei/gui/ingredients/ListElementInfo.java +++ b/Gui/src/main/java/mezz/jei/gui/ingredients/ListElementInfo.java @@ -9,6 +9,7 @@ import mezz.jei.common.util.Translator; import mezz.jei.common.config.IIngredientFilterConfig; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -32,6 +33,7 @@ public class ListElementInfo implements IListElementInfo { private final List modNames; private final ResourceLocation resourceLocation; private int sortedIndex = Integer.MAX_VALUE; + private final ItemStack itemStack; public static Optional> create(IListElement element, IIngredientManager ingredientManager, IModIdHelper modIdHelper) { ITypedIngredient value = element.getTypedIngredient(); @@ -67,6 +69,23 @@ protected ListElementInfo(IListElement element, IIngredientHelper ingredie .toList(); String displayName = IngredientInformationUtil.getDisplayName(ingredient, ingredientHelper); this.displayNameLowercase = Translator.toLowercaseWithLocale(displayName); + ItemStack anItemStack = null; + try { + anItemStack = ingredientHelper.getCheatItemStack(ingredient); + if (anItemStack != null && anItemStack.isEmpty()) { + String ingredientInfo = ingredientHelper.getErrorInfo(value.getIngredient()); + LOGGER.info("Ingredient creates Emtpy ItemStack when cheated. {}", ingredientInfo); + } + if (anItemStack == null) { + String ingredientInfo = ingredientHelper.getErrorInfo(value.getIngredient()); + LOGGER.info("Ingredient creates Null ItemStack when cheated. {}", ingredientInfo); + } + } catch (RuntimeException e) { + anItemStack = null; + String ingredientInfo = ingredientHelper.getErrorInfo(value.getIngredient()); + LOGGER.warn("Ingredient throws error when cheated. {}", ingredientInfo, e); + } + this.itemStack = anItemStack; } @Override @@ -159,4 +178,9 @@ public int getSortedIndex() { return sortedIndex; } + @Override + public ItemStack getCheatItemStack() { + return itemStack; + } + }