Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.21] Recipe priority system for solving overlaps in recipe ingredients and patterns #1152

Closed
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,11 @@
public static CompletableFuture<ReloadableServerResources> loadResources(
ResourceManager p_248588_,
LayeredRegistryAccess<RegistryLayer> p_335667_,
@@ -90,14 +_,27 @@
@@ -90,14 +_,26 @@
ReloadableServerResources reloadableserverresources = new ReloadableServerResources(
p_335211_.compositeAccess(), p_250212_, p_249301_, p_251126_
);
+ List<PreparableReloadListener> listeners = new java.util.ArrayList<>(reloadableserverresources.listeners());
+ listeners.addAll(net.neoforged.neoforge.event.EventHooks.onResourceReload(reloadableserverresources, p_335211_.compositeAccess()));
+ List<PreparableReloadListener> listeners = net.neoforged.neoforge.event.EventHooks.onResourceReload(reloadableserverresources, p_335211_.compositeAccess(), com.google.common.collect.Lists.newArrayList(reloadableserverresources.listeners()));
+ listeners.forEach(rl -> {
+ if (rl instanceof net.neoforged.neoforge.resource.ContextAwareReloadListener srl) srl.injectContext(reloadableserverresources.context, reloadableserverresources.registryLookup);
+ });
Expand Down
24 changes: 21 additions & 3 deletions patches/net/minecraft/world/item/crafting/RecipeManager.java.patch
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
--- a/net/minecraft/world/item/crafting/RecipeManager.java
+++ b/net/minecraft/world/item/crafting/RecipeManager.java
@@ -50,16 +_,22 @@
@@ -48,24 +_,37 @@

protected void apply(Map<ResourceLocation, JsonElement> p_44037_, ResourceManager p_44038_, ProfilerFiller p_44039_) {
this.hasErrors = false;
Builder<RecipeType<?>, RecipeHolder<?>> builder = ImmutableMultimap.builder();
- Builder<RecipeType<?>, RecipeHolder<?>> builder = ImmutableMultimap.builder();
+ com.google.common.collect.SetMultimap<Integer, com.mojang.datafixers.util.Pair<RecipeType<?>, RecipeHolder<?>>> builder = com.google.common.collect.MultimapBuilder.treeKeys().hashSetValues().build();
+ com.google.common.collect.LinkedListMultimap<RecipeType<?>, RecipeHolder<?>> finalBuilder = com.google.common.collect.LinkedListMultimap.create(); // Neo: Final version of "builder" that becomes "byType".
com.google.common.collect.ImmutableMap.Builder<ResourceLocation, RecipeHolder<?>> builder1 = ImmutableMap.builder();
- RegistryOps<JsonElement> registryops = this.registries.createSerializationContext(JsonOps.INSTANCE);
+ RegistryOps<JsonElement> registryops = this.makeConditionalOps(); // Neo: add condition context
+ net.neoforged.neoforge.common.crafting.RecipePriorityManager priorityManager = net.neoforged.neoforge.common.NeoForgeEventHandler.getRecipePriorityManager(); // Neo: get overriding recipe priorities
+ Map<ResourceLocation, Integer> priorities = priorityManager.getRecipePriorities();
bconlon1 marked this conversation as resolved.
Show resolved Hide resolved

for (Entry<ResourceLocation, JsonElement> entry : p_44037_.entrySet()) {
ResourceLocation resourcelocation = entry.getKey();
Expand All @@ -15,13 +21,25 @@
- Recipe<?> recipe = Recipe.CODEC.parse(registryops, entry.getValue()).getOrThrow(JsonParseException::new);
+ var decoded = Recipe.CONDITIONAL_CODEC.parse(registryops, entry.getValue()).getOrThrow(JsonParseException::new);
+ decoded.ifPresentOrElse(r -> {
+ int priority = 0;
+ if (priorities.containsKey(resourcelocation)) priority = priorities.get(resourcelocation);
bconlon1 marked this conversation as resolved.
Show resolved Hide resolved
+ Recipe<?> recipe = r.carrier();
RecipeHolder<?> recipeholder = new RecipeHolder<>(resourcelocation, recipe);
builder.put(recipe.getType(), recipeholder);
- builder.put(recipe.getType(), recipeholder);
+ builder.put(priority, com.mojang.datafixers.util.Pair.of(recipe.getType(), recipeholder));
builder1.put(resourcelocation, recipeholder);
+ }, () -> {
+ LOGGER.debug("Skipping loading recipe {} as its conditions were not met", resourcelocation);
+ });
} catch (IllegalArgumentException | JsonParseException jsonparseexception) {
LOGGER.error("Parsing error loading recipe {}", resourcelocation, jsonparseexception);
}
}

- this.byType = builder.build();
+ com.google.common.collect.Lists.reverse(new java.util.ArrayList<>(builder.entries())).forEach((e) -> finalBuilder.put(e.getValue().getFirst(), e.getValue().getSecond()));
+
+ this.byType = ImmutableMultimap.copyOf(finalBuilder);
this.byName = builder1.build();
LOGGER.info("Loaded {} recipes", this.byType.size());
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeManager;
import net.neoforged.bus.api.EventPriority;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.LogicalSide;
import net.neoforged.neoforge.common.crafting.RecipePriorityManager;
import net.neoforged.neoforge.common.loot.LootModifierManager;
import net.neoforged.neoforge.common.util.FakePlayerFactory;
import net.neoforged.neoforge.common.util.LogicalSidedProvider;
Expand Down Expand Up @@ -147,12 +149,15 @@ public void onCommandsRegister(RegisterCommandsEvent event) {
}

private static LootModifierManager INSTANCE;
private static RecipePriorityManager RECIPE_PRIORITY;
private static DataMapLoader DATA_MAPS;

@SubscribeEvent
public void onResourceReload(AddReloadListenerEvent event) {
INSTANCE = new LootModifierManager();
RECIPE_PRIORITY = new RecipePriorityManager();
event.addListener(INSTANCE);
event.addListenerBefore(RECIPE_PRIORITY, (listener) -> listener instanceof RecipeManager);
event.addListener(DATA_MAPS = new DataMapLoader(event.getConditionContext(), event.getRegistryAccess()));
}

Expand All @@ -162,6 +167,12 @@ static LootModifierManager getLootModifierManager() {
return INSTANCE;
}

public static RecipePriorityManager getRecipePriorityManager() {
if (RECIPE_PRIORITY == null)
throw new IllegalStateException("Can not retrieve RecipePriorityManager until resources have loaded once.");
return RECIPE_PRIORITY;
}

@SubscribeEvent
public void resourceReloadListeners(AddReloadListenerEvent event) {
event.addListener(CreativeModeTabRegistry.getReloadListener());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.common.crafting;

import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
import net.minecraft.util.GsonHelper;
import net.minecraft.util.profiling.ProfilerFiller;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class RecipePriorityManager extends SimpleJsonResourceReloadListener {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
public static final Logger LOGGER = LogManager.getLogger();

private Map<ResourceLocation, Integer> recipePriorities = ImmutableMap.of();
bconlon1 marked this conversation as resolved.
Show resolved Hide resolved
private static final String folder = "recipe_priorities";

public RecipePriorityManager() {
super(GSON, folder);
}

@Override
protected Map<ResourceLocation, JsonElement> prepare(ResourceManager resourceManager, ProfilerFiller profilerFiller) {
Map<ResourceLocation, JsonElement> map = super.prepare(resourceManager, profilerFiller);
List<ResourceLocation> finalLocations = new ArrayList<>();
for (String namespace : resourceManager.getNamespaces()) {
ResourceLocation resourceLocation = ResourceLocation.fromNamespaceAndPath(namespace, "recipe_priorities/recipe_priorities.json");
Optional<Resource> resource = resourceManager.getResource(resourceLocation);
if (resource.isPresent()) {
try (Reader reader = resource.get().openAsReader()) {
JsonObject jsonobject = GsonHelper.fromJson(GSON, reader, JsonObject.class);
boolean replace = GsonHelper.getAsBoolean(jsonobject, "replace", false);
if (replace)
finalLocations.clear();
JsonObject entries = GsonHelper.getAsJsonObject(jsonobject, "entries");
for (String entry : entries.keySet()) {
ResourceLocation loc = ResourceLocation.tryParse(entry);
finalLocations.remove(loc); //remove and re-add if needed, to update the ordering.
finalLocations.add(loc);
}
} catch (RuntimeException | IOException ioexception) {
LOGGER.error("Couldn't read recipe priority list {} in data pack {}", resourceLocation, resource.get().sourcePackId(), ioexception);
}
}
}
Map<ResourceLocation, JsonElement> finalMap = new HashMap<>();
//use layered config to fetch modifier data files (modifiers missing from config are disabled)
for (ResourceLocation location : finalLocations) {
finalMap.put(location, map.get(location));
}
return map;
bconlon1 marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
protected void apply(Map<ResourceLocation, JsonElement> resourceList, ResourceManager resourceManagerIn, ProfilerFiller profilerIn) {
ImmutableMap.Builder<ResourceLocation, Integer> builder = ImmutableMap.builder();
for (Map.Entry<ResourceLocation, JsonElement> entry : resourceList.entrySet()) {
JsonElement json = entry.getValue();
if (json instanceof JsonObject jsonObject) {
JsonElement entries = jsonObject.get("entries");
if (entries instanceof JsonObject entriesObject) {
for (var priorityEntry : entriesObject.entrySet()) {
ResourceLocation location = ResourceLocation.tryParse(priorityEntry.getKey());
int priority = priorityEntry.getValue().getAsInt();
if (location != null) {
builder.put(location, priority);
}
}
}
}
}
this.recipePriorities = builder.build();
LOGGER.info("Loaded {} recipe priority overrides", this.recipePriorities.size());
}

/**
* An immutable map of the registered recipe priorities in layered order.
*/
public Map<ResourceLocation, Integer> getRecipePriorities() {
return this.recipePriorities;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.common.data;

import com.google.common.collect.ImmutableList;
import com.google.gson.JsonObject;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import net.minecraft.core.HolderLookup;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;

public abstract class RecipePrioritiesProvider implements DataProvider {
private final PackOutput output;
private final CompletableFuture<HolderLookup.Provider> registriesLookup;
protected HolderLookup.Provider registries;
private final String modid;
private final Map<ResourceLocation, Integer> toSerialize = new HashMap<>();
private boolean replace = false;

public RecipePrioritiesProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries, String modid) {
this.output = output;
this.registriesLookup = registries;
this.modid = modid;
}

/**
* Sets the "replace" key in recipe_priorities to true.
*/
protected void replacing() {
this.replace = true;
}

/**
* Call {@link #add} here, which will pass in the necessary information to write the jsons.
*/
protected abstract void start();

@Override
public final CompletableFuture<?> run(CachedOutput cache) {
return this.registriesLookup.thenCompose(registries -> this.run(cache, registries));
}

protected CompletableFuture<?> run(CachedOutput cache, HolderLookup.Provider registries) {
this.registries = registries;
start();

Path path = this.output.getOutputFolder(PackOutput.Target.DATA_PACK).resolve(this.modid).resolve("recipe_priorities").resolve("recipe_priorities.json");

JsonObject entries = new JsonObject();
toSerialize.forEach((key, value) -> entries.addProperty(key.toString(), value));

JsonObject json = new JsonObject();
json.addProperty("replace", this.replace);
json.add("entries", entries);

ImmutableList.Builder<CompletableFuture<?>> futuresBuilder = new ImmutableList.Builder<>();
futuresBuilder.add(DataProvider.saveStable(cache, json, path));

return CompletableFuture.allOf(futuresBuilder.build().toArray(CompletableFuture[]::new));
}

public void add(ResourceLocation recipe, Integer priority) {
this.toSerialize.put(recipe, priority);
}

public void add(String recipe, Integer priority) {
add(ResourceLocation.fromNamespaceAndPath(this.modid, recipe), priority);
}

public void add(String id, String location, Integer priority) {
add(ResourceLocation.fromNamespaceAndPath(id, location), priority);
}

@Override
public String getName() {
return "Recipe Priorities : " + modid;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
package net.neoforged.neoforge.event;

import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Predicate;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.RegistryAccess;
import net.minecraft.server.ReloadableServerResources;
Expand All @@ -29,13 +29,14 @@
* The event is fired on the {@link NeoForge#EVENT_BUS}
*/
public class AddReloadListenerEvent extends Event {
private final List<PreparableReloadListener> listeners = new ArrayList<>();
private final List<PreparableReloadListener> listeners;
private final ReloadableServerResources serverResources;
private final RegistryAccess registryAccess;

public AddReloadListenerEvent(ReloadableServerResources serverResources, RegistryAccess registryAccess) {
public AddReloadListenerEvent(ReloadableServerResources serverResources, RegistryAccess registryAccess, List<PreparableReloadListener> listeners) {
this.serverResources = serverResources;
this.registryAccess = registryAccess;
this.listeners = listeners;
}
bconlon1 marked this conversation as resolved.
Show resolved Hide resolved

/**
Expand All @@ -45,6 +46,20 @@ public void addListener(PreparableReloadListener listener) {
listeners.add(new WrappedStateAwareListener(listener));
}

/**
* @param listener the listener to add to the ResourceManager on reload
* @param before a predicate for what listener to add this before
*/
public void addListenerBefore(PreparableReloadListener listener, Predicate<PreparableReloadListener> before) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method should ideally be split into its own PR and account the feedback provided in #598
@pupnewfster has stated that they don't have updating the PR in their priority list, so if you do end up porting it, you should use a toposort and named listeners.

int index = listeners.size() - 1;
for (PreparableReloadListener listener1 : listeners) {
if (before.test(listener1)) {
index = listeners.indexOf(listener1);
bconlon1 marked this conversation as resolved.
Show resolved Hide resolved
}
}
listeners.add(index, new WrappedStateAwareListener(listener));
}

public List<PreparableReloadListener> getListeners() {
return ImmutableList.copyOf(listeners);
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/net/neoforged/neoforge/event/EventHooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -788,8 +788,8 @@ public static long onSleepFinished(ServerLevel level, long newTime, long minTime
return event.getNewTime();
}

public static List<PreparableReloadListener> onResourceReload(ReloadableServerResources serverResources, RegistryAccess registryAccess) {
AddReloadListenerEvent event = new AddReloadListenerEvent(serverResources, registryAccess);
public static List<PreparableReloadListener> onResourceReload(ReloadableServerResources serverResources, RegistryAccess registryAccess, List<PreparableReloadListener> listeners) {
AddReloadListenerEvent event = new AddReloadListenerEvent(serverResources, registryAccess, listeners);
bconlon1 marked this conversation as resolved.
Show resolved Hide resolved
NeoForge.EVENT_BUS.post(event);
return event.getListeners();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"parent": "minecraft:recipes/root",
"criteria": {
"has_cherry_planks": {
"conditions": {
"items": [
{
"items": "minecraft:cherry_planks"
}
]
},
"trigger": "minecraft:inventory_changed"
},
"has_the_recipe": {
"conditions": {
"recipe": "neotests_recipe_priorities:higher_priority_test"
},
"trigger": "minecraft:recipe_unlocked"
}
},
"requirements": [
[
"has_the_recipe",
"has_cherry_planks"
]
],
"rewards": {
"recipes": [
"neotests_recipe_priorities:higher_priority_test"
]
}
}
Loading
Loading