aboutsummaryrefslogtreecommitdiff
path: root/runtime/src/main/java/me/shedaniel/rei/impl/PluginManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/src/main/java/me/shedaniel/rei/impl/PluginManager.java')
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/PluginManager.java627
1 files changed, 627 insertions, 0 deletions
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/PluginManager.java b/runtime/src/main/java/me/shedaniel/rei/impl/PluginManager.java
new file mode 100644
index 000000000..41da643a7
--- /dev/null
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/PluginManager.java
@@ -0,0 +1,627 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.*;
+import me.shedaniel.math.Rectangle;
+import me.shedaniel.rei.RoughlyEnoughItemsCore;
+import me.shedaniel.rei.api.*;
+import me.shedaniel.rei.api.fluid.FluidSupportProvider;
+import me.shedaniel.rei.api.ingredient.EntryIngredient;
+import me.shedaniel.rei.api.ingredient.EntryStack;
+import me.shedaniel.rei.api.ingredient.util.EntryStacks;
+import me.shedaniel.rei.api.plugins.REIPlugin;
+import me.shedaniel.rei.api.registry.CategoryRegistry;
+import me.shedaniel.rei.api.registry.EntryRegistry;
+import me.shedaniel.rei.api.registry.ParentReloadable;
+import me.shedaniel.rei.api.registry.Reloadable;
+import me.shedaniel.rei.api.registry.display.Display;
+import me.shedaniel.rei.api.registry.display.DisplayCategory;
+import me.shedaniel.rei.api.registry.screens.ExclusionZones;
+import me.shedaniel.rei.api.registry.screens.OverlayDecider;
+import me.shedaniel.rei.api.registry.screens.ScreenRegistry;
+import me.shedaniel.rei.api.subsets.SubsetsRegistry;
+import me.shedaniel.rei.api.util.CollectionUtils;
+import me.shedaniel.rei.impl.subsets.SubsetsRegistryImpl;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.Util;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
+import net.minecraft.network.chat.Component;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.InteractionResultHolder;
+import net.minecraft.world.item.crafting.Recipe;
+import net.minecraft.world.item.crafting.RecipeManager;
+import org.apache.commons.lang3.tuple.MutablePair;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+@ApiStatus.Internal
+@Environment(EnvType.CLIENT)
+public class PluginManager implements ParentReloadable {
+
+ private final List<Reloadable> reloadables = new ArrayList<>();
+ private final List<FocusedStackProvider> focusedStackProviders = Lists.newArrayList();
+ private final List<AutoTransferHandler> autoTransferHandlers = Lists.newArrayList();
+ private final List<RecipeFunction<?>> recipeFunctions = Lists.newArrayList();
+ private final Multimap<Class<? extends Screen>, ClickAreaHandler<?>> screenClickAreas = HashMultimap.create();
+ private final List<DisplayVisibilityHandler> displayVisibilityHandlers = Lists.newArrayList();
+ private final List<LiveDisplayGenerator<Display>> liveDisplayGenerators = Lists.newArrayList();
+ private RecipeManager recipeManager;
+ private boolean arePluginsLoading = false;
+
+ public PluginManager() {
+ reloadables.add(CategoryRegistry.getInstance());
+ }
+
+ @Override
+ public List<EntryStack<?>> findCraftableEntriesByItems(Iterable<? extends EntryStack<?>> inventoryItems) {
+ List<EntryStack<?>> craftables = new ArrayList<>();
+ for (List<Display> value : recipeDisplays.values())
+ for (Display display : Lists.newArrayList(value)) {
+ int slotsCraftable = 0;
+ List<EntryIngredient> requiredInput = display.getRequiredEntries();
+ for (EntryIngredient slot : requiredInput) {
+ if (slot.isEmpty()) {
+ slotsCraftable++;
+ continue;
+ }
+ back:
+ for (EntryStack<?> possibleType : inventoryItems) {
+ for (EntryStack<?> slotPossible : slot)
+ if (EntryStacks.equalsIgnoreCount(possibleType, slotPossible)) {
+ slotsCraftable++;
+ break back;
+ }
+ }
+ }
+ if (slotsCraftable == display.getRequiredEntries().size())
+ display.getResultingEntries().stream().flatMap(Collection::stream).collect(Collectors.toCollection(() -> craftables));
+ }
+ return craftables.stream().distinct().collect(Collectors.toList());
+ }
+
+ @Override
+ public boolean arePluginsLoading() {
+ return arePluginsLoading;
+ }
+
+ @Override
+ public void registerDisplay(Display display) {
+ ResourceLocation identifier = Objects.requireNonNull(display.getCategoryIdentifier());
+ if (!recipeDisplays.containsKey(identifier))
+ throw new IllegalArgumentException("Unable to identify category: " + identifier.toString());
+ recipeCount.increment();
+ recipeDisplays.get(identifier).add(display);
+ }
+
+ private void registerDisplay(ResourceLocation categoryIdentifier, Display display, int index) {
+ if (!recipeDisplays.containsKey(categoryIdentifier))
+ throw new IllegalArgumentException("Unable to identify category: " + categoryIdentifier.toString());
+ recipeCount.increment();
+ recipeDisplays.get(categoryIdentifier).add(index, display);
+ }
+
+ @Override
+ public Map<DisplayCategory<?>, List<Display>> buildMapFor(ClientHelper.ViewSearchBuilder builder) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ Set<ResourceLocation> categories = builder.getCategories();
+ List<EntryStack<?>> recipesFor = builder.getRecipesFor();
+ List<EntryStack<?>> usagesFor = builder.getUsagesFor();
+
+ Map<DisplayCategory<?>, List<Display>> result = Maps.newLinkedHashMap();
+ for (Map.Entry<DisplayCategory<?>, ResourceLocation> entry : this.categories.entrySet()) {
+ DisplayCategory<?> category = entry.getKey();
+ ResourceLocation categoryId = entry.getValue();
+ List<Display> allRecipesFromCategory = getAllRecipesFromCategory(category);
+
+ Set<Display> set = Sets.newLinkedHashSet();
+ if (categories.contains(categoryId)) {
+ for (Display display : allRecipesFromCategory) {
+ if (isDisplayVisible(display)) {
+ set.add(display);
+ }
+ }
+ if (!set.isEmpty()) {
+ CollectionUtils.getOrPutEmptyList(result, category).addAll(set);
+ }
+ continue;
+ }
+ for (Display display : allRecipesFromCategory) {
+ if (!isDisplayVisible(display)) continue;
+ if (!recipesFor.isEmpty()) {
+ back:
+ for (List<? extends EntryStack<?>> results : display.getResultingEntries()) {
+ for (EntryStack<?> otherEntry : results) {
+ for (EntryStack<?> stack : recipesFor) {
+ if (EntryStacks.equalsIgnoreCount(otherEntry, stack)) {
+ set.add(display);
+ break back;
+ }
+ }
+ }
+ }
+ }
+ if (!usagesFor.isEmpty()) {
+ back:
+ for (List<? extends EntryStack<?>> input : display.getInputEntries()) {
+ for (EntryStack<?> otherEntry : input) {
+ for (EntryStack<?> stack : usagesFor) {
+ if (EntryStacks.equalsIgnoreCount(otherEntry, stack)) {
+ set.add(display);
+ break back;
+ }
+ }
+ }
+ }
+ }
+ }
+ for (EntryStack<?> stack : usagesFor) {
+ if (isStackWorkStationOfCategory(categoryId, stack)) {
+ set.addAll(CollectionUtils.filter(allRecipesFromCategory, this::isDisplayVisible));
+ break;
+ }
+ }
+ if (!set.isEmpty()) {
+ CollectionUtils.getOrPutEmptyList(result, category).addAll(set);
+ }
+ }
+
+ for (LiveDisplayGenerator<Display> liveDisplayGenerator : liveDisplayGenerators) {
+ Set<Display> set = Sets.newLinkedHashSet();
+ for (EntryStack<?> stack : recipesFor) {
+ Optional<List<Display>> recipeForDisplays = liveDisplayGenerator.getRecipeFor(stack);
+ if (recipeForDisplays.isPresent()) {
+ for (Display display : recipeForDisplays.get()) {
+ if (isDisplayVisible(display))
+ set.add(display);
+ }
+ }
+ }
+ for (EntryStack<?> stack : usagesFor) {
+ Optional<List<Display>> usageForDisplays = liveDisplayGenerator.getUsageFor(stack);
+ if (usageForDisplays.isPresent()) {
+ for (Display display : usageForDisplays.get()) {
+ if (isDisplayVisible(display))
+ set.add(display);
+ }
+ }
+ }
+ Optional<List<Display>> displaysGenerated = liveDisplayGenerator.getDisplaysGenerated(builder);
+ if (displaysGenerated.isPresent()) {
+ for (Display display : displaysGenerated.get()) {
+ if (isDisplayVisible(display))
+ set.add(display);
+ }
+ }
+ if (!set.isEmpty()) {
+ CollectionUtils.getOrPutEmptyList(result, getCategory(liveDisplayGenerator.getCategoryIdentifier())).addAll(set);
+ }
+ }
+
+ String message = String.format("Built Recipe View in %s for %d categories, %d recipes for, %d usages for and %d live recipe generators.",
+ stopwatch.stop().toString(), categories.size(), recipesFor.size(), usagesFor.size(), liveDisplayGenerators.size());
+ if (ConfigObject.getInstance().doDebugSearchTimeRequired()) {
+ RoughlyEnoughItemsCore.LOGGER.info(message);
+ } else {
+ RoughlyEnoughItemsCore.LOGGER.trace(message);
+ }
+ return result;
+ }
+
+ @Override
+ public Map<DisplayCategory<?>, List<Display>> getRecipesFor(EntryStack<?> stack) {
+ return buildMapFor(ClientHelper.ViewSearchBuilder.builder().addRecipesFor(stack));
+ }
+
+ @Override
+ public DisplayCategory<?> getCategory(ResourceLocation identifier) {
+ return categories.inverse().get(identifier);
+ }
+
+ @Override
+ public RecipeManager getRecipeManager() {
+ return recipeManager;
+ }
+
+ private boolean isStackWorkStationOfCategory(ResourceLocation category, EntryStack<?> stack) {
+ for (List<? extends EntryStack<?>> stacks : getWorkingStations(category)) {
+ for (EntryStack<?> entryStack : stacks) {
+ if (EntryStacks.equalsFuzzy(entryStack, stack))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Map<DisplayCategory<?>, List<Display>> getUsagesFor(EntryStack<?> stack) {
+ return buildMapFor(ClientHelper.ViewSearchBuilder.builder().addUsagesFor(stack));
+ }
+
+ @Override
+ public List<DisplayCategory<?>> getAllCategories() {
+ return Lists.newArrayList(categories.keySet());
+ }
+
+ @Override
+ public Optional<ButtonAreaSupplier> getAutoCraftButtonArea(DisplayCategory<?> category) {
+ if (!autoCraftAreaSupplierMap.containsKey(category.getIdentifier()))
+ return Optional.ofNullable(bounds -> new Rectangle(bounds.getMaxX() - 16, bounds.getMaxY() - 16, 10, 10));
+ return Optional.ofNullable(autoCraftAreaSupplierMap.get(category.getIdentifier()));
+ }
+
+ @Override
+ public void registerAutoCraftButtonArea(ResourceLocation category, ButtonAreaSupplier rectangle) {
+ if (rectangle == null) {
+ autoCraftAreaSupplierMap.remove(category);
+ } else
+ autoCraftAreaSupplierMap.put(category, rectangle);
+ }
+
+ private void startSection(MutablePair<Stopwatch, String> sectionData, String section) {
+ sectionData.setRight(section);
+ RoughlyEnoughItemsCore.LOGGER.debug("Reloading Section: \"%s\"", section);
+ sectionData.getLeft().reset().start();
+ }
+
+ private void endSection(MutablePair<Stopwatch, String> sectionData) {
+ sectionData.getLeft().stop();
+ String section = sectionData.getRight();
+ RoughlyEnoughItemsCore.LOGGER.debug("Reloading Section: \"%s\" done in %s", section, sectionData.getLeft().toString());
+ sectionData.getLeft().reset();
+ }
+
+ private void pluginSection(MutablePair<Stopwatch, String> sectionData, String sectionName, List<REIPlugin> list, Consumer<REIPlugin> consumer) {
+ for (REIPlugin plugin : list) {
+ startSection(sectionData, sectionName + " for " + plugin.getPluginName());
+ try {
+ consumer.accept(plugin);
+ } catch (Throwable e) {
+ RoughlyEnoughItemsCore.LOGGER.error(plugin.getPluginName() + " plugin failed to " + sectionName + "!", e);
+ }
+ endSection(sectionData);
+ }
+ }
+
+ public void tryRecipesLoaded(RecipeManager recipeManager) {
+ try {
+ recipesLoaded(recipeManager);
+ } catch (Throwable throwable) {
+ throwable.printStackTrace();
+ }
+ arePluginsLoading = false;
+ }
+
+ public void recipesLoaded(RecipeManager recipeManager) {
+ long startTime = Util.getMillis();
+ MutablePair<Stopwatch, String> sectionData = new MutablePair<>(Stopwatch.createUnstarted(), "");
+
+ startSection(sectionData, "reset-data");
+ for (Reloadable reloadable : reloadables) {
+ reloadable.resetData();
+ }
+ arePluginsLoading = true;
+ ScreenHelper.clearLastRecipeScreenData();
+ recipeCount.setValue(0);
+ this.recipeManager = recipeManager;
+ this.recipeDisplays.clear();
+ this.categories.clear();
+ this.autoCraftAreaSupplierMap.clear();
+ this.screenClickAreas.clear();
+ this.recipeFunctions.clear();
+ this.displayVisibilityHandlers.clear();
+ this.liveDisplayGenerators.clear();
+ this.autoTransferHandlers.clear();
+ this.focusedStackProviders.clear();
+
+ ScreenRegistryImpl displayHelper = (ScreenRegistryImpl) ScreenRegistry.getInstance();
+ EntryRegistryImpl entryRegistry = (EntryRegistryImpl) EntryRegistry.getInstance();
+ EntryTypeRegistryImpl entryTypeRegistry = EntryTypeRegistryImpl.getInstance();
+
+ entryTypeRegistry.reset();
+ FavoriteEntryTypeRegistryImpl.getInstance().clear();
+ ((SubsetsRegistryImpl) SubsetsRegistry.getInstance()).reset();
+ ((FluidSupportProviderImpl) FluidSupportProvider.getInstance()).reset();
+ displayHelper.resetData();
+ List<REIPluginEntry> plugins = RoughlyEnoughItemsCore.getPlugins();
+ plugins.sort(Comparator.comparingInt(REIPluginEntry::getPriority).reversed());
+ RoughlyEnoughItemsCore.LOGGER.info("Reloading REI, registered %d plugins: %s", plugins.size(), plugins.stream().map(REIPluginEntry::getPluginName).collect(Collectors.joining(", ")));
+ Collections.reverse(plugins);
+ entryRegistry.resetToReloadStart();
+ List<REIPlugin> reiPlugins = new ArrayList<>();
+ endSection(sectionData);
+ for (REIPluginEntry plugin : plugins) {
+ startSection(sectionData, "pre-register for " + plugin.getPluginName());
+ try {
+ if (plugin instanceof REIPlugin) {
+ ((REIPlugin) plugin).preRegister();
+ reiPlugins.add((REIPlugin) plugin);
+ }
+ } catch (Throwable e) {
+ RoughlyEnoughItemsCore.LOGGER.error(plugin.getPluginName() + " plugin failed to pre register!", e);
+ }
+ endSection(sectionData);
+ }
+ pluginSection(sectionData, "register-entry-types", reiPlugins, plugin -> plugin.registerEntryTypes(entryTypeRegistry));
+ pluginSection(sectionData, "register-bounds", reiPlugins, plugin -> plugin.registerScreens(displayHelper));
+ pluginSection(sectionData, "register-entries", reiPlugins, plugin -> plugin.registerEntries(entryRegistry));
+ pluginSection(sectionData, "register-categories", reiPlugins, plugin -> plugin.registerCategories(this));
+ pluginSection(sectionData, "register-displays", reiPlugins, plugin -> plugin.registerDisplays(this));
+ pluginSection(sectionData, "register-others", reiPlugins, plugin -> plugin.registerOthers(this));
+ pluginSection(sectionData, "post-register", reiPlugins, REIPlugin::postRegister);
+ startSection(sectionData, "recipe-functions");
+ if (!recipeFunctions.isEmpty()) {
+ List<Recipe<?>> allSortedRecipes = getAllSortedRecipes();
+ for (int i = allSortedRecipes.size() - 1; i >= 0; i--) {
+ Recipe<?> recipe = allSortedRecipes.get(i);
+ for (RecipeFunction<?> recipeFunction : recipeFunctions) {
+ try {
+ if (recipeFunction.recipeFilter.test(recipe)) {
+ registerDisplay(recipeFunction.category, recipeFunction.get(recipe), 0);
+ }
+ } catch (Throwable e) {
+ RoughlyEnoughItemsCore.LOGGER.error("Failed to add recipes!", e);
+ }
+ }
+ }
+ }
+ endSection(sectionData);
+ startSection(sectionData, "fill-handlers");
+ if (getDisplayVisibilityHandlers().isEmpty())
+ registerRecipeVisibilityHandler(new DisplayVisibilityHandler() {
+ @Override
+ public InteractionResult handleDisplay(DisplayCategory<?> category, Display display) {
+ return InteractionResult.SUCCESS;
+ }
+
+ @Override
+ public float getPriority() {
+ return -1f;
+ }
+ });
+ registerFocusedStackProvider(new FocusedStackProvider() {
+ @Override
+ @NotNull
+ public InteractionResultHolder<EntryStack<?>> provide(Screen screen) {
+ if (screen instanceof AbstractContainerScreen) {
+ AbstractContainerScreen<?> containerScreen = (AbstractContainerScreen<?>) screen;
+ if (containerScreen.hoveredSlot != null && !containerScreen.hoveredSlot.getItem().isEmpty())
+ return InteractionResultHolder.success(EntryStacks.of(containerScreen.hoveredSlot.getItem()));
+ }
+ return InteractionResultHolder.pass(EntryStack.empty());
+ }
+
+ @Override
+ public double getPriority() {
+ return -1.0;
+ }
+ });
+ displayHelper.registerHandler(new OverlayDecider() {
+ @Override
+ public boolean isHandingScreen(Class<?> screen) {
+ return true;
+ }
+
+ @Override
+ public InteractionResult shouldScreenBeOverlaid(Class<?> screen) {
+ return AbstractContainerScreen.class.isAssignableFrom(screen) ? InteractionResult.SUCCESS : InteractionResult.PASS;
+ }
+
+ @Override
+ public float getPriority() {
+ return -10;
+ }
+ });
+ endSection(sectionData);
+
+ // Clear Cache
+ REIHelper.getInstance().getOverlay().ifPresent(REIOverlay::queueReloadOverlay);
+
+ startSection(sectionData, "entry-registry-finalise");
+
+ // Finish Reload
+ entryRegistry.finishReload();
+
+ endSection(sectionData);
+ startSection(sectionData, "entry-registry-refilter");
+
+ arePluginsLoading = false;
+ entryRegistry.refilter();
+
+ endSection(sectionData);
+ startSection(sectionData, "finalizing");
+
+ // Clear Cache Again!
+ REIHelper.getInstance().getOverlay().ifPresent(REIOverlay::queueReloadOverlay);
+
+ displayVisibilityHandlers.sort(Comparator.reverseOrder());
+ endSection(sectionData);
+
+ long usedTime = Util.getMillis() - startTime;
+ RoughlyEnoughItemsCore.LOGGER.info("Reloaded %d stack entries, %d recipes displays, %d exclusion zones suppliers, %d overlay deciders, %d visibility handlers and %d categories (%s) in %dms.",
+ entryRegistry.getEntryStacks().count(), recipeCount.getValue(), ExclusionZones.getInstance().getZonesCount(), displayHelper.getAllOverlayDeciders().size(), getDisplayVisibilityHandlers().size(), categories.size(), categories.keySet().stream().map(DisplayCategory::getTitle).map(Component::getString).collect(Collectors.joining(", ")), usedTime);
+ }
+
+ @Override
+ public AutoTransferHandler registerAutoCraftingHandler(AutoTransferHandler handler) {
+ autoTransferHandlers.add(handler);
+ autoTransferHandlers.sort(Comparator.reverseOrder());
+ return handler;
+ }
+
+ @Override
+ public void registerFocusedStackProvider(FocusedStackProvider provider) {
+ focusedStackProviders.add(provider);
+ focusedStackProviders.sort(Comparator.reverseOrder());
+ }
+
+ @Override
+ @Nullable
+ public EntryStack<?> getScreenFocusedStack(Screen screen) {
+ for (FocusedStackProvider provider : focusedStackProviders) {
+ InteractionResultHolder<EntryStack<?>> result = Objects.requireNonNull(provider.provide(screen));
+ if (result.getResult() == InteractionResult.SUCCESS) {
+ if (!result.getObject().isEmpty())
+ return result.getObject();
+ return null;
+ } else if (result.getResult() == InteractionResult.FAIL)
+ return null;
+ }
+ return null;
+ }
+
+ @Override
+ public List<AutoTransferHandler> getSortedAutoCraftingHandler() {
+ return autoTransferHandlers;
+ }
+
+ @Override
+ public int getDisplayCount() {
+ return recipeCount.getValue();
+ }
+
+ @Override
+ public List<Recipe<?>> getAllSortedRecipes() {
+ return getRecipeManager().getRecipes().parallelStream().sorted(RECIPE_COMPARATOR).collect(Collectors.toList());
+ }
+
+ @Override
+ public Map<DisplayCategory<?>, List<Display>> getAllRecipes() {
+ return buildMapFor(ClientHelper.ViewSearchBuilder.builder().addAllCategories());
+ }
+
+ @Override
+ public Map<DisplayCategory<?>, List<Display>> getAllRecipesNoHandlers() {
+ Map<DisplayCategory<?>, List<Display>> result = Maps.newLinkedHashMap();
+ for (Map.Entry<DisplayCategory<?>, ResourceLocation> entry : categories.entrySet()) {
+ DisplayCategory<?> category = entry.getKey();
+ ResourceLocation categoryId = entry.getValue();
+ List<Display> displays = recipeDisplays.get(categoryId);
+ if (displays != null && !displays.isEmpty()) {
+ result.put(category, displays);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public List<Display> getAllRecipesFromCategory(DisplayCategory<?> category) {
+ return recipeDisplays.get(category.getIdentifier());
+ }
+
+ @Override
+ public void registerRecipeVisibilityHandler(DisplayVisibilityHandler visibilityHandler) {
+ displayVisibilityHandlers.add(visibilityHandler);
+ }
+
+ @Override
+ public void unregisterRecipeVisibilityHandler(DisplayVisibilityHandler visibilityHandler) {
+ displayVisibilityHandlers.remove(visibilityHandler);
+ }
+
+ @Override
+ public List<DisplayVisibilityHandler> getDisplayVisibilityHandlers() {
+ return Collections.unmodifiableList(displayVisibilityHandlers);
+ }
+
+ @Override
+ public boolean isDisplayNotVisible(Display display) {
+ return !isDisplayVisible(display);
+ }
+
+ @Override
+ public boolean isDisplayVisible(Display display) {
+ DisplayCategory<?> category = getCategory(display.getCategoryIdentifier());
+ try {
+ for (DisplayVisibilityHandler displayVisibilityHandler : displayVisibilityHandlers) {
+ InteractionResult visibility = displayVisibilityHandler.handleDisplay(category, display);
+ if (visibility != InteractionResult.PASS)
+ return visibility == InteractionResult.SUCCESS;
+ }
+ } catch (Throwable throwable) {
+ RoughlyEnoughItemsCore.LOGGER.error("Failed to check if the recipe is visible!", throwable);
+ }
+ return true;
+ }
+
+ @Override
+ public <T extends AbstractContainerScreen<?>> void registerContainerClickArea(ScreenClickAreaProvider<T> rectangleSupplier, Class<T> screenClass, ResourceLocation... categories) {
+ registerClickArea(screen -> {
+ Rectangle rectangle = rectangleSupplier.provide(screen).clone();
+ rectangle.translate(screen.leftPos, screen.topPos);
+ return rectangle;
+ }, screenClass, categories);
+ }
+
+ @Override
+ public <T extends Screen> void registerClickArea(ScreenClickAreaProvider<T> rectangleSupplier, Class<T> screenClass, ResourceLocation... categories) {
+ registerClickArea(screenClass, rectangleSupplier.toHandler(() -> categories));
+ }
+
+ @Override
+ public <T extends Screen> void registerClickArea(Class<T> screenClass, ClickAreaHandler<T> handler) {
+ this.screenClickAreas.put(screenClass, handler);
+ }
+
+ @Override
+ public <T extends Recipe<?>> void registerRecipes(ResourceLocation category, Predicate<Recipe<?>> recipeFilter, Function<T, Display> mappingFunction) {
+ recipeFunctions.add(new RecipeFunction<>(category, recipeFilter, mappingFunction));
+ }
+
+ @Override
+ public void registerLiveRecipeGenerator(LiveDisplayGenerator<?> liveDisplayGenerator) {
+ liveDisplayGenerators.add((LiveDisplayGenerator<Display>) liveDisplayGenerator);
+ }
+
+ public Multimap<Class<? extends Screen>, ClickAreaHandler<?>> getClickAreas() {
+ return screenClickAreas;
+ }
+
+ private static class RecipeFunction<T extends Recipe<?>> {
+ private ResourceLocation category;
+ private Predicate<Recipe<?>> recipeFilter;
+ private Function<T, Display> mappingFunction;
+
+ public RecipeFunction(ResourceLocation category, Predicate<Recipe<?>> recipeFilter, Function<T, Display> mappingFunction) {
+ this.category = category;
+ this.recipeFilter = recipeFilter;
+ this.mappingFunction = mappingFunction;
+ }
+
+ private <A> Display get(A screen) {
+ return mappingFunction.apply((T) screen);
+ }
+ }
+}