Explorar o código

3.2.15

Fix #199
Close #195
Close #138
shedaniel %!s(int64=5) %!d(string=hai) anos
pai
achega
b41f5c557d
Modificáronse 36 ficheiros con 313 adicións e 99 borrados
  1. 1 1
      build.gradle
  2. 7 7
      gradle.properties
  3. 29 10
      src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java
  4. 2 0
      src/main/java/me/shedaniel/rei/api/ConfigObject.java
  5. 13 1
      src/main/java/me/shedaniel/rei/api/EntryRegistry.java
  6. 2 0
      src/main/java/me/shedaniel/rei/api/RecipeHelper.java
  7. 53 0
      src/main/java/me/shedaniel/rei/gui/ConfigReloadingScreen.java
  8. 9 8
      src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java
  9. 2 2
      src/main/java/me/shedaniel/rei/gui/OverlaySearchField.java
  10. 2 2
      src/main/java/me/shedaniel/rei/gui/PreRecipeViewingScreen.java
  11. 17 8
      src/main/java/me/shedaniel/rei/gui/RecipeViewingScreen.java
  12. 6 6
      src/main/java/me/shedaniel/rei/gui/VillagerRecipeViewingScreen.java
  13. 2 2
      src/main/java/me/shedaniel/rei/gui/entries/SimpleRecipeEntry.java
  14. 3 3
      src/main/java/me/shedaniel/rei/gui/widget/CraftableToggleButtonWidget.java
  15. 27 16
      src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java
  16. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/PanelWidget.java
  17. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/RecipeArrowWidget.java
  18. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/RecipeChoosePageWidget.java
  19. 12 0
      src/main/java/me/shedaniel/rei/gui/widget/ReloadConfigButtonWidget.java
  20. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/TabWidget.java
  21. 28 0
      src/main/java/me/shedaniel/rei/impl/ConfigManagerImpl.java
  22. 6 0
      src/main/java/me/shedaniel/rei/impl/ConfigObjectImpl.java
  23. 3 2
      src/main/java/me/shedaniel/rei/impl/EntryRegistryImpl.java
  24. 2 2
      src/main/java/me/shedaniel/rei/impl/FluidEntryStack.java
  25. 11 5
      src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java
  26. 2 2
      src/main/java/me/shedaniel/rei/listeners/ContainerScreenHooks.java
  27. 6 0
      src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java
  28. 2 2
      src/main/java/me/shedaniel/rei/plugin/brewing/DefaultBrewingCategory.java
  29. 2 2
      src/main/java/me/shedaniel/rei/plugin/campfire/DefaultCampfireCategory.java
  30. 2 2
      src/main/java/me/shedaniel/rei/plugin/composting/DefaultCompostingCategory.java
  31. 2 2
      src/main/java/me/shedaniel/rei/plugin/cooking/DefaultCookingCategory.java
  32. 2 2
      src/main/java/me/shedaniel/rei/plugin/crafting/DefaultCraftingCategory.java
  33. 2 2
      src/main/java/me/shedaniel/rei/plugin/stonecutting/DefaultStoneCuttingCategory.java
  34. 2 2
      src/main/java/me/shedaniel/rei/plugin/stripping/DefaultStrippingCategory.java
  35. 43 0
      src/main/java/me/shedaniel/rei/tests/plugin/REITestPlugin.java
  36. 3 0
      src/main/resources/assets/roughlyenoughitems/lang/en_us.json

+ 1 - 1
build.gradle

