From 661b3176c0a0bb175d77830859045fa5bdde67c9 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Sun, 5 Jul 2026 20:09:50 +0800 Subject: [PATCH 1/2] fix(rei): restore recipe viewer integration --- gradle.properties | 18 +- .../rei/LycheeREIClientPlugin.java | 136 +++++++++++ .../recipeviewer/rei/LycheeREIPlugin.java | 17 ++ .../rei/category/ReiRvHelper.java | 85 +++++++ .../rei/category/RvCategoryAdapter.java | 217 ++++++++++++++++++ .../rei/category/package-info.java | 4 + .../rei/display/AnvilCraftingDisplay.java | 55 +++++ .../rei/display/LycheeDisplay.java | 83 +++++++ .../rei/display/SimpleLycheeDisplay.java | 25 ++ .../rei/display/package-info.java | 4 + .../rei/element/LycheeEntryWidget.java | 61 +++++ .../rei/element/RenderElementAdapter.java | 104 +++++++++ .../rei/element/package-info.java | 4 + .../PostActionIngredientHelper.java | 93 ++++++++ .../PostActionIngredientRenderer.java | 45 ++++ .../rei/ingredient/package-info.java | 4 + .../compat/recipeviewer/rei/package-info.java | 4 + src/main/resources/fabric.mod.json | 2 + 18 files changed, 952 insertions(+), 9 deletions(-) create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/LycheeREIClientPlugin.java create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/LycheeREIPlugin.java create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/category/ReiRvHelper.java create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/category/RvCategoryAdapter.java create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/category/package-info.java create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/display/AnvilCraftingDisplay.java create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/display/LycheeDisplay.java create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/display/SimpleLycheeDisplay.java create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/display/package-info.java create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/element/LycheeEntryWidget.java create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/element/RenderElementAdapter.java create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/element/package-info.java create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/ingredient/PostActionIngredientHelper.java create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/ingredient/PostActionIngredientRenderer.java create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/ingredient/package-info.java create mode 100644 src/main/java/snownee/lychee/compat/recipeviewer/rei/package-info.java diff --git a/gradle.properties b/gradle.properties index ee5ea9ba..8a6360d6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,10 +3,10 @@ org.gradle.daemon=true # org.gradle.java.home=D:/Program Files/Eclipse Foundation/jdk-17.0.1.12-hotspot # Fabric Properties # check these on https://fabricmc.net/versions.html -minecraft_version=26.1 -archive_minecraft_version=26.1 -supported_version=26.1.X -loader_version=0.18.5 +minecraft_version=26.1.2 +archive_minecraft_version=26.1.2 +supported_version=26.1.2 +loader_version=0.19.3 loom_version=1.15-SNAPSHOT # Mod Properties @@ -17,25 +17,25 @@ maven_group=snownee.lychee archives_base_name=Lychee # Dependencies # https://fabricmc.net/develop/ -fabric_api_version=0.144.3+26.1 +fabric_api_version=0.154.0+26.1.2 # https://modrinth.com/mod/cloth-config/versions cloth_config_version=26.1.154 # https://modrinth.com/mod/modmenu/versions -modmenu_version=18.0.0-alpha.8 +modmenu_version=18.0.0-beta.1 # https://modrinth.com/mod/architectury-api/versions architectury_version=20.0.7 # https://modrinth.com/mod/kiwi/versions -kiwi_version=26.0.2 +kiwi_version=26.0.10 # https://modrinth.com/mod/rei/versions rei_version=26.1.819 # https://modrinth.com/mod/jei/versions # https://modmaven.dev/mezz/jei/ -jei_version=29.2.0.17 +jei_version=29.2.0.21 wthitVersion=12.2.1 milk_lib_version=1.3.61 kubejs_version=2101.7.1-build.181 rhino_version=2001.2.2-build.6 # https://modrinth.com/mod/jade/versions -jade_version=26.0.5 +jade_version=26.1.8 # https://modrinth.com/mod/emi/versions emi_version=1.1.22+1.21.1 \ No newline at end of file diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/LycheeREIClientPlugin.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/LycheeREIClientPlugin.java new file mode 100644 index 00000000..a7f0d608 --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/LycheeREIClientPlugin.java @@ -0,0 +1,136 @@ +package snownee.lychee.compat.recipeviewer.rei; + +import java.util.List; +import java.util.Objects; + +import org.joml.Vector2f; +import org.joml.Vector2fc; + +import dev.architectury.event.EventResult; +import me.shedaniel.math.Point; +import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.api.client.gui.DisplayRenderer; +import me.shedaniel.rei.api.client.gui.widgets.Arrow; +import me.shedaniel.rei.api.client.gui.widgets.Widget; +import me.shedaniel.rei.api.client.plugins.REIClientPlugin; +import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; +import me.shedaniel.rei.api.client.registry.category.extension.CategoryExtensionProvider; +import me.shedaniel.rei.api.client.registry.display.DisplayCategory; +import me.shedaniel.rei.api.client.registry.display.DisplayCategoryView; +import me.shedaniel.rei.api.client.registry.display.DisplayRegistry; +import me.shedaniel.rei.api.common.category.CategoryIdentifier; +import me.shedaniel.rei.api.common.display.Display; +import me.shedaniel.rei.api.common.entry.type.EntryType; +import me.shedaniel.rei.api.common.util.EntryIngredients; +import me.shedaniel.rei.plugin.common.BuiltinPlugin; +import net.minecraft.client.Minecraft; +import net.minecraft.resources.Identifier; +import net.minecraft.world.item.crafting.RecipeMap; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.item.crafting.display.SlotDisplay; +import snownee.lychee.Lychee; +import snownee.lychee.RecipeTypes; +import snownee.lychee.compat.recipeviewer.RvPlugin; +import snownee.lychee.compat.recipeviewer.SlotType; +import snownee.lychee.compat.recipeviewer.category.RvCategory; +import snownee.lychee.compat.recipeviewer.category.RvCategoryInstance; +import snownee.lychee.compat.recipeviewer.rei.category.ReiRvHelper; +import snownee.lychee.compat.recipeviewer.rei.category.RvCategoryAdapter; +import snownee.lychee.compat.recipeviewer.rei.display.AnvilCraftingDisplay; +import snownee.lychee.compat.recipeviewer.rei.display.LycheeDisplay; +import snownee.lychee.compat.recipeviewer.rei.display.SimpleLycheeDisplay; +import snownee.lychee.compat.recipeviewer.rei.element.LycheeEntryWidget; +import snownee.lychee.compat.recipeviewer.rei.element.RenderElementAdapter; +import snownee.lychee.util.ClientProxy; +import snownee.lychee.util.action.PostAction; +import snownee.lychee.util.recipe.ILycheeRecipe; + +public class LycheeREIClientPlugin implements REIClientPlugin { + public static final Identifier ID = Lychee.id("main"); + public static final EntryType POST_ACTION = EntryType.deferred(Lychee.id("post_action")); + private final RvPlugin rvPlugin = new RvPlugin<>(ReiRvHelper.INSTANCE); + + public static LycheeEntryWidget slot(Vector2fc startPoint, int x, int y, SlotType slotType) { + LycheeEntryWidget widget = new LycheeEntryWidget(new Point(startPoint.x() + x + 1, startPoint.y() + y + 1)); + widget.background(slotType.sprite); + return widget; + } + + private static RecipeMap clientRecipes() { + var connection = Minecraft.getInstance().getConnection(); + return connection == null ? RecipeMap.EMPTY : ClientProxy.recipes(connection.recipes()); + } + + @Override + public void registerCategories(CategoryRegistry registry) { + rvPlugin.init(clientRecipes()); + for (RvCategoryInstance rvCategory : rvPlugin.categories().values()) { + var category = new RvCategoryAdapter<>(rvCategory); + registry.add(category); + for (SlotDisplay display : rvCategory.workstations()) { + registry.addWorkstations(category.getCategoryIdentifier(), EntryIngredients.ofSlotDisplay(display)); + } + } + + CategoryExtensionProvider extensionProvider = (display, category, lastView) -> { + if (display instanceof LycheeDisplay lycheeDisplay) { + return new DisplayCategoryView<>() { + @Override + public DisplayRenderer getDisplayRenderer(Display display) { + return lastView.getDisplayRenderer(display); + } + + @Override + public List setupDisplay(Display display, Rectangle bounds) { + List widgets = lastView.setupDisplay(display, bounds); + Vector2fc infoPosition = null; + for (Widget widget : widgets) { + if (widget instanceof Arrow arrow) { + infoPosition = new Vector2f( + arrow.getBounds().getCenterX() - bounds.getX() - 4, + Math.max(arrow.getY() - bounds.getY() - 9, 4)); + break; + } + } + if (infoPosition != null && RvCategory.needInfo(lycheeDisplay.recipeValue())) { + widgets.add(new RenderElementAdapter( + RvCategory.infoIcon((net.minecraft.world.item.crafting.RecipeHolder) lycheeDisplay.recipeHolder()).at(infoPosition), + bounds.getLocation())); + } + return widgets; + } + }; + } + return lastView; + }; + registry.get((CategoryIdentifier) BuiltinPlugin.CRAFTING).registerExtension((CategoryExtensionProvider) extensionProvider); + registry.get((CategoryIdentifier) BuiltinPlugin.ANVIL).registerExtension((CategoryExtensionProvider) extensionProvider); + } + + @Override + public void registerDisplays(DisplayRegistry registry) { + for (RvCategoryInstance instance : rvPlugin.categories().values()) { + var id = CategoryIdentifier.of(instance.id()); + for (var recipe : instance.recipes()) { + registry.add(new SimpleLycheeDisplay<>(recipe, id)); + } + } + + RecipeMap recipeMap = clientRecipes(); + try { + recipeMap.byType(RecipeTypes.ANVIL_CRAFTING).stream() + .filter(it -> !it.value().isSpecial() && !it.value().hideInRecipeViewer()) + .map(AnvilCraftingDisplay::new) + .forEach(registry::add); + } catch (Throwable e) { + Lychee.LOGGER.error("Error when registering REI anvil crafting recipes", e); + } + + registry.registerVisibilityPredicate((DisplayCategory category, Display display) -> { + if (display instanceof LycheeDisplay lycheeDisplay && lycheeDisplay.recipeValue().hideInRecipeViewer()) { + return EventResult.interruptFalse(); + } + return EventResult.pass(); + }); + } +} diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/LycheeREIPlugin.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/LycheeREIPlugin.java new file mode 100644 index 00000000..be7f82d7 --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/LycheeREIPlugin.java @@ -0,0 +1,17 @@ +package snownee.lychee.compat.recipeviewer.rei; + +import me.shedaniel.rei.api.common.entry.type.EntryType; +import me.shedaniel.rei.api.common.entry.type.EntryTypeRegistry; +import me.shedaniel.rei.api.common.plugins.REICommonPlugin; +import snownee.lychee.Lychee; +import snownee.lychee.compat.recipeviewer.rei.ingredient.PostActionIngredientHelper; +import snownee.lychee.util.action.PostAction; + +public class LycheeREIPlugin implements REICommonPlugin { + public static final EntryType POST_ACTION = EntryType.deferred(Lychee.id("post_action")); + + @Override + public void registerEntryTypes(EntryTypeRegistry registry) { + registry.register(POST_ACTION, new PostActionIngredientHelper()); + } +} diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/category/ReiRvHelper.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/category/ReiRvHelper.java new file mode 100644 index 00000000..90bfc0ba --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/category/ReiRvHelper.java @@ -0,0 +1,85 @@ +package snownee.lychee.compat.recipeviewer.rei.category; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import com.mojang.blaze3d.platform.InputConstants; + +import me.shedaniel.clothconfig2.api.ModifierKeyCode; +import me.shedaniel.rei.api.client.config.ConfigObject; +import me.shedaniel.rei.api.client.favorites.FavoriteEntry; +import me.shedaniel.rei.api.client.view.ViewSearchBuilder; +import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.api.common.util.EntryStacks; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.material.Fluid; +import snownee.lychee.compat.recipeviewer.RvHelper; +import snownee.lychee.util.ui.InputAction; + +public class ReiRvHelper extends RvHelper { + public static final ReiRvHelper INSTANCE = new ReiRvHelper(); + + @Override + public boolean doAction(ItemStack stack, InputAction.Direct action) { + if (stack.isEmpty()) { + return false; + } + return doAction(EntryStacks.of(stack), action); + } + + @Override + public boolean doAction(Fluid fluid, InputAction.Direct action) { + return doAction(EntryStacks.of(fluid), action); + } + + @Override + public Optional toDirectAction(InputAction action) { + if (action instanceof InputAction.Direct direct) { + return Optional.of(direct); + } + ConfigObject configObject = ConfigObject.getInstance(); + List> keys = List.of( + Map.entry(configObject.getRecipeKeybind(), InputAction.Direct.SHOW_RECIPES), + Map.entry(configObject.getUsageKeybind(), InputAction.Direct.SHOW_USAGES), + Map.entry(configObject.getFavoriteKeyCode(), InputAction.Direct.FAVORITE)); + InputConstants.Key key = action.keyMapping(); + for (Map.Entry entry : keys) { + if (key.getType() == InputConstants.Type.MOUSE) { + if (entry.getKey().matchesMouse(key.getValue())) { + return Optional.of(entry.getValue()); + } + } else if (key != InputConstants.UNKNOWN && entry.getKey().matchesKey(key.getValue(), key.getValue())) { + return Optional.of(entry.getValue()); + } + } + if (key.getType() == InputConstants.Type.MOUSE) { + int button = key.getValue(); + if (configObject.getRecipeKeybind().getType() != InputConstants.Type.MOUSE && button == 0) { + return Optional.of(InputAction.Direct.SHOW_RECIPES); + } else if (configObject.getUsageKeybind().getType() != InputConstants.Type.MOUSE && button == 1) { + return Optional.of(InputAction.Direct.SHOW_USAGES); + } + } + return Optional.empty(); + } + + @Override + public boolean appendModName() { + return ConfigObject.getInstance().shouldAppendModNames(); + } + + @SuppressWarnings("UnstableApiUsage") + public boolean doAction(EntryStack entry, InputAction.Direct action) { + if (action == InputAction.Direct.SHOW_RECIPES) { + return ViewSearchBuilder.builder().addRecipesFor(entry).open(); + } else if (action == InputAction.Direct.SHOW_USAGES) { + return ViewSearchBuilder.builder().addUsagesFor(entry).open(); + } else if (action == InputAction.Direct.FAVORITE) { + FavoriteEntry favoriteEntry = FavoriteEntry.fromEntryStack(entry); + ConfigObject.getInstance().getFavoriteEntries().add(favoriteEntry); + return true; + } + return false; + } +} diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/category/RvCategoryAdapter.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/category/RvCategoryAdapter.java new file mode 100644 index 00000000..33404497 --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/category/RvCategoryAdapter.java @@ -0,0 +1,217 @@ +package snownee.lychee.compat.recipeviewer.rei.category; + +import java.util.List; + +import org.joml.Vector2f; +import org.joml.Vector2fc; +import org.jspecify.annotations.Nullable; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +import me.shedaniel.math.Point; +import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.api.client.entry.renderer.EntryRenderer; +import me.shedaniel.rei.api.client.gui.Renderer; +import me.shedaniel.rei.api.client.gui.compat.GuiGraphics; +import me.shedaniel.rei.api.client.gui.widgets.Tooltip; +import me.shedaniel.rei.api.client.gui.widgets.Widget; +import me.shedaniel.rei.api.client.gui.widgets.Widgets; +import me.shedaniel.rei.api.client.registry.display.DisplayCategory; +import me.shedaniel.rei.api.common.category.CategoryIdentifier; +import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.api.common.util.EntryIngredients; +import me.shedaniel.rei.api.common.util.EntryStacks; +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.ItemStack; +import snownee.lychee.action.DropItem; +import snownee.lychee.action.RandomSelect; +import snownee.lychee.compat.recipeviewer.RVs; +import snownee.lychee.compat.recipeviewer.SlotType; +import snownee.lychee.compat.recipeviewer.category.RvCategoryInstance; +import snownee.lychee.compat.recipeviewer.category.RvCategoryLayoutBuilder; +import snownee.lychee.compat.recipeviewer.category.RvCategoryWidgetBuilder; +import snownee.lychee.compat.recipeviewer.rei.LycheeREIClientPlugin; +import snownee.lychee.compat.recipeviewer.rei.LycheeREIPlugin; +import snownee.lychee.compat.recipeviewer.rei.display.LycheeDisplay; +import snownee.lychee.compat.recipeviewer.rei.element.RenderElementAdapter; +import snownee.lychee.compat.recipeviewer.rei.ingredient.PostActionIngredientRenderer; +import snownee.lychee.util.action.ActionRenderer; +import snownee.lychee.util.action.CompoundAction; +import snownee.lychee.util.action.PostAction; +import snownee.lychee.util.context.LycheeContext; +import snownee.lychee.util.recipe.ILycheeRecipe; + +public class RvCategoryAdapter> implements DisplayCategory> { + private final RvCategoryInstance instance; + private final CategoryIdentifier> categoryIdentifier; + private final Renderer icon; + + public RvCategoryAdapter(RvCategoryInstance instance) { + this.instance = instance; + this.categoryIdentifier = CategoryIdentifier.of(instance.id()); + this.icon = new RenderElementAdapter(instance.icon()); + } + + private static void slotGroup( + ImmutableList.Builder widgets, + Vector2fc startPoint, + float x, + float y, + List items, + SlotLayoutFunction layoutFunction) { + var size = Math.min(items.size(), 9); + var gridX = (int) Math.ceil(Math.sqrt(size)); + var gridY = (int) Math.ceil((float) size / gridX); + x -= gridX * 9; + y -= gridY * 9; + var index = 0; + for (var i = 0; i < gridY; i++) { + for (var j = 0; j < gridX; j++) { + if (index >= size) { + break; + } + layoutFunction.apply(widgets, startPoint, items.get(index), (int) (x + j * 19), (int) (y + i * 19)); + ++index; + } + } + } + + private static void actionSlot( + ImmutableList.Builder widgets, + Vector2fc startPoint, + PostAction action, + int x, + int y) { + var slot = LycheeREIClientPlugin.slot(startPoint, x, y, action.conditions().showingCount() == 0 ? SlotType.NORMAL : SlotType.CHANCE); + slot.markOutput(); + List> entries = Lists.newArrayList(); + buildActionSlot(entries, action, action); + slot.entries(entries); + widgets.add(slot); + } + + private static void buildActionSlot(List> entries, PostAction rootAction, PostAction action) { + switch (action) { + case DropItem dropItem -> { + EntryStack itemEntry = EntryStacks.of(dropItem.itemStack().create()); + if (action.commonProperties().icon() != null || action.commonProperties().customName() != null || + action.commonProperties().conditions().hasShowingConditions()) { + EntryStack actionEntry = EntryStack.of(LycheeREIPlugin.POST_ACTION, rootAction); + EntryRenderer actionRenderer = actionEntry.getRenderer(); + itemEntry.withRenderer(new EntryRenderer<>() { + @Override + public void render(EntryStack entry, GuiGraphics graphics, Rectangle bounds, int mouseX, int mouseY, float delta) { + actionRenderer.render(actionEntry, graphics, bounds, mouseX, mouseY, delta); + } + + @Override + public @Nullable Tooltip getTooltip(EntryStack entry, me.shedaniel.rei.api.client.gui.widgets.TooltipContext context) { + return null; + } + }); + } + itemEntry.tooltipProcessor((entry, tooltip) -> actionTooltip(rootAction, action, tooltip)); + entries.add(itemEntry); + } + case CompoundAction compoundAction -> + compoundAction.getChildActions().filter(it -> !it.hidden()).forEach(child -> buildActionSlot(entries, rootAction, child)); + default -> entries.add(EntryStack.of(LycheeREIPlugin.POST_ACTION, action)); + } + } + + private static Tooltip actionTooltip(PostAction rootAction, PostAction action, Tooltip tooltip) { + tooltip.entries().clear(); + List list; + var player = Minecraft.getInstance().player; + if (rootAction instanceof RandomSelect randomSelect) { + list = ActionRenderer.getTooltipsFromRandom(randomSelect, action, player); + } else { + list = ActionRenderer.of(rootAction).getTooltips(rootAction, player); + } + tooltip.entries().addAll(list.stream().map(Tooltip::entry).toList()); + return tooltip; + } + + @Override + public CategoryIdentifier> getCategoryIdentifier() { + return categoryIdentifier; + } + + @Override + public Component getTitle() { + return instance.title(); + } + + @Override + public Renderer getIcon() { + return icon; + } + + @Override + public int getDisplayWidth(LycheeDisplay display) { + return instance.width() + 10; + } + + @Override + public int getDisplayHeight() { + return instance.height() + 6; + } + + private void actionGroup(ImmutableList.Builder widgets, Vector2fc startPoint, R recipe, float x, float y) { + slotGroup(widgets, startPoint, x, y, recipe.postActions().stream().filter(it -> !it.hidden()).toList(), RvCategoryAdapter::actionSlot); + } + + private void ingredientGroup(ImmutableList.Builder widgets, Vector2fc startPoint, R recipe, float x, float y) { + var ingredients = RVs.generateShapelessInputs(recipe); + slotGroup( + widgets, startPoint, x, y, ingredients, (widgets0, startPoint0, ingredient, x0, y0) -> { + var slot = LycheeREIClientPlugin.slot(startPoint, x0, y0, ingredient.type); + if (ingredient.ingredient.isEmpty()) { + if (!ingredient.tooltips.isEmpty()) { + slot.entry(EntryStack.of(LycheeREIPlugin.POST_ACTION, PostActionIngredientRenderer.INGREDIENT_HACK_DUMMY)); + } + } else { + slot.entries(EntryIngredients.ofSlotDisplay(ingredient.display())); + } + slot.markInput(); + slot.setExtraTooltips(ingredient.tooltips); + widgets.add(slot); + }); + } + + @Override + public List setupDisplay(LycheeDisplay display, Rectangle bounds) { + var widgets = ImmutableList.builder(); + var startPoint = new Vector2f( + bounds.getCenterX() - (float) instance.width() / 2, + bounds.getCenterY() - (float) instance.height() / 2 + 1); + widgets.add(Widgets.createRecipeBase(bounds)); + var recipeHolder = display instanceof snownee.lychee.compat.recipeviewer.rei.display.SimpleLycheeDisplay simple ? simple.recipeHolder() : null; + if (recipeHolder == null) { + return widgets.build(); + } + var layoutBuilder = new RvCategoryLayoutBuilder.Wrapped<>(instance, recipeHolder) { + @Override + protected void _actionGroup(R recipe, Vector2fc position) { + RvCategoryAdapter.this.actionGroup(widgets, startPoint, recipe, position.x(), position.y()); + } + + @Override + protected void _ingredientGroup(R recipe, Vector2fc position) { + RvCategoryAdapter.this.ingredientGroup(widgets, startPoint, recipe, position.x(), position.y()); + } + }; + instance.type().configureLayout(layoutBuilder, recipeHolder); + var widgetBuilder = new RvCategoryWidgetBuilder<>(instance, recipeHolder); + instance.configureDecorations(widgetBuilder, recipeHolder); + widgetBuilder.sortElements().map(it -> new RenderElementAdapter(it, startPoint)).forEach(widgets::add); + return widgets.build(); + } + + @FunctionalInterface + interface SlotLayoutFunction { + void apply(ImmutableList.Builder widgets, Vector2fc startPoint, T item, int x, int y); + } +} diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/category/package-info.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/category/package-info.java new file mode 100644 index 00000000..d92943f1 --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/category/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package snownee.lychee.compat.recipeviewer.rei.category; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/display/AnvilCraftingDisplay.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/display/AnvilCraftingDisplay.java new file mode 100644 index 00000000..b7f6bbef --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/display/AnvilCraftingDisplay.java @@ -0,0 +1,55 @@ +package snownee.lychee.compat.recipeviewer.rei.display; + +import java.util.List; +import java.util.Objects; + +import me.shedaniel.rei.api.common.entry.EntryIngredient; +import me.shedaniel.rei.api.common.util.EntryIngredients; +import me.shedaniel.rei.plugin.common.displays.anvil.AnvilRecipe; +import me.shedaniel.rei.plugin.common.displays.anvil.DefaultAnvilDisplay; +import net.minecraft.client.Minecraft; +import net.minecraft.resources.Identifier; +import net.minecraft.util.context.ContextMap; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.RecipeHolder; +import net.minecraft.world.item.crafting.display.SlotDisplayContext; +import snownee.lychee.recipes.AnvilCraftingRecipe; + +public class AnvilCraftingDisplay extends DefaultAnvilDisplay implements LycheeDisplay { + private final RecipeHolder lycheeRecipe; + + public AnvilCraftingDisplay(RecipeHolder recipeHolder) { + super(makeRecipe(recipeHolder)); + this.lycheeRecipe = recipeHolder; + } + + private static AnvilRecipe makeRecipe(RecipeHolder recipeHolder) { + var recipe = recipeHolder.value(); + List ingredients = recipe.getIngredients(); + ContextMap context = SlotDisplayContext.fromLevel(Objects.requireNonNull(Minecraft.getInstance().level)); + List left = ingredients.getFirst().display().resolveForStacks(context); + List right = ingredients.size() == 1 ? List.of() : ingredients.getLast().display().resolveForStacks(context) + .stream() + .peek(it -> it.setCount(recipe.materialCost())) + .toList(); + return new AnvilRecipe(recipeHolder.id().identifier(), left, right, List.of(recipe.output().create())); + } + + @Override + public RecipeHolder recipeHolder() { + return lycheeRecipe; + } + + @Override + public List getOutputEntries() { + var ingredients = LycheeDisplay.super.getOutputEntries(); + ingredients.addAll(0, super.getOutputEntries()); + return ingredients; + } + + @Override + public java.util.Optional getDisplayLocation() { + return java.util.Optional.of(lycheeRecipe.id().identifier()); + } +} diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/display/LycheeDisplay.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/display/LycheeDisplay.java new file mode 100644 index 00000000..08a8017b --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/display/LycheeDisplay.java @@ -0,0 +1,83 @@ +package snownee.lychee.compat.recipeviewer.rei.display; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import com.google.common.collect.Lists; + +import dev.architectury.fluid.FluidStack; +import me.shedaniel.rei.api.common.display.Display; +import me.shedaniel.rei.api.common.display.DisplaySerializer; +import me.shedaniel.rei.api.common.entry.EntryIngredient; +import me.shedaniel.rei.api.common.util.EntryIngredients; +import net.minecraft.world.item.crafting.RecipeHolder; +import net.minecraft.resources.Identifier; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; +import snownee.lychee.util.action.PostActionDisplay; +import snownee.lychee.util.predicates.BlockPredicateExtensions; +import snownee.lychee.util.recipe.ILycheeRecipe; + +public interface LycheeDisplay> extends Display { + static List getInputEntries(ILycheeRecipe recipe) { + var ingredients = Lists.newArrayList(EntryIngredients.ofIngredients(recipe.getIngredients())); + recipe.getBlockInputs().stream() + .map(BlockPredicateExtensions::matchedFluids) + .flatMap(Set::stream) + .distinct() + .map(it -> EntryIngredients.of(FluidStack.create(it, FluidStack.bucketAmount()))) + .forEach(ingredients::add); + recipe.getBlockInputs().stream() + .map(BlockPredicateExtensions::matchedBlocks) + .flatMap(Set::stream) + .map(Block::asItem) + .filter(it -> !it.equals(Items.AIR)) + .distinct() + .map(EntryIngredients::of) + .forEach(ingredients::add); + return ingredients; + } + + static List getOutputEntries(ILycheeRecipe recipe) { + var ingredients = Lists.newArrayList(); + recipe.allActions().filter(it -> !it.hidden()) + .map(PostActionDisplay::getOutputItems) + .flatMap(List::stream) + .map(EntryIngredients::ofSlotDisplay) + .forEach(ingredients::add); + recipe.getBlockOutputs().stream() + .map(BlockPredicateExtensions::matchedFluids) + .flatMap(Set::stream) + .distinct() + .map(it -> EntryIngredients.of(FluidStack.create(it, FluidStack.bucketAmount()))) + .forEach(ingredients::add); + return ingredients; + } + + RecipeHolder recipeHolder(); + + default T recipeValue() { + return recipeHolder().value(); + } + + @Override + default List getInputEntries() { + return getInputEntries(recipeValue()); + } + + @Override + default List getOutputEntries() { + return getOutputEntries(recipeValue()); + } + + @Override + default Optional getDisplayLocation() { + return Optional.empty(); + } + + @Override + default DisplaySerializer getSerializer() { + return null; + } +} diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/display/SimpleLycheeDisplay.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/display/SimpleLycheeDisplay.java new file mode 100644 index 00000000..57794bd4 --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/display/SimpleLycheeDisplay.java @@ -0,0 +1,25 @@ +package snownee.lychee.compat.recipeviewer.rei.display; + +import java.util.Optional; + +import me.shedaniel.rei.api.common.category.CategoryIdentifier; +import net.minecraft.resources.Identifier; +import net.minecraft.world.item.crafting.RecipeHolder; +import snownee.lychee.util.recipe.ILycheeRecipe; + +public record SimpleLycheeDisplay>(RecipeHolder recipeHolder, CategoryIdentifier id) implements LycheeDisplay { + @Override + public CategoryIdentifier getCategoryIdentifier() { + return id; + } + + @Override + public T recipeValue() { + return recipeHolder.value(); + } + + @Override + public Optional getDisplayLocation() { + return Optional.of(recipeHolder.id().identifier()); + } +} diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/display/package-info.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/display/package-info.java new file mode 100644 index 00000000..f6add0e2 --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/display/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package snownee.lychee.compat.recipeviewer.rei.display; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/element/LycheeEntryWidget.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/element/LycheeEntryWidget.java new file mode 100644 index 00000000..cf23c02c --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/element/LycheeEntryWidget.java @@ -0,0 +1,61 @@ +package snownee.lychee.compat.recipeviewer.rei.element; + +import java.util.List; + +import org.jspecify.annotations.Nullable; + +import me.shedaniel.math.Point; +import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.api.client.gui.compat.GuiGraphics; +import me.shedaniel.rei.api.client.gui.widgets.Tooltip; +import me.shedaniel.rei.api.client.gui.widgets.TooltipContext; +import me.shedaniel.rei.impl.client.gui.widget.EntryWidget; +import net.minecraft.network.chat.Component; +import snownee.lychee.client.gui.ScreenElement; + +public class LycheeEntryWidget extends EntryWidget { + private @Nullable ScreenElement bg; + private List extraTooltips = List.of(); + + public LycheeEntryWidget(Point point) { + super(point); + } + + public LycheeEntryWidget(Rectangle bounds) { + super(bounds); + } + + @Override + protected void drawBackground(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + if (background) { + super.drawBackground(graphics, mouseX, mouseY, delta); + } else if (bg != null) { + Rectangle rect = getBounds(); + graphics.pose().pushMatrix(); + graphics.pose().translate(rect.x, rect.y); + bg.render(graphics); + graphics.pose().popMatrix(); + } + } + + public void background(ScreenElement bg) { + disableBackground(); + this.bg = bg; + } + + @Override + public @Nullable Tooltip getCurrentTooltip(TooltipContext context) { + Tooltip tooltip = super.getCurrentTooltip(context); + if (!extraTooltips.isEmpty()) { + if (tooltip == null) { + tooltip = Tooltip.create(); + } + tooltip.addAllTexts(extraTooltips); + } + return tooltip; + } + + public void setExtraTooltips(List tooltips) { + extraTooltips = tooltips; + } +} diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/element/RenderElementAdapter.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/element/RenderElementAdapter.java new file mode 100644 index 00000000..62363549 --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/element/RenderElementAdapter.java @@ -0,0 +1,104 @@ +package snownee.lychee.compat.recipeviewer.rei.element; + +import java.util.List; + +import org.joml.Vector2f; +import org.joml.Vector2fc; + + +import me.shedaniel.math.Point; +import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.api.client.gui.compat.GuiGraphics; +import me.shedaniel.rei.api.client.gui.widgets.Tooltip; +import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.client.input.MouseButtonInfo; +import snownee.lychee.client.gui.InteractiveRenderElement; +import snownee.lychee.client.gui.RenderElement; + +public class RenderElementAdapter extends WidgetWithBounds { + private final RenderElement element; + private final Rectangle bounds; + private final Vector2f startPoint; + + public RenderElementAdapter(RenderElement element) { + this.element = element; + this.bounds = new Rectangle(element.x(), element.y(), element.width(), element.height()); + this.startPoint = new Vector2f(0, 0); + } + + public RenderElementAdapter(RenderElement element, Point startPoint) { + this(element); + this.startPoint.set(startPoint.x, startPoint.y); + bounds.x += startPoint.x; + bounds.y += startPoint.y; + } + + public RenderElementAdapter(RenderElement element, Vector2fc startPoint) { + this(element); + this.startPoint.set(startPoint); + bounds.x += (int) startPoint.x(); + bounds.y += (int) startPoint.y(); + } + + @Override + public List children() { + return List.of(); + } + + @Override + public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) { + if (!(element instanceof GuiEventListener listener)) { + return false; + } + double relMouseX = event.x() - startPoint.x(); + double relMouseY = event.y() - startPoint.y(); + return listener.mouseClicked(new MouseButtonEvent(relMouseX, relMouseY, event.buttonInfo()), doubleClick); + } + + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { + if (!(element instanceof GuiEventListener listener)) { + return false; + } + double relMouseX = mouseX - startPoint.x(); + double relMouseY = mouseY - startPoint.y(); + return listener.mouseScrolled(relMouseX, relMouseY, scrollX, scrollY); + } + + @Override + public boolean keyPressed(KeyEvent event) { + if (!(element instanceof GuiEventListener listener)) { + return false; + } + return listener.keyPressed(event); + } + + @Override + public Rectangle getBounds() { + return bounds; + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { + double relMouseX = mouseX - startPoint.x(); + double relMouseY = mouseY - startPoint.y(); + if (element instanceof InteractiveRenderElement interactive) { + interactive.updateHoverState(relMouseX, relMouseY); + if (interactive.isHovered()) { + var tooltip = interactive.getTooltip(); + if (tooltip != null) { + Tooltip.create(tooltip).queue(); + } + } + } + var pose = graphics.pose(); + pose.pushMatrix(); + pose.translate(bounds.x - element.x(), bounds.y - element.y()); + element.render(graphics); + pose.popMatrix(); + } +} diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/element/package-info.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/element/package-info.java new file mode 100644 index 00000000..36839f4b --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/element/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package snownee.lychee.compat.recipeviewer.rei.element; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/ingredient/PostActionIngredientHelper.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/ingredient/PostActionIngredientHelper.java new file mode 100644 index 00000000..bd57e5dd --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/ingredient/PostActionIngredientHelper.java @@ -0,0 +1,93 @@ +package snownee.lychee.compat.recipeviewer.rei.ingredient; + +import java.util.Objects; +import java.util.stream.Stream; + +import org.jspecify.annotations.Nullable; + +import me.shedaniel.rei.api.client.entry.renderer.EntryRenderer; +import me.shedaniel.rei.api.common.entry.EntrySerializer; +import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.api.common.entry.comparison.ComparisonContext; +import me.shedaniel.rei.api.common.entry.type.EntryDefinition; +import me.shedaniel.rei.api.common.entry.type.EntryType; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.tags.TagKey; +import snownee.lychee.LycheeRegistries; +import snownee.lychee.compat.recipeviewer.rei.LycheeREIPlugin; +import snownee.lychee.util.CommonProxy; +import snownee.lychee.util.action.PostAction; + +public class PostActionIngredientHelper implements EntryDefinition { + @Override + public @Nullable String getContainingNamespace(EntryStack entry, PostAction value) { + Identifier key = LycheeRegistries.POST_ACTION.getKey(value.type()); + return key == null ? null : CommonProxy.wrapNamespace(key.getNamespace()); + } + + @Override + public Class getValueType() { + return PostAction.class; + } + + @Override + public EntryType getType() { + return LycheeREIPlugin.POST_ACTION; + } + + @Override + public EntryRenderer getRenderer() { + return PostActionIngredientRenderer.INSTANCE; + } + + @Override + public @Nullable Identifier getIdentifier(EntryStack entry, PostAction value) { + return LycheeRegistries.POST_ACTION.getKey(value.type()); + } + + @Override + public boolean isEmpty(EntryStack entry, PostAction value) { + return value == PostActionIngredientRenderer.INGREDIENT_HACK_DUMMY; + } + + @Override + public PostAction copy(EntryStack entry, PostAction value) { + return value; + } + + @Override + public PostAction normalize(EntryStack entry, PostAction value) { + return copy(entry, value); + } + + @Override + public PostAction wildcard(EntryStack entry, PostAction value) { + return copy(entry, value); + } + + @Override + public long hash(EntryStack entry, PostAction value, ComparisonContext context) { + return Objects.hashCode(value); + } + + @Override + public boolean equals(PostAction o1, PostAction o2, ComparisonContext context) { + return Objects.equals(o1, o2); + } + + @Override + public @Nullable EntrySerializer getSerializer() { + return null; + } + + @Override + public Component asFormattedText(EntryStack entry, PostAction value) { + return PostAction.getDisplayName(value); + } + + @Override + public Stream> getTagsFor(EntryStack entry, PostAction value) { + return Stream.of(); + } +} diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/ingredient/PostActionIngredientRenderer.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/ingredient/PostActionIngredientRenderer.java new file mode 100644 index 00000000..6262617b --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/ingredient/PostActionIngredientRenderer.java @@ -0,0 +1,45 @@ +package snownee.lychee.compat.recipeviewer.rei.ingredient; + +import org.jspecify.annotations.Nullable; + +import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.api.client.entry.renderer.EntryRenderer; +import me.shedaniel.rei.api.client.gui.compat.GuiGraphics; +import me.shedaniel.rei.api.client.gui.widgets.Tooltip; +import me.shedaniel.rei.api.client.gui.widgets.TooltipContext; +import me.shedaniel.rei.api.common.entry.EntryStack; +import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; +import snownee.lychee.action.PlaceBlock; +import snownee.lychee.util.action.ActionRenderer; +import snownee.lychee.util.action.PostAction; +import snownee.lychee.util.action.PostActionCommonProperties; +import snownee.lychee.util.predicates.BlockPredicateExtensions; + +public enum PostActionIngredientRenderer implements EntryRenderer { + INSTANCE; + + public static final PostAction INGREDIENT_HACK_DUMMY = new PlaceBlock( + PostActionCommonProperties.EMPTY, + BlockPredicateExtensions.ANY, + BlockPos.ZERO, + false); + + @Override + public void render(EntryStack entry, GuiGraphics graphics, Rectangle bounds, int mouseX, int mouseY, float delta) { + PostAction action = entry.getValue(); + if (entry.isEmpty() || action == INGREDIENT_HACK_DUMMY) { + return; + } + ActionRenderer.of(action).internalRender(action, graphics, bounds.x, bounds.y); + } + + @Override + public @Nullable Tooltip getTooltip(EntryStack entry, TooltipContext context) { + PostAction action = entry.getValue(); + if (action == INGREDIENT_HACK_DUMMY) { + return null; + } + return Tooltip.create(ActionRenderer.of(action).getTooltips(action, Minecraft.getInstance().player)); + } +} diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/ingredient/package-info.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/ingredient/package-info.java new file mode 100644 index 00000000..1b8bf09b --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/ingredient/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package snownee.lychee.compat.recipeviewer.rei.ingredient; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/rei/package-info.java b/src/main/java/snownee/lychee/compat/recipeviewer/rei/package-info.java new file mode 100644 index 00000000..e447a3ef --- /dev/null +++ b/src/main/java/snownee/lychee/compat/recipeviewer/rei/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package snownee.lychee.compat.recipeviewer.rei; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 228053f2..8c44ba75 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -29,8 +29,10 @@ "snownee.lychee.datagen.LycheeDataGen" ], "rei_common": [ + "snownee.lychee.compat.recipeviewer.rei.LycheeREIPlugin" ], "rei_client": [ + "snownee.lychee.compat.recipeviewer.rei.LycheeREIClientPlugin" ], "jei_mod_plugin": [ "snownee.lychee.compat.recipeviewer.jei.LycheeJEIPlugin" From 69f8e5a374f255c8807c514f9c598f10cf17e486 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Sun, 5 Jul 2026 23:59:24 +0800 Subject: [PATCH 2/2] feat: migrate to NeoForge 26.1.2 --- build.gradle | 125 +++++++++++------- gradle.properties | 21 +-- settings.gradle | 11 +- .../java/snownee/lychee/LycheeRegistries.java | 16 ++- .../snownee/lychee/RecipeBookCategories.java | 6 +- .../snownee/lychee/RecipeSerializers.java | 6 +- src/main/java/snownee/lychee/RecipeTypes.java | 21 +-- .../java/snownee/lychee/SlotDisplayTypes.java | 7 +- .../snownee/lychee/action/CustomAction.java | 4 +- .../lychee/client/gui/GuiGameElement.java | 5 +- .../recipe_api/AlwaysTrueIngredient.java | 25 +--- .../VisualOnlyComponentsIngredient.java | 31 +++-- .../lychee/compat/recipeviewer/RvPlugin.java | 2 +- .../jei/category/JeiRvHelper.java | 3 +- .../lychee/contextual/CustomCondition.java | 4 +- .../snownee/lychee/mixin/EntityMixin.java | 6 +- .../snownee/lychee/mixin/MixinPlugin.java | 4 +- .../lychee/mixin/action/MarkerMixin.java | 2 +- .../mixin/client/GuiEntityRendererMixin.java | 5 +- .../java/snownee/lychee/util/ClientProxy.java | 43 +++--- .../java/snownee/lychee/util/CommonProxy.java | 98 +++++--------- .../lychee/util/action/ActionData.java | 4 +- .../lychee/util/codec/ActionParsers.java | 15 +-- .../lychee/util/codec/LycheeCodecs.java | 20 ++- .../lychee/util/codec/LycheeParserUtils.java | 8 +- .../dripstone/DripstoneParticleService.java | 22 +-- .../resources/META-INF/accesstransformer.cfg | 36 +++++ .../resources/META-INF/neoforge.mods.toml | 55 ++++++++ src/main/resources/fabric.mod.json | 61 --------- 29 files changed, 361 insertions(+), 305 deletions(-) create mode 100644 src/main/resources/META-INF/accesstransformer.cfg create mode 100644 src/main/resources/META-INF/neoforge.mods.toml delete mode 100644 src/main/resources/fabric.mod.json diff --git a/build.gradle b/build.gradle index 2c671176..98d5f0a2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,18 +1,25 @@ plugins { - id 'net.fabricmc.fabric-loom' version "${loom_version}" + id 'java-library' + id 'net.neoforged.moddev' version "${moddevgradle_version}" id 'maven-publish' id "me.modmuss50.mod-publish-plugin" version "1.1.0" } -version = "mc${archive_minecraft_version}-Fabric-${mod_version}" +version = "mc${archive_minecraft_version}-NeoForge-${mod_version}" group = project.maven_group -var realVersion = project.mod_version + '+fabric' +var realVersion = project.mod_version + '+neoforge' base { archivesName = project.archives_base_name } repositories { + maven { url "https://maven.neoforged.net/releases" } + maven { url "https://maven.su5ed.dev/releases" } + maven { + name = "BlameJared" + url = "https://maven.blamejared.com/" + } maven { // location of the maven that hosts JEI files name = "Progwml6 maven" @@ -26,6 +33,13 @@ repositories { maven { url "https://maven.shedaniel.me/" } maven { url "https://maven.terraformersmc.com/releases/" } maven { url "https://maven.terraformersmc.com/" } + maven { + // Shedaniel's maven (Architectury API) + url = "https://maven.architectury.dev" + content { + includeGroup "dev.architectury" + } + } maven { url = "https://mvn.devos.one/snapshots/" } @@ -48,59 +62,79 @@ repositories { includeGroup "maven.modrinth" } } + flatDir { dir 'libs' } } -loom { - accessWidenerPath = file("src/main/resources/${project.mod_id}.classtweaker") -} +neoForge { + version = project.neo_version + + runs { + client { + client() + ideName = "Run ${project.mod_id} - Client" + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + server { + server() + ideName = "Run ${project.mod_id} - Server" + programArgument '--nogui' + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + gameTestServer { + type = "gameTestServer" + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + data { + clientData() + ideName = "Run ${project.mod_id} - Data" + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } + } -fabricApi { - configureDataGeneration() { - modId = project.mod_id - client = true - outputDirectory = file("src/generated/resources") - createSourceSet = true - strictValidation = true + mods { + "${mod_id}" { + sourceSet(sourceSets.main) + } } } +sourceSets.main.resources { srcDir 'src/generated/resources' } + dependencies { - minecraft "com.mojang:minecraft:${project.minecraft_version}" - implementation "net.fabricmc:fabric-loader:${project.loader_version}" - implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}" + implementation "org.sinytra.forgified-fabric-api:forgified-fabric-api:${project.fabric_version}" + jarJar(implementation("org.sinytra.forgified-fabric-api:fabric-particles-v1:${project.forgified_fabric_particles_version}")) - implementation("dev.architectury:architectury-fabric:${project.architectury_version}") - api("me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config_version}") { - exclude(group: "net.fabricmc.fabric-api") - } - implementation "com.terraformersmc:modmenu:${project.modmenu_version}" + implementation "dev.architectury:architectury-neoforge:${project.architectury_version}" + implementation "maven.modrinth:cloth-config:${project.cloth_config_version}+neoforge" + implementation "maven.modrinth:kiwi:${project.kiwi_version}+neoforge" + annotationProcessor "maven.modrinth:kiwi:${project.kiwi_version}+neoforge" + + implementation "maven.modrinth:jade:${project.jade_version}+neoforge" + implementation "me.shedaniel:RoughlyEnoughItems-neoforge:${project.rei_version}" + compileOnly "me.shedaniel:REIPluginCompatibilities-forge-annotations:12.+" - implementation "me.shedaniel:RoughlyEnoughItems-fabric:${project.rei_version}" + implementation "mezz.jei:jei-${project.jei_minecraft_version}-common-api:${project.jei_version}" + implementation "mezz.jei:jei-${project.jei_minecraft_version}-neoforge-api:${project.jei_version}" + implementation "mezz.jei:jei-${project.jei_minecraft_version}-neoforge:${project.jei_version}" - // https://modrinth.com/mod/kiwi/versions - implementation annotationProcessor("maven.modrinth:kiwi:${project.kiwi_version}+fabric") implementation fileTree(dir: "libs", include: ["*.jar"]) annotationProcessor fileTree(dir: "libs", include: ["*.jar"]) -// compileOnlyApi "mezz.jei:jei-${minecraft_version}-fabric-api:${jei_version}" - implementation "mezz.jei:jei-26.1-fabric:${jei_version}" - - // https://modrinth.com/mod/jade/versions - implementation "maven.modrinth:jade:${jade_version}+fabric" - implementation 'com.google.errorprone:error_prone_annotations:2.36.0' - -// compileOnly "dev.latvian.mods:kubejs-neoforge:${kubejs_version}" - -// compileOnly "dev.emi:emi-fabric:${emi_version}:api" -// implementation "dev.emi:emi-fabric:${emi_version}" } processResources { inputs.property "version", realVersion + inputs.property "neo_version_range", project.neo_version_range + inputs.property "minecraft_version_range", project.minecraft_version_range - filesMatching("fabric.mod.json") { - expand "version": realVersion + filesMatching("META-INF/neoforge.mods.toml") { + expand "file": ["jarVersion": realVersion], + "neo_version_range": project.neo_version_range, + "minecraft_version_range": project.minecraft_version_range } } @@ -133,36 +167,25 @@ jar { } } -configurations.configureEach { - resolutionStrategy { - force "dev.architectury:architectury-fabric:${project.architectury_version}" - } -} - publishMods { file = jar.archiveFile changelog = file('CHANGELOG.md').exists() ? file('CHANGELOG.md').text : '' type = ['release': STABLE, 'beta': BETA, 'alpha': ALPHA][release_type] - modLoaders.add("fabric") - modLoaders.add("quilt") - displayName = "[Fabric ${supported_version}] ${mod_version}" + modLoaders.add("neoforge") + displayName = "[NeoForge ${supported_version}] ${mod_version}" version = realVersion curseforge { projectId = "574650" accessToken = providers.environmentVariable("CURSE_TOKEN") minecraftVersions.add(minecraft_version) - minecraftVersions.add("26.1.1") - minecraftVersions.add("26.1.2") - requires("kiwi-fabric") + requires("kiwi") optional("cloth-config") } modrinth { projectId = "TdN6LxjM" accessToken = providers.environmentVariable("MODRINTH_TOKEN") minecraftVersions.add(minecraft_version) - minecraftVersions.add("26.1.1") - minecraftVersions.add("26.1.2") requires("kiwi") optional("cloth-config") } diff --git a/gradle.properties b/gradle.properties index 8a6360d6..906d0ed7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,14 @@ org.gradle.jvmargs=-Xmx3G -Xms3G -Duser.language=en org.gradle.daemon=true # org.gradle.java.home=D:/Program Files/Eclipse Foundation/jdk-17.0.1.12-hotspot -# Fabric Properties -# check these on https://fabricmc.net/versions.html +# Minecraft / Loader Properties minecraft_version=26.1.2 archive_minecraft_version=26.1.2 supported_version=26.1.2 -loader_version=0.19.3 -loom_version=1.15-SNAPSHOT +moddevgradle_version=2.0.141 +neo_version=26.1.2.77 +neo_version_range=[26.1.2,) +minecraft_version_range=[26.1.2,26.2) # Mod Properties mod_version=26.0.2 @@ -16,8 +17,9 @@ mod_id=lychee maven_group=snownee.lychee archives_base_name=Lychee # Dependencies -# https://fabricmc.net/develop/ -fabric_api_version=0.154.0+26.1.2 +# https://github.com/Sinytra/ForgifiedFabricAPI +fabric_version=0.154.0+3.1.0+26.1.2 +forgified_fabric_particles_version=5.0.15+77a9369f4c # https://modrinth.com/mod/cloth-config/versions cloth_config_version=26.1.154 # https://modrinth.com/mod/modmenu/versions @@ -25,12 +27,13 @@ modmenu_version=18.0.0-beta.1 # https://modrinth.com/mod/architectury-api/versions architectury_version=20.0.7 # https://modrinth.com/mod/kiwi/versions -kiwi_version=26.0.10 +kiwi_version=26.0.9 # https://modrinth.com/mod/rei/versions rei_version=26.1.819 # https://modrinth.com/mod/jei/versions -# https://modmaven.dev/mezz/jei/ -jei_version=29.2.0.21 +# https://maven.blamejared.com/mezz/jei/ +jei_version=29.12.0.40 +jei_minecraft_version=26.1.2 wthitVersion=12.2.1 milk_lib_version=1.3.61 kubejs_version=2101.7.1-build.181 diff --git a/settings.gradle b/settings.gradle index f91a4fe7..463e3044 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,9 +1,12 @@ pluginManagement { repositories { - maven { - name = 'Fabric' - url = 'https://maven.fabricmc.net/' - } + mavenLocal() gradlePluginPortal() + maven { url 'https://maven.neoforged.net/releases' } + maven { url "https://maven.architectury.dev/" } } } + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.9.0' +} diff --git a/src/main/java/snownee/lychee/LycheeRegistries.java b/src/main/java/snownee/lychee/LycheeRegistries.java index fdf9842f..5d9d6e7b 100644 --- a/src/main/java/snownee/lychee/LycheeRegistries.java +++ b/src/main/java/snownee/lychee/LycheeRegistries.java @@ -2,8 +2,8 @@ import com.mojang.serialization.Codec; -import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder; -import net.fabricmc.fabric.api.event.registry.RegistryAttribute; +import net.neoforged.neoforge.registries.NewRegistryEvent; +import net.neoforged.neoforge.registries.RegistryBuilder; import net.minecraft.core.MappedRegistry; import net.minecraft.resources.ResourceKey; import snownee.lychee.util.action.PostActionType; @@ -18,9 +18,15 @@ public final class LycheeRegistries { public static final MappedRegistry> CONTEXT_SERIALIZER = register("context_serializer"); public static final MappedRegistry> UI_ELEMENT = register("ui_element"); + public static void init(NewRegistryEvent event) { + event.register(CONTEXTUAL); + event.register(POST_ACTION); + event.register(CONTEXT); + event.register(CONTEXT_SERIALIZER); + event.register(UI_ELEMENT); + } + private static MappedRegistry register(String id) { - return FabricRegistryBuilder.create(ResourceKey.createRegistryKey(Lychee.id(id))) - .attribute(RegistryAttribute.SYNCED) - .buildAndRegister(); + return (MappedRegistry) new RegistryBuilder<>(ResourceKey.createRegistryKey(Lychee.id(id))).sync(true).create(); } } diff --git a/src/main/java/snownee/lychee/RecipeBookCategories.java b/src/main/java/snownee/lychee/RecipeBookCategories.java index ce80982c..81e5f6de 100644 --- a/src/main/java/snownee/lychee/RecipeBookCategories.java +++ b/src/main/java/snownee/lychee/RecipeBookCategories.java @@ -1,13 +1,15 @@ package snownee.lychee; -import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; +import net.neoforged.neoforge.registries.DeferredRegister; import net.minecraft.world.item.crafting.RecipeBookCategory; public class RecipeBookCategories { + public static final DeferredRegister RECIPE_BOOK_CATEGORIES = DeferredRegister.create(BuiltInRegistries.RECIPE_BOOK_CATEGORY, Lychee.ID); public static final RecipeBookCategory UNLISTED = register("unlisted", new RecipeBookCategory()); public static T register(String id, T serializer) { - return Registry.register(BuiltInRegistries.RECIPE_BOOK_CATEGORY, Lychee.id(id), serializer); + RECIPE_BOOK_CATEGORIES.register(id, () -> serializer); + return serializer; } } diff --git a/src/main/java/snownee/lychee/RecipeSerializers.java b/src/main/java/snownee/lychee/RecipeSerializers.java index 22318b84..ce103412 100644 --- a/src/main/java/snownee/lychee/RecipeSerializers.java +++ b/src/main/java/snownee/lychee/RecipeSerializers.java @@ -5,8 +5,8 @@ import com.google.common.collect.Lists; import com.mojang.serialization.MapCodec; -import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; +import net.neoforged.neoforge.registries.DeferredRegister; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.world.item.crafting.Recipe; @@ -29,6 +29,7 @@ import snownee.lychee.util.ui.CategoryModifier; public final class RecipeSerializers { + public static final DeferredRegister> RECIPE_SERIALIZERS = DeferredRegister.create(BuiltInRegistries.RECIPE_SERIALIZER, Lychee.ID); public static final List> ALL = Lists.newArrayList(); public static final RecipeSerializer CATEGORY_METADATA = register( "category_metadata", @@ -101,7 +102,8 @@ public static > RecipeSerializer register( StreamCodec streamCodec) { RecipeSerializer serializer = new RecipeSerializer<>(codec, streamCodec); ALL.add(serializer); - return Registry.register(BuiltInRegistries.RECIPE_SERIALIZER, Lychee.id(id), serializer); + RECIPE_SERIALIZERS.register(id, () -> serializer); + return serializer; } } diff --git a/src/main/java/snownee/lychee/RecipeTypes.java b/src/main/java/snownee/lychee/RecipeTypes.java index e938eff3..14b19495 100644 --- a/src/main/java/snownee/lychee/RecipeTypes.java +++ b/src/main/java/snownee/lychee/RecipeTypes.java @@ -5,13 +5,13 @@ import com.google.common.collect.Sets; -import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.Identifier; import net.minecraft.util.Util; import net.minecraft.world.item.crafting.RecipeMap; import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; +import net.neoforged.neoforge.registries.DeferredRegister; import snownee.lychee.recipes.AnvilCraftingRecipe; import snownee.lychee.recipes.BlockClickingRecipe; import snownee.lychee.recipes.BlockCrushingRecipe; @@ -46,6 +46,7 @@ public final class RecipeTypes { Objects.requireNonNull(LycheeLootContextParamSets.ALL); } + public static final DeferredRegister> RECIPE_TYPES = DeferredRegister.create(BuiltInRegistries.RECIPE_TYPE, Lychee.ID); public static final Set>> ALL = Sets.newLinkedHashSet(); public static final RecipeType CATEGORY_METADATA = register("category_metadata"); @@ -138,7 +139,8 @@ public final class RecipeTypes { public static >> T register(T recipeType) { ALL.add(recipeType); - return Registry.register(BuiltInRegistries.RECIPE_TYPE, recipeType.id, recipeType); + RECIPE_TYPES.register(recipeType.id.getPath(), () -> recipeType); + return recipeType; } public static void buildCache(RecipeMap recipeMap) { @@ -154,13 +156,14 @@ public static void buildCache(RecipeMap recipeMap) { @SuppressWarnings("unchecked") public static > T register(String name) { Identifier id = Lychee.id(name); - return (T) Registry.register( - BuiltInRegistries.RECIPE_TYPE, id, new RecipeType<>() { - @Override - public String toString() { - return id.toString(); - } - }); + T recipeType = (T) new RecipeType<>() { + @Override + public String toString() { + return id.toString(); + } + }; + RECIPE_TYPES.register(name, () -> recipeType); + return recipeType; } } diff --git a/src/main/java/snownee/lychee/SlotDisplayTypes.java b/src/main/java/snownee/lychee/SlotDisplayTypes.java index 60b7aa2b..9b813712 100644 --- a/src/main/java/snownee/lychee/SlotDisplayTypes.java +++ b/src/main/java/snownee/lychee/SlotDisplayTypes.java @@ -2,16 +2,17 @@ import com.mojang.serialization.MapCodec; -import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.Identifier; +import net.neoforged.neoforge.registries.DeferredRegister; import net.minecraft.world.item.crafting.display.SlotDisplay; import snownee.lychee.compat.recipe_api.VisualOnlyComponentsIngredient; import snownee.lychee.util.Displays; public class SlotDisplayTypes { + public static final DeferredRegister> SLOT_DISPLAYS = DeferredRegister.create(BuiltInRegistries.SLOT_DISPLAY, Lychee.ID); public static final SlotDisplay.Type VISUAL_ONLY = register( "visual_only", VisualOnlyComponentsIngredient.Display.CODEC, @@ -27,6 +28,8 @@ public static SlotDisplay.Type register( MapCodec codec, StreamCodec streamCodec) { Identifier id = Lychee.id(name); - return Registry.register(BuiltInRegistries.SLOT_DISPLAY, id, new SlotDisplay.Type<>(codec, streamCodec)); + SlotDisplay.Type type = new SlotDisplay.Type<>(codec, streamCodec); + SLOT_DISPLAYS.register(name, () -> type); + return type; } } diff --git a/src/main/java/snownee/lychee/action/CustomAction.java b/src/main/java/snownee/lychee/action/CustomAction.java index 3f6687a6..e4c5b026 100644 --- a/src/main/java/snownee/lychee/action/CustomAction.java +++ b/src/main/java/snownee/lychee/action/CustomAction.java @@ -11,7 +11,7 @@ import net.minecraft.network.chat.Component; import net.minecraft.util.ExtraCodecs; -import snownee.kiwi.util.codec.KCodecs; +import snownee.lychee.util.codec.LycheeCodecs; import snownee.lychee.context.ActionContext; import snownee.lychee.util.CommonProxy; import snownee.lychee.util.action.PostAction; @@ -91,7 +91,7 @@ public static class Type implements PostActionType { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( PostActionCommonProperties.MAP_CODEC.forGetter(CustomAction::commonProperties), ExtraCodecs.NON_EMPTY_STRING.fieldOf("id").forGetter(CustomAction::id), - ExtraCodecs.JSON.comapFlatMap(it -> KCodecs.tryCatch(it::getAsJsonObject), Function.identity()) + ExtraCodecs.JSON.comapFlatMap(it -> LycheeCodecs.tryCatch(it::getAsJsonObject), Function.identity()) .optionalFieldOf("data", new JsonObject()) .forGetter(CustomAction::data), Codec.BOOL.optionalFieldOf("repeatable", true).forGetter(CustomAction::repeatable), diff --git a/src/main/java/snownee/lychee/client/gui/GuiGameElement.java b/src/main/java/snownee/lychee/client/gui/GuiGameElement.java index 40a688c9..4e54acf2 100644 --- a/src/main/java/snownee/lychee/client/gui/GuiGameElement.java +++ b/src/main/java/snownee/lychee/client/gui/GuiGameElement.java @@ -10,6 +10,7 @@ import com.mojang.blaze3d.platform.Lighting; import com.mojang.math.Transformation; +import net.fabricmc.fabric.api.client.rendering.v1.FabricRenderState; import net.fabricmc.fabric.api.client.rendering.v1.RenderStateDataKey; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphicsExtractor; @@ -129,10 +130,10 @@ public GuiBlockRenderBuilder(@Nullable BlockState blockState, @Nullable GuiRende renderState = new BlockDisplayEntityRenderState(); renderState.entityType = EntityType.BLOCK_DISPLAY; renderState.renderState = createFreshRenderState(); - renderState.setData(CUSTOM_LIGHTING, Lighting.Entry.ITEMS_FLAT); + ((FabricRenderState) renderState).setData(CUSTOM_LIGHTING, Lighting.Entry.ITEMS_FLAT); if (!blockState.getFluidState().isEmpty()) { - renderState.setData(DRAW_FLUID_STATE, Unit.INSTANCE); + ((FabricRenderState) renderState).setData(DRAW_FLUID_STATE, Unit.INSTANCE); } this.blockState = this.blockState.rotate(Rotation.CLOCKWISE_180); diff --git a/src/main/java/snownee/lychee/compat/recipe_api/AlwaysTrueIngredient.java b/src/main/java/snownee/lychee/compat/recipe_api/AlwaysTrueIngredient.java index 5d37698d..9b6cd12b 100644 --- a/src/main/java/snownee/lychee/compat/recipe_api/AlwaysTrueIngredient.java +++ b/src/main/java/snownee/lychee/compat/recipe_api/AlwaysTrueIngredient.java @@ -1,24 +1,16 @@ package snownee.lychee.compat.recipe_api; -import java.util.stream.Stream; +import java.util.List; import com.mojang.serialization.MapCodec; -import net.minecraft.core.Holder; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.Identifier; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.ItemStackTemplate; -import net.minecraft.world.item.Items; -import net.minecraft.world.item.crafting.display.SlotDisplay; -import net.minecraft.world.level.block.TestBlock; -import net.minecraft.world.level.block.state.properties.TestBlockMode; import snownee.kiwi.recipe.CustomIngredient; import snownee.kiwi.recipe.CustomIngredientSerializer; import snownee.lychee.Lychee; -import snownee.lychee.util.Displays; public class AlwaysTrueIngredient implements CustomIngredient { public static final Identifier ID = Lychee.id("always_true"); @@ -30,15 +22,8 @@ public boolean test(ItemStack stack) { } @Override - public Stream> items() { - return Stream.empty(); - } - - @Override - public SlotDisplay display() { - return Displays.slot(ItemStackTemplate.fromNonEmptyStack(TestBlock.setModeOnStack( - new ItemStack(Items.TEST_BLOCK), - TestBlockMode.ACCEPT))); + public List getMatchingStacks() { + return List.of(); } @Override @@ -62,12 +47,12 @@ public Identifier getIdentifier() { } @Override - public MapCodec getCodec() { + public MapCodec getCodec(boolean allowEmpty) { return CODEC; } @Override - public StreamCodec getStreamCodec() { + public StreamCodec getPacketCodec() { return STREAM_CODEC; } } diff --git a/src/main/java/snownee/lychee/compat/recipe_api/VisualOnlyComponentsIngredient.java b/src/main/java/snownee/lychee/compat/recipe_api/VisualOnlyComponentsIngredient.java index 0812c978..d2d0f5ea 100644 --- a/src/main/java/snownee/lychee/compat/recipe_api/VisualOnlyComponentsIngredient.java +++ b/src/main/java/snownee/lychee/compat/recipe_api/VisualOnlyComponentsIngredient.java @@ -7,21 +7,18 @@ import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; -import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredient; -import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredientSerializer; -import net.fabricmc.fabric.api.recipe.v1.ingredient.FabricIngredient; -import net.minecraft.core.Holder; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.Identifier; import net.minecraft.util.context.ContextMap; import net.minecraft.world.flag.FeatureFlagSet; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.display.DisplayContentsFactory; import net.minecraft.world.item.crafting.display.SlotDisplay; +import snownee.kiwi.recipe.CustomIngredient; +import snownee.kiwi.recipe.CustomIngredientSerializer; import snownee.lychee.Lychee; import snownee.lychee.SlotDisplayTypes; @@ -47,19 +44,21 @@ public boolean test(ItemStack stack) { } @Override - public Stream> items() { - return Stream.empty(); - } - - @Override - public SlotDisplay display() { - return new Display(base.display(), components); + public List getMatchingStacks() { + return base.items() + .map(holder -> new ItemStack(holder.value())) + .map(stack -> { + ItemStack copy = stack.copy(); + copy.applyComponents(components); + return copy; + }) + .filter(base::test) + .toList(); } @Override public boolean requiresTesting() { - // TODO Fabric recipe api interface injection isn't working now - return ((FabricIngredient) (Object) base).requiresTesting(); + return !base.isSimple(); } @Override @@ -97,12 +96,12 @@ public Identifier getIdentifier() { } @Override - public MapCodec getCodec() { + public MapCodec getCodec(boolean allowEmpty) { return CODEC; } @Override - public StreamCodec getStreamCodec() { + public StreamCodec getPacketCodec() { return STREAM_CODEC; } } diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/RvPlugin.java b/src/main/java/snownee/lychee/compat/recipeviewer/RvPlugin.java index f773ab83..fc1f5094 100644 --- a/src/main/java/snownee/lychee/compat/recipeviewer/RvPlugin.java +++ b/src/main/java/snownee/lychee/compat/recipeviewer/RvPlugin.java @@ -135,7 +135,7 @@ public void init(RecipeMap recipeMap) { AllGuiTextures.DOWN_ARROW, Suppliers.memoize(() -> RVs.getIconBlock(category.recipes()))); }); - CommonProxy.RECIPE_CATEGORY_EVENT.invoker().accept(this); + CommonProxy.postRecipeCategoryEvent(this); for (var recipeType : RecipeTypes.ALL) { //noinspection unchecked diff --git a/src/main/java/snownee/lychee/compat/recipeviewer/jei/category/JeiRvHelper.java b/src/main/java/snownee/lychee/compat/recipeviewer/jei/category/JeiRvHelper.java index e54579ef..714373b6 100644 --- a/src/main/java/snownee/lychee/compat/recipeviewer/jei/category/JeiRvHelper.java +++ b/src/main/java/snownee/lychee/compat/recipeviewer/jei/category/JeiRvHelper.java @@ -11,7 +11,6 @@ import com.mojang.blaze3d.platform.InputConstants; import mezz.jei.api.constants.VanillaTypes; -import mezz.jei.api.fabric.ingredients.fluids.IJeiFluidIngredient; import mezz.jei.api.helpers.IJeiHelpers; import mezz.jei.api.helpers.IPlatformFluidHelper; import mezz.jei.api.recipe.RecipeIngredientRole; @@ -57,7 +56,7 @@ public boolean doAction(Fluid fluid, InputAction.Direct action) { var recipesGui = runtime.getRecipesGui(); var helpers = runtime.getJeiHelpers(); //noinspection unchecked - var fluidHelper = (IPlatformFluidHelper) helpers.getPlatformFluidHelper(); + var fluidHelper = (IPlatformFluidHelper) helpers.getPlatformFluidHelper(); var focusFactory = helpers.getFocusFactory(); var role = action == InputAction.Direct.SHOW_USAGES ? RecipeIngredientRole.INPUT : RecipeIngredientRole.OUTPUT; //noinspection deprecation diff --git a/src/main/java/snownee/lychee/contextual/CustomCondition.java b/src/main/java/snownee/lychee/contextual/CustomCondition.java index 5ac2e10e..32f4e7d6 100644 --- a/src/main/java/snownee/lychee/contextual/CustomCondition.java +++ b/src/main/java/snownee/lychee/contextual/CustomCondition.java @@ -17,7 +17,7 @@ import net.minecraft.util.TriState; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; -import snownee.kiwi.util.codec.KCodecs; +import snownee.lychee.util.codec.LycheeCodecs; import snownee.lychee.context.ActionContext; import snownee.lychee.util.CommonProxy; import snownee.lychee.util.context.LycheeContext; @@ -75,7 +75,7 @@ public static class Type implements ContextualConditionType { // TODO 需要测试 public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( ExtraCodecs.NON_EMPTY_STRING.fieldOf("id").forGetter(CustomCondition::id), - ExtraCodecs.JSON.comapFlatMap(it -> KCodecs.tryCatch(it::getAsJsonObject), Function.identity()) + ExtraCodecs.JSON.comapFlatMap(it -> LycheeCodecs.tryCatch(it::getAsJsonObject), Function.identity()) .optionalFieldOf("data", new JsonObject()) .forGetter(CustomCondition::data) ).apply(instance, CustomCondition::new)); diff --git a/src/main/java/snownee/lychee/mixin/EntityMixin.java b/src/main/java/snownee/lychee/mixin/EntityMixin.java index 90b1999a..ed6ad804 100644 --- a/src/main/java/snownee/lychee/mixin/EntityMixin.java +++ b/src/main/java/snownee/lychee/mixin/EntityMixin.java @@ -25,9 +25,9 @@ public abstract class EntityMixin implements TypedInstance> { @Inject( at = @At( value = "INVOKE", - target = "Lnet/minecraft/world/entity/Entity;hurtServer(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)Z" - ), method = "thunderHit", cancellable = true - ) + target = "Lnet/minecraft/world/entity/Entity;hurt(Lnet/minecraft/world/damagesource/DamageSource;F)V"), + method = "thunderHit", + cancellable = true) private void lychee_thunderHit_hurt(ServerLevel level, LightningBolt lightningBolt, CallbackInfo ci) { if (is(LycheeTags.LIGHTNING_IMMUNE)) { ci.cancel(); diff --git a/src/main/java/snownee/lychee/mixin/MixinPlugin.java b/src/main/java/snownee/lychee/mixin/MixinPlugin.java index 2f00355a..e0fd6ab8 100644 --- a/src/main/java/snownee/lychee/mixin/MixinPlugin.java +++ b/src/main/java/snownee/lychee/mixin/MixinPlugin.java @@ -8,11 +8,11 @@ import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; -import net.fabricmc.loader.api.FabricLoader; +import net.neoforged.fml.loading.LoadingModList; public class MixinPlugin implements IMixinConfigPlugin { private static boolean hasMod(String modId) { - return FabricLoader.getInstance().isModLoaded(modId); + return LoadingModList.get().getModFileById(modId) != null; } @Override diff --git a/src/main/java/snownee/lychee/mixin/action/MarkerMixin.java b/src/main/java/snownee/lychee/mixin/action/MarkerMixin.java index 024c2c44..02d29ca0 100644 --- a/src/main/java/snownee/lychee/mixin/action/MarkerMixin.java +++ b/src/main/java/snownee/lychee/mixin/action/MarkerMixin.java @@ -69,7 +69,7 @@ private void lychee_tick(CallbackInfo ci) { @Inject(at = @At("HEAD"), method = "readAdditionalSaveData") private void lychee_readAdditionalSaveData(ValueInput input, CallbackInfo ci) { - if (!input.contains("lychee")) { + if (input.child(Lychee.ID).isEmpty()) { return; } Optional result = input.read(Lychee.ID, ActionData.Builder.CODEC); diff --git a/src/main/java/snownee/lychee/mixin/client/GuiEntityRendererMixin.java b/src/main/java/snownee/lychee/mixin/client/GuiEntityRendererMixin.java index ae11baf7..26863a0d 100644 --- a/src/main/java/snownee/lychee/mixin/client/GuiEntityRendererMixin.java +++ b/src/main/java/snownee/lychee/mixin/client/GuiEntityRendererMixin.java @@ -7,6 +7,7 @@ import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Local; import com.mojang.blaze3d.platform.Lighting; +import net.fabricmc.fabric.api.client.rendering.v1.FabricRenderState; import net.minecraft.client.gui.render.pip.GuiEntityRenderer; import net.minecraft.client.renderer.feature.FeatureRenderDispatcher; @@ -26,7 +27,7 @@ private void lychee_setCustomLighting( Lighting.Entry entry, Operation original, @Local(argsOnly = true) GuiEntityRenderState entityState) { - original.call(lighting, entityState.renderState().getDataOrDefault(GuiGameElement.CUSTOM_LIGHTING, entry)); + original.call(lighting, ((FabricRenderState) entityState.renderState()).getDataOrDefault(GuiGameElement.CUSTOM_LIGHTING, entry)); } @WrapOperation( @@ -37,7 +38,7 @@ private void lychee_setRenderFluidFlag( FeatureRenderDispatcher featureRenderDispatcher, Operation original, @Local(argsOnly = true) GuiEntityRenderState entityState) { - boolean drawFluid = entityState.renderState().getData(GuiGameElement.DRAW_FLUID_STATE) != null; + boolean drawFluid = ((FabricRenderState) entityState.renderState()).getData(GuiGameElement.DRAW_FLUID_STATE) != null; if (drawFluid) { GuiGameElement.DRAW_FLUID_STATE_FLAG.set(Unit.INSTANCE); original.call(featureRenderDispatcher); diff --git a/src/main/java/snownee/lychee/util/ClientProxy.java b/src/main/java/snownee/lychee/util/ClientProxy.java index 74db3b3a..440683d4 100644 --- a/src/main/java/snownee/lychee/util/ClientProxy.java +++ b/src/main/java/snownee/lychee/util/ClientProxy.java @@ -1,14 +1,19 @@ package snownee.lychee.util; import java.text.MessageFormat; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.Objects; import org.jspecify.annotations.Nullable; -import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.particle.v1.ParticleProviderRegistry; +import net.fabricmc.fabric.api.recipe.v1.FabricRecipeAccess; import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant; import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariantAttributes; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.common.Mod; import net.minecraft.client.Minecraft; import net.minecraft.client.resources.language.I18n; import net.minecraft.network.chat.Component; @@ -20,7 +25,7 @@ import net.minecraft.world.item.crafting.RecipeMap; import net.minecraft.world.level.material.Fluid; import snownee.kiwi.loader.Platform; -import snownee.kiwi.util.KEvent; +import snownee.lychee.Lychee; import snownee.lychee.client.gui.InteractiveRenderElement; import snownee.lychee.util.action.ActionRenderer; import snownee.lychee.util.particles.dripstone.DripstoneParticleService; @@ -28,18 +33,11 @@ import snownee.lychee.util.ui.ElementRenderer; import snownee.lychee.util.ui.InputAction; -public class ClientProxy implements ClientModInitializer { - private static final KEvent RECIPE_VIEWER_WIDGET_INPUT_EVENT = - KEvent.createArrayBacked( - RecipeViewerWidgetInputListener.class, listeners -> (recipe, location, action) -> { - for (var listener : listeners) { - if (listener.on(recipe, location, action)) { - return true; - } - } - return false; - }); +@Mod(value = Lychee.ID, dist = Dist.CLIENT) +public class ClientProxy { + + private static final List RECIPE_VIEWER_WIDGET_INPUT_LISTENERS = new CopyOnWriteArrayList<>(); public static boolean hasJade = Platform.isModLoaded("jade"); public static MutableComponent format(String s, Object... objects) { @@ -51,7 +49,7 @@ public static MutableComponent format(String s, Object... objects) { } public static void registerWidgetInputListener(RecipeViewerWidgetInputListener listener) { - RECIPE_VIEWER_WIDGET_INPUT_EVENT.register(listener); + RECIPE_VIEWER_WIDGET_INPUT_LISTENERS.add(listener); } public static boolean postWidgetInputEvent( @@ -59,7 +57,15 @@ public static boolean postWidgetInputEvent( String id, InputAction action, @Nullable InteractiveRenderElement element) { - return action.isMouseOver(element) && RECIPE_VIEWER_WIDGET_INPUT_EVENT.invoker().on(recipe, id, action); + if (!action.isMouseOver(element)) { + return false; + } + for (var listener : RECIPE_VIEWER_WIDGET_INPUT_LISTENERS) { + if (listener.on(recipe, id, action)) { + return true; + } + } + return false; } public static Component getFluidName(Fluid fluid) { @@ -68,15 +74,14 @@ public static Component getFluidName(Fluid fluid) { @Nullable public static RecipeHolder recipe(ResourceKey> id) { - return Objects.requireNonNull(Minecraft.getInstance().getConnection()).recipes().getSynchronizedRecipes().get(id); + return ((FabricRecipeAccess) Objects.requireNonNull(Minecraft.getInstance().getConnection()).recipes()).getSynchronizedRecipes().get(id); } public static RecipeMap recipes(RecipeAccess recipes) { - return RecipeMap.create(recipes.getSynchronizedRecipes().recipes()); + return RecipeMap.create(((FabricRecipeAccess) recipes).getSynchronizedRecipes().recipes()); } - @Override - public void onInitializeClient() { + public ClientProxy(IEventBus modEventBus) { ParticleProviderRegistry.getInstance().register( DripstoneParticleService.DRIPSTONE_DRIPPING, ParticleFactories.Dripping::new diff --git a/src/main/java/snownee/lychee/util/CommonProxy.java b/src/main/java/snownee/lychee/util/CommonProxy.java index 437945e0..9221b2f7 100644 --- a/src/main/java/snownee/lychee/util/CommonProxy.java +++ b/src/main/java/snownee/lychee/util/CommonProxy.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; import org.jspecify.annotations.Nullable; @@ -12,17 +13,18 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.serialization.JsonOps; -import net.fabricmc.api.ModInitializer; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.neoforge.common.Tags; +import net.neoforged.neoforge.registries.NeoForgeRegistries; +import net.neoforged.fml.common.Mod; import net.fabricmc.fabric.api.event.player.AttackBlockCallback; import net.fabricmc.fabric.api.event.player.UseBlockCallback; -import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredientSerializer; +import snownee.kiwi.recipe.CustomIngredientSerializer; import net.fabricmc.fabric.api.recipe.v1.sync.RecipeSynchronization; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.core.Registry; import net.minecraft.core.dispenser.BlockSource; -import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.TagParser; @@ -50,9 +52,7 @@ import net.minecraft.world.level.block.PointedDripstoneBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; -import snownee.kiwi.Mod; import snownee.kiwi.loader.Platform; -import snownee.kiwi.util.KEvent; import snownee.lychee.Lychee; import snownee.lychee.LycheeRegistries; import snownee.lychee.LycheeTags; @@ -78,37 +78,10 @@ import snownee.lychee.util.ui.UIElementType; @Mod(Lychee.ID) -public class CommonProxy implements ModInitializer { - public static final KEvent CUSTOM_ACTION_EVENT = KEvent.createArrayBacked( - CustomActionListener.class, - listeners -> (id, action, recipe) -> { - for (var listener : listeners) { - if (listener.on(id, action, recipe)) { - return true; - } - } - return false; - } - ); - public static final KEvent CUSTOM_CONDITION_EVENT = KEvent.createArrayBacked( - CustomConditionListener.class, - listeners -> (id, condition) -> { - for (var listener : listeners) { - if (listener.on(id, condition)) { - return true; - } - } - return false; - } - ); - public static final KEvent>> RECIPE_CATEGORY_EVENT = KEvent.createArrayBacked( - Consumer.class, - listeners -> plugin -> { - for (var listener : listeners) { - listener.accept(plugin); - } - } - ); +public class CommonProxy { + private static final List CUSTOM_ACTION_LISTENERS = new CopyOnWriteArrayList<>(); + private static final List CUSTOM_CONDITION_LISTENERS = new CopyOnWriteArrayList<>(); + private static final List>> RECIPE_CATEGORY_LISTENERS = new CopyOnWriteArrayList<>(); @Unique public static final ThreadLocal<@Nullable JsonFragmentManager> fragmentManagerProvider = new ThreadLocal<>(); public static boolean hasDFLib = Platform.isModLoaded("dripstone_fluid_lib"); @@ -215,7 +188,7 @@ private static double clamp(double v, int target) { } public static boolean isSimpleIngredient(Ingredient ingredient) { - return !ingredient.requiresTesting(); + return ingredient.isSimple(); } public static JsonObject tagToJson(CompoundTag tag) { @@ -235,15 +208,15 @@ public static CompoundTag jsonToTag(JsonElement json) { } public static void registerCustomActionListener(CustomActionListener listener) { - CUSTOM_ACTION_EVENT.register(listener); + CUSTOM_ACTION_LISTENERS.add(listener); } public static void registerCustomConditionListener(CustomConditionListener listener) { - CUSTOM_CONDITION_EVENT.register(listener); + CUSTOM_CONDITION_LISTENERS.add(listener); } public static void registerRecipeCategoryListener(Consumer> listener) { - RECIPE_CATEGORY_EVENT.register(listener); + RECIPE_CATEGORY_LISTENERS.add(listener); } public static void postCustomActionEvent( @@ -251,16 +224,26 @@ public static void postCustomActionEvent( CustomAction action, ILycheeRecipe recipe ) { - CUSTOM_ACTION_EVENT.invoker().on(id, action, recipe); + for (var listener : CUSTOM_ACTION_LISTENERS) { + listener.on(id, action, recipe); + } } public static void postCustomConditionEvent(String id, CustomCondition condition) { - CUSTOM_CONDITION_EVENT.invoker().on(id, condition); + for (var listener : CUSTOM_CONDITION_LISTENERS) { + listener.on(id, condition); + } + } + + public static void postRecipeCategoryEvent(RvPlugin plugin) { + for (var listener : RECIPE_CATEGORY_LISTENERS) { + listener.accept(plugin); + } } public static IngredientType getIngredientType(Ingredient ingredient) { var customIngredient = ingredient.getCustomIngredient(); - if (customIngredient != null && customIngredient.getSerializer() == AlwaysTrueIngredient.SERIALIZER) { + if (customIngredient != null && Objects.equals(NeoForgeRegistries.INGREDIENT_TYPES.getKey(customIngredient.getType()), AlwaysTrueIngredient.ID)) { return IngredientType.ANY; } if (ingredient.isEmpty()) { // TODO not compatible with AIR_INGREDIENT! @@ -295,15 +278,20 @@ public static boolean dispensePlacement(BlockSource pSource, ItemStack pStack, D } public static String getTagTranslationKey(TagKey key) { - return key.getTranslationKey(); + return Tags.getTagTranslationKey(key); } public static void hurtAndBreak(ItemStack itemStack, int damage, ServerLevel level, @Nullable LivingEntity entity) { itemStack.hurtAndBreak(damage, level, entity instanceof ServerPlayer player ? player : null, $ -> {}); } - @Override - public void onInitialize() { + public CommonProxy(IEventBus modEventBus) { + modEventBus.addListener(LycheeRegistries::init); + RecipeTypes.RECIPE_TYPES.register(modEventBus); + RecipeSerializers.RECIPE_SERIALIZERS.register(modEventBus); + RecipeBookCategories.RECIPE_BOOK_CATEGORIES.register(modEventBus); + SlotDisplayTypes.SLOT_DISPLAYS.register(modEventBus); + DripstoneParticleService.PARTICLE_TYPES.register(modEventBus); Objects.requireNonNull(RecipeTypes.ALL); Objects.requireNonNull(LycheeTags.FIRE_IMMUNE); Objects.requireNonNull(LycheeRegistries.CONTEXTUAL); @@ -325,22 +313,6 @@ public void onInitialize() { UseBlockCallback.EVENT.register(BlockInteractingRecipe::invoke); AttackBlockCallback.EVENT.register(BlockClickingRecipe::invoke); - // Dripstone recipes - Registry.register( - BuiltInRegistries.PARTICLE_TYPE, - Lychee.id("dripstone_dripping"), - DripstoneParticleService.DRIPSTONE_DRIPPING - ); - Registry.register( - BuiltInRegistries.PARTICLE_TYPE, - Lychee.id("dripstone_falling"), - DripstoneParticleService.DRIPSTONE_FALLING - ); - Registry.register( - BuiltInRegistries.PARTICLE_TYPE, - Lychee.id("dripstone_splash"), - DripstoneParticleService.DRIPSTONE_SPLASH - ); } public interface CustomActionListener { diff --git a/src/main/java/snownee/lychee/util/action/ActionData.java b/src/main/java/snownee/lychee/util/action/ActionData.java index ab413575..4bb43059 100644 --- a/src/main/java/snownee/lychee/util/action/ActionData.java +++ b/src/main/java/snownee/lychee/util/action/ActionData.java @@ -11,7 +11,7 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.world.level.Level; -import snownee.kiwi.util.codec.KCodecs; +import snownee.lychee.util.codec.LycheeCodecs; import snownee.lychee.LycheeLootContextParamSets; import snownee.lychee.context.ActionContext; import snownee.lychee.context.LootParamsContext; @@ -63,7 +63,7 @@ public record Builder(LycheeContext context, int delayedTicks, boolean avoidDefa Codec.INT.fieldOf("delayedTicks").forGetter(Builder::delayedTicks), Codec.BOOL.optionalFieldOf("avoidDefault", false).forGetter(Builder::avoidDefault), Codec.INT.fieldOf("state") - .flatXmap(it -> KCodecs.tryCatch(() -> ActionContext.State.values()[it]), it -> DataResult.success(it.ordinal())) + .flatXmap(it -> LycheeCodecs.tryCatch(() -> ActionContext.State.values()[it]), it -> DataResult.success(it.ordinal())) .forGetter(Builder::state), Codec.list(Job.CODEC) .fieldOf("jobs") diff --git a/src/main/java/snownee/lychee/util/codec/ActionParsers.java b/src/main/java/snownee/lychee/util/codec/ActionParsers.java index b9bddfc4..e8ff4517 100644 --- a/src/main/java/snownee/lychee/util/codec/ActionParsers.java +++ b/src/main/java/snownee/lychee/util/codec/ActionParsers.java @@ -12,7 +12,6 @@ import net.minecraft.core.registries.Registries; import net.minecraft.world.item.ItemStackTemplate; import net.minecraft.world.phys.Vec3; -import snownee.kiwi.util.codec.KCodecs; import snownee.lychee.action.AddItemCooldown; import snownee.lychee.action.Delay; import snownee.lychee.action.DropItem; @@ -68,13 +67,13 @@ class Drop implements LycheeParser { public DataResult parse(Context context, StringReader reader) { DataResult itemResult = LycheeParserUtils.readParam( reader, - r -> KCodecs.tryCatch(() -> ParsedItem.read(reader).template())).orElseThrow(); + r -> LycheeCodecs.tryCatch(() -> ParsedItem.read(reader).template())).orElseThrow(); if (itemResult.isSuccess()) { return itemResult.map(item -> new DropItem(PostActionCommonProperties.EMPTY, item)); } DataResult xpResult = LycheeParserUtils.readParam( reader, - r -> KCodecs.tryCatch(() -> { + r -> LycheeCodecs.tryCatch(() -> { int i = r.readInt(); r.expect('x'); r.expect('p'); @@ -95,7 +94,7 @@ class Run implements LycheeParser { public DataResult parse(Context context, StringReader reader) { DataResult result = LycheeParserUtils.readParam( reader, - r -> KCodecs.tryCatch(reader::readQuotedString)).orElseThrow(); + r -> LycheeCodecs.tryCatch(reader::readQuotedString)).orElseThrow(); return result.map(s -> new Execute(PostActionCommonProperties.EMPTY, s, false)); } } @@ -105,7 +104,7 @@ class DelayParser implements LycheeParser { public DataResult parse(Context context, StringReader reader) { DataResult result = LycheeParserUtils.readParam( reader, - r -> KCodecs.tryCatch(() -> { + r -> LycheeCodecs.tryCatch(() -> { float f = r.readFloat(); Preconditions.checkArgument(f > 0, "Delay must be positive"); return f; @@ -119,7 +118,7 @@ class ItemCooldown implements LycheeParser { public DataResult parse(Context context, StringReader reader) { DataResult result = LycheeParserUtils.readParam( reader, - r -> KCodecs.tryCatch(() -> { + r -> LycheeCodecs.tryCatch(() -> { float f = r.readFloat(); Preconditions.checkArgument(f > 0, "Cooldown must be positive"); return f; @@ -133,7 +132,7 @@ class CopyComponentParser implements LycheeParser { public DataResult parse(Context context, StringReader reader) { DataResult result = LycheeParserUtils.readParam( reader, - r -> KCodecs.tryCatch(reader::readUnquotedString)).orElseThrow(); + r -> LycheeCodecs.tryCatch(reader::readUnquotedString)).orElseThrow(); return result.map(s -> new CopyComponent( PostActionCommonProperties.EMPTY, LycheeCodecs.WILDCARD_COMPONENTS.parse(JavaOps.INSTANCE, s).getOrThrow(), @@ -147,7 +146,7 @@ class RemoveComponentParser implements LycheeParser { public DataResult parse(Context context, StringReader reader) { DataResult result = LycheeParserUtils.readParam( reader, - r -> KCodecs.tryCatch(reader::readUnquotedString)).orElseThrow(); + r -> LycheeCodecs.tryCatch(reader::readUnquotedString)).orElseThrow(); return result.map(s -> new RemoveComponent( PostActionCommonProperties.EMPTY, LycheeCodecs.WILDCARD_COMPONENTS.parse(JavaOps.INSTANCE, s).getOrThrow(), diff --git a/src/main/java/snownee/lychee/util/codec/LycheeCodecs.java b/src/main/java/snownee/lychee/util/codec/LycheeCodecs.java index f5cf2003..c02b592a 100644 --- a/src/main/java/snownee/lychee/util/codec/LycheeCodecs.java +++ b/src/main/java/snownee/lychee/util/codec/LycheeCodecs.java @@ -40,7 +40,6 @@ import net.minecraft.world.item.Items; import net.minecraft.world.item.crafting.Ingredient; import snownee.kiwi.recipe.SizedIngredient; -import snownee.kiwi.util.codec.KCodecs; public final class LycheeCodecs { public static final ThreadLocal<@Nullable Unit> skipComponentsValidation = new ThreadLocal<>(); @@ -165,7 +164,7 @@ public Stream keys(DynamicOps ops) { public static final Codec ITEM_STACK_TEMPLATE = Codec.withAlternative( ITEM_STACK_TEMPLATE_MAP_CODEC.codec(), ExtraCodecs.NON_EMPTY_STRING.flatXmap( - s -> KCodecs.tryCatch(() -> ParsedItem.read(new StringReader(s)).template()), + s -> tryCatch(() -> ParsedItem.read(new StringReader(s)).template()), _ -> DataResult.error(() -> "Encoding shorthand ItemStack is not supported"))); public static final MapCodec OFFSET = RecordCodecBuilder.mapCodec(posInstance -> posInstance.group( @@ -194,7 +193,7 @@ public static > Codec sizeLimit(Codec listCodec, int //TODO move to Kiwi public static final Codec INGREDIENT = Codec.withAlternative( Ingredient.CODEC, ExtraCodecs.NON_EMPTY_STRING.flatXmap( - s -> KCodecs.tryCatch(() -> { + s -> tryCatch(() -> { StringReader reader = new StringReader(s); ParsedItem parsedItem = ParsedItem.read(reader); Preconditions.checkArgument(!reader.canRead(), "Cannot parse %s", s); @@ -203,7 +202,7 @@ public static > Codec sizeLimit(Codec listCodec, int public static final Codec SIZED_INGREDIENT = Codec.withAlternative( SizedIngredient.CODEC, ExtraCodecs.NON_EMPTY_STRING.flatXmap( - s -> KCodecs.tryCatch(() -> { + s -> tryCatch(() -> { StringReader reader = new StringReader(s); ParsedItem parsedItem = ParsedItem.read(reader); Preconditions.checkArgument(!reader.canRead(), "Cannot parse %s", s); @@ -240,4 +239,17 @@ public RecordBuilder coApply(DynamicOps ops, A input, RecordBuilder } }); } + + public static DataResult tryCatch(ThrowingSupplier supplier) { + try { + return DataResult.success(supplier.get()); + } catch (Exception e) { + return DataResult.error(e::getMessage); + } + } + + @FunctionalInterface + public interface ThrowingSupplier { + T get() throws Exception; + } } diff --git a/src/main/java/snownee/lychee/util/codec/LycheeParserUtils.java b/src/main/java/snownee/lychee/util/codec/LycheeParserUtils.java index 357d496c..7e70c741 100644 --- a/src/main/java/snownee/lychee/util/codec/LycheeParserUtils.java +++ b/src/main/java/snownee/lychee/util/codec/LycheeParserUtils.java @@ -12,7 +12,6 @@ import net.minecraft.core.HolderGetter; import net.minecraft.world.level.block.Block; import net.minecraft.world.phys.Vec3; -import snownee.kiwi.util.codec.ThrowingFunction; import snownee.lychee.util.predicates.BlockPredicateExtensions; public class LycheeParserUtils { @@ -83,7 +82,7 @@ public static DataResult readVec3(StringReader reader) throws CommandSynta return DataResult.success(new Vec3(x, y, z)); } - public static Optional> readParam(StringReader reader, ThrowingFunction> parser) { + public static Optional> readParam(StringReader reader, ThrowingParser parser) { int cursor = reader.getCursor(); try { reader.expect(' '); @@ -97,4 +96,9 @@ public static Optional> readParam(StringReader reader, Throwin return Optional.empty(); } } + + @FunctionalInterface + public interface ThrowingParser { + DataResult apply(StringReader reader) throws Exception; + } } diff --git a/src/main/java/snownee/lychee/util/particles/dripstone/DripstoneParticleService.java b/src/main/java/snownee/lychee/util/particles/dripstone/DripstoneParticleService.java index b7d02b76..1ab1f6c1 100644 --- a/src/main/java/snownee/lychee/util/particles/dripstone/DripstoneParticleService.java +++ b/src/main/java/snownee/lychee/util/particles/dripstone/DripstoneParticleService.java @@ -10,11 +10,13 @@ import net.fabricmc.fabric.api.particle.v1.FabricParticleTypes; import net.minecraft.core.BlockPos; import net.minecraft.core.particles.BlockParticleOption; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.particles.ParticleType; import net.minecraft.tags.FluidTags; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.registries.DeferredRegister; import snownee.kiwi.loader.Platform; import snownee.lychee.Lychee; import snownee.lychee.RecipeTypes; @@ -24,15 +26,17 @@ public class DripstoneParticleService { public static final Cache particleHandlers = CacheBuilder.newBuilder().build(); - public static final ParticleType DRIPSTONE_DRIPPING = FabricParticleTypes.complex( - BlockParticleOption::codec, - BlockParticleOption::streamCodec); - public static final ParticleType DRIPSTONE_FALLING = FabricParticleTypes.complex( - BlockParticleOption::codec, - BlockParticleOption::streamCodec); - public static final ParticleType DRIPSTONE_SPLASH = FabricParticleTypes.complex( - BlockParticleOption::codec, - BlockParticleOption::streamCodec); + public static final DeferredRegister> PARTICLE_TYPES = DeferredRegister.create(BuiltInRegistries.PARTICLE_TYPE, Lychee.ID); + + public static final ParticleType DRIPSTONE_DRIPPING = register("dripstone_dripping"); + public static final ParticleType DRIPSTONE_FALLING = register("dripstone_falling"); + public static final ParticleType DRIPSTONE_SPLASH = register("dripstone_splash"); + + private static ParticleType register(String name) { + ParticleType type = FabricParticleTypes.complex(BlockParticleOption::codec, BlockParticleOption::streamCodec); + PARTICLE_TYPES.register(name, () -> type); + return type; + } public static boolean spawnDripParticle(Level level, BlockPos blockPos, BlockState blockState) { var sourceBlock = findBlockAboveStalactite(level, blockPos, blockState); diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 00000000..41ef932a --- /dev/null +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,36 @@ +public net.minecraft.advancements.criterion.StatePropertiesPredicate$PropertyMatcher +public net.minecraft.advancements.criterion.StatePropertiesPredicate$PropertyMatcher (Ljava/lang/String;Lnet/minecraft/advancements/criterion/StatePropertiesPredicate$ValueMatcher;)V +public net.minecraft.advancements.criterion.StatePropertiesPredicate$ExactMatcher +public net.minecraft.advancements.criterion.StatePropertiesPredicate$ExactMatcher (Ljava/lang/String;)V +public net.minecraft.advancements.criterion.StatePropertiesPredicate$RangedMatcher + +public net.minecraft.world.level.storage.loot.IntRange min +public net.minecraft.world.level.storage.loot.IntRange max + +public net.minecraft.client.particle.DripParticle (Lnet/minecraft/client/multiplayer/ClientLevel;DDDLnet/minecraft/world/level/material/Fluid;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;)V +public net.minecraft.client.particle.DripParticle isGlowing +public net.minecraft.client.particle.DripParticle$DripstoneFallAndLandParticle +public net.minecraft.client.particle.DripParticle$DripstoneFallAndLandParticle (Lnet/minecraft/client/multiplayer/ClientLevel;DDDLnet/minecraft/world/level/material/Fluid;Lnet/minecraft/core/particles/ParticleOptions;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;)V +public net.minecraft.client.particle.DripParticle$DripHangParticle +public net.minecraft.client.particle.DripParticle$DripHangParticle (Lnet/minecraft/client/multiplayer/ClientLevel;DDDLnet/minecraft/world/level/material/Fluid;Lnet/minecraft/core/particles/ParticleOptions;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;)V + +public net.minecraft.world.level.block.DropperBlock DISPENSE_BEHAVIOUR + +public net.minecraft.advancements.criterion.LocationPredicate$PositionPredicate + +public net.minecraft.world.entity.item.FallingBlockEntity blockState + +public net.minecraft.world.Difficulty BY_ID + +public net.minecraft.world.item.crafting.RecipeManager RECIPE_LISTER +public net.minecraft.world.item.crafting.RecipeManager recipes + +public net.minecraft.world.level.Level DEFAULT_EXPLOSION_BLOCK_PARTICLES + +public net.minecraft.world.item.crafting.PlacementInfo (Ljava/util/List;Lit/unimi/dsi/fastutil/ints/IntList;)V + +public net.minecraft.commands.arguments.item.ItemParser$State + +public net.minecraft.world.level.ServerExplosion$StackCollector + +public net.minecraft.client.Minecraft blockModelResolver diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 00000000..de53f37a --- /dev/null +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,55 @@ +modLoader="javafml" +loaderVersion="*" +license="ARR" + +[[mods]] +modId="lychee" +version="${file.jarVersion}" +displayName="Lychee Tweaker" +authors="Snownee" +credits="SettingDust, MikhailTapio" +displayURL="https://www.curseforge.com/minecraft/mc-mods/lychee-fabric" +issueTrackerURL="https://github.com/Snownee/Lychee/issues" +description="Data-driven in-world crafting" +logoFile="assets/lychee/logo.png" + +[[mixins]] +config="lychee.mixins.json" + +[[accessTransformers]] +file="META-INF/accesstransformer.cfg" + +[[dependencies.lychee]] +modId="minecraft" +type="required" +versionRange="${minecraft_version_range}" + +[[dependencies.lychee]] +modId="neoforge" +type="required" +versionRange="${neo_version_range}" + +[[dependencies.lychee]] +modId="kiwi" +type="required" +versionRange="[26,)" + +[[dependencies.lychee]] +modId="cloth_config" +type="optional" +versionRange="[26.1.154,)" + +[[dependencies.lychee]] +modId="jei" +type="optional" +versionRange="[29.12.0.40,)" + +[[dependencies.lychee]] +modId="roughlyenoughitems" +type="optional" +versionRange="[26.1.819,)" + +[[dependencies.lychee]] +modId="jade" +type="optional" +versionRange="[26.1.8,)" diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json deleted file mode 100644 index 8c44ba75..00000000 --- a/src/main/resources/fabric.mod.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "schemaVersion": 1, - "id": "lychee", - "version": "${version}", - "name": "Lychee Tweaker", - "description": "Data-driven in-world crafting", - "authors": [ - "Snownee" - ], - "contributors": [ - "SettingDust", - "MikhailTapio" - ], - "contact": { - "homepage": "https://www.curseforge.com/minecraft/mc-mods/lychee-fabric", - "sources": "https://github.com/Snownee/Lychee", - "issues": "https://github.com/Snownee/Lychee/issues" - }, - "license": "ARR", - "icon": "assets/lychee/logo.png", - "entrypoints": { - "main": [ - "snownee.lychee.util.CommonProxy" - ], - "client": [ - "snownee.lychee.util.ClientProxy" - ], - "fabric-datagen": [ - "snownee.lychee.datagen.LycheeDataGen" - ], - "rei_common": [ - "snownee.lychee.compat.recipeviewer.rei.LycheeREIPlugin" - ], - "rei_client": [ - "snownee.lychee.compat.recipeviewer.rei.LycheeREIClientPlugin" - ], - "jei_mod_plugin": [ - "snownee.lychee.compat.recipeviewer.jei.LycheeJEIPlugin" - ], - "emi": [ - ] - }, - "mixins": [ - "lychee.mixins.json" - ], - "accessWidener": "lychee.classtweaker", - "depends": { - "kiwi": ">=26" - }, - "custom": { - "modmenu": { - "links": { - "modmenu.discord": "https://discord.gg/KzGQW7a", - "modmenu.donate": "https://ko-fi.com/snownee" - }, - "badges": [ - "library" - ] - } - } -}