Browse Source

Beacon Base 4.0.2

Signed-off-by: shedaniel <daniel@shedaniel.me>
shedaniel 5 năm trước cách đây
mục cha
commit
31ac989940

+ 1 - 1
gradle.properties

@@ -1,4 +1,4 @@
-mod_version=4.0.1-unstable
+mod_version=4.0.2-unstable
 minecraft_version=20w07a
 minecraft_version=20w07a
 yarn_version=20w07a+build.1
 yarn_version=20w07a+build.1
 fabricloader_version=0.7.8+build.184
 fabricloader_version=0.7.8+build.184

+ 11 - 1
src/main/java/me/shedaniel/rei/api/RecipeHelper.java

@@ -75,11 +75,21 @@ public interface RecipeHelper {
     List<List<EntryStack>> getWorkingStations(Identifier category);
     List<List<EntryStack>> getWorkingStations(Identifier category);
     
     
     /**
     /**
-     * Registers a recipe display
+     * Registers a recipe display.
+     *
+     * @param display the recipe display
+     */
+    void registerDisplay(RecipeDisplay display);
+    
+    /**
+     * Registers a recipe display.
      *
      *
      * @param categoryIdentifier the category to display in
      * @param categoryIdentifier the category to display in
      * @param display            the recipe display
      * @param display            the recipe display
      */
      */
+    @ApiStatus.Internal
+    @ApiStatus.ScheduledForRemoval
+    @Deprecated
     void registerDisplay(Identifier categoryIdentifier, RecipeDisplay display);
     void registerDisplay(Identifier categoryIdentifier, RecipeDisplay display);
     
     
     /**
     /**

+ 1 - 1
src/main/java/me/shedaniel/rei/gui/RecipeViewingScreen.java

@@ -87,7 +87,7 @@ public class RecipeViewingScreen extends Screen {
             if (widget instanceof EntryWidget) {
             if (widget instanceof EntryWidget) {
                 EntryWidget entry = (EntryWidget) widget;
                 EntryWidget entry = (EntryWidget) widget;
                 if (entry.entries().size() > 1) {
                 if (entry.entries().size() > 1) {
-                    EntryStack stack = CollectionUtils.firstOrNullEqualsAll(entry.entries(), mainStackToNotice);
+                    EntryStack stack = CollectionUtils.findFirstOrNullEqualsEntryIgnoreAmount(entry.entries(), mainStackToNotice);
                     if (stack != null) {
                     if (stack != null) {
                         entry.clearStacks();
                         entry.clearStacks();
                         entry.entry(stack);
                         entry.entry(stack);

+ 1 - 1
src/main/java/me/shedaniel/rei/gui/config/entry/FilteringEntry.java

@@ -507,7 +507,7 @@ public class FilteringEntry extends AbstractConfigListEntry<List<EntryStack>> {
         }
         }
         
         
         public boolean isFiltered() {
         public boolean isFiltered() {
-            return CollectionUtils.findFirstOrNullEquals(configFiltered, getCurrentEntry()) != null;
+            return CollectionUtils.findFirstOrNullEqualsEntryIgnoreAmount(configFiltered, getCurrentEntry()) != null;
         }
         }
         
         
         @Override
         @Override

+ 2 - 2
src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java

@@ -636,7 +636,7 @@ public class EntryListWidget extends WidgetWithBounds {
             if (stacks instanceof CopyOnWriteArrayList) {
             if (stacks instanceof CopyOnWriteArrayList) {
                 for (EntryStack stack : stacks) {
                 for (EntryStack stack : stacks) {
                     if (canLastSearchTermsBeAppliedTo(stack)) {
                     if (canLastSearchTermsBeAppliedTo(stack)) {
-                        if (workingItems != null && CollectionUtils.findFirstOrNullEquals(workingItems, stack) == null)
+                        if (workingItems != null && CollectionUtils.findFirstOrNullEqualsEntryIgnoreAmount(workingItems, stack) == null)
                             continue;
                             continue;
                         list.add(stack.copy().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE).setting(EntryStack.Settings.Item.RENDER_ENCHANTMENT_GLINT, RENDER_ENCHANTMENT_GLINT));
                         list.add(stack.copy().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE).setting(EntryStack.Settings.Item.RENDER_ENCHANTMENT_GLINT, RENDER_ENCHANTMENT_GLINT));
                     }
                     }
@@ -657,7 +657,7 @@ public class EntryListWidget extends WidgetWithBounds {
             List<EntryStack> workingItems = checkCraftable ? RecipeHelper.getInstance().findCraftableEntriesByItems(CollectionUtils.map(ScreenHelper.inventoryStacks, EntryStack::create)) : null;
             List<EntryStack> workingItems = checkCraftable ? RecipeHelper.getInstance().findCraftableEntriesByItems(CollectionUtils.map(ScreenHelper.inventoryStacks, EntryStack::create)) : null;
             for (EntryStack stack : ConfigObject.getInstance().getFavorites()) {
             for (EntryStack stack : ConfigObject.getInstance().getFavorites()) {
                 if (canLastSearchTermsBeAppliedTo(stack)) {
                 if (canLastSearchTermsBeAppliedTo(stack)) {
-                    if (workingItems != null && CollectionUtils.findFirstOrNullEquals(workingItems, stack) == null)
+                    if (workingItems != null && CollectionUtils.findFirstOrNullEqualsEntryIgnoreAmount(workingItems, stack) == null)
                         continue;
                         continue;
                     list.add(stack.copy().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE).setting(EntryStack.Settings.Item.RENDER_ENCHANTMENT_GLINT, RENDER_ENCHANTMENT_GLINT));
                     list.add(stack.copy().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE).setting(EntryStack.Settings.Item.RENDER_ENCHANTMENT_GLINT, RENDER_ENCHANTMENT_GLINT));
                 }
                 }

+ 3 - 3
src/main/java/me/shedaniel/rei/gui/widget/EntryWidget.java

@@ -236,9 +236,9 @@ public class EntryWidget extends WidgetWithBounds {
                 entry.setAmount(127);
                 entry.setAmount(127);
                 if (keyCode.matchesKey(int_1, int_2)) {
                 if (keyCode.matchesKey(int_1, int_2)) {
                     if (reverseFavoritesAction())
                     if (reverseFavoritesAction())
-                        ConfigManager.getInstance().getFavorites().remove(entry);
-                    else if (!CollectionUtils.anyMatchEqualsAll(ConfigManager.getInstance().getFavorites(), entry))
-                        ConfigManager.getInstance().getFavorites().add(entry);
+                        ConfigObject.getInstance().getFavorites().remove(entry);
+                    else if (!CollectionUtils.anyMatchEqualsEntryIgnoreAmount(ConfigObject.getInstance().getFavorites(), entry))
+                        ConfigObject.getInstance().getFavorites().add(entry);
                     ConfigManager.getInstance().saveConfig();
                     ConfigManager.getInstance().saveConfig();
                     FavoritesListWidget favoritesListWidget = ContainerScreenOverlay.getFavoritesListWidget();
                     FavoritesListWidget favoritesListWidget = ContainerScreenOverlay.getFavoritesListWidget();
                     if (favoritesListWidget != null)
                     if (favoritesListWidget != null)

+ 2 - 2
src/main/java/me/shedaniel/rei/gui/widget/FavoritesListWidget.java

@@ -241,7 +241,7 @@ public class FavoritesListWidget extends WidgetWithBounds {
                 List<EntryStack> workingItems = checkCraftable ? RecipeHelper.getInstance().findCraftableEntriesByItems(CollectionUtils.map(ScreenHelper.inventoryStacks, EntryStack::create)) : null;
                 List<EntryStack> workingItems = checkCraftable ? RecipeHelper.getInstance().findCraftableEntriesByItems(CollectionUtils.map(ScreenHelper.inventoryStacks, EntryStack::create)) : null;
                 for (EntryStack stack : ConfigObject.getInstance().getFavorites()) {
                 for (EntryStack stack : ConfigObject.getInstance().getFavorites()) {
                     if (listWidget.canLastSearchTermsBeAppliedTo(stack)) {
                     if (listWidget.canLastSearchTermsBeAppliedTo(stack)) {
-                        if (checkCraftable && CollectionUtils.findFirstOrNullEquals(workingItems, stack) == null)
+                        if (checkCraftable && CollectionUtils.findFirstOrNullEqualsEntryIgnoreAmount(workingItems, stack) == null)
                             continue;
                             continue;
                         list.add(stack.copy().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE).setting(EntryStack.Settings.Item.RENDER_ENCHANTMENT_GLINT, RENDER_ENCHANTMENT_GLINT));
                         list.add(stack.copy().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE).setting(EntryStack.Settings.Item.RENDER_ENCHANTMENT_GLINT, RENDER_ENCHANTMENT_GLINT));
                     }
                     }
@@ -259,7 +259,7 @@ public class FavoritesListWidget extends WidgetWithBounds {
                 boolean checkCraftable = ConfigManager.getInstance().isCraftableOnlyEnabled() && !ScreenHelper.inventoryStacks.isEmpty();
                 boolean checkCraftable = ConfigManager.getInstance().isCraftableOnlyEnabled() && !ScreenHelper.inventoryStacks.isEmpty();
                 List<EntryStack> workingItems = checkCraftable ? RecipeHelper.getInstance().findCraftableEntriesByItems(CollectionUtils.map(ScreenHelper.inventoryStacks, EntryStack::create)) : null;
                 List<EntryStack> workingItems = checkCraftable ? RecipeHelper.getInstance().findCraftableEntriesByItems(CollectionUtils.map(ScreenHelper.inventoryStacks, EntryStack::create)) : null;
                 for (EntryStack stack : ConfigObject.getInstance().getFavorites()) {
                 for (EntryStack stack : ConfigObject.getInstance().getFavorites()) {
-                    if (checkCraftable && CollectionUtils.findFirstOrNullEquals(workingItems, stack) == null)
+                    if (checkCraftable && CollectionUtils.findFirstOrNullEqualsEntryIgnoreAmount(workingItems, stack) == null)
                         continue;
                         continue;
                     list.add(stack.copy().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE).setting(EntryStack.Settings.Item.RENDER_ENCHANTMENT_GLINT, RENDER_ENCHANTMENT_GLINT));
                     list.add(stack.copy().setting(EntryStack.Settings.RENDER_COUNTS, EntryStack.Settings.FALSE).setting(EntryStack.Settings.Item.RENDER_ENCHANTMENT_GLINT, RENDER_ENCHANTMENT_GLINT));
                 }
                 }

+ 9 - 0
src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java

@@ -115,6 +115,15 @@ public class RecipeHelperImpl implements RecipeHelper {
         recipeCategoryListMap.get(categoryIdentifier).add(display);
         recipeCategoryListMap.get(categoryIdentifier).add(display);
     }
     }
     
     
+    @Override
+    public void registerDisplay(RecipeDisplay display) {
+        Identifier identifier = Objects.requireNonNull(display.getRecipeCategory());
+        if (!recipeCategoryListMap.containsKey(identifier))
+            return;
+        recipeCount[0]++;
+        recipeCategoryListMap.get(identifier).add(display);
+    }
+    
     private void registerDisplay(Identifier categoryIdentifier, RecipeDisplay display, int index) {
     private void registerDisplay(Identifier categoryIdentifier, RecipeDisplay display, int index) {
         if (!recipeCategoryListMap.containsKey(categoryIdentifier))
         if (!recipeCategoryListMap.containsKey(categoryIdentifier))
             return;
             return;

+ 16 - 15
src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java

@@ -19,6 +19,8 @@ import me.shedaniel.rei.gui.widget.QueuedTooltip;
 import me.shedaniel.rei.impl.ClientHelperImpl;
 import me.shedaniel.rei.impl.ClientHelperImpl;
 import me.shedaniel.rei.impl.RenderingEntry;
 import me.shedaniel.rei.impl.RenderingEntry;
 import me.shedaniel.rei.impl.ScreenHelper;
 import me.shedaniel.rei.impl.ScreenHelper;
+import me.shedaniel.rei.plugin.beacon.DefaultBeaconBaseCategory;
+import me.shedaniel.rei.plugin.beacon.DefaultBeaconBaseDisplay;
 import me.shedaniel.rei.plugin.blasting.DefaultBlastingDisplay;
 import me.shedaniel.rei.plugin.blasting.DefaultBlastingDisplay;
 import me.shedaniel.rei.plugin.brewing.DefaultBrewingCategory;
 import me.shedaniel.rei.plugin.brewing.DefaultBrewingCategory;
 import me.shedaniel.rei.plugin.brewing.DefaultBrewingDisplay;
 import me.shedaniel.rei.plugin.brewing.DefaultBrewingDisplay;
@@ -42,6 +44,7 @@ import me.shedaniel.rei.plugin.stonecutting.DefaultStoneCuttingDisplay;
 import me.shedaniel.rei.plugin.stripping.DefaultStrippingCategory;
 import me.shedaniel.rei.plugin.stripping.DefaultStrippingCategory;
 import me.shedaniel.rei.plugin.stripping.DefaultStrippingDisplay;
 import me.shedaniel.rei.plugin.stripping.DefaultStrippingDisplay;
 import me.shedaniel.rei.plugin.stripping.DummyAxeItem;
 import me.shedaniel.rei.plugin.stripping.DummyAxeItem;
+import me.shedaniel.rei.utils.CollectionUtils;
 import net.minecraft.block.ComposterBlock;
 import net.minecraft.block.ComposterBlock;
 import net.minecraft.block.entity.AbstractFurnaceBlockEntity;
 import net.minecraft.block.entity.AbstractFurnaceBlockEntity;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.MinecraftClient;
@@ -53,6 +56,7 @@ import net.minecraft.fluid.Fluid;
 import net.minecraft.item.*;
 import net.minecraft.item.*;
 import net.minecraft.potion.PotionUtil;
 import net.minecraft.potion.PotionUtil;
 import net.minecraft.recipe.*;
 import net.minecraft.recipe.*;
+import net.minecraft.tag.BlockTags;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.registry.Registry;
 import net.minecraft.util.registry.Registry;
@@ -73,6 +77,7 @@ public class DefaultPlugin implements REIPluginV0 {
     public static final Identifier PLUGIN = new Identifier("roughlyenoughitems", "default_plugin");
     public static final Identifier PLUGIN = new Identifier("roughlyenoughitems", "default_plugin");
     public static final Identifier COMPOSTING = new Identifier("minecraft", "plugins/composting");
     public static final Identifier COMPOSTING = new Identifier("minecraft", "plugins/composting");
     public static final Identifier FUEL = new Identifier("minecraft", "plugins/fuel");
     public static final Identifier FUEL = new Identifier("minecraft", "plugins/fuel");
+    public static final Identifier BEACON = new Identifier("roughlyenoughitems", "plugins/beacon");
     public static final Identifier INFO = new Identifier("roughlyenoughitems", "plugins/information");
     public static final Identifier INFO = new Identifier("roughlyenoughitems", "plugins/information");
     private static final Identifier DISPLAY_TEXTURE = new Identifier("roughlyenoughitems", "textures/gui/display.png");
     private static final Identifier DISPLAY_TEXTURE = new Identifier("roughlyenoughitems", "textures/gui/display.png");
     private static final Identifier DISPLAY_TEXTURE_DARK = new Identifier("roughlyenoughitems", "textures/gui/display_dark.png");
     private static final Identifier DISPLAY_TEXTURE_DARK = new Identifier("roughlyenoughitems", "textures/gui/display_dark.png");
@@ -171,6 +176,7 @@ public class DefaultPlugin implements REIPluginV0 {
         recipeHelper.registerCategory(new DefaultBrewingCategory());
         recipeHelper.registerCategory(new DefaultBrewingCategory());
         recipeHelper.registerCategory(new DefaultCompostingCategory());
         recipeHelper.registerCategory(new DefaultCompostingCategory());
         recipeHelper.registerCategory(new DefaultStrippingCategory());
         recipeHelper.registerCategory(new DefaultStrippingCategory());
+        recipeHelper.registerCategory(new DefaultBeaconBaseCategory());
     }
     }
     
     
     @Override
     @Override
@@ -178,13 +184,6 @@ public class DefaultPlugin implements REIPluginV0 {
         if (!ConfigObject.getInstance().isLoadingDefaultPlugin()) {
         if (!ConfigObject.getInstance().isLoadingDefaultPlugin()) {
             return;
             return;
         }
         }
-        //        DefaultPlugin.registerInfoDisplay(DefaultInformationDisplay.createFromEntry(EntryStack.create(Items.FURNACE), new LiteralText("Furnace Info"))
-        //                .lines(new LiteralText("Furnace is a nice block, crafted using 8 cobblestone."),
-        //                        new LiteralText("An amazing tool to burn lil taters."),
-        //                        new LiteralText("Now available in a store next to you."),
-        //                        new LiteralText("Now with 60% off for an limited time!"),
-        //                        new LiteralText("Get it with coupon code: ").append(new LiteralText("TATERS").formatted(Formatting.BOLD))
-        //                        ));
         recipeHelper.registerRecipes(CRAFTING, ShapelessRecipe.class, DefaultShapelessDisplay::new);
         recipeHelper.registerRecipes(CRAFTING, ShapelessRecipe.class, DefaultShapelessDisplay::new);
         recipeHelper.registerRecipes(CRAFTING, ShapedRecipe.class, DefaultShapedDisplay::new);
         recipeHelper.registerRecipes(CRAFTING, ShapedRecipe.class, DefaultShapedDisplay::new);
         recipeHelper.registerRecipes(SMELTING, SmeltingRecipe.class, DefaultSmeltingDisplay::new);
         recipeHelper.registerRecipes(SMELTING, SmeltingRecipe.class, DefaultSmeltingDisplay::new);
@@ -193,10 +192,10 @@ public class DefaultPlugin implements REIPluginV0 {
         recipeHelper.registerRecipes(CAMPFIRE, CampfireCookingRecipe.class, DefaultCampfireDisplay::new);
         recipeHelper.registerRecipes(CAMPFIRE, CampfireCookingRecipe.class, DefaultCampfireDisplay::new);
         recipeHelper.registerRecipes(STONE_CUTTING, StonecuttingRecipe.class, DefaultStoneCuttingDisplay::new);
         recipeHelper.registerRecipes(STONE_CUTTING, StonecuttingRecipe.class, DefaultStoneCuttingDisplay::new);
         for (DefaultBrewingDisplay display : BREWING_DISPLAYS) {
         for (DefaultBrewingDisplay display : BREWING_DISPLAYS) {
-            recipeHelper.registerDisplay(BREWING, display);
+            recipeHelper.registerDisplay(display);
         }
         }
         for (Map.Entry<Item, Integer> entry : AbstractFurnaceBlockEntity.createFuelTimeMap().entrySet()) {
         for (Map.Entry<Item, Integer> entry : AbstractFurnaceBlockEntity.createFuelTimeMap().entrySet()) {
-            recipeHelper.registerDisplay(FUEL, new DefaultFuelDisplay(EntryStack.create(entry.getKey()), entry.getValue()));
+            recipeHelper.registerDisplay(new DefaultFuelDisplay(EntryStack.create(entry.getKey()), entry.getValue()));
         }
         }
         List<EntryStack> arrowStack = Collections.singletonList(EntryStack.create(Items.ARROW));
         List<EntryStack> arrowStack = Collections.singletonList(EntryStack.create(Items.ARROW));
         for (EntryStack entry : EntryRegistry.getInstance().getStacksList()) {
         for (EntryStack entry : EntryRegistry.getInstance().getStacksList()) {
@@ -211,7 +210,7 @@ public class DefaultPlugin implements REIPluginV0 {
                 PotionUtil.setPotion(outputStack, PotionUtil.getPotion(entry.getItemStack()));
                 PotionUtil.setPotion(outputStack, PotionUtil.getPotion(entry.getItemStack()));
                 PotionUtil.setCustomPotionEffects(outputStack, PotionUtil.getCustomPotionEffects(entry.getItemStack()));
                 PotionUtil.setCustomPotionEffects(outputStack, PotionUtil.getCustomPotionEffects(entry.getItemStack()));
                 List<EntryStack> output = Collections.singletonList(EntryStack.create(outputStack).addSetting(EntryStack.Settings.CHECK_TAGS, EntryStack.Settings.TRUE));
                 List<EntryStack> output = Collections.singletonList(EntryStack.create(outputStack).addSetting(EntryStack.Settings.CHECK_TAGS, EntryStack.Settings.TRUE));
-                recipeHelper.registerDisplay(CRAFTING, new DefaultCustomDisplay(null, input, output));
+                recipeHelper.registerDisplay(new DefaultCustomDisplay(null, input, output));
             }
             }
         }
         }
         Map<ItemConvertible, Float> map = Maps.newLinkedHashMap();
         Map<ItemConvertible, Float> map = Maps.newLinkedHashMap();
@@ -228,19 +227,19 @@ public class DefaultPlugin implements REIPluginV0 {
             for (int j = i; j < i + 48; j++)
             for (int j = i; j < i + 48; j++)
                 if (j < stacks.size())
                 if (j < stacks.size())
                     thisStacks.add(stacks.get(j));
                     thisStacks.add(stacks.get(j));
-            recipeHelper.registerDisplay(COMPOSTING, new DefaultCompostingDisplay(MathHelper.floor(i / 48f), thisStacks, map, Lists.newArrayList(map.keySet()), new ItemStack[]{new ItemStack(Items.BONE_MEAL)}));
+            recipeHelper.registerDisplay(new DefaultCompostingDisplay(MathHelper.floor(i / 48f), thisStacks, map, Lists.newArrayList(map.keySet()), new ItemStack[]{new ItemStack(Items.BONE_MEAL)}));
         }
         }
         DummyAxeItem.getStrippedBlocksMap().entrySet().stream().sorted(Comparator.comparing(b -> Registry.BLOCK.getId(b.getKey()))).forEach(set -> {
         DummyAxeItem.getStrippedBlocksMap().entrySet().stream().sorted(Comparator.comparing(b -> Registry.BLOCK.getId(b.getKey()))).forEach(set -> {
-            recipeHelper.registerDisplay(STRIPPING, new DefaultStrippingDisplay(new ItemStack(set.getKey()), new ItemStack(set.getValue())));
+            recipeHelper.registerDisplay(new DefaultStrippingDisplay(new ItemStack(set.getKey()), new ItemStack(set.getValue())));
         });
         });
+        recipeHelper.registerDisplay(new DefaultBeaconBaseDisplay(CollectionUtils.map(Lists.newArrayList(BlockTags.BEACON_BASE_BLOCKS.values()), ItemStack::new)));
     }
     }
     
     
     @Override
     @Override
     public void postRegister() {
     public void postRegister() {
         RecipeHelper.getInstance().registerCategory(new DefaultInformationCategory());
         RecipeHelper.getInstance().registerCategory(new DefaultInformationCategory());
-        for (DefaultInformationDisplay display : INFO_DISPLAYS) {
-            RecipeHelper.getInstance().registerDisplay(INFO, display);
-        }
+        for (DefaultInformationDisplay display : INFO_DISPLAYS)
+            RecipeHelper.getInstance().registerDisplay(display);
         // Sit tight! This will be a fast journey!
         // Sit tight! This will be a fast journey!
         long time = System.currentTimeMillis();
         long time = System.currentTimeMillis();
         for (EntryStack stack : EntryRegistry.getInstance().getStacksList())
         for (EntryStack stack : EntryRegistry.getInstance().getStacksList())
@@ -357,8 +356,10 @@ public class DefaultPlugin implements REIPluginV0 {
         recipeHelper.registerWorkingStations(BREWING, EntryStack.create(Items.BREWING_STAND));
         recipeHelper.registerWorkingStations(BREWING, EntryStack.create(Items.BREWING_STAND));
         recipeHelper.registerWorkingStations(STONE_CUTTING, EntryStack.create(Items.STONECUTTER));
         recipeHelper.registerWorkingStations(STONE_CUTTING, EntryStack.create(Items.STONECUTTER));
         recipeHelper.registerWorkingStations(COMPOSTING, EntryStack.create(Items.COMPOSTER));
         recipeHelper.registerWorkingStations(COMPOSTING, EntryStack.create(Items.COMPOSTER));
+        recipeHelper.registerWorkingStations(BEACON, EntryStack.create(Items.BEACON));
         recipeHelper.removeAutoCraftButton(FUEL);
         recipeHelper.removeAutoCraftButton(FUEL);
         recipeHelper.removeAutoCraftButton(COMPOSTING);
         recipeHelper.removeAutoCraftButton(COMPOSTING);
+        recipeHelper.removeAutoCraftButton(BEACON);
         recipeHelper.removeAutoCraftButton(INFO);
         recipeHelper.removeAutoCraftButton(INFO);
         recipeHelper.registerScreenClickArea(new Rectangle(88, 32, 28, 23), CraftingTableScreen.class, CRAFTING);
         recipeHelper.registerScreenClickArea(new Rectangle(88, 32, 28, 23), CraftingTableScreen.class, CRAFTING);
         recipeHelper.registerScreenClickArea(new Rectangle(137, 29, 10, 13), InventoryScreen.class, CRAFTING);
         recipeHelper.registerScreenClickArea(new Rectangle(137, 29, 10, 13), InventoryScreen.class, CRAFTING);

+ 240 - 0
src/main/java/me/shedaniel/rei/plugin/beacon/DefaultBeaconBaseCategory.java

@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2018, 2019, 2020 shedaniel
+ * Licensed under the MIT License (the "License").
+ */
+
+package me.shedaniel.rei.plugin.beacon;
+
+import com.google.common.collect.Lists;
+import com.mojang.blaze3d.systems.RenderSystem;
+import me.shedaniel.clothconfig2.ClothConfigInitializer;
+import me.shedaniel.clothconfig2.api.ScissorsHandler;
+import me.shedaniel.clothconfig2.gui.widget.DynamicEntryListWidget;
+import me.shedaniel.math.api.Rectangle;
+import me.shedaniel.math.impl.PointHelper;
+import me.shedaniel.rei.api.EntryStack;
+import me.shedaniel.rei.api.RecipeCategory;
+import me.shedaniel.rei.gui.entries.RecipeEntry;
+import me.shedaniel.rei.gui.widget.*;
+import me.shedaniel.rei.plugin.DefaultPlugin;
+import me.shedaniel.rei.utils.CollectionUtils;
+import net.minecraft.block.Blocks;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.render.BufferBuilder;
+import net.minecraft.client.render.Tessellator;
+import net.minecraft.client.render.VertexFormats;
+import net.minecraft.client.resource.language.I18n;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.math.MathHelper;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+public class DefaultBeaconBaseCategory implements RecipeCategory<DefaultBeaconBaseDisplay> {
+    @Override
+    public Identifier getIdentifier() {
+        return DefaultPlugin.BEACON;
+    }
+    
+    @Override
+    public String getCategoryName() {
+        return I18n.translate("category.rei.beacon_base");
+    }
+    
+    @Override
+    public EntryStack getLogo() {
+        return EntryStack.create(Blocks.BEACON);
+    }
+    
+    @Override
+    public RecipeEntry getSimpleRenderer(DefaultBeaconBaseDisplay recipe) {
+        String name = getCategoryName();
+        return new RecipeEntry() {
+            @Override
+            public int getHeight() {
+                return 10 + MinecraftClient.getInstance().textRenderer.fontHeight;
+            }
+            
+            @Nullable
+            @Override
+            public QueuedTooltip getTooltip(int mouseX, int mouseY) {
+                return null;
+            }
+            
+            @Override
+            public void render(Rectangle rectangle, int mouseX, int mouseY, float delta) {
+                MinecraftClient.getInstance().textRenderer.draw(name, rectangle.x + 5, rectangle.y + 6, -1);
+            }
+        };
+    }
+    
+    @Override
+    public List<Widget> setupDisplay(Supplier<DefaultBeaconBaseDisplay> recipeDisplaySupplier, Rectangle bounds) {
+        DefaultBeaconBaseDisplay display = recipeDisplaySupplier.get();
+        List<Widget> widgets = Lists.newArrayList();
+        widgets.add(EntryWidget.create(bounds.getCenterX() - 8, bounds.y + 3).entry(getLogo()));
+        Rectangle rectangle = new Rectangle(bounds.getCenterX() - (bounds.width / 2) - 1, bounds.y + 23, bounds.width + 2, bounds.height - 28);
+        widgets.add(new SlotBaseWidget(rectangle));
+        widgets.add(new ScrollableSlotsWidget(rectangle, CollectionUtils.map(display.getEntries(), t -> EntryWidget.create(0, 0).noBackground().entry(t))));
+        return widgets;
+    }
+    
+    @Override
+    public int getDisplayHeight() {
+        return 140;
+    }
+    
+    @Override
+    public int getFixedRecipesPerPage() {
+        return 1;
+    }
+    
+    private static class ScrollableSlotsWidget extends WidgetWithBounds {
+        private Rectangle bounds;
+        private List<EntryWidget> widgets;
+        private double target;
+        private double scroll;
+        private long start;
+        private long duration;
+        
+        public ScrollableSlotsWidget(Rectangle bounds, List<EntryWidget> widgets) {
+            this.bounds = bounds;
+            this.widgets = Lists.newArrayList(widgets);
+        }
+        
+        @Override
+        public boolean mouseScrolled(double double_1, double double_2, double double_3) {
+            if (containsMouse(double_1, double_2)) {
+                offset(ClothConfigInitializer.getScrollStep() * -double_3, true);
+                return true;
+            }
+            return false;
+        }
+        
+        public void offset(double value, boolean animated) {
+            scrollTo(target + value, animated);
+        }
+        
+        public void scrollTo(double value, boolean animated) {
+            scrollTo(value, animated, ClothConfigInitializer.getScrollDuration());
+        }
+        
+        public void scrollTo(double value, boolean animated, long duration) {
+            target = clamp(value);
+            
+            if (animated) {
+                start = System.currentTimeMillis();
+                this.duration = duration;
+            } else
+                scroll = target;
+        }
+        
+        public final double clamp(double v) {
+            return clamp(v, DynamicEntryListWidget.SmoothScrollingSettings.CLAMP_EXTENSION);
+        }
+        
+        public final double clamp(double v, double clampExtension) {
+            return MathHelper.clamp(v, -clampExtension, getMaxScroll() + clampExtension);
+        }
+        
+        protected int getMaxScroll() {
+            return Math.max(0, this.getMaxScrollPosition() - this.getBounds().height + 1);
+        }
+        
+        protected int getMaxScrollPosition() {
+            return MathHelper.ceil(widgets.size() / 8f) * 18;
+        }
+        
+        @Override
+        public Rectangle getBounds() {
+            return bounds;
+        }
+        
+        @Override
+        public void render(int mouseX, int mouseY, float delta) {
+            updatePosition(delta);
+            Rectangle innerBounds = new Rectangle(bounds.x + 1, bounds.y + 1, bounds.width - 7, bounds.height - 2);
+            ScissorsHandler.INSTANCE.scissor(innerBounds);
+            for (int y = 0; y < MathHelper.ceil(widgets.size() / 8f); y++) {
+                for (int x = 0; x < 8; x++) {
+                    int index = y * 8 + x;
+                    if (widgets.size() <= index)
+                        break;
+                    EntryWidget widget = widgets.get(index);
+                    widget.getBounds().setLocation(bounds.x + 1 + x * 18, (int) (bounds.y + 1 + y * 18 - scroll));
+                    widget.render(mouseX, mouseY, delta);
+                }
+            }
+            ScissorsHandler.INSTANCE.removeLastScissor();
+            ScissorsHandler.INSTANCE.scissor(bounds);
+            RenderSystem.enableBlend();
+            RenderSystem.blendFuncSeparate(770, 771, 0, 1);
+            RenderSystem.disableAlphaTest();
+            RenderSystem.shadeModel(7425);
+            RenderSystem.disableTexture();
+            renderScrollBar();
+            RenderSystem.enableTexture();
+            RenderSystem.shadeModel(7424);
+            RenderSystem.enableAlphaTest();
+            RenderSystem.disableBlend();
+            ScissorsHandler.INSTANCE.removeLastScissor();
+        }
+        
+        @SuppressWarnings("deprecation")
+        private void renderScrollBar() {
+            int maxScroll = getMaxScroll();
+            int scrollbarPositionMinX = getBounds().getMaxX() - 7;
+            int scrollbarPositionMaxX = scrollbarPositionMinX + 6;
+            Tessellator tessellator = Tessellator.getInstance();
+            BufferBuilder buffer = tessellator.getBuffer();
+            if (maxScroll > 0) {
+                int height = (int) (((this.getBounds().height - 2f) * (this.getBounds().height - 2f)) / this.getMaxScrollPosition());
+                height = MathHelper.clamp(height, 32, this.getBounds().height - 2);
+                height -= Math.min((scroll < 0 ? (int) -scroll : scroll > maxScroll ? (int) scroll - maxScroll : 0), height * .95);
+                height = Math.max(10, height);
+                int minY = Math.min(Math.max((int) scroll * (this.getBounds().height - 2 - height) / maxScroll + getBounds().y + 1, getBounds().y + 1), getBounds().getMaxY() - 1 - height);
+                
+                boolean hovered = new Rectangle(scrollbarPositionMinX, minY, scrollbarPositionMaxX - scrollbarPositionMinX, height).contains(PointHelper.fromMouse());
+                int bottomC = hovered ? 168 : 128;
+                int topC = hovered ? 222 : 172;
+                
+                // Black Bar
+                buffer.begin(7, VertexFormats.POSITION_TEXTURE_COLOR);
+                buffer.vertex(scrollbarPositionMinX, this.getBounds().y + 1, 0.0D).texture(0, 1).color(0, 0, 0, 255).next();
+                buffer.vertex(scrollbarPositionMaxX, this.getBounds().y + 1, 0.0D).texture(1, 1).color(0, 0, 0, 255).next();
+                buffer.vertex(scrollbarPositionMaxX, getBounds().getMaxY() - 1, 0.0D).texture(1, 0).color(0, 0, 0, 255).next();
+                buffer.vertex(scrollbarPositionMinX, getBounds().getMaxY() - 1, 0.0D).texture(0, 0).color(0, 0, 0, 255).next();
+                tessellator.draw();
+                
+                // Bottom
+                buffer.begin(7, VertexFormats.POSITION_TEXTURE_COLOR);
+                buffer.vertex(scrollbarPositionMinX, minY + height, 0.0D).texture(0, 1).color(bottomC, bottomC, bottomC, 255).next();
+                buffer.vertex(scrollbarPositionMaxX, minY + height, 0.0D).texture(1, 1).color(bottomC, bottomC, bottomC, 255).next();
+                buffer.vertex(scrollbarPositionMaxX, minY, 0.0D).texture(1, 0).color(bottomC, bottomC, bottomC, 255).next();
+                buffer.vertex(scrollbarPositionMinX, minY, 0.0D).texture(0, 0).color(bottomC, bottomC, bottomC, 255).next();
+                tessellator.draw();
+                
+                // Top
+                buffer.begin(7, VertexFormats.POSITION_TEXTURE_COLOR);
+                buffer.vertex(scrollbarPositionMinX, (minY + height - 1), 0.0D).texture(0, 1).color(topC, topC, topC, 255).next();
+                buffer.vertex((scrollbarPositionMaxX - 1), (minY + height - 1), 0.0D).texture(1, 1).color(topC, topC, topC, 255).next();
+                buffer.vertex((scrollbarPositionMaxX - 1), minY, 0.0D).texture(1, 0).color(topC, topC, topC, 255).next();
+                buffer.vertex(scrollbarPositionMinX, minY, 0.0D).texture(0, 0).color(topC, topC, topC, 255).next();
+                tessellator.draw();
+            }
+        }
+        
+        private void updatePosition(float delta) {
+            double[] target = new double[]{this.target};
+            this.scroll = ClothConfigInitializer.handleScrollingPosition(target, this.scroll, this.getMaxScroll(), delta, this.start, this.duration);
+            this.target = target[0];
+        }
+        
+        @Override
+        public List<? extends Element> children() {
+            return widgets;
+        }
+    }
+}

+ 45 - 0
src/main/java/me/shedaniel/rei/plugin/beacon/DefaultBeaconBaseDisplay.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018, 2019, 2020 shedaniel
+ * Licensed under the MIT License (the "License").
+ */
+
+package me.shedaniel.rei.plugin.beacon;
+
+import me.shedaniel.rei.api.EntryStack;
+import me.shedaniel.rei.api.ItemStackHook;
+import me.shedaniel.rei.api.RecipeDisplay;
+import me.shedaniel.rei.plugin.DefaultPlugin;
+import me.shedaniel.rei.utils.CollectionUtils;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.Identifier;
+
+import java.util.Collections;
+import java.util.List;
+
+public class DefaultBeaconBaseDisplay implements RecipeDisplay {
+    
+    private List<EntryStack> entries;
+    
+    public DefaultBeaconBaseDisplay(List<ItemStack> entries) {
+        this.entries = CollectionUtils.map(entries, EntryStack::create);
+    }
+    
+    @Override
+    public List<List<EntryStack>> getInputEntries() {
+        return Collections.singletonList(entries);
+    }
+    
+    public List<EntryStack> getEntries() {
+        return entries;
+    }
+    
+    @Override
+    public List<EntryStack> getOutputEntries() {
+        return Collections.emptyList();
+    }
+    
+    @Override
+    public Identifier getRecipeCategory() {
+        return DefaultPlugin.BEACON;
+    }
+}

+ 16 - 0
src/main/java/me/shedaniel/rei/utils/CollectionUtils.java

@@ -56,6 +56,14 @@ public class CollectionUtils {
         return false;
         return false;
     }
     }
     
     