@@ -93,7 +93,7 @@ publishing {
     publications {
         mavenJava(MavenPublication) {
             artifact(file("${project.buildDir}/libs/$archivesBaseName-${version}-maven.jar")) {
-                builtBy remapMavenJar
+                builtBy remapJar
             }
             artifact(sourcesJar) {
                 builtBy remapSourcesJar

+ 7 - 7
gradle.properties

@@ -1,9 +1,9 @@
-mod_version=3.2.14
-minecraft_version=1.15-pre2
-yarn_version=1.15-pre2+build.3
-fabricloader_version=0.7.1+build.173
+mod_version=3.2.15
+minecraft_version=1.15-pre6
+yarn_version=1.15-pre6+build.1
+fabricloader_version=0.7.2+build.174
 cloth_events_version=1.0.1-unstable.201911010702
 cloth_config_version=2.4-unstable.201911031154
-modmenu_version=1.7.15-unstable.19w42a+build.11
-fabric_api=0.4.15+build.267-1.15
-autoconfig1u=1.2.3
+modmenu_version=1.8.0+build.16
+fabric_api=0.4.20+build.273-1.15
+autoconfig1u=1.2.4

+ 29 - 10
src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java

@@ -16,6 +16,7 @@ import me.shedaniel.rei.gui.ContainerScreenOverlay;
 import me.shedaniel.rei.impl.*;
 import me.shedaniel.rei.listeners.RecipeBookButtonWidgetHooks;
 import me.shedaniel.rei.listeners.RecipeBookGuiHooks;
+import me.shedaniel.rei.tests.plugin.REITestPlugin;
 import net.fabricmc.api.ClientModInitializer;
 import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
 import net.fabricmc.loader.api.FabricLoader;
@@ -38,6 +39,7 @@ import net.minecraft.container.Slot;
 import net.minecraft.item.ItemStack;
 import net.minecraft.item.Items;
 import net.minecraft.recipe.Ingredient;
+import net.minecraft.recipe.RecipeManager;
 import net.minecraft.text.LiteralText;
 import net.minecraft.util.ActionResult;
 import net.minecraft.util.Identifier;
@@ -207,24 +209,41 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer {
                 RoughlyEnoughItemsCore.LOGGER.error("[REI] Can't load REI plugins from %s: %s", reiPlugin.getClass(), e.getLocalizedMessage());
             }
         }
+        
+        // Test Only
+        loadTestPlugins();
     }
     
     @SuppressWarnings("deprecation")
-    private void registerClothEvents() {
-        final Identifier recipeButtonTex = new Identifier("textures/gui/recipe_button.png");
-        AtomicLong lastSync = new AtomicLong(-1);
-        ClothClientHooks.SYNC_RECIPES.register((minecraftClient, recipeManager, synchronizeRecipesS2CPacket) -> {
+    private void loadTestPlugins() {
+        if (System.getProperty("rei.test", "false").equals("true")) {
+            registerPlugin(new REITestPlugin());
+        }
+    }
+    
+    @Internal
+    @Deprecated
+    public static void syncRecipes(AtomicLong lastSync) {
+        if (lastSync != null) {
             if (lastSync.get() > 0 && System.currentTimeMillis() - lastSync.get() <= 5000) {
                 RoughlyEnoughItemsCore.LOGGER.warn("[REI] Suppressing Sync Recipes!");
                 return;
             }
             lastSync.set(System.currentTimeMillis());
-            if (ConfigManager.getInstance().getConfig().doesRegisterRecipesInAnotherThread()) {
-                CompletableFuture.runAsync(() -> ((RecipeHelperImpl) RecipeHelper.getInstance()).recipesLoaded(recipeManager), SYNC_RECIPES);
-            } else {
-                ((RecipeHelperImpl) RecipeHelper.getInstance()).recipesLoaded(recipeManager);
-            }
-        });
+        }
+        RecipeManager recipeManager = MinecraftClient.getInstance().getNetworkHandler().getRecipeManager();
+        if (ConfigManager.getInstance().getConfig().doesRegisterRecipesInAnotherThread()) {
+            CompletableFuture.runAsync(() -> ((RecipeHelperImpl) RecipeHelper.getInstance()).recipesLoaded(recipeManager), SYNC_RECIPES);
+        } else {
+            ((RecipeHelperImpl) RecipeHelper.getInstance()).recipesLoaded(recipeManager);
+        }
+    }
+    
+    @SuppressWarnings("deprecation")
+    private void registerClothEvents() {
+        final Identifier recipeButtonTex = new Identifier("textures/gui/recipe_button.png");
+        AtomicLong lastSync = new AtomicLong(-1);
+        ClothClientHooks.SYNC_RECIPES.register((minecraftClient, recipeManager, synchronizeRecipesS2CPacket) -> syncRecipes(lastSync));
         ClothClientHooks.SCREEN_ADD_BUTTON.register((minecraftClient, screen, abstractButtonWidget) -> {
             if (ConfigManager.getInstance().getConfig().doesDisableRecipeBook() && screen instanceof AbstractContainerScreen && abstractButtonWidget instanceof TexturedButtonWidget)
                 if (((RecipeBookButtonWidgetHooks) abstractButtonWidget).rei_getTexture().equals(recipeButtonTex))

+ 2 - 0
src/main/java/me/shedaniel/rei/api/ConfigObject.java

@@ -79,6 +79,8 @@ public interface ConfigObject {
     
     boolean doesRegisterRecipesInAnotherThread();
     
+    boolean doesSnapToRows();
+    
     @Retention(RetentionPolicy.RUNTIME)
     @Target({ElementType.FIELD})
     public @interface DontApplyFieldName {

+ 13 - 1
src/main/java/me/shedaniel/rei/api/EntryRegistry.java

@@ -44,7 +44,19 @@ public interface EntryRegistry {
      * @param afterEntry the stack to put after
      * @param stack      the stack to register
      */
-    void registerEntryAfter(EntryStack afterEntry, EntryStack stack);
+    default void registerEntryAfter(EntryStack afterEntry, EntryStack stack) {
+        registerEntryAfter(afterEntry, stack, true);
+    }
+    
+    /**
+     * Registers an new stack to the entry list
+     *
+     * @param afterEntry           the stack to put after
+     * @param stack                the stack to register
+     * @param checkAlreadyContains whether the list should check if it is already on the list
+     */
+    @Deprecated
+    void registerEntryAfter(EntryStack afterEntry, EntryStack stack, boolean checkAlreadyContains);
     
     /**
      * Registers multiple stacks to the item list

+ 2 - 0
src/main/java/me/shedaniel/rei/api/RecipeHelper.java

@@ -206,6 +206,8 @@ public interface RecipeHelper {
     
     List<RecipeHelper.ScreenClickArea> getScreenClickAreas();
     
+    boolean arePluginsLoading();
+    
     interface ScreenClickArea {
         Class<? extends AbstractContainerScreen> getScreenClass();
         

+ 53 - 0
src/main/java/me/shedaniel/rei/gui/ConfigReloadingScreen.java

@@ -0,0 +1,53 @@
+package me.shedaniel.rei.gui;
+
+import me.shedaniel.rei.api.RecipeHelper;
+import me.shedaniel.rei.api.annotations.Internal;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.resource.language.I18n;
+import net.minecraft.client.util.NarratorManager;
+import net.minecraft.util.Util;
+
+@Deprecated
+@Internal
+public class ConfigReloadingScreen extends Screen {
+    
+    private Screen parent;
+    
+    public ConfigReloadingScreen(Screen parent) {
+        super(NarratorManager.EMPTY);
+        this.parent = parent;
+    }
+    
+    @Override
+    public boolean shouldCloseOnEsc() {
+        return false;
+    }
+    
+    @Override
+    public void render(int int_1, int int_2, float float_1) {
+        this.renderDirtBackground(0);
+        if (!RecipeHelper.getInstance().arePluginsLoading())
+            minecraft.openScreen(parent);
+        this.drawCenteredString(this.font, I18n.translate("text.rei.config.is.reloading"), this.width / 2, this.height / 2 - 50, 16777215);
+        String string_3;
+        switch ((int) (Util.getMeasuringTimeMs() / 300L % 4L)) {
+            case 0:
+            default:
+                string_3 = "O o o";
+                break;
+            case 1:
+            case 3:
+                string_3 = "o O o";
+                break;
+            case 2:
+                string_3 = "o o O";
+        }
+        this.drawCenteredString(this.font, string_3, this.width / 2, this.height / 2 - 41, 8421504);
+        super.render(int_1, int_2, float_1);
+    }
+    
+    @Override
+    public boolean isPauseScreen() {
+        return false;
+    }
+}

+ 9 - 8
src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java

@@ -22,7 +22,8 @@ import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.Element;
 import net.minecraft.client.gui.screen.Screen;
 import net.minecraft.client.gui.screen.ingame.AbstractContainerScreen;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.render.Tessellator;
 import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.resource.language.I18n;
@@ -174,7 +175,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
             @Override
             public void render(int mouseX, int mouseY, float delta) {
                 super.render(mouseX, mouseY, delta);
-                GuiLighting.disable();
+                DiffuseLighting.disable();
                 Rectangle bounds = getBounds();
                 if (ClientHelper.getInstance().isCheating() && RoughlyEnoughItemsCore.hasOperatorPermission()) {
                     if (RoughlyEnoughItemsCore.hasPermissionToUsePackets())
@@ -251,7 +252,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
                     @Override
                     public void render(int mouseX, int mouseY, float delta) {
                         super.render(mouseX, mouseY, delta);
-                        GuiLighting.disable();
+                        DiffuseLighting.disable();
                         MinecraftClient.getInstance().getTextureManager().bindTexture(CHEST_GUI_TEXTURE);
                         RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
                         blit(getBounds().x + 3, getBounds().y + 3, weather.getId() * 14, 14, 14, 14);
@@ -430,7 +431,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
             ENTRY_LIST_WIDGET.updateSearch(ScreenHelper.getSearchField().getText());
         }
         if (OverlaySearchField.isSearching) {
-            GuiLighting.disable();
+            DiffuseLighting.disable();
             setBlitOffset(200);
             if (MinecraftClient.getInstance().currentScreen instanceof AbstractContainerScreen) {
                 ContainerScreenHooks hooks = (ContainerScreenHooks) MinecraftClient.getInstance().currentScreen;
@@ -442,7 +443,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
             setBlitOffset(0);
         }
         RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-        GuiLighting.disable();
+        DiffuseLighting.disable();
         this.renderWidgets(mouseX, mouseY, delta);
         if (MinecraftClient.getInstance().currentScreen instanceof AbstractContainerScreen && ConfigManager.getInstance().getConfig().areClickableRecipeArrowsEnabled()) {
             ContainerScreenHooks hooks = (ContainerScreenHooks) MinecraftClient.getInstance().currentScreen;
@@ -505,10 +506,10 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
         if (!ConfigManager.getInstance().getConfig().isEntryListWidgetScrolled())
             buttonLeft.enabled = buttonRight.enabled = ENTRY_LIST_WIDGET.getTotalPages() != 1;
         widgets.forEach(widget -> {
-            GuiLighting.disable();
+            DiffuseLighting.disable();
             widget.render(int_1, int_2, float_1);
         });
-        GuiLighting.disable();
+        DiffuseLighting.disable();
     }
     
     @Override
@@ -529,7 +530,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
             }
         }
         for (Widget widget : widgets)
-            if (widget.mouseScrolled(i, j, amount))
+            if (widget != ENTRY_LIST_WIDGET && widget.mouseScrolled(i, j, amount))
                 return true;
         return false;
     }

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

@@ -10,7 +10,7 @@ import me.shedaniel.math.impl.PointHelper;
 import me.shedaniel.rei.gui.widget.TextFieldWidget;
 import me.shedaniel.rei.impl.ScreenHelper;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.client.sound.PositionedSoundInstance;
 import net.minecraft.client.util.InputUtil;
@@ -30,7 +30,7 @@ public class OverlaySearchField extends TextFieldWidget {
     
     @SuppressWarnings("deprecation")
     public void laterRender(int int_1, int int_2, float float_1) {
-        GuiLighting.disable();
+        DiffuseLighting.disable();
         RenderSystem.disableDepthTest();
         setEditableColor(ContainerScreenOverlay.getEntryListWidget().getAllStacks().isEmpty() && !getText().isEmpty() ? 16733525 : isSearching ? -852212 : (containsMouse(PointHelper.fromMouse()) || isFocused()) ? (ScreenHelper.isDarkModeEnabled() ? -17587 : -1) : -6250336);
         setSuggestion(!isFocused() && getText().isEmpty() ? I18n.translate("text.rei.search.field.suggestion") : null);

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

@@ -19,7 +19,7 @@ import me.shedaniel.rei.impl.ScreenHelper;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.Element;
 import net.minecraft.client.gui.screen.Screen;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.client.sound.PositionedSoundInstance;
 import net.minecraft.sound.SoundEvents;
@@ -73,7 +73,7 @@ public class PreRecipeViewingScreen extends Screen {
         }
         super.render(int_1, int_2, float_1);
         this.widgets.forEach(widget -> {
-            GuiLighting.disable();
+            DiffuseLighting.disable();
             widget.render(int_1, int_2, float_1);
         });
     }

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

@@ -17,7 +17,7 @@ import me.shedaniel.rei.utils.CollectionUtils;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.Element;
 import net.minecraft.client.gui.screen.Screen;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.client.sound.PositionedSoundInstance;
 import net.minecraft.client.util.Window;
@@ -27,6 +27,7 @@ import net.minecraft.util.Formatting;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.math.MathHelper;
 
+import javax.annotation.Nullable;
 import java.util.*;
 import java.util.function.Supplier;
 
@@ -46,6 +47,8 @@ public class RecipeViewingScreen extends Screen {
     public boolean choosePageActivated;
     public RecipeChoosePageWidget recipeChoosePageWidget;
     private Rectangle bounds;
+    @Nullable
+    private CategoryBaseWidget workingStationsBaseWidget;
     private RecipeCategory<RecipeDisplay> selectedCategory;
     private ButtonWidget recipeBack, recipeNext, categoryBack, categoryNext;
     
@@ -67,6 +70,11 @@ public class RecipeViewingScreen extends Screen {
         this.choosePageActivated = false;
     }
     
+    @Nullable
+    public CategoryBaseWidget getWorkingStationsBaseWidget() {
+        return workingStationsBaseWidget;
+    }
+    
     @Override
     public boolean keyPressed(int int_1, int int_2, int int_3) {
         if (int_1 == 256 && choosePageActivated) {
@@ -292,7 +300,8 @@ public class RecipeViewingScreen extends Screen {
             recipeChoosePageWidget = new RecipeChoosePageWidget(this, page, getTotalPages(selectedCategory));
         else
             recipeChoosePageWidget = null;
-        
+    
+        workingStationsBaseWidget = null;
         List<List<EntryStack>> workingStations = RecipeHelper.getInstance().getWorkingStations(selectedCategory.getIdentifier());
         if (!workingStations.isEmpty()) {
             int hh = MathHelper.floor((bounds.height - 16) / 18f);
@@ -300,7 +309,7 @@ public class RecipeViewingScreen extends Screen {
             int innerWidth = MathHelper.ceil(workingStations.size() / ((float) hh));
             int xx = bounds.x - (10 + innerWidth * 18) + 6;
             int yy = bounds.y + 16;
-            preWidgets.add(new CategoryBaseWidget(new Rectangle(xx - 6, yy - 6, 15 + innerWidth * 18, 11 + actualHeight * 18)));
+            preWidgets.add(workingStationsBaseWidget = new CategoryBaseWidget(new Rectangle(xx - 6, yy - 6, 15 + innerWidth * 18, 11 + actualHeight * 18)));
             int index = 0;
             List<String> list = Collections.singletonList(Formatting.YELLOW.toString() + I18n.translate("text.rei.working_station"));
             xx += (innerWidth - 1) * 18;
@@ -364,7 +373,7 @@ public class RecipeViewingScreen extends Screen {
     public void render(int mouseX, int mouseY, float delta) {
         this.fillGradient(0, 0, this.width, this.height, -1072689136, -804253680);
         preWidgets.forEach(widget -> {
-            GuiLighting.disable();
+            DiffuseLighting.disable();
             widget.render(mouseX, mouseY, delta);
         });
         if (selectedCategory != null)
@@ -383,19 +392,19 @@ public class RecipeViewingScreen extends Screen {
             if (!tab.isSelected())
                 tab.render(mouseX, mouseY, delta);
         }
-        GuiLighting.disable();
+        DiffuseLighting.disable();
         super.render(mouseX, mouseY, delta);
         widgets.forEach(widget -> {
-            GuiLighting.disable();
+            DiffuseLighting.disable();
             widget.render(mouseX, mouseY, delta);
         });
         RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-        GuiLighting.disable();
+        DiffuseLighting.disable();
         for (TabWidget tab : tabs) {
             if (tab.isSelected())
                 tab.render(mouseX, mouseY, delta);
         }
-        GuiLighting.disable();
+        DiffuseLighting.disable();
         ScreenHelper.getLastOverlay().render(mouseX, mouseY, delta);
         ScreenHelper.getLastOverlay().lateRender(mouseX, mouseY, delta);
         if (choosePageActivated) {

+ 6 - 6
src/main/java/me/shedaniel/rei/gui/VillagerRecipeViewingScreen.java

@@ -24,7 +24,7 @@ import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.Element;
 import net.minecraft.client.gui.screen.Screen;
 import net.minecraft.client.render.BufferBuilder;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.render.Tessellator;
 import net.minecraft.client.render.VertexFormats;
 import net.minecraft.client.resource.language.I18n;
@@ -350,10 +350,10 @@ public class VillagerRecipeViewingScreen extends Screen {
         this.fillGradient(0, 0, this.width, this.height, -1072689136, -804253680);
         int yOffset = 0;
         this.widgets.forEach(widget -> {
-            GuiLighting.disable();
+            DiffuseLighting.disable();
             widget.render(mouseX, mouseY, delta);
         });
-        GuiLighting.disable();
+        DiffuseLighting.disable();
         ScreenHelper.getLastOverlay().render(mouseX, mouseY, delta);
         RenderSystem.pushMatrix();
         ScissorsHandler.INSTANCE.scissor(new Rectangle(0, scrollListBounds.y + 1, width, scrollListBounds.height - 2));
@@ -361,14 +361,14 @@ public class VillagerRecipeViewingScreen extends Screen {
             ButtonWidget buttonWidget = buttonWidgets.get(i);
             buttonWidget.getBounds().y = scrollListBounds.y + 1 + yOffset - (int) scroll;
             if (buttonWidget.getBounds().getMaxY() > scrollListBounds.getMinY() && buttonWidget.getBounds().getMinY() < scrollListBounds.getMaxY()) {
-                GuiLighting.disable();
+                DiffuseLighting.disable();
                 buttonWidget.render(mouseX, mouseY, delta);
             }
             yOffset += buttonWidget.getBounds().height;
         }
         for (int i = 0; i < buttonWidgets.size(); i++) {
             if (buttonWidgets.get(i).getBounds().getMaxY() > scrollListBounds.getMinY() && buttonWidgets.get(i).getBounds().getMinY() < scrollListBounds.getMaxY()) {
-                GuiLighting.disable();
+                DiffuseLighting.disable();
                 recipeRenderers.get(i).setZ(1);
                 recipeRenderers.get(i).render(buttonWidgets.get(i).getBounds(), mouseX, mouseY, delta);
                 ScreenHelper.getLastOverlay().addTooltip(recipeRenderers.get(i).getTooltip(mouseX, mouseY));
@@ -387,7 +387,7 @@ public class VillagerRecipeViewingScreen extends Screen {
             boolean hovered = (new Rectangle(scrollbarPositionMinX, minY, scrollbarPositionMaxX - scrollbarPositionMinX, height)).contains(PointHelper.fromMouse());
             float bottomC = (hovered ? .67f : .5f) * (ScreenHelper.isDarkModeEnabled() ? 0.8f : 1f);
             float topC = (hovered ? .87f : .67f) * (ScreenHelper.isDarkModeEnabled() ? 0.8f : 1f);
-            GuiLighting.disable();
+            DiffuseLighting.disable();
             RenderSystem.disableTexture();
             RenderSystem.enableBlend();
             RenderSystem.disableAlphaTest();

+ 2 - 2
src/main/java/me/shedaniel/rei/gui/entries/SimpleRecipeEntry.java

@@ -12,7 +12,7 @@ import me.shedaniel.rei.gui.widget.EntryWidget;
 import me.shedaniel.rei.gui.widget.QueuedTooltip;
 import me.shedaniel.rei.utils.CollectionUtils;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.Pair;
 import net.minecraft.util.math.MathHelper;
@@ -95,7 +95,7 @@ public class SimpleRecipeEntry extends RecipeEntry {
         }
         xx = bounds.x + 4 + 18 * (getItemsPerLine() - 2);
         yy = bounds.y + getHeight() / 2 - 8;
-        GuiLighting.disable();
+        DiffuseLighting.disable();
         MinecraftClient.getInstance().getTextureManager().bindTexture(CHEST_GUI_TEXTURE);
         blit(xx, yy, 0, 28, 18, 18);
         xx += 18;

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

@@ -10,7 +10,7 @@ import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.rei.api.ConfigManager;
 import net.minecraft.block.Blocks;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.render.item.ItemRenderer;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.item.ItemStack;
@@ -33,14 +33,14 @@ public abstract class CraftableToggleButtonWidget extends ButtonWidget {
     }
     
     public void lateRender(int mouseX, int mouseY, float delta) {
-        GuiLighting.disable();
+        DiffuseLighting.disable();
         super.render(mouseX, mouseY, delta);
         
         this.itemRenderer.zOffset = getBlitOffset();
         Rectangle bounds = getBounds();
         this.itemRenderer.renderGuiItem(new ItemStack(Blocks.CRAFTING_TABLE), bounds.x + 2, bounds.y + 2);
         this.itemRenderer.zOffset = 0.0F;
-        GuiLighting.disable();
+        DiffuseLighting.disable();
         MinecraftClient.getInstance().getTextureManager().bindTexture(CHEST_GUI_TEXTURE);
         RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
         int color = ConfigManager.getInstance().isCraftableOnlyEnabled() ? 939579655 : 956235776;

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

@@ -21,7 +21,7 @@ import me.shedaniel.rei.impl.SearchArgument;
 import me.shedaniel.rei.utils.CollectionUtils;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.BufferBuilder;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.render.Tessellator;
 import net.minecraft.client.render.VertexFormats;
 import net.minecraft.item.ItemGroup;
@@ -40,6 +40,7 @@ import java.util.stream.Collectors;
 
 public class EntryListWidget extends WidgetWithBounds {
     
+    private static final boolean LAZY = true;
     private static final String SPACE = " ", EMPTY = "";
     private static final Supplier<Boolean> RENDER_EXTRA_CONFIG = ConfigManager.getInstance().getConfig()::doesRenderEntryExtraOverlay;
     @SuppressWarnings("deprecation")
@@ -56,6 +57,7 @@ public class EntryListWidget extends WidgetWithBounds {
     protected double scroll;
     protected long start;
     protected long duration;
+    protected int blockedCount;
     private Rectangle bounds, innerBounds;
     private List<EntryStack> allStacks = null;
     private List<EntryListEntry> entries = Collections.emptyList();
@@ -64,12 +66,7 @@ public class EntryListWidget extends WidgetWithBounds {
     private boolean draggingScrollBar = false;
     
     protected final int getMaxScrollPosition() {
-        int candidate = 0;
-        for (EntryListEntry entry : entries) {
-            if (entry.getCurrentEntry().getType() != EntryStack.Type.EMPTY && entry.backupY > candidate)
-                candidate = entry.backupY;
-        }
-        return candidate;
+        return MathHelper.ceil((allStacks.size() + blockedCount) / (innerBounds.width / 18f)) * 18;
     }
     
     protected final int getMaxScroll() {
@@ -141,27 +138,31 @@ public class EntryListWidget extends WidgetWithBounds {
     public void render(int mouseX, int mouseY, float delta) {
         if (ConfigManager.getInstance().getConfig().isEntryListWidgetScrolled()) {
             for (EntryListEntry entry : entries) entry.clearStacks();
-            int nextIndex = 0;
-            for (int i = 0; i < allStacks.size(); i++) {
+            ScissorsHandler.INSTANCE.scissor(bounds);
+            int skip = Math.max(0, MathHelper.floor(scroll / 18f));
+            int nextIndex = skip * innerBounds.width / 18;
+            int i = nextIndex;
+            blockedCount = 0;
+            back:
+            for (; i < allStacks.size(); i++) {
                 EntryStack stack = allStacks.get(i);
                 while (true) {
                     EntryListEntry entry = entries.get(nextIndex);
                     entry.getBounds().y = (int) (entry.backupY - scroll);
+                    if (entry.getBounds().y > bounds.getMaxY())
+                        break back;
                     if (notSteppingOnExclusionZones(entry.getBounds().x, entry.getBounds().y, innerBounds)) {
                         entry.entry(stack);
+                        entry.render(mouseX, mouseY, delta);
                         nextIndex++;
                         break;
                     } else {
+                        blockedCount++;
                         nextIndex++;
                     }
                 }
             }
             updatePosition(delta);
-            ScissorsHandler.INSTANCE.scissor(bounds);
-            for (EntryListEntry widget : entries) {
-                if (widget.getBounds().getMaxY() >= bounds.y && widget.getBounds().y <= bounds.getMaxY())
-                    widget.render(mouseX, mouseY, delta);
-            }
             ScissorsHandler.INSTANCE.removeLastScissor();
             renderScrollbar();
         } else {
@@ -187,7 +188,11 @@ public class EntryListWidget extends WidgetWithBounds {
                 int int_2 = innerBounds.height;
                 int int_3 = MathHelper.clamp((int) ((float) (int_2 * int_2) / (float) getMaxScrollPosition()), 32, int_2 - 8);
                 double double_6 = Math.max(1.0D, double_5 / (double) (int_2 - int_3));
-                scrollTo(MathHelper.clamp((float) (scroll + double_4 * double_6), 0, height - innerBounds.height), false);
+                float to = MathHelper.clamp((float) (scroll + double_4 * double_6), 0, height - innerBounds.height);
+                if (ConfigManager.getInstance().getConfig().doesSnapToRows()) {
+                    double nearestRow = Math.round(to / 18.0) * 18.0;
+                    scrollTo(nearestRow, false);
+                } else scrollTo(to, false);
             }
         }
         return super.mouseDragged(mouseX, mouseY, int_1, double_3, double_4);
@@ -208,7 +213,7 @@ public class EntryListWidget extends WidgetWithBounds {
             float bottomC = (hovered ? .67f : .5f) * (ScreenHelper.isDarkModeEnabled() ? 0.8f : 1f);
             float topC = (hovered ? .87f : .67f) * (ScreenHelper.isDarkModeEnabled() ? 0.8f : 1f);
             
-            GuiLighting.disable();
+            DiffuseLighting.disable();
             RenderSystem.disableTexture();
             RenderSystem.enableBlend();
             RenderSystem.disableAlphaTest();
@@ -241,6 +246,12 @@ public class EntryListWidget extends WidgetWithBounds {
             target -= target * (1 - ClothConfigInitializer.getBounceBackMultiplier()) * delta / 3;
         } else if (target > getMaxScroll()) {
             target = (target - getMaxScroll()) * (1 - (1 - ClothConfigInitializer.getBounceBackMultiplier()) * delta / 3) + getMaxScroll();
+        } else if (ConfigManager.getInstance().getConfig().doesSnapToRows()) {
+            double nearestRow = Math.round(target / 18.0) * 18.0;
+            if (!DynamicNewSmoothScrollingEntryListWidget.Precision.almostEquals(target, nearestRow, DynamicNewSmoothScrollingEntryListWidget.Precision.FLOAT_EPSILON))
+                target += (nearestRow - target) * Math.min(delta / 2.0, 1.0);
+            else
+                target = nearestRow;
         }
         if (!DynamicNewSmoothScrollingEntryListWidget.Precision.almostEquals(scroll, target, DynamicNewSmoothScrollingEntryListWidget.Precision.FLOAT_EPSILON))
             scroll = (float) DynamicNewSmoothScrollingEntryListWidget.Interpolation.expoEase(scroll, target, Math.min((System.currentTimeMillis() - start) / ((double) duration), 1));

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

@@ -10,7 +10,7 @@ import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.rei.api.ConfigManager;
 import me.shedaniel.rei.gui.config.RecipeScreenType;
 import me.shedaniel.rei.impl.ScreenHelper;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.util.Identifier;
 
 import java.util.Collections;
@@ -59,7 +59,7 @@ public class PanelWidget extends WidgetWithBounds {
         float blue = ((color >> 0) & 0xFF) / 255f;
         float alpha = ((color >> 32) & 0xFF) / 255f;
         RenderSystem.color4f(red, green, blue, alpha);
-        GuiLighting.disable();
+        DiffuseLighting.disable();
         minecraft.getTextureManager().bindTexture(ScreenHelper.isDarkModeEnabled() ? CHEST_GUI_TEXTURE_DARK : CHEST_GUI_TEXTURE);
         int x = bounds.x, y = bounds.y, width = bounds.width, height = bounds.height;
         int xTextureOffset = getXTextureOffset();

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

@@ -10,7 +10,7 @@ import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.rei.plugin.DefaultPlugin;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.Element;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.util.math.MathHelper;
 
 import java.util.Collections;
@@ -35,7 +35,7 @@ public class RecipeArrowWidget extends WidgetWithBounds {
     @Override
     public void render(int mouseX, int mouseY, float delta) {
         RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-        GuiLighting.disable();
+        DiffuseLighting.disable();
         MinecraftClient.getInstance().getTextureManager().bindTexture(DefaultPlugin.getDisplayTexture());
         blit(x, y, 106, 91, 24, 17);
         if (animated) {

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

@@ -12,7 +12,7 @@ import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.rei.gui.RecipeViewingScreen;
 import me.shedaniel.rei.impl.ScreenHelper;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.client.util.Window;
 import net.minecraft.util.math.MathHelper;
@@ -140,7 +140,7 @@ public class RecipeChoosePageWidget extends DraggableWidget {
     @Override
     public void render(int i, int i1, float v) {
         widgets.forEach(widget -> {
-            GuiLighting.disable();
+            DiffuseLighting.disable();
             RenderSystem.translatef(0, 0, 800);
             widget.render(i, i1, v);
             RenderSystem.translatef(0, 0, -800);

+ 12 - 0
src/main/java/me/shedaniel/rei/gui/widget/ReloadConfigButtonWidget.java

@@ -0,0 +1,12 @@
+package me.shedaniel.rei.gui.widget;
+
+import me.shedaniel.rei.api.annotations.Internal;
+import net.minecraft.client.gui.widget.ButtonWidget;
+
+@Internal
+@Deprecated
+public class ReloadConfigButtonWidget extends ButtonWidget {
+    public ReloadConfigButtonWidget(int x, int y, int width, int height, String text, PressAction action) {
+        super(x, y, width, height, text, action);
+    }
+}

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

@@ -11,7 +11,7 @@ import me.shedaniel.rei.api.ClientHelper;
 import me.shedaniel.rei.api.EntryStack;
 import me.shedaniel.rei.api.RecipeCategory;
 import me.shedaniel.rei.impl.ScreenHelper;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.util.Formatting;
 import net.minecraft.util.Identifier;
 
@@ -69,7 +69,7 @@ public class TabWidget extends WidgetWithBounds {
     public void render(int mouseX, int mouseY, float delta) {
         if (shown) {
             RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-            GuiLighting.disable();
+            DiffuseLighting.disable();
             minecraft.getTextureManager().bindTexture(ScreenHelper.isDarkModeEnabled() ? CHEST_GUI_TEXTURE_DARK : CHEST_GUI_TEXTURE);
             this.blit(bounds.x, bounds.y + 2, selected ? 28 : 0, 192, 28, (selected ? 30 : 27));
             logo.setZ(100);

+ 28 - 0
src/main/java/me/shedaniel/rei/impl/ConfigManagerImpl.java

@@ -9,13 +9,19 @@ import me.sargunvohra.mcmods.autoconfig1u.AutoConfig;
 import me.sargunvohra.mcmods.autoconfig1u.gui.ConfigScreenProvider;
 import me.sargunvohra.mcmods.autoconfig1u.gui.registry.GuiRegistry;
 import me.sargunvohra.mcmods.autoconfig1u.serializer.JanksonConfigSerializer;
+import me.shedaniel.cloth.hooks.ScreenHooks;
 import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
 import me.shedaniel.rei.RoughlyEnoughItemsCore;
 import me.shedaniel.rei.api.ConfigManager;
 import me.shedaniel.rei.api.ConfigObject;
+import me.shedaniel.rei.api.RecipeHelper;
 import me.shedaniel.rei.api.annotations.Internal;
+import me.shedaniel.rei.gui.ConfigReloadingScreen;
+import me.shedaniel.rei.gui.credits.CreditsScreen;
+import me.shedaniel.rei.gui.widget.ReloadConfigButtonWidget;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.widget.AbstractPressableButtonWidget;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.text.LiteralText;
 
@@ -77,6 +83,28 @@ public class ConfigManagerImpl implements ConfigManager {
             provider.setI13nFunction(manager -> "config.roughlyenoughitems");
             provider.setOptionFunction((baseI13n, field) -> field.isAnnotationPresent(ConfigObject.DontApplyFieldName.class) ? baseI13n : String.format("%s.%s", baseI13n, field.getName()));
             provider.setCategoryFunction((baseI13n, categoryName) -> String.format("%s.%s", baseI13n, categoryName));
+            provider.setBuildFunction(builder -> {
+                return builder.setAfterInitConsumer(screen -> {
+                    if (MinecraftClient.getInstance().getNetworkHandler() != null && MinecraftClient.getInstance().getNetworkHandler().getRecipeManager() != null) {
+                        ((ScreenHooks) screen).cloth_addButton(new ReloadConfigButtonWidget(4, 4, 100, 20, I18n.translate("text.rei.reload_config"), buttonWidget -> {
+                            RoughlyEnoughItemsCore.syncRecipes(null);
+                        }) {
+                            @Override
+                            public void render(int int_1, int int_2, float float_1) {
+                                if (RecipeHelper.getInstance().arePluginsLoading()) {
+                                    MinecraftClient.getInstance().openScreen(new ConfigReloadingScreen(MinecraftClient.getInstance().currentScreen));
+                                } else super.render(int_1, int_2, float_1);
+                            }
+                        });
+                    }
+                    ((ScreenHooks) screen).cloth_addButton(new AbstractPressableButtonWidget(screen.width - 104, 4, 100, 20, I18n.translate("text.rei.credits")) {
+                        @Override
+                        public void onPress() {
+                            MinecraftClient.getInstance().openScreen(new CreditsScreen(screen));
+                        }
+                    });
+                }).build();
+            });
             return provider.get();
         } catch (Exception e) {
             e.printStackTrace();

+ 6 - 0
src/main/java/me/shedaniel/rei/impl/ConfigObjectImpl.java

@@ -193,6 +193,11 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData {
         return technical.registerRecipesInAnotherThread;
     }
     
+    @Override
+    public boolean doesSnapToRows() {
+        return appearance.snapToRows;
+    }
+    
     public static class General {
         @Comment("Declares whether cheating mode is on.")
         private boolean cheating = false;
@@ -233,6 +238,7 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData {
         private boolean villagerScreenPermanentScrollBar = false;
         @Comment("Declares whether if entry list widget is scrolled.")
         private boolean scrollingEntryListWidget = false;
+        private boolean snapToRows = false;
     }
     
     public static class Technical {

+ 3 - 2
src/main/java/me/shedaniel/rei/impl/EntryRegistryImpl.java

@@ -39,8 +39,9 @@ public class EntryRegistryImpl implements EntryRegistry {
     }
     
     @Override
-    public void registerEntryAfter(EntryStack afterEntry, EntryStack stack) {
-        if (!stack.isEmpty() && !alreadyContain(stack))
+    @Deprecated
+    public void registerEntryAfter(EntryStack afterEntry, EntryStack stack, boolean checkAlreadyContains) {
+        if (!stack.isEmpty() && (!checkAlreadyContains || !alreadyContain(stack)))
             if (afterEntry == null || afterEntry.isEmpty())
                 entries.add(stack);
             else {

+ 2 - 2
src/main/java/me/shedaniel/rei/impl/FluidEntryStack.java

@@ -15,7 +15,7 @@ import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler;
 import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.render.BufferBuilder;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.render.Tessellator;
 import net.minecraft.client.render.VertexFormats;
 import net.minecraft.client.texture.Sprite;
@@ -183,7 +183,7 @@ public class FluidEntryStack extends AbstractEntryStack {
                 int g = (color >> 8 & 255);
                 int b = (color & 255);
                 MinecraftClient.getInstance().getTextureManager().bindTexture(SpriteAtlasTexture.BLOCK_ATLAS_TEX);
-                GuiLighting.disable();
+                DiffuseLighting.disable();
                 Tessellator tess = Tessellator.getInstance();
                 BufferBuilder bb = tess.getBuffer();
                 bb.begin(7, VertexFormats.POSITION_TEXTURE_COLOR);

+ 11 - 5
src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java

@@ -57,6 +57,7 @@ public class RecipeHelperImpl implements RecipeHelper {
     private final List<DisplayVisibilityHandler> displayVisibilityHandlers = Lists.newArrayList();
     private final List<LiveRecipeGenerator<RecipeDisplay>> liveRecipeGenerators = Lists.newArrayList();
     private RecipeManager recipeManager;
+    private boolean arePluginsLoading = false;
     
     @Override
     public List<EntryStack> findCraftableEntriesByItems(List<EntryStack> inventoryItems) {
@@ -88,6 +89,11 @@ public class RecipeHelperImpl implements RecipeHelper {
         return craftables.stream().distinct().collect(Collectors.toList());
     }
     
+    @Override
+    public boolean arePluginsLoading() {
+        return arePluginsLoading;
+    }
+    
     @Override
     public void registerCategory(RecipeCategory<?> category) {
         categories.add(category);
@@ -218,6 +224,8 @@ public class RecipeHelperImpl implements RecipeHelper {
     
     @SuppressWarnings("deprecation")
     public void recipesLoaded(RecipeManager recipeManager) {
+        long startTime = System.currentTimeMillis();
+        arePluginsLoading = true;
         ScreenHelper.clearData();
         this.recipeCount.set(0);
         this.recipeManager = recipeManager;
@@ -235,11 +243,8 @@ public class RecipeHelperImpl implements RecipeHelper {
         BaseBoundsHandler baseBoundsHandler = new BaseBoundsHandlerImpl();
         DisplayHelper.getInstance().registerBoundsHandler(baseBoundsHandler);
         ((DisplayHelperImpl) DisplayHelper.getInstance()).setBaseBoundsHandler(baseBoundsHandler);
-        long startTime = System.currentTimeMillis();
-        List<REIPluginEntry> plugins = new LinkedList<>(RoughlyEnoughItemsCore.getPlugins());
-        plugins.sort((first, second) -> {
-            return second.getPriority() - first.getPriority();
-        });
+        List<REIPluginEntry> plugins = Lists.newLinkedList(RoughlyEnoughItemsCore.getPlugins());
+        plugins.sort(Comparator.comparingInt(REIPluginEntry::getPriority).reversed());
         RoughlyEnoughItemsCore.LOGGER.info("[REI] Loading %d plugins: %s", plugins.size(), plugins.stream().map(REIPluginEntry::getPluginIdentifier).map(Identifier::toString).collect(Collectors.joining(", ")));
         Collections.reverse(plugins);
         EntryRegistry.getInstance().getStacksList().clear();
@@ -319,6 +324,7 @@ public class RecipeHelperImpl implements RecipeHelper {
         
         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.", EntryRegistry.getInstance().getStacksList().size(), recipeCount.get(), DisplayHelper.getInstance().getAllBoundsHandlers().size(), getDisplayVisibilityHandlers().size(), categories.size(), String.join(", ", categories.stream().map(RecipeCategory::getCategoryName).collect(Collectors.toList())), usedTime);
+        arePluginsLoading = false;
     }
     
     @Override

+ 2 - 2
src/main/java/me/shedaniel/rei/listeners/ContainerScreenHooks.java

@@ -13,10 +13,10 @@ import org.spongepowered.asm.mixin.gen.Accessor;
 @Mixin(AbstractContainerScreen.class)
 public interface ContainerScreenHooks {
     
-    @Accessor("left")
+    @Accessor("x")
     int rei_getContainerLeft();
     
-    @Accessor("top")
+    @Accessor("y")
     int rei_getContainerTop();
     
     @Accessor("containerWidth")

+ 6 - 0
src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java

@@ -14,6 +14,7 @@ import me.shedaniel.rei.api.*;
 import me.shedaniel.rei.api.plugins.REIPluginV0;
 import me.shedaniel.rei.gui.RecipeViewingScreen;
 import me.shedaniel.rei.gui.VillagerRecipeViewingScreen;
+import me.shedaniel.rei.gui.widget.CategoryBaseWidget;
 import me.shedaniel.rei.impl.ScreenHelper;
 import me.shedaniel.rei.plugin.blasting.DefaultBlastingDisplay;
 import me.shedaniel.rei.plugin.brewing.DefaultBrewingCategory;
@@ -217,6 +218,11 @@ public class DefaultPlugin implements REIPluginV0 {
         }
         displayHelper.getBaseBoundsHandler().registerExclusionZones(AbstractInventoryScreen.class, new DefaultPotionEffectExclusionZones());
         displayHelper.getBaseBoundsHandler().registerExclusionZones(RecipeBookProvider.class, new DefaultRecipeBookExclusionZones());
+        displayHelper.getBaseBoundsHandler().registerExclusionZones(RecipeViewingScreen.class, isLeftSide -> {
+            CategoryBaseWidget widget = ((RecipeViewingScreen) MinecraftClient.getInstance().currentScreen).getWorkingStationsBaseWidget();
+            if (widget == null) return Collections.emptyList();
+            return Collections.singletonList(widget.getBounds().clone());
+        });
         displayHelper.registerBoundsHandler(new DisplayHelper.DisplayBoundsHandler<AbstractContainerScreen<?>>() {
             @Override
             public Class<?> getBaseSupportedClass() {

+ 2 - 2
src/main/java/me/shedaniel/rei/plugin/brewing/DefaultBrewingCategory.java

@@ -16,7 +16,7 @@ import me.shedaniel.rei.gui.widget.Widget;
 import me.shedaniel.rei.plugin.DefaultPlugin;
 import net.minecraft.block.Blocks;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.item.Items;
 import net.minecraft.util.Identifier;
@@ -53,7 +53,7 @@ public class DefaultBrewingCategory implements RecipeCategory<DefaultBrewingDisp
             public void render(int mouseX, int mouseY, float delta) {
                 super.render(mouseX, mouseY, delta);
                 RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-                GuiLighting.disable();
+                DiffuseLighting.disable();
                 MinecraftClient.getInstance().getTextureManager().bindTexture(DefaultPlugin.getDisplayTexture());
                 blit(startPoint.x, startPoint.y, 0, 108, 103, 59);
                 int width = MathHelper.ceil((System.currentTimeMillis() / 250 % 18d) / 1f);

+ 2 - 2
src/main/java/me/shedaniel/rei/plugin/campfire/DefaultCampfireCategory.java

@@ -18,7 +18,7 @@ import me.shedaniel.rei.impl.ScreenHelper;
 import me.shedaniel.rei.plugin.DefaultPlugin;
 import net.minecraft.block.Blocks;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.math.MathHelper;
@@ -53,7 +53,7 @@ public class DefaultCampfireCategory implements RecipeCategory<DefaultCampfireDi
             public void render(int mouseX, int mouseY, float delta) {
                 super.render(mouseX, mouseY, delta);
                 RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-                GuiLighting.disable();
+                DiffuseLighting.disable();
                 MinecraftClient.getInstance().getTextureManager().bindTexture(DefaultPlugin.getDisplayTexture());
                 blit(startPoint.x, startPoint.y, 0, 167, 82, 54);
                 int height = MathHelper.ceil((System.currentTimeMillis() / 250 % 14d) / 1f);

+ 2 - 2
src/main/java/me/shedaniel/rei/plugin/composting/DefaultCompostingCategory.java

@@ -19,7 +19,7 @@ import me.shedaniel.rei.gui.widget.Widget;
 import me.shedaniel.rei.plugin.DefaultPlugin;
 import net.minecraft.block.Blocks;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.item.ItemConvertible;
 import net.minecraft.util.Identifier;
@@ -78,7 +78,7 @@ public class DefaultCompostingCategory implements RecipeCategory<DefaultComposti
             @Override
             public void render(int mouseX, int mouseY, float partialTicks) {
                 RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-                GuiLighting.disable();
+                DiffuseLighting.disable();
                 MinecraftClient.getInstance().getTextureManager().bindTexture(DefaultPlugin.getDisplayTexture());
                 this.blit(startingPoint.x, startingPoint.y, 28, 221, 55, 26);
             }

+ 2 - 2
src/main/java/me/shedaniel/rei/plugin/cooking/DefaultCookingCategory.java

@@ -20,7 +20,7 @@ import me.shedaniel.rei.gui.widget.Widget;
 import me.shedaniel.rei.plugin.DefaultPlugin;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawableHelper;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.math.MathHelper;
@@ -60,7 +60,7 @@ public class DefaultCookingCategory implements TransferRecipeCategory<DefaultCoo
             public void render(int mouseX, int mouseY, float delta) {
                 super.render(mouseX, mouseY, delta);
                 RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-                GuiLighting.disable();
+                DiffuseLighting.disable();
                 MinecraftClient.getInstance().getTextureManager().bindTexture(DefaultPlugin.getDisplayTexture());
                 blit(startPoint.x, startPoint.y, 0, 54, 82, 54);
                 int height = MathHelper.ceil((System.currentTimeMillis() / 250 % 14d) / 1f);

+ 2 - 2
src/main/java/me/shedaniel/rei/plugin/crafting/DefaultCraftingCategory.java

@@ -19,7 +19,7 @@ import me.shedaniel.rei.plugin.DefaultPlugin;
 import net.minecraft.block.Blocks;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawableHelper;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.util.Identifier;
 
@@ -64,7 +64,7 @@ public class DefaultCraftingCategory implements TransferRecipeCategory<DefaultCr
             public void render(int mouseX, int mouseY, float delta) {
                 super.render(mouseX, mouseY, delta);
                 RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-                GuiLighting.disable();
+                DiffuseLighting.disable();
                 MinecraftClient.getInstance().getTextureManager().bindTexture(DefaultPlugin.getDisplayTexture());
                 blit(startPoint.x, startPoint.y, 0, 0, 116, 54);
             }

+ 2 - 2
src/main/java/me/shedaniel/rei/plugin/stonecutting/DefaultStoneCuttingCategory.java

@@ -16,7 +16,7 @@ import me.shedaniel.rei.gui.widget.Widget;
 import me.shedaniel.rei.plugin.DefaultPlugin;
 import net.minecraft.block.Blocks;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.util.Identifier;
 
@@ -50,7 +50,7 @@ public class DefaultStoneCuttingCategory implements RecipeCategory<DefaultStoneC
             public void render(int mouseX, int mouseY, float delta) {
                 super.render(mouseX, mouseY, delta);
                 RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-                GuiLighting.disable();
+                DiffuseLighting.disable();
                 MinecraftClient.getInstance().getTextureManager().bindTexture(DefaultPlugin.getDisplayTexture());
                 blit(startPoint.x, startPoint.y, 0, 221, 82, 26);
             }

+ 2 - 2
src/main/java/me/shedaniel/rei/plugin/stripping/DefaultStrippingCategory.java

@@ -15,7 +15,7 @@ import me.shedaniel.rei.gui.widget.RecipeBaseWidget;
 import me.shedaniel.rei.gui.widget.Widget;
 import me.shedaniel.rei.plugin.DefaultPlugin;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.item.Items;
 import net.minecraft.util.Identifier;
@@ -50,7 +50,7 @@ public class DefaultStrippingCategory implements RecipeCategory<DefaultStripping
             public void render(int mouseX, int mouseY, float delta) {
                 super.render(mouseX, mouseY, delta);
                 RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-                GuiLighting.disable();
+                DiffuseLighting.disable();
                 MinecraftClient.getInstance().getTextureManager().bindTexture(DefaultPlugin.getDisplayTexture());
                 blit(startPoint.x, startPoint.y, 0, 221, 82, 26);
             }

+ 43 - 0
src/main/java/me/shedaniel/rei/tests/plugin/REITestPlugin.java

@@ -0,0 +1,43 @@
+package me.shedaniel.rei.tests.plugin;
+
+import me.shedaniel.rei.api.EntryRegistry;
+import me.shedaniel.rei.api.EntryStack;
+import me.shedaniel.rei.api.plugins.REIPluginV0;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.Registry;
+import org.apache.logging.log4j.LogManager;
+import org.jetbrains.annotations.TestOnly;
+
+@TestOnly
+@Deprecated
+public class REITestPlugin implements REIPluginV0 {
+    
+    @Override
+    public void preRegister() {
+        LogManager.getLogger().error("REI Test Plugin is enabled! If you see this unintentionally, please report this!");
+    }
+    
+    @Override
+    public Identifier getPluginIdentifier() {
+        return new Identifier("roughlyenoughitems:test_dev_plugin");
+    }
+    
+    @Override
+    public void registerEntries(EntryRegistry entryRegistry) {
+        for (Item item : Registry.ITEM) {
+            entryRegistry.registerEntryAfter(null, EntryStack.create(item), false);
+            entryRegistry.registerEntryAfter(null, EntryStack.create(item), false);
+            entryRegistry.registerEntryAfter(null, EntryStack.create(item), false);
+            try {
+                for (ItemStack stack : entryRegistry.getAllStacksFromItem(item)) {
+                    entryRegistry.registerEntryAfter(null, EntryStack.create(stack), false);
+                    entryRegistry.registerEntryAfter(null, EntryStack.create(stack), false);
+                    entryRegistry.registerEntryAfter(null, EntryStack.create(stack), false);
+                }
+            } catch (Exception e) {
+            }
+        }
+    }
+}

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

@@ -60,6 +60,8 @@
   "text.rei.choose_page": "Choose Page",
   "text.rei.gamemode_button.tooltip": "Switch Game Mode\n§7Switch to %s mode.\n\n§7Shift-Click to switch in a reverse cycle.",
   "text.rei.weather_button.tooltip": "Switch Weather\n§7Switch to %s.",
+  "text.rei.reload_config": "Reload Config",
+  "text.rei.config.is.reloading": "Config is reloading!",
   "text.rei.enabled": "Yes",
   "text.rei.disabled": "No",
   "text.rei.short_gamemode.survival": "S",
@@ -140,6 +142,7 @@
   "config.roughlyenoughitems.itemCheatingMode.rei_like": "Normal",
   "config.roughlyenoughitems.itemCheatingMode.jei_like": "Inverted",
   "config.roughlyenoughitems.appendModNames": "Append Mod Names:",
+  "config.roughlyenoughitems.snapToRows": "Entry List Snap To Rows:",
   "config.roughlyenoughitems.toastDisplayedOnCopyIdentifier": "Copy Identifier Toast:",
   "config.roughlyenoughitems.scrollingEntryListWidget": "Entry List Action:",
   "config.roughlyenoughitems.scrollingEntryListWidget.boolean.true": "Scrolled",