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

View file

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

View file

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

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;
import com.almostreliable.unified.api.RecipeContext;
import com.almostreliable.unified.api.RecipeHandler;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import net.minecraft.resources.ResourceLocation;
import com.almostreliable.unified.api.RecipeTransformations;
import java.util.Set;
@ -18,42 +14,14 @@ public class GenericRecipeHandler implements RecipeHandler {
RecipeConstants.RESULT,
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
public void transformRecipe(JsonObject json, RecipeContext context) {
public void collectTransformations(RecipeTransformations builder) {
for (String inputKey : inputKeys) {
if (json.has(inputKey)) {
context.replaceIngredient(json.get(inputKey));
}
builder.replaceIngredient(inputKey);
}
for (String outputKey : outputKeys) {
JsonElement jsonElement = json.get(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);
}
builder.replaceResult(outputKey);
}
}
}

View file

@ -2,9 +2,9 @@ package com.almostreliable.unified.handler;
import com.almostreliable.unified.api.RecipeContext;
import com.almostreliable.unified.api.RecipeHandler;
import com.almostreliable.unified.api.RecipeTransformations;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
@ -12,32 +12,23 @@ public class RecipeHandlerFactory {
private final Map<ResourceLocation, RecipeHandler> transformersByType = new HashMap<>();
private final Map<String, RecipeHandler> transformersByModId = new HashMap<>();
@Nullable
public RecipeHandler create(RecipeContext context) {
public void create(RecipeTransformations builder, 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());
if (byType != null) {
return byType;
byType.collectTransformations(builder);
}
RecipeHandler byMod = transformersByModId.get(context.getModId());
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) {

View file

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

View file

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