RecipeHelperImpl.java 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. /*
  2. * Roughly Enough Items by Danielshe.
  3. * Licensed under the MIT License.
  4. */
  5. package me.shedaniel.rei.client;
  6. import com.google.common.collect.Lists;
  7. import com.google.common.collect.Maps;
  8. import me.shedaniel.rei.RoughlyEnoughItemsCore;
  9. import me.shedaniel.rei.api.*;
  10. import net.minecraft.client.gui.screen.ingame.AbstractContainerScreen;
  11. import net.minecraft.item.ItemStack;
  12. import net.minecraft.recipe.Recipe;
  13. import net.minecraft.recipe.RecipeManager;
  14. import net.minecraft.util.ActionResult;
  15. import net.minecraft.util.Identifier;
  16. import java.awt.*;
  17. import java.util.List;
  18. import java.util.*;
  19. import java.util.concurrent.atomic.AtomicInteger;
  20. import java.util.function.Function;
  21. import java.util.function.Predicate;
  22. import java.util.stream.Collectors;
  23. public class RecipeHelperImpl implements RecipeHelper {
  24. private static final Comparator<DisplayVisibilityHandler> VISIBILITY_HANDLER_COMPARATOR;
  25. private static final Comparator<Recipe> RECIPE_COMPARATOR = (o1, o2) -> {
  26. int int_1 = o1.getId().getNamespace().compareTo(o2.getId().getNamespace());
  27. if (int_1 == 0)
  28. int_1 = o1.getId().getPath().compareTo(o2.getId().getPath());
  29. return int_1;
  30. };
  31. static {
  32. Comparator<DisplayVisibilityHandler> comparator = Comparator.comparingDouble(DisplayVisibilityHandler::getPriority);
  33. VISIBILITY_HANDLER_COMPARATOR = comparator.reversed();
  34. }
  35. private final List<AutoCraftingHandler> autoCraftingHandlers = Lists.newArrayList();
  36. private final List<RecipeFunction> recipeFunctions = Lists.newArrayList();
  37. private final List<ScreenClickArea> screenClickAreas = Lists.newArrayList();
  38. private final AtomicInteger recipeCount = new AtomicInteger();
  39. private final Map<Identifier, List<RecipeDisplay>> recipeCategoryListMap = Maps.newHashMap();
  40. private final List<RecipeCategory> categories = Lists.newArrayList();
  41. private final Map<Identifier, ButtonAreaSupplier> speedCraftAreaSupplierMap = Maps.newHashMap();
  42. private final Map<Identifier, List<List<ItemStack>>> categoryWorkingStations = Maps.newHashMap();
  43. private final List<DisplayVisibilityHandler> displayVisibilityHandlers = Lists.newArrayList();
  44. private final List<LiveRecipeGenerator<?>> liveRecipeGenerators = Lists.newArrayList();
  45. private RecipeManager recipeManager;
  46. @Override
  47. public List<ItemStack> findCraftableByItems(List<ItemStack> inventoryItems) {
  48. List<ItemStack> craftables = new ArrayList<>();
  49. for(List<RecipeDisplay> value : recipeCategoryListMap.values())
  50. for(RecipeDisplay recipeDisplay : value) {
  51. int slotsCraftable = 0;
  52. List<List<ItemStack>> requiredInput = (List<List<ItemStack>>) recipeDisplay.getRequiredItems();
  53. for(List<ItemStack> slot : requiredInput) {
  54. if (slot.isEmpty()) {
  55. slotsCraftable++;
  56. continue;
  57. }
  58. boolean slotDone = false;
  59. for(ItemStack possibleType : inventoryItems) {
  60. for(ItemStack slotPossible : slot)
  61. if (ItemStack.areItemsEqualIgnoreDamage(slotPossible, possibleType)) {
  62. slotsCraftable++;
  63. slotDone = true;
  64. break;
  65. }
  66. if (slotDone)
  67. break;
  68. }
  69. }
  70. if (slotsCraftable == recipeDisplay.getRequiredItems().size())
  71. craftables.addAll((List<ItemStack>) recipeDisplay.getOutput());
  72. }
  73. return craftables.stream().distinct().collect(Collectors.toList());
  74. }
  75. @Override
  76. public void registerCategory(RecipeCategory category) {
  77. categories.add(category);
  78. recipeCategoryListMap.put(category.getIdentifier(), Lists.newLinkedList());
  79. categoryWorkingStations.put(category.getIdentifier(), Lists.newLinkedList());
  80. }
  81. @Override
  82. public void registerWorkingStations(Identifier category, List<ItemStack>... workingStations) {
  83. categoryWorkingStations.get(category).addAll(Arrays.asList(workingStations));
  84. }
  85. @Override
  86. public void registerWorkingStations(Identifier category, ItemStack... workingStations) {
  87. categoryWorkingStations.get(category).addAll(Arrays.asList(workingStations).stream().map(Collections::singletonList).collect(Collectors.toList()));
  88. }
  89. @Override
  90. public List<List<ItemStack>> getWorkingStations(Identifier category) {
  91. return categoryWorkingStations.get(category);
  92. }
  93. @Override
  94. public void registerDisplay(Identifier categoryIdentifier, RecipeDisplay display) {
  95. if (!recipeCategoryListMap.containsKey(categoryIdentifier))
  96. return;
  97. recipeCount.incrementAndGet();
  98. recipeCategoryListMap.get(categoryIdentifier).add(display);
  99. }
  100. private void registerDisplay(Identifier categoryIdentifier, RecipeDisplay display, int index) {
  101. if (!recipeCategoryListMap.containsKey(categoryIdentifier))
  102. return;
  103. recipeCount.incrementAndGet();
  104. recipeCategoryListMap.get(categoryIdentifier).add(index, display);
  105. }
  106. @Override
  107. public Map<RecipeCategory<?>, List<RecipeDisplay>> getRecipesFor(ItemStack stack) {
  108. Map<Identifier, List<RecipeDisplay>> categoriesMap = new HashMap<>();
  109. categories.forEach(f -> categoriesMap.put(f.getIdentifier(), Lists.newArrayList()));
  110. for(Map.Entry<Identifier, List<RecipeDisplay>> entry : recipeCategoryListMap.entrySet()) {
  111. RecipeCategory category = getCategory(entry.getKey());
  112. for(RecipeDisplay recipeDisplay : entry.getValue())
  113. for(ItemStack outputStack : (List<ItemStack>) recipeDisplay.getOutput())
  114. if (category.checkTags() ? ItemStack.areEqualIgnoreDamage(stack, outputStack) : ItemStack.areItemsEqualIgnoreDamage(stack, outputStack))
  115. categoriesMap.get(recipeDisplay.getRecipeCategory()).add(recipeDisplay);
  116. }
  117. for(LiveRecipeGenerator liveRecipeGenerator : liveRecipeGenerators)
  118. ((Optional<List>) liveRecipeGenerator.getRecipeFor(stack)).ifPresent(o -> categoriesMap.get(liveRecipeGenerator.getCategoryIdentifier()).addAll(o));
  119. Map<RecipeCategory<?>, List<RecipeDisplay>> recipeCategoryListMap = Maps.newLinkedHashMap();
  120. categories.forEach(category -> {
  121. if (categoriesMap.containsKey(category.getIdentifier()) && !categoriesMap.get(category.getIdentifier()).isEmpty())
  122. recipeCategoryListMap.put(category, categoriesMap.get(category.getIdentifier()).stream().filter(display -> isDisplayVisible(display)).collect(Collectors.toList()));
  123. });
  124. for(RecipeCategory<?> category : Lists.newArrayList(recipeCategoryListMap.keySet()))
  125. if (recipeCategoryListMap.get(category).isEmpty())
  126. recipeCategoryListMap.remove(category);
  127. return recipeCategoryListMap;
  128. }
  129. @Override
  130. public RecipeCategory getCategory(Identifier identifier) {
  131. return categories.stream().filter(category -> category.getIdentifier().equals(identifier)).findFirst().orElse(null);
  132. }
  133. @Override
  134. public RecipeManager getRecipeManager() {
  135. return recipeManager;
  136. }
  137. @Override
  138. public Map<RecipeCategory<?>, List<RecipeDisplay>> getUsagesFor(ItemStack stack) {
  139. Map<Identifier, List<RecipeDisplay>> categoriesMap = new HashMap<>();
  140. categories.forEach(f -> categoriesMap.put(f.getIdentifier(), Lists.newArrayList()));
  141. for(Map.Entry<Identifier, List<RecipeDisplay>> entry : recipeCategoryListMap.entrySet()) {
  142. RecipeCategory category = getCategory(entry.getKey());
  143. for(RecipeDisplay recipeDisplay : entry.getValue()) {
  144. boolean found = false;
  145. for(List<ItemStack> input : (List<List<ItemStack>>) recipeDisplay.getInput()) {
  146. for(ItemStack itemStack : input) {
  147. if (category.checkTags() ? ItemStack.areEqualIgnoreDamage(itemStack, stack) : ItemStack.areItemsEqualIgnoreDamage(itemStack, stack)) {
  148. categoriesMap.get(recipeDisplay.getRecipeCategory()).add(recipeDisplay);
  149. found = true;
  150. break;
  151. }
  152. }
  153. if (found)
  154. break;
  155. }
  156. }
  157. }
  158. for(LiveRecipeGenerator liveRecipeGenerator : liveRecipeGenerators)
  159. ((Optional<List>) liveRecipeGenerator.getUsageFor(stack)).ifPresent(o -> categoriesMap.get(liveRecipeGenerator.getCategoryIdentifier()).addAll(o));
  160. Map<RecipeCategory<?>, List<RecipeDisplay>> recipeCategoryListMap = Maps.newLinkedHashMap();
  161. categories.forEach(category -> {
  162. if (categoriesMap.containsKey(category.getIdentifier()) && !categoriesMap.get(category.getIdentifier()).isEmpty())
  163. recipeCategoryListMap.put(category, categoriesMap.get(category.getIdentifier()).stream().filter(display -> isDisplayVisible(display)).collect(Collectors.toList()));
  164. });
  165. for(RecipeCategory<?> category : Lists.newArrayList(recipeCategoryListMap.keySet()))
  166. if (recipeCategoryListMap.get(category).isEmpty())
  167. recipeCategoryListMap.remove(category);
  168. return recipeCategoryListMap;
  169. }
  170. @Override
  171. public List<RecipeCategory> getAllCategories() {
  172. return new LinkedList<>(categories);
  173. }
  174. @Override
  175. public Optional<ButtonAreaSupplier> getSpeedCraftButtonArea(RecipeCategory category) {
  176. if (!speedCraftAreaSupplierMap.containsKey(category.getIdentifier()))
  177. return Optional.ofNullable(bounds -> new Rectangle((int) bounds.getMaxX() - 16, (int) bounds.getMaxY() - 16, 10, 10));
  178. return Optional.ofNullable(speedCraftAreaSupplierMap.get(category.getIdentifier()));
  179. }
  180. @Override
  181. public void registerSpeedCraftButtonArea(Identifier category, ButtonAreaSupplier rectangle) {
  182. if (rectangle == null) {
  183. if (speedCraftAreaSupplierMap.containsKey(category))
  184. speedCraftAreaSupplierMap.remove(category);
  185. } else
  186. speedCraftAreaSupplierMap.put(category, rectangle);
  187. }
  188. @SuppressWarnings("deprecation")
  189. @Override
  190. public void registerDefaultSpeedCraftButtonArea(Identifier category) {
  191. registerSpeedCraftButtonArea(category, bounds -> new Rectangle((int) bounds.getMaxX() - 16, (int) bounds.getMaxY() - 16, 10, 10));
  192. }
  193. @SuppressWarnings("deprecation")
  194. public void recipesLoaded(RecipeManager recipeManager) {
  195. this.recipeCount.set(0);
  196. this.recipeManager = recipeManager;
  197. this.recipeCategoryListMap.clear();
  198. this.categories.clear();
  199. this.speedCraftAreaSupplierMap.clear();
  200. this.screenClickAreas.clear();
  201. this.categoryWorkingStations.clear();
  202. this.recipeFunctions.clear();
  203. this.displayVisibilityHandlers.clear();
  204. this.liveRecipeGenerators.clear();
  205. this.autoCraftingHandlers.clear();
  206. ((DisplayHelperImpl) RoughlyEnoughItemsCore.getDisplayHelper()).resetCache();
  207. BaseBoundsHandler baseBoundsHandler = new BaseBoundsHandlerImpl();
  208. RoughlyEnoughItemsCore.getDisplayHelper().registerBoundsHandler(baseBoundsHandler);
  209. ((DisplayHelperImpl) RoughlyEnoughItemsCore.getDisplayHelper()).setBaseBoundsHandler(baseBoundsHandler);
  210. long startTime = System.currentTimeMillis();
  211. List<REIPluginEntry> plugins = new LinkedList<>(RoughlyEnoughItemsCore.getPlugins());
  212. plugins.sort((first, second) -> {
  213. return second.getPriority() - first.getPriority();
  214. });
  215. RoughlyEnoughItemsCore.LOGGER.info("[REI] Loading %d plugins: %s", plugins.size(), plugins.stream().map(REIPluginEntry::getPluginIdentifier).map(Identifier::toString).collect(Collectors.joining(", ")));
  216. Collections.reverse(plugins);
  217. RoughlyEnoughItemsCore.getItemRegisterer().getModifiableItemList().clear();
  218. PluginDisabler pluginDisabler = RoughlyEnoughItemsCore.getPluginDisabler();
  219. plugins.forEach(plugin -> {
  220. Identifier identifier = plugin.getPluginIdentifier();
  221. try {
  222. if (pluginDisabler.isFunctionEnabled(identifier, PluginFunction.REGISTER_ITEMS))
  223. plugin.registerItems(RoughlyEnoughItemsCore.getItemRegisterer());
  224. if (pluginDisabler.isFunctionEnabled(identifier, PluginFunction.REGISTER_CATEGORIES))
  225. plugin.registerPluginCategories(this);
  226. if (pluginDisabler.isFunctionEnabled(identifier, PluginFunction.REGISTER_RECIPE_DISPLAYS))
  227. plugin.registerRecipeDisplays(this);
  228. if (pluginDisabler.isFunctionEnabled(identifier, PluginFunction.REGISTER_BOUNDS))
  229. plugin.registerBounds(RoughlyEnoughItemsCore.getDisplayHelper());
  230. if (pluginDisabler.isFunctionEnabled(identifier, PluginFunction.REGISTER_OTHERS))
  231. plugin.registerOthers(this);
  232. } catch (Exception e) {
  233. RoughlyEnoughItemsCore.LOGGER.error("[REI] " + identifier.toString() + " plugin failed to load!", e);
  234. }
  235. });
  236. if (!recipeFunctions.isEmpty()) {
  237. List<Recipe> allSortedRecipes = getAllSortedRecipes();
  238. Collections.reverse(allSortedRecipes);
  239. recipeFunctions.forEach(recipeFunction -> {
  240. try {
  241. allSortedRecipes.stream().filter(recipe -> recipeFunction.recipeFilter.test(recipe)).forEach(t -> registerDisplay(recipeFunction.category, (RecipeDisplay) recipeFunction.mappingFunction.apply(t), 0));
  242. } catch (Exception e) {
  243. RoughlyEnoughItemsCore.LOGGER.error("[REI] Failed to add recipes!", e);
  244. }
  245. });
  246. }
  247. if (getDisplayVisibilityHandlers().isEmpty())
  248. registerRecipeVisibilityHandler(new DisplayVisibilityHandler() {
  249. @Override
  250. public ActionResult handleDisplay(RecipeCategory<?> category, RecipeDisplay display) {
  251. return ActionResult.SUCCESS;
  252. }
  253. @Override
  254. public float getPriority() {
  255. return -1f;
  256. }
  257. });
  258. // Clear Cache
  259. ((DisplayHelperImpl) RoughlyEnoughItemsCore.getDisplayHelper()).resetCache();
  260. ScreenHelper.getOptionalOverlay().ifPresent(overlay -> overlay.shouldReInit = true);
  261. long usedTime = System.currentTimeMillis() - startTime;
  262. 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.getItemRegisterer().getItemList().size(), recipeCount.get(), RoughlyEnoughItemsCore.getDisplayHelper().getAllBoundsHandlers().size(), getDisplayVisibilityHandlers().size(), categories.size(), String.join(", ", categories.stream().map(RecipeCategory::getCategoryName).collect(Collectors.toList())), usedTime);
  263. }
  264. @Override
  265. public AutoCraftingHandler registerAutoCraftingHandler(AutoCraftingHandler handler) {
  266. autoCraftingHandlers.add(handler);
  267. return handler;
  268. }
  269. @Override
  270. public List<AutoCraftingHandler> getSortedAutoCraftingHandler() {
  271. return autoCraftingHandlers.stream().sorted(Comparator.comparingDouble(AutoCraftingHandler::getPriority).reversed()).collect(Collectors.toList());
  272. }
  273. @Override
  274. public int getRecipeCount() {
  275. return recipeCount.get();
  276. }
  277. @Override
  278. public List<Recipe> getAllSortedRecipes() {
  279. return getRecipeManager().values().stream().sorted(RECIPE_COMPARATOR).collect(Collectors.toList());
  280. }
  281. @Override
  282. public Map<RecipeCategory<?>, List<RecipeDisplay>> getAllRecipes() {
  283. Map<RecipeCategory<?>, List<RecipeDisplay>> map = Maps.newLinkedHashMap();
  284. categories.forEach(recipeCategory -> {
  285. if (recipeCategoryListMap.containsKey(recipeCategory.getIdentifier())) {
  286. List<RecipeDisplay> list = recipeCategoryListMap.get(recipeCategory.getIdentifier()).stream().filter(display -> isDisplayVisible(display)).collect(Collectors.toList());
  287. if (!list.isEmpty())
  288. map.put(recipeCategory, list);
  289. }
  290. });
  291. return map;
  292. }
  293. @Override
  294. public List<RecipeDisplay> getAllRecipesFromCategory(RecipeCategory category) {
  295. return recipeCategoryListMap.get(category.getIdentifier());
  296. }
  297. @Override
  298. public void registerRecipeVisibilityHandler(DisplayVisibilityHandler visibilityHandler) {
  299. displayVisibilityHandlers.add(visibilityHandler);
  300. }
  301. @Override
  302. public void unregisterRecipeVisibilityHandler(DisplayVisibilityHandler visibilityHandler) {
  303. displayVisibilityHandlers.remove(visibilityHandler);
  304. }
  305. @Override
  306. public List<DisplayVisibilityHandler> getDisplayVisibilityHandlers() {
  307. return Collections.unmodifiableList(displayVisibilityHandlers);
  308. }
  309. @SuppressWarnings("deprecation")
  310. @Override
  311. public boolean isDisplayVisible(RecipeDisplay display, boolean respectConfig) {
  312. return isDisplayVisible(display);
  313. }
  314. @SuppressWarnings("deprecation")
  315. @Override
  316. public boolean isDisplayVisible(RecipeDisplay display) {
  317. RecipeCategory category = getCategory(display.getRecipeCategory());
  318. List<DisplayVisibilityHandler> list = getDisplayVisibilityHandlers().stream().sorted(VISIBILITY_HANDLER_COMPARATOR).collect(Collectors.toList());
  319. for(DisplayVisibilityHandler displayVisibilityHandler : list) {
  320. try {
  321. ActionResult visibility = displayVisibilityHandler.handleDisplay(category, display);
  322. if (visibility != ActionResult.PASS)
  323. return visibility == ActionResult.SUCCESS;
  324. } catch (Throwable throwable) {
  325. RoughlyEnoughItemsCore.LOGGER.error("[REI] Failed to check if the recipe is visible!", throwable);
  326. }
  327. }
  328. return true;
  329. }
  330. @Override
  331. public void registerScreenClickArea(Rectangle rectangle, Class<? extends AbstractContainerScreen> screenClass, Identifier... categories) {
  332. this.screenClickAreas.add(new ScreenClickArea(screenClass, rectangle, categories));
  333. }
  334. @Override
  335. public <T extends Recipe<?>> void registerRecipes(Identifier category, Class<T> recipeClass, Function<T, RecipeDisplay> mappingFunction) {
  336. recipeFunctions.add(new RecipeFunction(category, recipe -> recipeClass.isAssignableFrom(recipe.getClass()), mappingFunction));
  337. }
  338. @Override
  339. public <T extends Recipe<?>> void registerRecipes(Identifier category, Function<Recipe, Boolean> recipeFilter, Function<T, RecipeDisplay> mappingFunction) {
  340. recipeFunctions.add(new RecipeFunction(category, recipeFilter::apply, mappingFunction));
  341. }
  342. @Override
  343. public <T extends Recipe<?>> void registerRecipes(Identifier category, Predicate<Recipe> recipeFilter, Function<T, RecipeDisplay> mappingFunction) {
  344. recipeFunctions.add(new RecipeFunction(category, recipeFilter, mappingFunction));
  345. }
  346. @Override
  347. public void registerLiveRecipeGenerator(LiveRecipeGenerator<?> liveRecipeGenerator) {
  348. liveRecipeGenerators.add(liveRecipeGenerator);
  349. }
  350. @Override
  351. public List<ScreenClickArea> getScreenClickAreas() {
  352. return screenClickAreas;
  353. }
  354. public class ScreenClickArea {
  355. Class<? extends AbstractContainerScreen> screenClass;
  356. Rectangle rectangle;
  357. Identifier[] categories;
  358. private ScreenClickArea(Class<? extends AbstractContainerScreen> screenClass, Rectangle rectangle, Identifier[] categories) {
  359. this.screenClass = screenClass;
  360. this.rectangle = rectangle;
  361. this.categories = categories;
  362. }
  363. public Class<? extends AbstractContainerScreen> getScreenClass() {
  364. return screenClass;
  365. }
  366. public Rectangle getRectangle() {
  367. return rectangle;
  368. }
  369. public Identifier[] getCategories() {
  370. return categories;
  371. }
  372. }
  373. private class RecipeFunction {
  374. Identifier category;
  375. Predicate<Recipe> recipeFilter;
  376. Function mappingFunction;
  377. public RecipeFunction(Identifier category, Predicate<Recipe> recipeFilter, Function<?, RecipeDisplay> mappingFunction) {
  378. this.category = category;
  379. this.recipeFilter = recipeFilter;
  380. this.mappingFunction = mappingFunction;
  381. }
  382. }
  383. }