Implement RecipeTransformations

This commit is contained in:
LLytho 2022-06-22 17:03:31 +02:00
parent 328a193421
commit a2d51836c6
10 changed files with 217 additions and 138 deletions

View file

@ -1,7 +1,8 @@
package com.almostreliable.unified; package com.almostreliable.unified;
import com.almostreliable.unified.api.RecipeHandler;
import com.almostreliable.unified.handler.RecipeHandlerFactory; import com.almostreliable.unified.handler.RecipeHandlerFactory;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
@ -34,7 +35,7 @@ public abstract class AlmostUnifiedRuntime {
} }
public void transformRecipes(Map<ResourceLocation, JsonElement> recipes, ReplacementMap replacementMap) { public void transformRecipes(Map<ResourceLocation, JsonElement> recipes, ReplacementMap replacementMap) {
Map<ResourceLocation, Integer> typeCount = new HashMap<>(); Multimap<ResourceLocation, ResourceLocation> typeCount = HashMultimap.create();
int transformedRecipes = 0; int transformedRecipes = 0;
long start = System.nanoTime(); long start = System.nanoTime();
@ -47,7 +48,7 @@ public abstract class AlmostUnifiedRuntime {
// TODO for debugging remove this later // TODO for debugging remove this later
ResourceLocation recipeType = getRecipeType(json); ResourceLocation recipeType = getRecipeType(json);
typeCount.compute(recipeType, (rl, old) -> old == null ? 1 : old + 1); typeCount.put(recipeType, entry.getKey());
} }
} }
} }
@ -57,8 +58,12 @@ public abstract class AlmostUnifiedRuntime {
transformedRecipes, transformedRecipes,
recipes.size(), recipes.size(),
timeElapsed / 1000_000D); timeElapsed / 1000_000D);
typeCount.entrySet().stream().sorted(Comparator.comparing(o -> o.getKey().toString())).forEach(entry -> { // TODO Pls remove this on release
AlmostUnified.LOG.info("{}: {}", StringUtils.leftPad(entry.getKey().toString(), 50), entry.getValue()); typeCount.asMap().entrySet().stream().sorted(Comparator.comparing(o -> o.getKey().toString())).forEach((e) -> {
AlmostUnified.LOG.info("{}: {} | {}",
StringUtils.leftPad(e.getKey().toString(), 40),
StringUtils.leftPad(String.valueOf(e.getValue().size()), 4),
e.getValue().toString());
}); });
} }
@ -69,16 +74,12 @@ public abstract class AlmostUnifiedRuntime {
return null; return null;
} }
RecipeContextImpl ctx = new RecipeContextImpl(recipeType, id, json, replacementMap);
try { try {
RecipeHandler recipeHandler = recipeHandlerFactory.create(ctx); RecipeContextImpl ctx = new RecipeContextImpl(recipeType, id, json, replacementMap);
if (recipeHandler == null) { RecipeTransformationsImpl builder = new RecipeTransformationsImpl();
return null; recipeHandlerFactory.create(builder, ctx);
}
JsonObject copy = json.deepCopy(); JsonObject copy = json.deepCopy();
recipeHandler.transformRecipe(copy, ctx); builder.transform(copy, ctx);
if (!json.equals(copy)) { if (!json.equals(copy)) {
return copy; return copy;
} }
@ -92,7 +93,6 @@ public abstract class AlmostUnifiedRuntime {
return null; return null;
} }
@Nullable @Nullable
protected ResourceLocation getRecipeType(JsonObject recipeJson) { protected ResourceLocation getRecipeType(JsonObject recipeJson) {
String type = recipeJson.get("type").getAsString(); String type = recipeJson.get("type").getAsString();
@ -108,7 +108,6 @@ public abstract class AlmostUnifiedRuntime {
throw new IllegalStateException("Internal error. TagManager was not updated correctly"); throw new IllegalStateException("Internal error. TagManager was not updated correctly");
} }
TagMap tagMap = TagMap.create(tagManager); TagMap tagMap = TagMap.create(tagManager);
Map<ResourceLocation, TagKey<Item>> itemToTagMapping = new HashMap<>(allowedTags.size()); Map<ResourceLocation, TagKey<Item>> itemToTagMapping = new HashMap<>(allowedTags.size());

View file

@ -1,7 +1,8 @@
package com.almostreliable.unified; package com.almostreliable.unified;
import com.almostreliable.unified.api.RecipeContext; import com.almostreliable.unified.api.RecipeContext;
import com.almostreliable.unified.utils.JsonUtils; import com.almostreliable.unified.handler.RecipeConstants;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
@ -10,11 +11,10 @@ import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.UnaryOperator;
@SuppressWarnings("SameParameterValue")
public class RecipeContextImpl implements RecipeContext { public class RecipeContextImpl implements RecipeContext {
public static final String TAG = "tag";
public static final String ITEM = "item";
private final ResourceLocation type; private final ResourceLocation type;
private final ResourceLocation id; private final ResourceLocation id;
private final JsonObject currentRecipe; private final JsonObject currentRecipe;
@ -53,41 +53,55 @@ public class RecipeContextImpl implements RecipeContext {
return replacementMap.getPreferredTag(item); return replacementMap.getPreferredTag(item);
} }
@Override @Nullable
public boolean replaceIngredient(JsonElement element) { protected JsonElement depthReplace(JsonElement element, String potentialFrom, String potentialTo, UnaryOperator<JsonPrimitive> primitiveCallback) {
AtomicBoolean changed = new AtomicBoolean(false); if (element instanceof JsonPrimitive primitive) {
return primitiveCallback.apply(primitive);
}
JsonUtils.arrayForEach(element, JsonObject.class, json -> { if (element instanceof JsonObject object) {
if (json.get(ITEM) instanceof JsonPrimitive primitive) { JsonElement replace = depthReplace(object.get(potentialFrom), potentialFrom, potentialTo, primitiveCallback);
ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString()); if (replace != null) {
TagKey<Item> tag = getPreferredTagByItem(item); object.remove(potentialFrom);
if (tag != null) { object.add(potentialTo, replace);
json.remove(ITEM); }
json.addProperty(TAG, tag.location().toString()); }
changed.set(true);
if (element instanceof JsonArray array) {
for (int i = 0; i < array.size(); i++) {
JsonElement replace = depthReplace(array.get(i), potentialFrom, potentialTo, primitiveCallback);
if (replace != null) {
array.set(i, replace);
} }
} }
}); }
return changed.get(); return null;
} }
@Override @Override
public boolean replaceResult(JsonElement element) { @Nullable
AtomicBoolean changed = new AtomicBoolean(false); public JsonElement replaceIngredient(JsonElement element) {
return depthReplace(element, RecipeConstants.ITEM, RecipeConstants.TAG, primitive -> {
JsonUtils.arrayForEach(element, JsonObject.class, json -> { ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString());
if (json.get(ITEM) instanceof JsonPrimitive primitive) { TagKey<Item> tag = getPreferredTagByItem(item);
ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString()); if (tag != null) {
ResourceLocation replacement = getReplacementForItem(item); return new JsonPrimitive(tag.location().toString());
if (replacement != null) {
json.addProperty(ITEM, replacement.toString());
changed.set(true);
}
} }
return null;
}); });
}
return changed.get(); @Override
public JsonElement replaceResult(JsonElement element) {
return depthReplace(element, RecipeConstants.ITEM, RecipeConstants.ITEM, primitive -> {
ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString());
ResourceLocation replacement = getReplacementForItem(item);
if (replacement != null) {
return new JsonPrimitive(replacement.toString());
}
return null;
});
} }
@Override @Override

