Set up tests & clean up

This commit is contained in:
LLytho 2022-06-23 18:47:03 +02:00
parent ce9543c55d
commit 455d275508
29 changed files with 774 additions and 189 deletions

1
.gitignore vendored
View file

@ -19,5 +19,6 @@ build
.gradle .gradle
# other # other
*.log
eclipse eclipse
run run

View file

@ -45,6 +45,12 @@ dependencies {
* DON'T USE THIS! NEEDED TO COMPILE THIS PROJECT * DON'T USE THIS! NEEDED TO COMPILE THIS PROJECT
*/ */
modCompileOnly("net.fabricmc:fabric-loader:${fabricLoaderVersion}") modCompileOnly("net.fabricmc:fabric-loader:${fabricLoaderVersion}")
/**
* Test dependencies
*/
testImplementation ("org.junit.jupiter:junit-jupiter-api:5.8.1")
testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.8.1")
} }
tasks.processResources { tasks.processResources {
@ -75,3 +81,7 @@ buildConfig {
packageName(project.group as String) packageName(project.group as String)
} }
tasks.withType<Test> {
useJUnitPlatform()
}

View file

@ -6,12 +6,12 @@ import com.almostreliable.unified.utils.ReplacementMap;
import com.almostreliable.unified.utils.TagMap; import com.almostreliable.unified.utils.TagMap;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.tags.TagManager; import net.minecraft.tags.TagManager;
import net.minecraft.world.item.Item;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.*; import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public abstract class AlmostUnifiedRuntime { public abstract class AlmostUnifiedRuntime {
@ -29,7 +29,8 @@ public abstract class AlmostUnifiedRuntime {
config.load(); config.load();
modPriorities = config.getModPriorities(); modPriorities = config.getModPriorities();
onRun(); onRun();
ReplacementMap replacementMap = createContext(config.getAllowedTags(), modPriorities); TagMap tagMap = createTagMap();
ReplacementMap replacementMap = new ReplacementMap(tagMap, config.getAllowedTags(), modPriorities);
RecipeTransformer transformer = new RecipeTransformer(recipeHandlerFactory, replacementMap); RecipeTransformer transformer = new RecipeTransformer(recipeHandlerFactory, replacementMap);
transformer.transformRecipes(recipes); transformer.transformRecipes(recipes);
} }
@ -46,26 +47,5 @@ public abstract class AlmostUnifiedRuntime {
return TagMap.create(tagManager); return TagMap.create(tagManager);
} }
protected ReplacementMap createContext(List<TagKey<Item>> allowedTags, List<String> modPriorities) {
TagMap tagMap = createTagMap();
Map<ResourceLocation, TagKey<Item>> itemToTagMapping = new HashMap<>(allowedTags.size());
for (TagKey<Item> tag : allowedTags) {
Collection<ResourceLocation> items = tagMap.getItems(tag);
for (ResourceLocation item : items) {
if (itemToTagMapping.containsKey(item)) {
AlmostUnified.LOG.warn("Item '{}' already has a tag '{}' for recipe replacement. Skipping this tag",
item,
tag);
continue;
}
itemToTagMapping.put(item, tag);
}
}
return new ReplacementMap(tagMap, itemToTagMapping, modPriorities);
}
protected abstract void onRun(); protected abstract void onRun();
} }

View file

