From 28025895e0da1e6079264dbfe951e7fd9bf069d8 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 11 Aug 2019 23:28:33 +0800 Subject: Scrollable Entry List? --- .../me/shedaniel/rei/impl/RecipeHelperImpl.java | 442 +++++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java (limited to 'src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java') diff --git a/src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java b/src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java new file mode 100644 index 000000000..46eb6afde --- /dev/null +++ b/src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java @@ -0,0 +1,442 @@ +/* + * Roughly Enough Items by Danielshe. + * Licensed under the MIT License. + */ + +package me.shedaniel.rei.impl; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import me.shedaniel.rei.RoughlyEnoughItemsCore; +import me.shedaniel.rei.api.*; +import me.shedaniel.rei.api.plugins.REIPluginV0; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.SemanticVersion; +import net.fabricmc.loader.api.Version; +import net.minecraft.client.gui.screen.ingame.AbstractContainerScreen; +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.Recipe; +import net.minecraft.recipe.RecipeManager; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Identifier; + +import java.awt.*; +import java.util.List; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class RecipeHelperImpl implements RecipeHelper { + + private static final Comparator VISIBILITY_HANDLER_COMPARATOR; + private static final Comparator RECIPE_COMPARATOR = (o1, o2) -> { + int int_1 = o1.getId().getNamespace().compareTo(o2.getId().getNamespace()); + if (int_1 == 0) + int_1 = o1.getId().getPath().compareTo(o2.getId().getPath()); + return int_1; + }; + + static { + Comparator comparator = Comparator.comparingDouble(DisplayVisibilityHandler::getPriority); + VISIBILITY_HANDLER_COMPARATOR = comparator.reversed(); + } + + private final List autoTransferHandlers = Lists.newArrayList(); + private final List recipeFunctions = Lists.newArrayList(); + private final List screenClickAreas = Lists.newArrayList(); + private final AtomicInteger recipeCount = new AtomicInteger(); + private final Map> recipeCategoryListMap = Maps.newHashMap(); + private final List categories = Lists.newArrayList(); + private final Map speedCraftAreaSupplierMap = Maps.newHashMap(); + private final Map>> categoryWorkingStations = Maps.newHashMap(); + private final List displayVisibilityHandlers = Lists.newArrayList(); + private final List> liveRecipeGenerators = Lists.newArrayList(); + private RecipeManager recipeManager; + + @Override + public List findCraftableByItems(List inventoryItems) { + List craftables = new ArrayList<>(); + for (List value : recipeCategoryListMap.values()) + for (RecipeDisplay recipeDisplay : value) { + int slotsCraftable = 0; + List> requiredInput = (List>) recipeDisplay.getRequiredItems(); + for (List slot : requiredInput) { + if (slot.isEmpty()) { + slotsCraftable++; + continue; + } + boolean slotDone = false; + for (ItemStack possibleType : inventoryItems) { + for (ItemStack slotPossible : slot) + if (ItemStack.areItemsEqualIgnoreDamage(slotPossible, possibleType)) { + slotsCraftable++; + slotDone = true; + break; + } + if (slotDone) + break; + } + } + if (slotsCraftable == recipeDisplay.getRequiredItems().size()) + craftables.addAll((List) recipeDisplay.getOutput()); + } + return craftables.stream().distinct().collect(Collectors.toList()); + } + + @Override + public void registerCategory(RecipeCategory category) { + categories.add(category); + recipeCategoryListMap.put(category.getIdentifier(), Lists.newLinkedList()); + categoryWorkingStations.put(category.getIdentifier(), Lists.newLinkedList()); + } + + @Override + public void registerWorkingStations(Identifier category, List... workingStations) { + categoryWorkingStations.get(category).addAll(Arrays.asList(workingStations)); + } + + @Override + public void registerWorkingStations(Identifier category, ItemStack... workingStations) { + categoryWorkingStations.get(category).addAll(Arrays.asList(workingStations).stream().map(Collections::singletonList).collect(Collectors.toList())); + } + + @Override + public List> getWorkingStations(Identifier category) { + return categoryWorkingStations.get(category); + } + + @Override + public void registerDisplay(Identifier categoryIdentifier, RecipeDisplay display) { + if (!recipeCategoryListMap.containsKey(categoryIdentifier)) + return; + recipeCount.incrementAndGet(); + recipeCategoryListMap.get(categoryIdentifier).add(display); + } + + private void registerDisplay(Identifier categoryIdentifier, RecipeDisplay display, int index) { + if (!recipeCategoryListMap.containsKey(categoryIdentifier)) + return; + recipeCount.incrementAndGet(); + recipeCategoryListMap.get(categoryIdentifier).add(index, display); + } + + @Override + public Map, List> getRecipesFor(ItemStack stack) { + Map> categoriesMap = new HashMap<>(); + categories.forEach(f -> categoriesMap.put(f.getIdentifier(), Lists.newArrayList())); + for (Map.Entry> entry : recipeCategoryListMap.entrySet()) { + RecipeCategory category = getCategory(entry.getKey()); + for (RecipeDisplay recipeDisplay : entry.getValue()) + for (ItemStack outputStack : (List) recipeDisplay.getOutput()) + if (category.checkTags() ? ItemStack.areEqualIgnoreDamage(stack, outputStack) : ItemStack.areItemsEqualIgnoreDamage(stack, outputStack)) + categoriesMap.get(recipeDisplay.getRecipeCategory()).add(recipeDisplay); + } + for (LiveRecipeGenerator liveRecipeGenerator : liveRecipeGenerators) + ((Optional) liveRecipeGenerator.getRecipeFor(stack)).ifPresent(o -> categoriesMap.get(liveRecipeGenerator.getCategoryIdentifier()).addAll(o)); + Map, List> recipeCategoryListMap = Maps.newLinkedHashMap(); + categories.forEach(category -> { + if (categoriesMap.containsKey(category.getIdentifier()) && !categoriesMap.get(category.getIdentifier()).isEmpty()) + recipeCategoryListMap.put(category, categoriesMap.get(category.getIdentifier()).stream().filter(display -> isDisplayVisible(display)).collect(Collectors.toList())); + }); + for (RecipeCategory category : Lists.newArrayList(recipeCategoryListMap.keySet())) + if (recipeCategoryListMap.get(category).isEmpty()) + recipeCategoryListMap.remove(category); + return recipeCategoryListMap; + } + + @Override + public RecipeCategory getCategory(Identifier identifier) { + return categories.stream().filter(category -> category.getIdentifier().equals(identifier)).findFirst().orElse(null); + } + + @Override + public RecipeManager getRecipeManager() { + return recipeManager; + } + + @Override + public Map, List> getUsagesFor(ItemStack stack) { + Map> categoriesMap = new HashMap<>(); + categories.forEach(f -> categoriesMap.put(f.getIdentifier(), Lists.newArrayList())); + for (Map.Entry> entry : recipeCategoryListMap.entrySet()) { + RecipeCategory category = getCategory(entry.getKey()); + for (RecipeDisplay recipeDisplay : entry.getValue()) { + boolean found = false; + for (List input : (List>) recipeDisplay.getInput()) { + for (ItemStack itemStack : input) { + if (category.checkTags() ? ItemStack.areEqualIgnoreDamage(itemStack, stack) : ItemStack.areItemsEqualIgnoreDamage(itemStack, stack)) { + categoriesMap.get(recipeDisplay.getRecipeCategory()).add(recipeDisplay); + found = true; + break; + } + } + if (found) + break; + } + } + } + for (LiveRecipeGenerator liveRecipeGenerator : liveRecipeGenerators) + ((Optional) liveRecipeGenerator.getUsageFor(stack)).ifPresent(o -> categoriesMap.get(liveRecipeGenerator.getCategoryIdentifier()).addAll(o)); + Map, List> recipeCategoryListMap = Maps.newLinkedHashMap(); + categories.forEach(category -> { + if (categoriesMap.containsKey(category.getIdentifier()) && !categoriesMap.get(category.getIdentifier()).isEmpty()) + recipeCategoryListMap.put(category, categoriesMap.get(category.getIdentifier()).stream().filter(display -> isDisplayVisible(display)).collect(Collectors.toList())); + }); + for (RecipeCategory category : Lists.newArrayList(recipeCategoryListMap.keySet())) + if (recipeCategoryListMap.get(category).isEmpty()) + recipeCategoryListMap.remove(category); + return recipeCategoryListMap; + } + + @Override + public List getAllCategories() { + return new LinkedList<>(categories); + } + + @Override + public Optional getSpeedCraftButtonArea(RecipeCategory category) { + if (!speedCraftAreaSupplierMap.containsKey(category.getIdentifier())) + return Optional.ofNullable(bounds -> new Rectangle((int) bounds.getMaxX() - 16, (int) bounds.getMaxY() - 16, 10, 10)); + return Optional.ofNullable(speedCraftAreaSupplierMap.get(category.getIdentifier())); + } + + @Override + public void registerSpeedCraftButtonArea(Identifier category, ButtonAreaSupplier rectangle) { + if (rectangle == null) { + if (speedCraftAreaSupplierMap.containsKey(category)) + speedCraftAreaSupplierMap.remove(category); + } else + speedCraftAreaSupplierMap.put(category, rectangle); + } + + @SuppressWarnings("deprecation") + @Override + public void registerDefaultSpeedCraftButtonArea(Identifier category) { + registerSpeedCraftButtonArea(category, bounds -> new Rectangle((int) bounds.getMaxX() - 16, (int) bounds.getMaxY() - 16, 10, 10)); + } + + @SuppressWarnings("deprecation") + public void recipesLoaded(RecipeManager recipeManager) { + this.recipeCount.set(0); + this.recipeManager = recipeManager; + this.recipeCategoryListMap.clear(); + this.categories.clear(); + this.speedCraftAreaSupplierMap.clear(); + this.screenClickAreas.clear(); + this.categoryWorkingStations.clear(); + this.recipeFunctions.clear(); + this.displayVisibilityHandlers.clear(); + this.liveRecipeGenerators.clear(); + this.autoTransferHandlers.clear(); + ((DisplayHelperImpl) RoughlyEnoughItemsCore.getDisplayHelper()).resetCache(); + BaseBoundsHandler baseBoundsHandler = new BaseBoundsHandlerImpl(); + RoughlyEnoughItemsCore.getDisplayHelper().registerBoundsHandler(baseBoundsHandler); + ((DisplayHelperImpl) RoughlyEnoughItemsCore.getDisplayHelper()).setBaseBoundsHandler(baseBoundsHandler); + long startTime = System.currentTimeMillis(); + List plugins = new LinkedList<>(RoughlyEnoughItemsCore.getPlugins()); + plugins.sort((first, second) -> { + return second.getPriority() - first.getPriority(); + }); + RoughlyEnoughItemsCore.LOGGER.info("[REI] Loading %d plugins: %s", plugins.size(), plugins.stream().map(REIPluginEntry::getPluginIdentifier).map(Identifier::toString).collect(Collectors.joining(", "))); + Collections.reverse(plugins); + RoughlyEnoughItemsCore.getEntryRegistry().getModifiableEntryList().clear(); + Version reiVersion = FabricLoader.getInstance().getModContainer("roughlyenoughitems").get().getMetadata().getVersion(); + if (!(reiVersion instanceof SemanticVersion)) + RoughlyEnoughItemsCore.LOGGER.warn("[REI] Roughly Enough Items is not using semantic versioning, will be ignoring plugins' minimum versions!"); + plugins.forEach(plugin -> { + Identifier identifier = plugin.getPluginIdentifier(); + try { + if (plugin instanceof REIPluginV0) { + if (reiVersion instanceof SemanticVersion) + if (((REIPluginV0) plugin).getMinimumVersion().compareTo((SemanticVersion) reiVersion) > 0) { + throw new IllegalStateException("Requires " + ((REIPluginV0) plugin).getMinimumVersion().getFriendlyString() + " REI version!"); + } + ((REIPluginV0) plugin).registerPluginCategories(this); + ((REIPluginV0) plugin).registerRecipeDisplays(this); + ((REIPluginV0) plugin).registerBounds(RoughlyEnoughItemsCore.getDisplayHelper()); + ((REIPluginV0) plugin).registerOthers(this); + ((REIPluginV0) plugin).registerItems(RoughlyEnoughItemsCore.getEntryRegistry()); + } else { + throw new IllegalStateException("Invaild Plugin Class!"); + } + } catch (Exception e) { + RoughlyEnoughItemsCore.LOGGER.error("[REI] " + identifier.toString() + " plugin failed to load!", e); + } + }); + if (!recipeFunctions.isEmpty()) { + List allSortedRecipes = getAllSortedRecipes(); + Collections.reverse(allSortedRecipes); + recipeFunctions.forEach(recipeFunction -> { + try { + allSortedRecipes.stream().filter(recipe -> recipeFunction.recipeFilter.test(recipe)).forEach(t -> registerDisplay(recipeFunction.category, (RecipeDisplay) recipeFunction.mappingFunction.apply(t), 0)); + } catch (Exception e) { + RoughlyEnoughItemsCore.LOGGER.error("[REI] Failed to add recipes!", e); + } + }); + } + if (getDisplayVisibilityHandlers().isEmpty()) + registerRecipeVisibilityHandler(new DisplayVisibilityHandler() { + @Override + public ActionResult handleDisplay(RecipeCategory category, RecipeDisplay display) { + return ActionResult.SUCCESS; + } + + @Override + public float getPriority() { + return -1f; + } + }); + // Clear Cache + ((DisplayHelperImpl) RoughlyEnoughItemsCore.getDisplayHelper()).resetCache(); + ScreenHelper.getOptionalOverlay().ifPresent(overlay -> overlay.shouldReInit = true); + + long usedTime = System.currentTimeMillis() - startTime; + RoughlyEnoughItemsCore.LOGGER.info("[REI] Registered %d stack entries, %d recipes displays, %d bounds handler, %d visibility handlers and %d categories (%s) in %d ms.", RoughlyEnoughItemsCore.getEntryRegistry().getEntryList().size(), recipeCount.get(), RoughlyEnoughItemsCore.getDisplayHelper().getAllBoundsHandlers().size(), getDisplayVisibilityHandlers().size(), categories.size(), String.join(", ", categories.stream().map(RecipeCategory::getCategoryName).collect(Collectors.toList())), usedTime); + } + + @Override + public AutoTransferHandler registerAutoCraftingHandler(AutoTransferHandler handler) { + autoTransferHandlers.add(handler); + return handler; + } + + @Override + public List getSortedAutoCraftingHandler() { + return autoTransferHandlers.stream().sorted(Comparator.comparingDouble(AutoTransferHandler::getPriority).reversed()).collect(Collectors.toList()); + } + + @Override + public int getRecipeCount() { + return recipeCount.get(); + } + + @Override + public List getAllSortedRecipes() { + return getRecipeManager().values().stream().sorted(RECIPE_COMPARATOR).collect(Collectors.toList()); + } + + @Override + public Map, List> getAllRecipes() { + Map, List> map = Maps.newLinkedHashMap(); + categories.forEach(recipeCategory -> { + if (recipeCategoryListMap.containsKey(recipeCategory.getIdentifier())) { + List list = recipeCategoryListMap.get(recipeCategory.getIdentifier()).stream().filter(display -> isDisplayVisible(display)).collect(Collectors.toList()); + if (!list.isEmpty()) + map.put(recipeCategory, list); + } + }); + return map; + } + + @Override + public List getAllRecipesFromCategory(RecipeCategory category) { + return recipeCategoryListMap.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); + } + + @SuppressWarnings("deprecation") + @Override + public boolean isDisplayVisible(RecipeDisplay display, boolean respectConfig) { + return isDisplayVisible(display); + } + + @SuppressWarnings("deprecation") + @Override + public boolean isDisplayVisible(RecipeDisplay display) { + RecipeCategory category = getCategory(display.getRecipeCategory()); + List list = getDisplayVisibilityHandlers().stream().sorted(VISIBILITY_HANDLER_COMPARATOR).collect(Collectors.toList()); + for (DisplayVisibilityHandler displayVisibilityHandler : list) { + try { + ActionResult visibility = displayVisibilityHandler.handleDisplay(category, display); + if (visibility != ActionResult.PASS) + return visibility == ActionResult.SUCCESS; + } catch (Throwable throwable) { + RoughlyEnoughItemsCore.LOGGER.error("[REI] Failed to check if the recipe is visible!", throwable); + } + } + return true; + } + + @Override + public void registerScreenClickArea(Rectangle rectangle, Class screenClass, Identifier... categories) { + this.screenClickAreas.add(new ScreenClickAreaImpl(screenClass, rectangle, categories)); + } + + @Override + public > void registerRecipes(Identifier category, Class recipeClass, Function mappingFunction) { + recipeFunctions.add(new RecipeFunction(category, recipe -> recipeClass.isAssignableFrom(recipe.getClass()), mappingFunction)); + } + + @Override + public > void registerRecipes(Identifier category, Function recipeFilter, Function mappingFunction) { + recipeFunctions.add(new RecipeFunction(category, recipeFilter::apply, mappingFunction)); + } + + @Override + public > void registerRecipes(Identifier category, Predicate recipeFilter, Function mappingFunction) { + recipeFunctions.add(new RecipeFunction(category, recipeFilter, mappingFunction)); + } + + @Override + public void registerLiveRecipeGenerator(LiveRecipeGenerator liveRecipeGenerator) { + liveRecipeGenerators.add(liveRecipeGenerator); + } + + @Override + public List getScreenClickAreas() { + return screenClickAreas; + } + + private class ScreenClickAreaImpl implements ScreenClickArea { + Class screenClass; + Rectangle rectangle; + Identifier[] categories; + + private ScreenClickAreaImpl(Class screenClass, Rectangle rectangle, Identifier[] categories) { + this.screenClass = screenClass; + this.rectangle = rectangle; + this.categories = categories; + } + + public Class getScreenClass() { + return screenClass; + } + + public Rectangle getRectangle() { + return rectangle; + } + + public Identifier[] getCategories() { + return categories; + } + } + + private class RecipeFunction { + Identifier category; + Predicate recipeFilter; + Function mappingFunction; + + public RecipeFunction(Identifier category, Predicate recipeFilter, Function mappingFunction) { + this.category = category; + this.recipeFilter = recipeFilter; + this.mappingFunction = mappingFunction; + } + } + +} -- cgit