View file

@ -0,0 +1,78 @@
package com.almostreliable.unified;
import com.almostreliable.unified.api.RecipeContext;
import com.almostreliable.unified.api.RecipeTransformations;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
public class RecipeTransformationsImpl implements RecipeTransformations {
private final Map<String, Entry<?>> consumers = new HashMap<>();
@Override
public void forEachObject(String property, BiFunction<JsonObject, RecipeContext, JsonObject> consumer) {
BiFunction<JsonArray, RecipeContext, JsonArray> arrayConsumer = (array, ctx) -> {
for (int i = 0; i < array.size(); i++) {
JsonElement element = array.get(i);
if (element instanceof JsonObject obj) {
JsonObject result = consumer.apply(obj, ctx);
if (result != null) {
array.set(i, result);
}
}
}
return array;
};
put(property, JsonArray.class, arrayConsumer);
}
@Override
public void replaceIngredient(String property) {
put(property, JsonElement.class, (jsonElement, context) -> context.replaceIngredient(jsonElement));
}
@Override
public void replaceResult(String property) {
put(property, JsonElement.class, (jsonElement, context) -> context.replaceResult(jsonElement));
}
@Override
public void put(String property, BiFunction<JsonElement, RecipeContext, JsonElement> consumer) {
consumers.put(property, new Entry<>(JsonElement.class, consumer));
}
@Override
public <T extends JsonElement> void put(String property, Class<T> type, BiFunction<T, RecipeContext, T> consumer) {
consumers.put(property, new Entry<>(type, consumer));
}
public void transform(JsonObject json, RecipeContext context) {
for (var e : json.entrySet()) {
Entry<?> consumer = consumers.get(e.getKey());
if (consumer != null) {
JsonElement result = consumer.apply(e.getValue(), context);
if (result != null) {
e.setValue(result);
}
}
}
}
private record Entry<T extends JsonElement>(Class<T> expectedType,
BiFunction<T, RecipeContext, T> func) {
@Nullable
T apply(JsonElement json, RecipeContext context) {
if (expectedType.isInstance(json)) {
return func.apply(expectedType.cast(json), context);
}
return null;
}
}
}