@ -1,14 +1,13 @@
package com.almostreliable.unified; package com.almostreliable.unified;
import com.almostreliable.unified.utils.UnifyTag;
import com.electronwill.nightconfig.core.Config; import com.electronwill.nightconfig.core.Config;
import com.electronwill.nightconfig.core.ConfigSpec; import com.electronwill.nightconfig.core.ConfigSpec;
import com.electronwill.nightconfig.core.file.CommentedFileConfig; import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.file.FileConfig; import com.electronwill.nightconfig.core.file.FileConfig;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
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;
@ -164,13 +163,13 @@ public class ModConfig {
return currentConfig.get(UNIFICATION_MOD_PRIORITIES); return currentConfig.get(UNIFICATION_MOD_PRIORITIES);
} }
public List<TagKey<Item>> getAllowedTags() { public List<UnifyTag<Item>> getAllowedTags() {
if (currentConfig == null) { if (currentConfig == null) {
throw new IllegalStateException("Config is not loaded"); throw new IllegalStateException("Config is not loaded");
} }
Multimap<String, String> variables = compileVariables(); Multimap<String, String> variables = compileVariables();
List<TagKey<Item>> collectedPattern = new ArrayList<>(); List<UnifyTag<Item>> collectedPattern = new ArrayList<>();
Collection<String> patterns = currentConfig.get(UNIFICATION_PATTERN); Collection<String> patterns = currentConfig.get(UNIFICATION_PATTERN);
for (String pattern : patterns) { for (String pattern : patterns) {
@ -183,7 +182,7 @@ public class ModConfig {
if (rl == null) { if (rl == null) {
AlmostUnified.LOG.warn("Invalid pattern: " + s); AlmostUnified.LOG.warn("Invalid pattern: " + s);
} else { } else {
TagKey<Item> tag = TagKey.create(Registry.ITEM_REGISTRY, rl); UnifyTag<Item> tag = UnifyTag.item(rl);
collectedPattern.add(tag); collectedPattern.add(tag);
} }
} }

View file

@ -10,4 +10,5 @@ public class RecipeConstants {
public static final String OUTPUT = "output"; public static final String OUTPUT = "output";
public static final String RESULT = "result"; public static final String RESULT = "result";
public static final String RESULTS = "results"; public static final String RESULTS = "results";
public static final String VALUE = "value";
} }

View file

@ -1,11 +1,13 @@
package com.almostreliable.unified.api.recipe; package com.almostreliable.unified.api.recipe;
import com.almostreliable.unified.utils.UnifyTag;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
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.function.UnaryOperator;
public interface RecipeContext { public interface RecipeContext {
@ -13,19 +15,18 @@ public interface RecipeContext {
ResourceLocation getReplacementForItem(@Nullable ResourceLocation item); ResourceLocation getReplacementForItem(@Nullable ResourceLocation item);
@Nullable @Nullable
ResourceLocation getPreferredItemByTag(TagKey<Item> tag); ResourceLocation getPreferredItemByTag(@Nullable UnifyTag<Item> tag);
@Nullable @Nullable
TagKey<Item> getPreferredTagByItem(@Nullable ResourceLocation item); UnifyTag<Item> getPreferredTagByItem(@Nullable ResourceLocation item);
JsonElement replaceIngredient(JsonElement element); JsonElement createIngredientReplacement(@Nullable JsonElement element);
JsonElement replaceResult(JsonElement element); @Nullable
JsonElement createResultReplacement(@Nullable JsonElement element);
ResourceLocation getType(); ResourceLocation getType();
ResourceLocation getId();
boolean hasProperty(String property); boolean hasProperty(String property);
default String getModId() { default String getModId() {

View file

@ -9,10 +9,6 @@ public interface RecipeTransformations {
void forEachObject(String property, BiFunction<JsonObject, RecipeContext, JsonObject> consumer); 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); void put(String property, BiFunction<JsonElement, RecipeContext, JsonElement> consumer);
<T extends JsonElement> void put(String property, Class<T> type, BiFunction<T, RecipeContext, T> consumer); <T extends JsonElement> void put(String property, Class<T> type, BiFunction<T, RecipeContext, T> consumer);

View file

@ -1,6 +1,7 @@
package com.almostreliable.unified.api.recipe; package com.almostreliable.unified.api.recipe;
import com.almostreliable.unified.utils.TagMap; import com.almostreliable.unified.utils.TagMap;
import com.almostreliable.unified.utils.UnifyTag;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey; import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
@ -19,5 +20,5 @@ public interface ReplacementFallbackStrategy {
* @throws IllegalStateException if returning the lookupItem * @throws IllegalStateException if returning the lookupItem
*/ */
@Nullable @Nullable
ResourceLocation getFallback(TagKey<Item> tag, Collection<ResourceLocation> potentialItems, TagMap tags); ResourceLocation getFallback(UnifyTag<Item> tag, Collection<ResourceLocation> potentialItems, TagMap tags);
} }

View file

@ -2,28 +2,26 @@ package com.almostreliable.unified.recipe;
import com.almostreliable.unified.api.recipe.RecipeConstants; import com.almostreliable.unified.api.recipe.RecipeConstants;
import com.almostreliable.unified.api.recipe.RecipeContext; import com.almostreliable.unified.api.recipe.RecipeContext;
import com.almostreliable.unified.utils.JsonUtils;
import com.almostreliable.unified.utils.ReplacementMap; import com.almostreliable.unified.utils.ReplacementMap;
import com.almostreliable.unified.utils.UnifyTag;
import com.google.gson.JsonArray; 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;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
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.function.UnaryOperator;
@SuppressWarnings("SameParameterValue") @SuppressWarnings("SameParameterValue")
public class RecipeContextImpl implements RecipeContext { public class RecipeContextImpl implements RecipeContext {
private final ResourceLocation type; private final ResourceLocation type;
private final ResourceLocation id;
private final JsonObject currentRecipe; private final JsonObject currentRecipe;
private final ReplacementMap replacementMap; private final ReplacementMap replacementMap;
public RecipeContextImpl(ResourceLocation type, ResourceLocation id, JsonObject currentRecipe, ReplacementMap replacementMap) { public RecipeContextImpl(ResourceLocation type, JsonObject currentRecipe, ReplacementMap replacementMap) {
this.type = type; this.type = type;
this.id = id;
this.currentRecipe = currentRecipe; this.currentRecipe = currentRecipe;
this.replacementMap = replacementMap; this.replacementMap = replacementMap;
} }
@ -40,13 +38,17 @@ public class RecipeContextImpl implements RecipeContext {
@Nullable @Nullable
@Override @Override
public ResourceLocation getPreferredItemByTag(TagKey<Item> tag) { public ResourceLocation getPreferredItemByTag(@Nullable UnifyTag<Item> tag) {
if (tag == null) {
return null;
}
return replacementMap.getPreferredItemByTag(tag); return replacementMap.getPreferredItemByTag(tag);
} }
@Nullable @Nullable
@Override @Override
public TagKey<Item> getPreferredTagByItem(@Nullable ResourceLocation item) { public UnifyTag<Item> getPreferredTagByItem(@Nullable ResourceLocation item) {
if (item == null) { if (item == null) {
return null; return null;
} }
@ -54,58 +56,74 @@ public class RecipeContextImpl implements RecipeContext {
return replacementMap.getPreferredTag(item); return replacementMap.getPreferredTag(item);
} }
@Override
@Nullable @Nullable
protected JsonElement depthReplace(JsonElement element, String potentialFrom, String potentialTo, UnaryOperator<JsonPrimitive> primitiveCallback) { public JsonElement createIngredientReplacement(@Nullable JsonElement element) {
if (element instanceof JsonPrimitive primitive) { if (element == null) {
return primitiveCallback.apply(primitive); return null;
}
JsonElement copy = element.deepCopy();
tryReplacingItemInIngredient(copy);
return element.equals(copy) ? null : copy;
}
private void tryReplacingItemInIngredient(@Nullable JsonElement element) {
if (element instanceof JsonArray array) {
for (JsonElement e : array) {
tryReplacingItemInIngredient(e);
}
} }
if (element instanceof JsonObject object) { if (element instanceof JsonObject object) {
JsonElement replace = depthReplace(object.get(potentialFrom), tryReplacingItemInIngredient(object.get(RecipeConstants.VALUE));
potentialFrom, tryReplacingItemInIngredient(object.get(RecipeConstants.INGREDIENT));
potentialTo,
primitiveCallback);
if (replace != null) {
object.remove(potentialFrom);
object.add(potentialTo, replace);
}
}
if (element instanceof JsonArray array) { if (object.get(RecipeConstants.ITEM) instanceof JsonPrimitive primitive) {
for (int i = 0; i < array.size(); i++) { ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString());
JsonElement replace = depthReplace(array.get(i), potentialFrom, potentialTo, primitiveCallback); UnifyTag<Item> tag = getPreferredTagByItem(item);
if (replace != null) { if (tag != null) {
array.set(i, replace); object.remove(RecipeConstants.ITEM);
object.add(RecipeConstants.TAG, new JsonPrimitive(tag.location().toString()));
} }
} }
} }
return null;
} }
@Override @Override
@Nullable @Nullable
public JsonElement replaceIngredient(JsonElement element) { public JsonElement createResultReplacement(@Nullable JsonElement element) {
return depthReplace(element, RecipeConstants.ITEM, RecipeConstants.TAG, primitive -> { if (element == null) {
ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString());
TagKey<Item> tag = getPreferredTagByItem(item);
if (tag != null) {
return new JsonPrimitive(tag.location().toString());
}
return null; return null;
}); }
JsonElement copy = element.deepCopy();
JsonElement result = tryCreateResultReplacement(copy);
return element.equals(result) ? null : result;
} }
@Override @Nullable
public JsonElement replaceResult(JsonElement element) { private JsonElement tryCreateResultReplacement(JsonElement element) {
return depthReplace(element, RecipeConstants.ITEM, RecipeConstants.ITEM, primitive -> { if (element instanceof JsonPrimitive primitive) {
ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString()); ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString());
ResourceLocation replacement = getReplacementForItem(item); ResourceLocation replacement = getReplacementForItem(item);
if (replacement != null) { if (replacement != null) {
return new JsonPrimitive(replacement.toString()); return new JsonPrimitive(replacement.toString());
} }
return null; return null;
}); }
if (element instanceof JsonArray array) {
if (JsonUtils.replaceOn(array, this::tryCreateResultReplacement)) {
return element;
}
}
if (element instanceof JsonObject object) {
if (JsonUtils.replaceOn(object, RecipeConstants.ITEM, this::tryCreateResultReplacement)) {
return element;
}
}
return null;
} }
@Override @Override
@ -113,11 +131,6 @@ public class RecipeContextImpl implements RecipeContext {
return type; return type;
} }
@Override
public ResourceLocation getId() {
return id;
}
@Override @Override
public boolean hasProperty(String property) { public boolean hasProperty(String property) {
return currentRecipe.has(property); return currentRecipe.has(property);

View file

@ -32,16 +32,6 @@ public class RecipeTransformationsImpl implements RecipeTransformations {
put(property, JsonArray.class, arrayConsumer); 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 @Override
public void put(String property, BiFunction<JsonElement, RecipeContext, JsonElement> consumer) { public void put(String property, BiFunction<JsonElement, RecipeContext, JsonElement> consumer) {
consumers.put(property, new Entry<>(JsonElement.class, consumer)); consumers.put(property, new Entry<>(JsonElement.class, consumer));
@ -52,16 +42,37 @@ public class RecipeTransformationsImpl implements RecipeTransformations {
consumers.put(property, new Entry<>(type, consumer)); consumers.put(property, new Entry<>(type, consumer));
} }
public void transform(JsonObject json, RecipeContext context) { @Nullable
public JsonObject transform(JsonObject json, RecipeContext context) {
JsonObject changedValues = new JsonObject();
for (var e : json.entrySet()) { for (var e : json.entrySet()) {
Entry<?> consumer = consumers.get(e.getKey()); Entry<?> consumer = consumers.get(e.getKey());
if (consumer != null) { if (consumer != null) {
JsonElement result = consumer.apply(e.getValue(), context); JsonElement currentElement = e.getValue();
if (result != null) { JsonElement transformedElement = consumer.apply(currentElement.deepCopy(), context);
e.setValue(result); if (transformedElement != null && !transformedElement.equals(currentElement)) {
changedValues.add(e.getKey(), transformedElement);
} }
} }
} }
if (changedValues.size() == 0) {
return null;
}
// helps to preserve the order of the elements
JsonObject result = new JsonObject();
for (var entry : json.entrySet()) {
JsonElement changedValue = changedValues.get(entry.getKey());
if (changedValue != null) {
result.add(entry.getKey(), changedValue);
} else {
result.add(entry.getKey(), entry.getValue());
}
}
return result;
} }
private record Entry<T extends JsonElement>(Class<T> expectedType, private record Entry<T extends JsonElement>(Class<T> expectedType,

View file

@ -32,7 +32,7 @@ public class RecipeTransformer {
long start = System.nanoTime(); long start = System.nanoTime();
for (var entry : recipes.entrySet()) { for (var entry : recipes.entrySet()) {
if (entry.getValue() instanceof JsonObject json) { if (entry.getValue() instanceof JsonObject json) {
JsonObject transformedJson = transformRecipe(entry.getKey(), json); JsonObject transformedJson = transformRecipe(json);
if (transformedJson != null) { if (transformedJson != null) {
transformedRecipes++; transformedRecipes++;
entry.setValue(transformedJson); entry.setValue(transformedJson);
@ -54,25 +54,24 @@ public class RecipeTransformer {
AlmostUnified.LOG.info("{}: {} | {}", AlmostUnified.LOG.info("{}: {} | {}",
StringUtils.leftPad(e.getKey().toString(), 40), StringUtils.leftPad(e.getKey().toString(), 40),
StringUtils.leftPad(String.valueOf(e.getValue().size()), 4), StringUtils.leftPad(String.valueOf(e.getValue().size()), 4),
e.getValue().toString()); e.getValue().stream().map(rl -> "\"" + rl.toString() + "\"").toList().toString());
}); });
} }
@Nullable @Nullable
public JsonObject transformRecipe(ResourceLocation id, JsonObject json) { public JsonObject transformRecipe(JsonObject json) {
ResourceLocation recipeType = getRecipeType(json); ResourceLocation recipeType = getRecipeType(json);
if (recipeType == null) { if (recipeType == null) {
return null; return null;
} }
try { try {
RecipeContextImpl ctx = new RecipeContextImpl(recipeType, id, json, replacementMap); RecipeContextImpl ctx = new RecipeContextImpl(recipeType, json, replacementMap);
RecipeTransformationsImpl builder = new RecipeTransformationsImpl(); RecipeTransformationsImpl builder = new RecipeTransformationsImpl();
factory.create(builder, ctx); factory.create(builder, ctx);
JsonObject copy = json.deepCopy(); JsonObject result = builder.transform(json, ctx);
builder.transform(copy, ctx); if (result != null) {
if (!json.equals(copy)) { return result;
return copy;
} }
} catch (Exception e) { } catch (Exception e) {
AlmostUnified.LOG.warn("Error transforming recipe type '{}': {}", AlmostUnified.LOG.warn("Error transforming recipe type '{}': {}",

View file

@ -2,6 +2,7 @@ package com.almostreliable.unified.recipe.fallbacks;
import com.almostreliable.unified.api.recipe.ReplacementFallbackStrategy; import com.almostreliable.unified.api.recipe.ReplacementFallbackStrategy;
import com.almostreliable.unified.utils.TagMap; import com.almostreliable.unified.utils.TagMap;
import com.almostreliable.unified.utils.UnifyTag;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey; import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
@ -12,7 +13,7 @@ import java.util.Comparator;
// TODO use config and not this :D // TODO use config and not this :D
public class StoneStrataFallbackStrategy implements ReplacementFallbackStrategy { public class StoneStrataFallbackStrategy implements ReplacementFallbackStrategy {
@Override @Override
public ResourceLocation getFallback(TagKey<Item> tag, Collection<ResourceLocation> potentialItems, TagMap tags) { public ResourceLocation getFallback(UnifyTag<Item> tag, Collection<ResourceLocation> potentialItems, TagMap tags) {
if (tag.location().getPath().contains("ores")) { if (tag.location().getPath().contains("ores")) {
return potentialItems return potentialItems
.stream() .stream()

View file

@ -18,11 +18,11 @@ public class GenericRecipeHandler implements RecipeHandler {
@Override @Override
public void collectTransformations(RecipeTransformations builder) { public void collectTransformations(RecipeTransformations builder) {
for (String inputKey : inputKeys) { for (String inputKey : inputKeys) {
builder.replaceIngredient(inputKey); builder.put(inputKey, (json, ctx) -> ctx.createIngredientReplacement(json));
} }
for (String outputKey : outputKeys) { for (String outputKey : outputKeys) {
builder.replaceResult(outputKey); builder.put(outputKey, (json, ctx) -> ctx.createResultReplacement(json));
} }
} }
} }

View file

@ -20,15 +20,15 @@ public class RecipeHandlerFactory {
ShapedRecipeKeyHandler.INSTANCE.collectTransformations(builder); ShapedRecipeKeyHandler.INSTANCE.collectTransformations(builder);
} }
RecipeHandler byType = transformersByType.get(context.getType());
if (byType != null) {
byType.collectTransformations(builder);
}
RecipeHandler byMod = transformersByModId.get(context.getModId()); RecipeHandler byMod = transformersByModId.get(context.getModId());
if (byMod != null) { if (byMod != null) {
byMod.collectTransformations(builder); byMod.collectTransformations(builder);
} }
RecipeHandler byType = transformersByType.get(context.getType());
if (byType != null) {
byType.collectTransformations(builder);
}
} }
public void registerForType(ResourceLocation type, RecipeHandler transformer) { public void registerForType(ResourceLocation type, RecipeHandler transformer) {

View file

@ -14,7 +14,7 @@ public class ShapedRecipeKeyHandler implements RecipeHandler {
public void collectTransformations(RecipeTransformations builder) { public void collectTransformations(RecipeTransformations builder) {
builder.put(KEY_PROPERTY, JsonObject.class, (json, context) -> { builder.put(KEY_PROPERTY, JsonObject.class, (json, context) -> {
for (var entry : json.entrySet()) { for (var entry : json.entrySet()) {
JsonElement result = context.replaceIngredient(entry.getValue()); JsonElement result = context.createIngredientReplacement(entry.getValue());
if (result != null) { if (result != null) {
entry.setValue(result); entry.setValue(result);
} }

View file

@ -0,0 +1,86 @@
package com.almostreliable.unified.utils;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.apache.commons.lang3.StringUtils;
public class JsonQuery {
private final JsonElement element;
public static JsonQuery of(JsonElement element) {
return new JsonQuery(element);
}
public static JsonQuery of(JsonElement element, String path) {
String[] parts = path.split("/");
JsonQuery current = of(element);
for (String part : parts) {
if(StringUtils.isNumeric(part)) {
current = current.get(Integer.parseInt(part));
} else {
current = current.get(part);
}
}
return current;
}
JsonQuery(JsonElement element) {
this.element = element;
}
public JsonQuery get(String identifier) {
if(!element.isJsonObject()) {
throw new IllegalArgumentException("Expected JsonObject, got " + element.getClass());
}
JsonElement child = element.getAsJsonObject().get(identifier);
if(child == null) {
return null;
}
return new JsonQuery(child);
}
public JsonQuery get(int index) {
if(!element.isJsonArray()) {
throw new IllegalArgumentException("Expected JsonArray, got " + element.getClass());
}
JsonElement child = element.getAsJsonArray().get(index);
if(child == null) {
return null;
}
return new JsonQuery(child);
}
public JsonQuery get(String identifier, int index) {
return get(identifier).get(index);
}
public JsonObject asObject() {
return element.getAsJsonObject();
}
public JsonArray asArray() {
return element.getAsJsonArray();
}
public String asString() {
return element.getAsString();
}
public int asInt() {
return element.getAsInt();
}
public boolean asBoolean() {
return element.getAsBoolean();
}
public float asFloat() {
return element.getAsFloat();
}
}

View file

@ -2,9 +2,11 @@ package com.almostreliable.unified.utils;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.UnaryOperator;
public class JsonUtils { public class JsonUtils {
public static JsonArray arrayOrSelf(@Nullable JsonElement element) { public static JsonArray arrayOrSelf(@Nullable JsonElement element) {
@ -28,4 +30,46 @@ public class JsonUtils {
} }
} }
} }
/**
* Loops through the array and applies the given callback to each element.
* If the callback returns non-null, the element is replaced with the returned value.
*
* @param jsonArray The array to loop through.
* @param callback The callback to apply to each element.
* @return true if any elements were replaced, false otherwise.
*/
public static boolean replaceOn(JsonArray jsonArray, UnaryOperator<JsonElement> callback) {
boolean changed = false;
for (int i = 0; i < jsonArray.size(); i++) {
JsonElement result = callback.apply(jsonArray.get(i));
if (result != null) {
jsonArray.set(i, result);
changed = true;
}
}
return changed;
}
/**
* Replaces the element for the key through given callback.
* If the callback returns non-null, the element is replaced with the returned value.
*
* @param jsonObject The object to loop through.
* @param callback The callback to apply to each element.
* @return true if the element was replaced, false otherwise.
*/
public static boolean replaceOn(JsonObject jsonObject, String key, UnaryOperator<JsonElement> callback) {
JsonElement element = jsonObject.get(key);
if (element == null) {
return false;
}
JsonElement result = callback.apply(element);
if (result != null) {
jsonObject.add(key, result);
return true;
}
return false;
}
} }

View file

@ -1,5 +1,6 @@
package com.almostreliable.unified.utils; package com.almostreliable.unified.utils;
import com.almostreliable.unified.AlmostUnified;
import com.almostreliable.unified.api.recipe.ReplacementFallbackStrategy; import com.almostreliable.unified.api.recipe.ReplacementFallbackStrategy;
import com.almostreliable.unified.recipe.fallbacks.StoneStrataFallbackStrategy; import com.almostreliable.unified.recipe.fallbacks.StoneStrataFallbackStrategy;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
@ -8,6 +9,7 @@ import net.minecraft.world.item.Item;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -15,24 +17,43 @@ public class ReplacementMap {
private final Collection<String> modPriorities; private final Collection<String> modPriorities;
private final TagMap tagMap; private final TagMap tagMap;
private final Map<ResourceLocation, TagKey<Item>> itemToTagMapping; private final Map<ResourceLocation, UnifyTag<Item>> itemToTagMapping;
// TODO - In the future this may be a list of multiple fallbacks. // TODO - In the future this may be a list of multiple fallbacks.
private final ReplacementFallbackStrategy fallbackStrategy = new StoneStrataFallbackStrategy(); private final ReplacementFallbackStrategy fallbackStrategy = new StoneStrataFallbackStrategy();
public ReplacementMap(TagMap tagMap, Map<ResourceLocation, TagKey<Item>> itemToTagMapping, List<String> modPriorities) { public ReplacementMap(TagMap tagMap, List<UnifyTag<Item>> allowedTags, List<String> modPriorities) {
this.tagMap = tagMap; this.tagMap = tagMap;
this.itemToTagMapping = itemToTagMapping;
this.modPriorities = modPriorities; this.modPriorities = modPriorities;
this.itemToTagMapping = createItemMapping(allowedTags);
}
protected Map<ResourceLocation, UnifyTag<Item>> createItemMapping(List<UnifyTag<Item>> allowedTags) {
Map<ResourceLocation, UnifyTag<Item>> itemToTagMapping = new HashMap<>(allowedTags.size());
for (UnifyTag<Item> tag : allowedTags) {
Collection<ResourceLocation> items = tagMap.getItems(tag);
for (ResourceLocation item : items) {
if (itemToTagMapping.containsKey(item)) {
AlmostUnified.LOG.warn("Item '{}' already has a tag '{}' for recipe replacement. Skipping this tag",
item,
tag);
continue;
}
itemToTagMapping.put(item, tag);
}
}
return itemToTagMapping;
} }
@Nullable @Nullable
public TagKey<Item> getPreferredTag(ResourceLocation item) { public UnifyTag<Item> getPreferredTag(ResourceLocation item) {
return itemToTagMapping.get(item); return itemToTagMapping.get(item);
} }
@Nullable @Nullable
public ResourceLocation getReplacementForItem(ResourceLocation item) { public ResourceLocation getReplacementForItem(ResourceLocation item) {
TagKey<Item> tag = getPreferredTag(item); UnifyTag<Item> tag = getPreferredTag(item);
if (tag == null) { if (tag == null) {
return null; return null;
} }
@ -46,13 +67,17 @@ public class ReplacementMap {
} }
@Nullable @Nullable
public ResourceLocation getPreferredItemByTag(TagKey<Item> tag) { public ResourceLocation getPreferredItemByTag(UnifyTag<Item> tag) {
return getPreferredItemByTag(tag, null); return getPreferredItemByTag(tag, null);
} }
@Nullable @Nullable
public ResourceLocation getPreferredItemByTag(TagKey<Item> tag, @Nullable String ignoredNamespace) { public ResourceLocation getPreferredItemByTag(UnifyTag<Item> tag, @Nullable String ignoredNamespace) {
for (String mod : modPriorities) { for (String mod : modPriorities) {
if(mod.equals(ignoredNamespace)) {
return null;
}
List<ResourceLocation> sameModItems = tagMap List<ResourceLocation> sameModItems = tagMap
.getItems(tag) .getItems(tag)
.stream() .stream()
@ -62,7 +87,7 @@ public class ReplacementMap {
return sameModItems.get(0); return sameModItems.get(0);
} }
if (sameModItems.size() > 1 && !mod.equals(ignoredNamespace)) { if (sameModItems.size() > 1) {
ResourceLocation fallback = fallbackStrategy.getFallback(tag, sameModItems, tagMap); ResourceLocation fallback = fallbackStrategy.getFallback(tag, sameModItems, tagMap);
if (fallback != null) { if (fallback != null) {
return fallback; return fallback;

View file

@ -12,8 +12,8 @@ import net.minecraft.world.item.Item;
import java.util.*; import java.util.*;
public class TagMap { public class TagMap {
private final Map<TagKey<Item>, Set<ResourceLocation>> tagsToItems = new HashMap<>(); private final Map<UnifyTag<Item>, Set<ResourceLocation>> tagsToItems = new HashMap<>();
private final Map<ResourceLocation, Set<TagKey<Item>>> itemsToTags = new HashMap<>(); private final Map<ResourceLocation, Set<UnifyTag<Item>>> itemsToTags = new HashMap<>();
protected TagMap() {} protected TagMap() {}
@ -31,7 +31,7 @@ public class TagMap {
TagMap tagMap = new TagMap(); TagMap tagMap = new TagMap();
for (var entry : tags.entrySet()) { for (var entry : tags.entrySet()) {
TagKey<Item> tag = TagKey.create(Registry.ITEM_REGISTRY, entry.getKey()); UnifyTag<Item> tag = UnifyTag.item(entry.getKey());
Tag<? extends Holder<?>> holderTag = entry.getValue(); Tag<? extends Holder<?>> holderTag = entry.getValue();
for (Holder<?> holder : holderTag.getValues()) { for (Holder<?> holder : holderTag.getValues()) {
@ -46,16 +46,24 @@ public class TagMap {
return tagMap; return tagMap;
} }
protected void put(TagKey<Item> tag, ResourceLocation item) { protected void put(UnifyTag<Item> tag, ResourceLocation item) {
tagsToItems.computeIfAbsent(tag, k -> new HashSet<>()).add(item); tagsToItems.computeIfAbsent(tag, k -> new HashSet<>()).add(item);
itemsToTags.computeIfAbsent(item, k -> new HashSet<>()).add(tag); itemsToTags.computeIfAbsent(item, k -> new HashSet<>()).add(tag);
} }
public Collection<ResourceLocation> getItems(TagKey<Item> tag) { public Collection<ResourceLocation> getItems(UnifyTag<Item> tag) {
return Collections.unmodifiableSet(tagsToItems.getOrDefault(tag, Collections.emptySet())); return Collections.unmodifiableSet(tagsToItems.getOrDefault(tag, Collections.emptySet()));
} }
public Collection<TagKey<Item>> getTags(ResourceLocation items) { public Collection<UnifyTag<Item>> getTags(ResourceLocation items) {
return Collections.unmodifiableSet(itemsToTags.getOrDefault(items, Collections.emptySet())); return Collections.unmodifiableSet(itemsToTags.getOrDefault(items, Collections.emptySet()));
} }
public int tagSize() {
return tagsToItems.size();
}
public int itemSize() {
return itemsToTags.size();
}
} }

View file

@ -0,0 +1,15 @@
package com.almostreliable.unified.utils;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
public record UnifyTag<T>(Class<T> boundType, ResourceLocation location) {
public static UnifyTag<Item> item(ResourceLocation location) {
return new UnifyTag<>(Item.class, location);
}
@Override
public String toString() {
return "UnifyTag[" + boundType.getSimpleName().toLowerCase() + " / " + location + "]";
}
}

View file

@ -10,9 +10,9 @@ import javax.annotation.Nullable;
public class Utils { public class Utils {
public static final ResourceLocation UNUSED_ID = new ResourceLocation(BuildConfig.MOD_ID, "unused_id"); public static final ResourceLocation UNUSED_ID = new ResourceLocation(BuildConfig.MOD_ID, "unused_id");
public static final TagKey<Item> UNUSED_TAG = TagKey.create(Registry.ITEM_REGISTRY, UNUSED_ID); public static final UnifyTag<Item> UNUSED_TAG = UnifyTag.item(UNUSED_ID);
public static TagKey<Item> toItemTag(@Nullable String tag) { public static UnifyTag<Item> toItemTag(@Nullable String tag) {
if (tag == null) { if (tag == null) {
return UNUSED_TAG; return UNUSED_TAG;
} }
@ -22,6 +22,6 @@ public class Utils {
return UNUSED_TAG; return UNUSED_TAG;
} }
return TagKey.create(Registry.ITEM_REGISTRY, rl); return UnifyTag.item(rl);
} }
} }

View file

@ -0,0 +1,23 @@
package com.almostreliable.unified;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class FakeResourceKeyRegistry {
@SuppressWarnings("unchecked")
public static <T> ResourceKey<Registry<T>> create(String name) {
try {
Constructor<?> c = ResourceKey.class.getDeclaredConstructor(ResourceLocation.class, ResourceLocation.class);
c.setAccessible(true);
return (ResourceKey<Registry<T>>) c.newInstance(new ResourceLocation("test_registry"),
new ResourceLocation(name));
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
}

View file

@ -0,0 +1,76 @@
package com.almostreliable.unified;
import com.almostreliable.unified.recipe.RecipeTransformer;
import com.almostreliable.unified.recipe.handler.RecipeHandlerFactory;
import com.almostreliable.unified.utils.ReplacementMap;
import com.almostreliable.unified.utils.TagMapTests;
import com.almostreliable.unified.utils.UnifyTag;
import com.google.gson.Gson;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import java.util.List;
import java.util.function.Consumer;
public class TestUtils {
public static final String TEST_MOD_1 = "test_mod_1";
public static final String TEST_MOD_2 = "test_mod_2";
public static final String TEST_MOD_3 = "test_mod_3";
public static final String TEST_MOD_4 = "test_mod_4";
public static final String TEST_MOD_5 = "test_mod_5";
public static final List<String> TEST_MOD_PRIORITIES = List.of(TEST_MOD_1,
TEST_MOD_2,
TEST_MOD_3,
TEST_MOD_4,
TEST_MOD_5);
public static final ResourceKey<Registry<Item>> FAKE_ITEM_REGISTRY = FakeResourceKeyRegistry.create("item");
public static final UnifyTag<Item> BRONZE_ORES_TAG = tag("forge:ores/bronze");
public static final UnifyTag<Item> INVAR_ORES_TAG = tag("forge:ores/invar");
public static final UnifyTag<Item> TIN_ORES_TAG = tag("forge:ores/tin");
public static final UnifyTag<Item> SILVER_ORES_TAG = tag("forge:ores/silver");
public static final List<UnifyTag<Item>> TEST_ALLOWED_TAGS = List.of(BRONZE_ORES_TAG,
INVAR_ORES_TAG,
TIN_ORES_TAG,
SILVER_ORES_TAG);
/**
* ResourceKey is null because otherwise tests can't run because Minecraft is not bootstrapped ...
*
* @param name the name of the tag
* @return a TagKey for the given name
*/
public static UnifyTag<Item> tag(String name) {
return UnifyTag.item(new ResourceLocation(name));
}
public static ResourceLocation mod1RL(String name) {
return new ResourceLocation(TEST_MOD_1, name);
}
public static ResourceLocation mod2RL(String name) {
return new ResourceLocation(TEST_MOD_2, name);
}
public static ResourceLocation mod3RL(String name) {
return new ResourceLocation(TEST_MOD_3, name);
}
public static ResourceLocation mod4RL(String name) {
return new ResourceLocation(TEST_MOD_4, name);
}
public static ResourceLocation mod5RL(String name) {
return new ResourceLocation(TEST_MOD_5, name);
}
public static RecipeTransformer basicTransformer(Consumer<RecipeHandlerFactory> consumer) {
ReplacementMap map = new ReplacementMap(TagMapTests.testTagMap(),
TestUtils.TEST_ALLOWED_TAGS,
TestUtils.TEST_MOD_PRIORITIES);
RecipeHandlerFactory factory = new RecipeHandlerFactory();
consumer.accept(factory);
return new RecipeTransformer(factory, map);
}
}

View file

@ -0,0 +1,97 @@
package com.almostreliable.unified.recipe;
import com.almostreliable.unified.TestUtils;
import com.almostreliable.unified.utils.JsonQuery;
import com.almostreliable.unified.utils.ReplacementMap;
import com.almostreliable.unified.utils.TagMapTests;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import net.minecraft.resources.ResourceLocation;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
public class RecipeContextImplTest {
private static String testJson = """
{
"input": [
{
"tag": "tag_replace_me"
},
[
{
"item": "item_replace_me"
},
{
"item": "item_replace_me"
},
[
{
"item": "item_replace_me"
},
{
"tag": "tag_replace_me"
}
]
]
],
"results": {
"item": "item_replace_me"
}
}""";
public static String mekaTest = """
{
"type": "mekanism:combining",
"mainInput": { "amount": 8, "ingredient": { "tag": "forge:raw_materials/tin" } },
"extraInput": { "ingredient": { "tag": "forge:cobblestone/normal" } },
"output": { "item": "mekanism:tin_ore" }
}
""";
@Test
public void depthReplace_MekaTest() {
JsonObject json = new Gson().fromJson(mekaTest, JsonObject.class);
ReplacementMap map = new ReplacementMap(TagMapTests.testTagMap(),
TestUtils.TEST_ALLOWED_TAGS,
TestUtils.TEST_MOD_PRIORITIES);
RecipeContextImpl context = new RecipeContextImpl(new ResourceLocation("test"), json, map);
JsonElement result = context.replaceResultOld(json.getAsJsonObject("output"));
assertNull(result);
}
@Test
public void depthReplace_NothingReplaced() {
JsonObject json = new Gson().fromJson(testJson, JsonObject.class);
ReplacementMap map = new ReplacementMap(TagMapTests.testTagMap(),
TestUtils.TEST_ALLOWED_TAGS,
TestUtils.TEST_MOD_PRIORITIES);
RecipeContextImpl context = new RecipeContextImpl(new ResourceLocation("test"), json, map);
JsonElement result = context.depthReplace(json,
"not_existing",
"item",
primitive -> new JsonPrimitive("dont_find_this"));
assertNull(result);
}
@Test
public void depthReplace_Items() {
JsonObject json = new Gson().fromJson(testJson, JsonObject.class);
ReplacementMap map = new ReplacementMap(TagMapTests.testTagMap(),
TestUtils.TEST_ALLOWED_TAGS,
TestUtils.TEST_MOD_PRIORITIES);
RecipeContextImpl context = new RecipeContextImpl(new ResourceLocation("test"), json, map);
JsonElement result = context.depthReplace(json,
"item",
"item",
primitive -> new JsonPrimitive("item_was_replaced"));
assertNotNull(result);
assertEquals(JsonQuery.of(result, "input/0/item").asString(), "item_was_replaced");
assertEquals(JsonQuery.of(result, "input/1/1/item").asString(), "item_was_replaced");
assertEquals(JsonQuery.of(result, "input/1/2/0/item").asString(), "item_was_replaced");
assertEquals(JsonQuery.of(result, "result/item").asString(), "item_was_replaced");
}
}

View file

@ -0,0 +1,49 @@
package com.almostreliable.unified.utils;
import com.almostreliable.unified.TestUtils;
import com.google.common.collect.Lists;
import net.minecraft.resources.ResourceLocation;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class ReplacementMapTests {
@Test
public void getPreferredItemByTag() {
ReplacementMap map = new ReplacementMap(TagMapTests.testTagMap(),
TestUtils.TEST_ALLOWED_TAGS,
TestUtils.TEST_MOD_PRIORITIES);
assertEquals(map.getPreferredItemByTag(TestUtils.BRONZE_ORES_TAG), TestUtils.mod1RL("bronze_ore"));
assertNotEquals(map.getPreferredItemByTag(TestUtils.BRONZE_ORES_TAG), TestUtils.mod2RL("bronze_ore"));
assertEquals(map.getPreferredItemByTag(TestUtils.INVAR_ORES_TAG), TestUtils.mod1RL("invar_ore"));
assertNotEquals(map.getPreferredItemByTag(TestUtils.INVAR_ORES_TAG), TestUtils.mod2RL("invar_ore"));
assertEquals(map.getPreferredItemByTag(TestUtils.TIN_ORES_TAG), TestUtils.mod3RL("tin_ore"));
assertNotEquals(map.getPreferredItemByTag(TestUtils.TIN_ORES_TAG), TestUtils.mod4RL("tin_ore"));
assertEquals(map.getPreferredItemByTag(TestUtils.SILVER_ORES_TAG), TestUtils.mod3RL("silver_ore"));
assertNotEquals(map.getPreferredItemByTag(TestUtils.SILVER_ORES_TAG), TestUtils.mod4RL("silver_ore"));
}
@Test
public void getPreferredItemByTag_ReversePriority() {
// We reverse the order. See `testTagMap` for the mapping.
List<String> reverse = Lists.reverse(TestUtils.TEST_MOD_PRIORITIES);
ReplacementMap reverseMap = new ReplacementMap(TagMapTests.testTagMap(), TestUtils.TEST_ALLOWED_TAGS, reverse);
assertEquals(reverseMap.getPreferredItemByTag(TestUtils.BRONZE_ORES_TAG), TestUtils.mod3RL("bronze_ore"));
assertEquals(reverseMap.getPreferredItemByTag(TestUtils.INVAR_ORES_TAG), TestUtils.mod4RL("invar_ore"));
assertEquals(reverseMap.getPreferredItemByTag(TestUtils.TIN_ORES_TAG), TestUtils.mod4RL("tin_ore"));
assertEquals(reverseMap.getPreferredItemByTag(TestUtils.SILVER_ORES_TAG), TestUtils.mod5RL("silver_ore"));
}
@Test
public void getPreferredTag() {
ReplacementMap map = new ReplacementMap(TagMapTests.testTagMap(),
TestUtils.TEST_ALLOWED_TAGS,
TestUtils.TEST_MOD_PRIORITIES);
assertEquals(map.getPreferredTag(TestUtils.mod1RL("bronze_ore")), TestUtils.BRONZE_ORES_TAG);
assertNull(map.getPreferredTag(new ResourceLocation("minecraft:diamond")), "We don't have a tag for diamond");
}
}

View file

@ -0,0 +1,59 @@
package com.almostreliable.unified.utils;
import com.almostreliable.unified.TestUtils;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TagMapTests {
public static TagMap testTagMap() {
TagMap tagMap = new TagMap();
UnifyTag<Item> bronzeOreTag = UnifyTag.item(new ResourceLocation("forge:ores/bronze"));
UnifyTag<Item> invarOreTag = UnifyTag.item(new ResourceLocation("forge:ores/invar"));
UnifyTag<Item> tinOreTag = UnifyTag.item(new ResourceLocation("forge:ores/tin"));
UnifyTag<Item> silverOreTag = UnifyTag.item(new ResourceLocation("forge:ores/silver"));
tagMap.put(bronzeOreTag, TestUtils.mod1RL("bronze_ore"));
tagMap.put(bronzeOreTag, TestUtils.mod2RL("bronze_ore"));
tagMap.put(bronzeOreTag, TestUtils.mod3RL("bronze_ore"));
tagMap.put(invarOreTag, TestUtils.mod1RL("invar_ore"));
tagMap.put(invarOreTag, TestUtils.mod2RL("invar_ore"));
tagMap.put(invarOreTag, TestUtils.mod3RL("invar_ore"));
tagMap.put(invarOreTag, TestUtils.mod4RL("invar_ore"));
tagMap.put(tinOreTag, TestUtils.mod3RL("tin_ore"));
tagMap.put(tinOreTag, TestUtils.mod4RL("tin_ore"));
tagMap.put(silverOreTag, TestUtils.mod3RL("silver_ore"));
tagMap.put(silverOreTag, TestUtils.mod4RL("silver_ore"));
tagMap.put(silverOreTag, TestUtils.mod5RL("silver_ore"));
return tagMap;
}
@Test
public void simpleCheck() {
TagMap tagMap = new TagMap();
UnifyTag<Item> bronzeOreTag = UnifyTag.item(new ResourceLocation("forge:ores/bronze"));
tagMap.put(bronzeOreTag, TestUtils.mod1RL("bronze_ore"));
tagMap.put(bronzeOreTag, TestUtils.mod2RL("bronze_ore"));
tagMap.put(bronzeOreTag, TestUtils.mod3RL("bronze_ore"));
tagMap.put(bronzeOreTag, TestUtils.mod4RL("bronze_ore"));
tagMap.put(bronzeOreTag, TestUtils.mod5RL("bronze_ore"));
assertEquals(tagMap.getItems(bronzeOreTag).size(), 5);
assertEquals(tagMap.getTags(TestUtils.mod1RL("bronze_ore")).size(), 1);
assertEquals(tagMap.getTags(TestUtils.mod2RL("bronze_ore")).size(), 1);
assertEquals(tagMap.getTags(TestUtils.mod3RL("bronze_ore")).size(), 1);
assertEquals(tagMap.getTags(TestUtils.mod4RL("bronze_ore")).size(), 1);
assertEquals(tagMap.getTags(TestUtils.mod5RL("bronze_ore")).size(), 1);
tagMap.put(UnifyTag.item(new ResourceLocation("forge:ores/invar")), TestUtils.mod1RL("invar_ore"));
assertEquals(tagMap.tagSize(), 2);
assertEquals(tagMap.itemSize(), 6);
}
}

View file

@ -61,6 +61,9 @@ minecraft {
sourceSets.main.get().resources.srcDir("src/generated/resources") sourceSets.main.get().resources.srcDir("src/generated/resources")
// from millions of solutions, this is the only one which works... :-)
val commonTests: SourceSetOutput = project(":Common").sourceSets["test"].output
dependencies { dependencies {
minecraft("net.minecraftforge:forge:${minecraftVersion}-${forgeVersion}") minecraft("net.minecraftforge:forge:${minecraftVersion}-${forgeVersion}")
compileOnly(project(":Common")) compileOnly(project(":Common"))
@ -80,6 +83,14 @@ dependencies {
runtimeOnly(fg.deobf("curse.maven:nihilo-400012:3810814")) runtimeOnly(fg.deobf("curse.maven:nihilo-400012:3810814"))
annotationProcessor("org.spongepowered:mixin:${mixinVersion}:processor") annotationProcessor("org.spongepowered:mixin:${mixinVersion}:processor")
/**
* Test dependencies
*/
testImplementation(project(":Common"))
testImplementation(commonTests)
testImplementation ("org.junit.jupiter:junit-jupiter-api:5.8.1")
testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.8.1")
} }
mixin { mixin {
@ -101,6 +112,10 @@ tasks {
} }
} }
tasks.withType<Test> {
useJUnitPlatform()
}
publishing { publishing {
publications { publications {
register("mavenJava", MavenPublication::class) { register("mavenJava", MavenPublication::class) {

View file

@ -6,6 +6,7 @@ import com.almostreliable.unified.api.recipe.RecipeHandler;
import com.almostreliable.unified.api.recipe.RecipeTransformations; import com.almostreliable.unified.api.recipe.RecipeTransformations;
import com.almostreliable.unified.utils.JsonUtils; import com.almostreliable.unified.utils.JsonUtils;
import com.almostreliable.unified.utils.Utils; import com.almostreliable.unified.utils.Utils;
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 net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
@ -20,79 +21,65 @@ public class IERecipeHandler implements RecipeHandler {
// TODO make it cleaner // TODO make it cleaner
@Override @Override
public void collectTransformations(RecipeTransformations builder) { public void collectTransformations(RecipeTransformations builder) {
builder.put("input0", (json, context) -> { builder.put("input0", this::replaceIEIngredient); // alloy recipes, refinery
replaceIEIngredient(json, context); builder.put("input1", this::replaceIEIngredient); // alloy recipes, refinery
return json; builder.put(RecipeConstants.INPUT,
}); // alloy recipes, refinery this::replaceIEIngredient); // arc furnace, squeezer, cloche, coke oven, fermenter, fertilizer, metal_press
builder.put("input1", (json, context) -> { builder.put("additives", this::replaceIEIngredient); // arc furnace
replaceIEIngredient(json, context); builder.put(RecipeConstants.INPUTS, this::replaceIEIngredient); // blueprint, mixer
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
// alloy recipes, crusher // alloy recipes, crusher
builder.forEachObject("secondaries", (jsonObject, context) -> { builder.forEachObject("secondaries",
replaceIEResult(jsonObject.get(RecipeConstants.OUTPUT), context); (jsonObject, context) ->
return jsonObject; replaceIEResult(jsonObject.get(RecipeConstants.OUTPUT), context) instanceof JsonObject
}); ? jsonObject : null);
builder.put(RecipeConstants.RESULT, this::replaceIEResult);
builder.put(RecipeConstants.RESULT, (json, context) -> { builder.put(RecipeConstants.RESULTS, this::replaceIEResult);
replaceIEResult(json, context);
return json;
});
builder.put(RecipeConstants.RESULTS, (json, context) -> {
replaceIEResult(json, context);
return json;
});
} }
protected void replaceIEResult(@Nullable JsonElement element, RecipeContext context) { @Nullable
if (element == null) { protected JsonElement replaceIEResult(@Nullable JsonElement element, RecipeContext context) {
return; if (element instanceof JsonArray array) {
if (JsonUtils.replaceOn(array, e -> this.replaceIEResult(e, context))) {
return array;
}
} }
JsonUtils.arrayForEach(element, JsonObject.class, json -> { if (element instanceof JsonObject object) {
if (json.has(RecipeConstants.ITEM)) { JsonElement tag = object.get(RecipeConstants.TAG);
context.replaceResult(json); if (tag != null) {
} else if (json.has(RecipeConstants.TAG)) {
/* /*
* Immersive Engineering allows tags in result and filters them. So we replace the tags with * Immersive Engineering allows tags in result and filters them. So we replace the tags with
* the preferred item from our config. * the preferred item from our config.
*/ */
ResourceLocation item = context.getPreferredItemByTag(Utils.toItemTag(json ResourceLocation item = context.getPreferredItemByTag(Utils.toItemTag(object
.get(RecipeConstants.TAG) .get(RecipeConstants.TAG)
.getAsString())); .getAsString()));
if (item != null) { if (item != null) {
json.remove(RecipeConstants.TAG); object.remove(RecipeConstants.TAG);
json.addProperty(RecipeConstants.ITEM, item.toString()); object.addProperty(RecipeConstants.ITEM, item.toString());
return object;
} }
} else if (json.has(BASE_KEY)) {
replaceIEResult(json.get(BASE_KEY), context);
} }
});
JsonElement base = object.get(BASE_KEY);
if (base != null) {
JsonElement baseResult = replaceIEResult(base, context);
if (baseResult != null) {
object.add(BASE_KEY, baseResult);
return object;
}
}
}
return context.createResultReplacement(element);
} }
protected void replaceIEIngredient(@Nullable JsonElement element, RecipeContext context) { @Nullable
if (element == null) { public JsonElement replaceIEIngredient(@Nullable JsonElement element, RecipeContext context) {
return; if (element instanceof JsonObject json && json.has(BASE_KEY)) {
return context.createIngredientReplacement(json.get(BASE_KEY));
} }
if (element instanceof JsonObject json && json.has(BASE_KEY)) { return context.createIngredientReplacement(element);
context.replaceIngredient(json.get(BASE_KEY));
} else {
context.replaceIngredient(element);
}
} }
} }

View file

@ -0,0 +1,88 @@
package com.almostreliable.unified.compat.ie;
import com.almostreliable.unified.TestUtils;
import com.almostreliable.unified.api.ModConstants;
import com.almostreliable.unified.recipe.RecipeTransformer;
import com.almostreliable.unified.utils.JsonQuery;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class IERecipeHandlerTest {
public final Gson gson = new Gson();
private final String simpleAlloyRecipe = """
{
"type": "immersiveengineering:alloy",
"time": 200,
"result": { "count": 2, "base_ingredient": { "tag": "forge:ingots/electrum" } },
"input0": { "tag": "forge:ingots/gold" },
"input1": { "tag": "forge:ingots/silver" }
}
""";
@Test
public void notMatching() {
RecipeTransformer transformer = TestUtils.basicTransformer(f -> f.registerForMod(ModConstants.IE,
new IERecipeHandler()));
JsonObject alloy = gson.fromJson(simpleAlloyRecipe, JsonObject.class);
JsonObject result = transformer.transformRecipe(alloy);
assertNull(result, "Nothing to transform, so it should be null");
}
@Test
public void resultTagMatches() {
RecipeTransformer transformer = TestUtils.basicTransformer(f -> f.registerForMod(ModConstants.IE,
new IERecipeHandler()));
JsonObject alloy = gson.fromJson(simpleAlloyRecipe, JsonObject.class);
alloy
.getAsJsonObject("result")
.getAsJsonObject("base_ingredient")
.addProperty("tag", TestUtils.BRONZE_ORES_TAG.location().toString());
JsonObject result = transformer.transformRecipe(alloy);
assertNotEquals(result, alloy, "Result should be different");
assertNotNull(result, "Result should not be null");
assertNull(JsonQuery.of(result, "result/base_ingredient/tag"), "Tag key should be removed");
assertEquals(JsonQuery.of(result, "result/base_ingredient/item").asString(),
TestUtils.mod1RL("bronze_ore").toString(),
"Result should be bronze_ore");
}
@Test
public void resultItemMatches() {
RecipeTransformer transformer = TestUtils.basicTransformer(f -> f.registerForMod(ModConstants.IE,
new IERecipeHandler()));
JsonObject alloy = gson.fromJson(simpleAlloyRecipe, JsonObject.class);
alloy.getAsJsonObject("result").getAsJsonObject("base_ingredient").remove("tag");
alloy
.getAsJsonObject("result")
.getAsJsonObject("base_ingredient")
.addProperty("item", TestUtils.mod3RL("bronze_ore").toString());
JsonObject result = transformer.transformRecipe(alloy);
assertNotEquals(result, alloy, "Result should be different");
assertNotNull(result, "Result should not be null");
assertEquals(JsonQuery.of(result, ("result/base_ingredient/item")).asString(),
TestUtils.mod1RL("bronze_ore").toString(),
"Transformer should replace bronze_ore from mod3 with bronze_ore from mod1");
}
@Test
public void inputAlloyItemMatches() {
RecipeTransformer transformer = TestUtils.basicTransformer(f -> f.registerForMod(ModConstants.IE,
new IERecipeHandler()));
JsonObject alloy = gson.fromJson(simpleAlloyRecipe, JsonObject.class);
alloy.getAsJsonObject("result").getAsJsonObject("base_ingredient").remove("tag");
alloy
.getAsJsonObject("result")
.getAsJsonObject("base_ingredient")
.addProperty("item", TestUtils.mod3RL("bronze_ore").toString());
JsonObject result = transformer.transformRecipe(alloy);
assertNotEquals(result, alloy, "Result should be different");
assertNotNull(result, "Result should not be null");
assertEquals(JsonQuery.of(result, ("result/base_ingredient/item")).asString(),
TestUtils.mod1RL("bronze_ore").toString(),
"Transformer should replace bronze_ore from mod3 with bronze_ore from mod1");
}
}