+    public static boolean anyMatchEqualsEntryIgnoreAmount(List<EntryStack> list, EntryStack stack) {
+        for (EntryStack t : list) {
+            if (t.equalsIgnoreAmount(stack))
+                return true;
+        }
+        return false;
+    }
+    
     public static EntryStack firstOrNullEqualsAll(List<EntryStack> list, EntryStack stack) {
     public static EntryStack firstOrNullEqualsAll(List<EntryStack> list, EntryStack stack) {
         for (EntryStack t : list) {
         for (EntryStack t : list) {
             if (t.equalsAll(stack))
             if (t.equalsAll(stack))
@@ -64,6 +72,14 @@ public class CollectionUtils {
         return null;
         return null;
     }
     }
     
     
+    public static EntryStack findFirstOrNullEqualsEntryIgnoreAmount(Collection<EntryStack> list, EntryStack stack) {
+        for (EntryStack t : list) {
+            if (t.equalsIgnoreAmount(stack))
+                return t;
+        }
+        return null;
+    }
+    
     public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
     public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
         List<T> l = Lists.newArrayList();
         List<T> l = Lists.newArrayList();
         for (T t : list) {
         for (T t : list) {

+ 1 - 0
src/main/resources/assets/roughlyenoughitems/lang/en_us.json

@@ -28,6 +28,7 @@
   "category.rei.brewing.result": "Resulted Potion",
   "category.rei.brewing.result": "Resulted Potion",
   "category.rei.composting": "Composting",
   "category.rei.composting": "Composting",
   "category.rei.stripping": "Stripping",
   "category.rei.stripping": "Stripping",
+  "category.rei.beacon_base": "Beacon Base",
   "category.rei.information": "Information",
   "category.rei.information": "Information",
   "text.rei.composting.chance": "§e%d%% Chance",
   "text.rei.composting.chance": "§e%d%% Chance",
   "text.rei.composting.page": "Page %d",
   "text.rei.composting.page": "Page %d",