View file

@ -18,16 +18,14 @@ public interface RecipeContext {
@Nullable @Nullable
TagKey<Item> getPreferredTagByItem(@Nullable ResourceLocation item); TagKey<Item> getPreferredTagByItem(@Nullable ResourceLocation item);
boolean replaceIngredient(JsonElement element); JsonElement replaceIngredient(JsonElement element);
boolean replaceResult(JsonElement element); JsonElement replaceResult(JsonElement element);
ResourceLocation getType(); ResourceLocation getType();
ResourceLocation getId(); ResourceLocation getId();
// String getIterateProperty();
boolean hasProperty(String property); boolean hasProperty(String property);
default String getModId() { default String getModId() {

View file

@ -1,13 +1,5 @@
package com.almostreliable.unified.api; package com.almostreliable.unified.api;
import com.google.gson.JsonObject;
@FunctionalInterface
public interface RecipeHandler { public interface RecipeHandler {
void collectTransformations(RecipeTransformations builder);
void transformRecipe(JsonObject json, RecipeContext context);
default String getName() {
return getClass().getSimpleName();
}
} }

View file

@ -0,0 +1,18 @@
package com.almostreliable.unified.api;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.function.BiFunction;
public interface RecipeTransformations {
void forEachObject(String property, BiFunction<JsonObject, RecipeContext, JsonObject> consumer);
void replaceIngredient(String property);
void replaceResult(String property);
void put(String property, BiFunction<JsonElement, RecipeContext, JsonElement> consumer);
<T extends JsonElement> void put(String property, Class<T> type, BiFunction<T, RecipeContext, T> consumer);
}

View file

@ -1,11 +1,7 @@
package com.almostreliable.unified.handler; package com.almostreliable.unified.handler;
import com.almostreliable.unified.api.RecipeContext;
import com.almostreliable.unified.api.RecipeHandler; import com.almostreliable.unified.api.RecipeHandler;
import com.google.gson.JsonElement; import com.almostreliable.unified.api.RecipeTransformations;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import net.minecraft.resources.ResourceLocation;
import java.util.Set; import java.util.Set;
@ -18,42 +14,14 @@ public class GenericRecipeHandler implements RecipeHandler {
RecipeConstants.RESULT, RecipeConstants.RESULT,
RecipeConstants.RESULTS); RecipeConstants.RESULTS);
public boolean hasInputOrOutputProperty(RecipeContext property) {
for (String inputKey : inputKeys) {
if (property.hasProperty(inputKey)) {
return true;
}
}
for (String outputKey : outputKeys) {
if (property.hasProperty(outputKey)) {
return true;
}
}
return false;
}
@Override @Override
public void transformRecipe(JsonObject json, RecipeContext context) { public void collectTransformations(RecipeTransformations builder) {
for (String inputKey : inputKeys) { for (String inputKey : inputKeys) {
if (json.has(inputKey)) { builder.replaceIngredient(inputKey);
context.replaceIngredient(json.get(inputKey));
}
} }
for (String outputKey : outputKeys) { for (String outputKey : outputKeys) {
JsonElement jsonElement = json.get(outputKey); builder.replaceResult(outputKey);
if (jsonElement instanceof JsonPrimitive) {
ResourceLocation item = context.getReplacementForItem(ResourceLocation.tryParse(jsonElement.getAsString()));
if (item != null) {
json.addProperty(outputKey, item.toString());
}
} else if (jsonElement != null) {
// Forge patched recipe results to also allow JsonObjects. See SimpleCookingSerializer::fromJson as an example.
context.replaceResult(jsonElement);
}
} }
} }
} }

