From 67171a5ff24ed77e6c4cc889543e8dfb543e8fe5 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Sun, 20 Dec 2020 19:59:49 +0800 Subject: wip more Signed-off-by: shedaniel --- .../me/shedaniel/rei/impl/RecipeHelperImpl.java | 648 +++++++++++++++++++++ 1 file changed, 648 insertions(+) create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java (limited to 'runtime/src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java') diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java new file mode 100644 index 000000000..d48fcec02 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java @@ -0,0 +1,648 @@ +/* + * 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.entry.EntryStacks; +import me.shedaniel.rei.api.fluid.FluidSupportProvider; +import me.shedaniel.rei.api.plugins.REIPluginV0; +import me.shedaniel.rei.api.subsets.SubsetsRegistry; +import me.shedaniel.rei.impl.subsets.SubsetsRegistryImpl; +import me.shedaniel.rei.utils.CollectionUtils; +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.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; +import java.util.stream.Stream; + +@ApiStatus.Internal +@Environment(EnvType.CLIENT) +public class RecipeHelperImpl implements RecipeHelper { + + private static final Comparator FOCUSED_STACK_PROVIDER_COMPARATOR = Comparator.comparingDouble(FocusedStackProvider::getPriority).reversed(); + private static final Comparator VISIBILITY_HANDLER_COMPARATOR = Comparator.comparingDouble(DisplayVisibilityHandler::getPriority).reversed(); + @SuppressWarnings("rawtypes") + private static final Comparator RECIPE_COMPARATOR = Comparator.comparing((Recipe o) -> o.getId().getNamespace()).thenComparing(o -> o.getId().getPath()); + + private final List focusedStackProviders = Lists.newArrayList(); + private final List autoTransferHandlers = Lists.newArrayList(); + private final List> recipeFunctions = Lists.newArrayList(); + private final Multimap, ClickAreaHandler> screenClickAreas = HashMultimap.create(); + private final int[] recipeCount = {0}; + private final Map> recipeDisplays = Maps.newHashMap(); + private final BiMap, ResourceLocation> categories = HashBiMap.create(); + private final Map autoCraftAreaSupplierMap = Maps.newHashMap(); + private final Map>>> categoryWorkingStations = Maps.newHashMap(); + private final List displayVisibilityHandlers = Lists.newArrayList(); + private final List> liveRecipeGenerators = Lists.newArrayList(); + private RecipeManager recipeManager; + private boolean arePluginsLoading = false; + + @Override + public List> findCraftableEntriesByItems(Iterable> inventoryItems) { + List> craftables = new ArrayList<>(); + for (List value : recipeDisplays.values()) + for (RecipeDisplay recipeDisplay : Lists.newArrayList(value)) { + int slotsCraftable = 0; + List>> requiredInput = recipeDisplay.getRequiredEntries(); + for (List> 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 == recipeDisplay.getRequiredEntries().size()) + recipeDisplay.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 registerCategory(RecipeCategory category) { + categories.put(category, category.getIdentifier()); + recipeDisplays.put(category.getIdentifier(), Lists.newArrayList()); + categoryWorkingStations.put(category.getIdentifier(), Lists.newArrayList()); + } + + @SafeVarargs + @Override + public final void registerWorkingStations(ResourceLocation category, List>... workingStations) { + categoryWorkingStations.get(category).addAll(Arrays.asList(workingStations)); + } + + @Override + public void registerWorkingStations(ResourceLocation category, EntryStack... workingStations) { + categoryWorkingStations.get(category).addAll(Stream.of(workingStations).map(Collections::singletonList).collect(Collectors.toList())); + } + + @Override + public List>> getWorkingStations(ResourceLocation category) { + return categoryWorkingStations.getOrDefault(category, Collections.emptyList()); + } + + @Override + public void registerDisplay(RecipeDisplay display) { + ResourceLocation identifier = Objects.requireNonNull(display.getRecipeCategory()); + if (!recipeDisplays.containsKey(identifier)) + return; + recipeCount[0]++; + recipeDisplays.get(identifier).add(display); + } + + private void registerDisplay(ResourceLocation categoryIdentifier, RecipeDisplay display, int index) { + if (!recipeDisplays.containsKey(categoryIdentifier)) + return; + recipeCount[0]++; + recipeDisplays.get(categoryIdentifier).add(index, display); + } + + @Override + public Map, List> buildMapFor(ClientHelper.ViewSearchBuilder builder) { + Stopwatch stopwatch = Stopwatch.createStarted(); + Set categories = builder.getCategories(); + List> recipesFor = builder.getRecipesFor(); + List> usagesFor = builder.getUsagesFor(); + + Map, List> result = Maps.newLinkedHashMap(); + for (Map.Entry, ResourceLocation> entry : this.categories.entrySet()) { + RecipeCategory category = entry.getKey(); + ResourceLocation categoryId = entry.getValue(); + List allRecipesFromCategory = getAllRecipesFromCategory(category); + + Set set = Sets.newLinkedHashSet(); + if (categories.contains(categoryId)) { + for (RecipeDisplay display : allRecipesFromCategory) { + if (isDisplayVisible(display)) { + set.add(display); + } + } + if (!set.isEmpty()) { + CollectionUtils.getOrPutEmptyList(result, category).addAll(set); + } + continue; + } + for (RecipeDisplay display : allRecipesFromCategory) { + if (!isDisplayVisible(display)) continue; + if (!recipesFor.isEmpty()) { + back: + for (List> 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> 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 (LiveRecipeGenerator liveRecipeGenerator : liveRecipeGenerators) { + Set set = Sets.newLinkedHashSet(); + for (EntryStack stack : recipesFor) { + Optional> recipeForDisplays = liveRecipeGenerator.getRecipeFor(stack); + if (recipeForDisplays.isPresent()) { + for (RecipeDisplay display : recipeForDisplays.get()) { + if (isDisplayVisible(display)) + set.add(display); + } + } + } + for (EntryStack stack : usagesFor) { + Optional> usageForDisplays = liveRecipeGenerator.getUsageFor(stack); + if (usageForDisplays.isPresent()) { + for (RecipeDisplay display : usageForDisplays.get()) { + if (isDisplayVisible(display)) + set.add(display); + } + } + } + Optional> displaysGenerated = liveRecipeGenerator.getDisplaysGenerated(builder); + if (displaysGenerated.isPresent()) { + for (RecipeDisplay display : displaysGenerated.get()) { + if (isDisplayVisible(display)) + set.add(display); + } + } + if (!set.isEmpty()) { + CollectionUtils.getOrPutEmptyList(result, getCategory(liveRecipeGenerator.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(), liveRecipeGenerators.size()); + if (ConfigObject.getInstance().doDebugSearchTimeRequired()) { + RoughlyEnoughItemsCore.LOGGER.info(message); + } else { + RoughlyEnoughItemsCore.LOGGER.trace(message); + } + return result; + } + + @Override + public Map, List> getRecipesFor(EntryStack stack) { + return buildMapFor(ClientHelper.ViewSearchBuilder.builder().addRecipesFor(stack)); + } + + @Override + public RecipeCategory getCategory(ResourceLocation identifier) { + return categories.inverse().get(identifier); + } + + @Override + public RecipeManager getRecipeManager() { + return recipeManager; + } + + private boolean isStackWorkStationOfCategory(ResourceLocation category, EntryStack stack) { + for (List> stacks : getWorkingStations(category)) { + for (EntryStack entryStack : stacks) { + if (EntryStacks.equalsFuzzy(entryStack, stack)) + return true; + } + } + return false; + } + + @Override + public Map, List> getUsagesFor(EntryStack stack) { + return buildMapFor(ClientHelper.ViewSearchBuilder.builder().addUsagesFor(stack)); + } + + @Override + public List> getAllCategories() { + return Lists.newArrayList(categories.keySet()); + } + + @Override + public Optional getAutoCraftButtonArea(RecipeCategory 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 sectionData, String section) { + sectionData.setRight(section); + RoughlyEnoughItemsCore.LOGGER.debug("Reloading Section: \"%s\"", section); + sectionData.getLeft().reset().start(); + } + + private void endSection(MutablePair 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 sectionData, String sectionName, List list, Consumer consumer) { + for (REIPluginV0 plugin : list) { + startSection(sectionData, sectionName + " for " + plugin.getPluginIdentifier().toString()); + try { + consumer.accept(plugin); + } catch (Throwable e) { + RoughlyEnoughItemsCore.LOGGER.error(plugin.getPluginIdentifier().toString() + " 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 sectionData = new MutablePair<>(Stopwatch.createUnstarted(), ""); + + startSection(sectionData, "reset-data"); + arePluginsLoading = true; + ScreenHelper.clearLastRecipeScreenData(); + recipeCount[0] = 0; + this.recipeManager = recipeManager; + this.recipeDisplays.clear(); + this.categories.clear(); + this.autoCraftAreaSupplierMap.clear(); + this.screenClickAreas.clear(); + this.categoryWorkingStations.clear(); + this.recipeFunctions.clear(); + this.displayVisibilityHandlers.clear(); + this.liveRecipeGenerators.clear(); + this.autoTransferHandlers.clear(); + this.focusedStackProviders.clear(); + + DisplayHelperImpl displayHelper = (DisplayHelperImpl) DisplayHelper.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(); + displayHelper.resetCache(); + BaseBoundsHandler baseBoundsHandler = new BaseBoundsHandlerImpl(); + displayHelper.registerHandler(baseBoundsHandler); + displayHelper.setBaseBoundsHandler(baseBoundsHandler); + List 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::getPluginIdentifier).map(ResourceLocation::toString).collect(Collectors.joining(", "))); + Collections.reverse(plugins); + entryRegistry.resetToReloadStart(); + List reiPluginV0s = new ArrayList<>(); + endSection(sectionData); + for (REIPluginEntry plugin : plugins) { + startSection(sectionData, "pre-register for " + plugin.getPluginIdentifier().toString()); + try { + if (plugin instanceof REIPluginV0) { + ((REIPluginV0) plugin).preRegister(); + reiPluginV0s.add((REIPluginV0) plugin); + } + } catch (Throwable e) { + RoughlyEnoughItemsCore.LOGGER.error(plugin.getPluginIdentifier().toString() + " plugin failed to pre register!", e); + } + endSection(sectionData); + } + pluginSection(sectionData, "register-entry-types", reiPluginV0s, plugin -> plugin.registerEntryTypes(entryTypeRegistry)); + pluginSection(sectionData, "register-bounds", reiPluginV0s, plugin -> plugin.registerBounds(displayHelper)); + pluginSection(sectionData, "register-entries", reiPluginV0s, plugin -> plugin.registerEntries(entryRegistry)); + pluginSection(sectionData, "register-categories", reiPluginV0s, plugin -> plugin.registerPluginCategories(this)); + pluginSection(sectionData, "register-displays", reiPluginV0s, plugin -> plugin.registerRecipeDisplays(this)); + pluginSection(sectionData, "register-others", reiPluginV0s, plugin -> plugin.registerOthers(this)); + pluginSection(sectionData, "post-register", reiPluginV0s, REIPluginV0::postRegister); + startSection(sectionData, "recipe-functions"); + if (!recipeFunctions.isEmpty()) { + List> 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(RecipeCategory category, RecipeDisplay display) { + return InteractionResult.SUCCESS; + } + + @Override + public float getPriority() { + return -1f; + } + }); + registerFocusedStackProvider(new FocusedStackProvider() { + @Override + @NotNull + public InteractionResultHolder> 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 + displayHelper.resetCache(); + 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! + displayHelper.resetCache(); + REIHelper.getInstance().getOverlay().ifPresent(REIOverlay::queueReloadOverlay); + + displayVisibilityHandlers.sort(VISIBILITY_HANDLER_COMPARATOR); + 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[0], BaseBoundsHandler.getInstance().supplierSize(), displayHelper.getAllOverlayDeciders().size(), getDisplayVisibilityHandlers().size(), categories.size(), categories.keySet().stream().map(RecipeCategory::getCategoryName).collect(Collectors.joining(", ")), usedTime); + } + + @Override + public AutoTransferHandler registerAutoCraftingHandler(AutoTransferHandler handler) { + autoTransferHandlers.add(handler); + autoTransferHandlers.sort(Comparator.comparingDouble(AutoTransferHandler::getPriority).reversed()); + return handler; + } + + @Override + public void registerFocusedStackProvider(FocusedStackProvider provider) { + focusedStackProviders.add(provider); + focusedStackProviders.sort(FOCUSED_STACK_PROVIDER_COMPARATOR); + } + + @Override + @Nullable + public EntryStack getScreenFocusedStack(Screen screen) { + for (FocusedStackProvider provider : focusedStackProviders) { + InteractionResultHolder> 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 getSortedAutoCraftingHandler() { + return autoTransferHandlers; + } + + @Override + public int getRecipeCount() { + return recipeCount[0]; + } + + @Override + public List> getAllSortedRecipes() { + return getRecipeManager().getRecipes().parallelStream().sorted(RECIPE_COMPARATOR).collect(Collectors.toList()); + } + + @Override + public Map, List> getAllRecipes() { + return buildMapFor(ClientHelper.ViewSearchBuilder.builder().addAllCategories()); + } + + @Override + public Map, List> getAllRecipesNoHandlers() { + Map, List> result = Maps.newLinkedHashMap(); + for (Map.Entry, ResourceLocation> entry : categories.entrySet()) { + RecipeCategory category = entry.getKey(); + ResourceLocation categoryId = entry.getValue(); + List displays = recipeDisplays.get(categoryId); + if (displays != null && !displays.isEmpty()) { + result.put(category, displays); + } + } + return result; + } + + @Override + public List getAllRecipesFromCategory(RecipeCategory 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 getDisplayVisibilityHandlers() { + return Collections.unmodifiableList(displayVisibilityHandlers); + } + + @Override + public boolean isDisplayNotVisible(RecipeDisplay display) { + return !isDisplayVisible(display); + } + + @Override + public boolean isDisplayVisible(RecipeDisplay display) { + RecipeCategory category = getCategory(display.getRecipeCategory()); + 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 > void registerContainerClickArea(ScreenClickAreaProvider rectangleSupplier, Class screenClass, ResourceLocation... categories) { + registerClickArea(screen -> { + Rectangle rectangle = rectangleSupplier.provide(screen).clone(); + rectangle.translate(screen.leftPos, screen.topPos); + return rectangle; + }, screenClass, categories); + } + + @Override + public void registerClickArea(ScreenClickAreaProvider rectangleSupplier, Class screenClass, ResourceLocation... categories) { + registerClickArea(screenClass, rectangleSupplier.toHandler(() -> categories)); + } + + @Override + public void registerClickArea(Class screenClass, ClickAreaHandler handler) { + this.screenClickAreas.put(screenClass, handler); + } + + @Override + public > void registerRecipes(ResourceLocation category, Predicate> recipeFilter, Function mappingFunction) { + recipeFunctions.add(new RecipeFunction<>(category, recipeFilter, mappingFunction)); + } + + @Override + public void registerLiveRecipeGenerator(LiveRecipeGenerator liveRecipeGenerator) { + liveRecipeGenerators.add((LiveRecipeGenerator) liveRecipeGenerator); + } + + public Multimap, ClickAreaHandler> getClickAreas() { + return screenClickAreas; + } + + private static class RecipeFunction> { + private ResourceLocation category; + private Predicate> recipeFilter; + private Function mappingFunction; + + public RecipeFunction(ResourceLocation category, Predicate> recipeFilter, Function mappingFunction) { + this.category = category; + this.recipeFilter = recipeFilter; + this.mappingFunction = mappingFunction; + } + + private RecipeDisplay get(A screen) { + return mappingFunction.apply((T) screen); + } + } +} -- cgit