View file

@ -2,9 +2,9 @@ package com.almostreliable.unified.handler;
import com.almostreliable.unified.api.RecipeContext; import com.almostreliable.unified.api.RecipeContext;
import com.almostreliable.unified.api.RecipeHandler; import com.almostreliable.unified.api.RecipeHandler;
import com.almostreliable.unified.api.RecipeTransformations;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -12,32 +12,23 @@ public class RecipeHandlerFactory {
private final Map<ResourceLocation, RecipeHandler> transformersByType = new HashMap<>(); private final Map<ResourceLocation, RecipeHandler> transformersByType = new HashMap<>();
private final Map<String, RecipeHandler> transformersByModId = new HashMap<>(); private final Map<String, RecipeHandler> transformersByModId = new HashMap<>();
@Nullable public void create(RecipeTransformations builder, RecipeContext context) {
public RecipeHandler create(RecipeContext context) { GenericRecipeHandler.INSTANCE.collectTransformations(builder);
if (context.hasProperty(ShapedRecipeKeyHandler.PATTERN_PROPERTY) &&
context.hasProperty(ShapedRecipeKeyHandler.KEY_PROPERTY)) {
ShapedRecipeKeyHandler.INSTANCE.collectTransformations(builder);
}
RecipeHandler byType = transformersByType.get(context.getType()); RecipeHandler byType = transformersByType.get(context.getType());
if (byType != null) { if (byType != null) {
return byType; byType.collectTransformations(builder);
} }
RecipeHandler byMod = transformersByModId.get(context.getModId()); RecipeHandler byMod = transformersByModId.get(context.getModId());
if (byMod != null) { if (byMod != null) {
return byMod; byMod.collectTransformations(builder);
} }
if (context.hasProperty(ShapedRecipeKeyHandler.PATTERN_PROPERTY) &&
context.hasProperty(ShapedRecipeKeyHandler.KEY_PROPERTY)) {
return ShapedRecipeKeyHandler.INSTANCE;
}
if (GenericRecipeHandler.INSTANCE.hasInputOrOutputProperty(context)) {
return GenericRecipeHandler.INSTANCE;
}
return null;
}
public void registerForType(String type, RecipeHandler transformer) {
transformersByType.put(new ResourceLocation(type), transformer);
} }
public void registerForType(ResourceLocation type, RecipeHandler transformer) { public void registerForType(ResourceLocation type, RecipeHandler transformer) {

View file

@ -1,7 +1,7 @@
package com.almostreliable.unified.handler; package com.almostreliable.unified.handler;
import com.almostreliable.unified.api.RecipeContext;
import com.almostreliable.unified.api.RecipeHandler; import com.almostreliable.unified.api.RecipeHandler;
import com.almostreliable.unified.api.RecipeTransformations;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
@ -11,16 +11,15 @@ public class ShapedRecipeKeyHandler implements RecipeHandler {
public static final String KEY_PROPERTY = "key"; public static final String KEY_PROPERTY = "key";
@Override @Override
public void transformRecipe(JsonObject json, RecipeContext context) { public void collectTransformations(RecipeTransformations builder) {
if (json.get(KEY_PROPERTY) instanceof JsonObject object) { builder.put(KEY_PROPERTY, JsonObject.class, (json, context) -> {
for (var entry : object.entrySet()) { for (var entry : json.entrySet()) {
context.replaceIngredient(entry.getValue()); JsonElement result = context.replaceIngredient(entry.getValue());
if (result != null) {
entry.setValue(result);
}
} }
} return json;
});
JsonElement result = json.get("result");
if (result != null) {
context.replaceResult(result);
}
} }
} }

View file

@ -2,6 +2,7 @@ package com.almostreliable.unified.compat.ie;
import com.almostreliable.unified.api.RecipeContext; import com.almostreliable.unified.api.RecipeContext;
import com.almostreliable.unified.api.RecipeHandler; import com.almostreliable.unified.api.RecipeHandler;
import com.almostreliable.unified.api.RecipeTransformations;
import com.almostreliable.unified.handler.RecipeConstants; import com.almostreliable.unified.handler.RecipeConstants;
import com.almostreliable.unified.utils.JsonUtils; import com.almostreliable.unified.utils.JsonUtils;
import com.almostreliable.unified.utils.Utils; import com.almostreliable.unified.utils.Utils;
@ -16,24 +17,45 @@ public class IERecipeHandler implements RecipeHandler {
// From IE // From IE
protected static final String BASE_KEY = "base_ingredient"; protected static final String BASE_KEY = "base_ingredient";
// TODO make it cleaner
@Override @Override
public void transformRecipe(JsonObject json, RecipeContext context) { public void collectTransformations(RecipeTransformations builder) {
builder.put("input0", (json, context) -> {
replaceIEIngredient(json, context);
return json;
}); // alloy recipes, refinery
builder.put("input1", (json, context) -> {
replaceIEIngredient(json, context);
return json;
}); // alloy recipes, refinery
builder.put(RecipeConstants.INPUT, (json, context) -> {
replaceIEIngredient(json, context);
return json;
}); // arc furnace, squeezer, cloche, coke oven, fermenter, fertilizer, metal_press
builder.put("additives", (json, context) -> {
replaceIEIngredient(json, context);
return json;
}); // arc furnace
builder.put(RecipeConstants.INPUTS, (json, context) -> {
replaceIEIngredient(json, context);
return json;
}); // blueprint, mixer
replaceIEIngredient(json.get("input0"), context); // alloy recipes, refinery // alloy recipes, crusher
replaceIEIngredient(json.get("input1"), context); // alloy recipes, refinery builder.forEachObject("secondaries", (jsonObject, context) -> {
replaceIEIngredient(json.get(RecipeConstants.INPUT), replaceIEResult(jsonObject.get(RecipeConstants.OUTPUT), context);
context); // arc furnace, squeezer, cloche, coke oven, fermenter, fertilizer, metal_press return jsonObject;
replaceIEIngredient(json.get("additives"), context); // arc furnace
replaceIEIngredient(json.get(RecipeConstants.INPUTS), context); // blueprint, mixer
replaceIEResult(json.get("secondaries"), context); // alloy recipes, crusher
JsonUtils.arrayForEach(json.get("secondaries"), JsonObject.class, secondary -> {
replaceIEResult(secondary.get(RecipeConstants.OUTPUT), context);
}); });
replaceIEResult(json.get(RecipeConstants.RESULT), context); builder.put(RecipeConstants.RESULT, (json, context) -> {
replaceIEResult(json.get(RecipeConstants.RESULTS), context); replaceIEResult(json, context);
return json;
});
builder.put(RecipeConstants.RESULTS, (json, context) -> {
replaceIEResult(json, context);
return json;
});
} }
protected void replaceIEResult(@Nullable JsonElement element, RecipeContext context) { protected void replaceIEResult(@Nullable JsonElement element, RecipeContext context) {