add support for custom ingredient types and add api endpoint

This commit is contained in:
rlnt 2024-10-06 16:58:10 +02:00
parent 259f847395
commit ad177ad311
No known key found for this signature in database
11 changed files with 209 additions and 7 deletions

View file

@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning].
## Unreleased
- added support for custom ingredient types
- added API endpoint for registering custom ingredient unifiers
- fixed recipe viewer integration endpoints for Fabric
- fixed unnecessary memory usage for debug handler
- fixed Mekanism recipe unifier using wrong recipe keys

View file

@ -2,11 +2,13 @@ package com.almostreliable.unified.api.plugin;
import net.minecraft.resources.ResourceLocation;
import com.almostreliable.unified.api.unification.recipe.CustomIngredientUnifier;
import com.almostreliable.unified.api.unification.recipe.CustomIngredientUnifierRegistry;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifier;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry;
/**
* Implemented by plugins that wish to register their own {@link RecipeUnifier}s.
* Implemented by plugins that wish to register their own unifiers.
* <p>
* NeoForge plugins should attach the {@link AlmostUnifiedNeoPlugin} annotation for discovery.<br>
* Fabric plugins should use the {@code almostunified} entrypoint.
@ -34,4 +36,12 @@ public interface AlmostUnifiedPlugin {
* @param registry the {@link RecipeUnifierRegistry} to register with
*/
default void registerRecipeUnifiers(RecipeUnifierRegistry registry) {}
/**
* Allows registration of custom {@link CustomIngredientUnifier}s.
*
* @param registry the {@link CustomIngredientUnifierRegistry} to register with
* @since 1.2.0
*/
default void registerCustomIngredientUnifiers(CustomIngredientUnifierRegistry registry) {}
}

View file

@ -0,0 +1,38 @@
package com.almostreliable.unified.api.unification.recipe;
import com.almostreliable.unified.api.plugin.AlmostUnifiedPlugin;
import com.google.gson.JsonObject;
/**
* Implemented on custom ingredient unifiers.
* <p>
* Custom unifiers will tell Almost Unified how to handle specific ingredients.<br>
* When the unification process encounters a {@link JsonObject} that contains a {@code type} property, it will check
* if a custom unifier is associated with that type and call
* {@link CustomIngredientUnifier#unify(UnificationHelper, JsonObject)}. If no unifier is found, the default
* transformation will be used.
* <p>
* Unifiers can be registered for a specific type. Registering a custom unifier will skip the default transformation
* of the {@link JsonObject} for the given type.
* <p>
* Registration is handled in {@link CustomIngredientUnifierRegistry} which can be obtained in
* {@link AlmostUnifiedPlugin#registerCustomIngredientUnifiers(CustomIngredientUnifierRegistry)}.
*
* @since 1.2.0
*/
public interface CustomIngredientUnifier {
/**
* Uses of the given {@link UnificationHelper} to unify the given ingredient {@link JsonObject}.
* <p>
* The unifier only receives the serialized ingredient as {@link JsonObject} without the {@code type} property.<br>
* If changes to the {@link JsonObject} are necessary, the original {@link JsonObject} should be modified in-place.
* The method should return true if the {@link JsonObject} was modified.
*
* @param helper the helper to aid in the unification
* @param jsonObject the ingredient to unify as a {@link JsonObject}
* @return true if the ingredient was changed, false otherwise
*/
boolean unify(UnificationHelper helper, JsonObject jsonObject);
}

View file

@ -0,0 +1,37 @@
package com.almostreliable.unified.api.unification.recipe;
import net.minecraft.resources.ResourceLocation;
import com.google.gson.JsonObject;
import org.jetbrains.annotations.Nullable;
/**
* The registry holding all {@link CustomIngredientUnifier}s.
* <p>
* {@link CustomIngredientUnifier}s can be registered per type.
*
* @since 1.2.0
*/
public interface CustomIngredientUnifierRegistry {
/**
* Registers a {@link CustomIngredientUnifier} for a specific type.
* <p>
* If a custom ingredient unifier is associated with the given type, the internal transformation for the
* {@link JsonObject} will be skipped.
*
* @param type the type to register the custom ingredient unifier for
* @param customIngredientUnifier the custom ingredient unifier
*/
void registerForType(ResourceLocation type, CustomIngredientUnifier customIngredientUnifier);
/**
* Retrieves the respective {@link CustomIngredientUnifier} for the given type.
*
* @param type the type to retrieve the {@link CustomIngredientUnifier} for
* @return the {@link CustomIngredientUnifier} for the given type
*/
@Nullable
CustomIngredientUnifier getCustomIngredientUnifier(ResourceLocation type);
}

View file

@ -1,5 +1,7 @@
package com.almostreliable.unified.api.unification.recipe;
import net.minecraft.resources.ResourceLocation;
import com.almostreliable.unified.api.constant.RecipeConstants;
import com.almostreliable.unified.api.unification.TagSubstitutions;
import com.almostreliable.unified.api.unification.UnificationLookup;
@ -35,6 +37,16 @@ public interface UnificationHelper {
*/
UnificationLookup getUnificationLookup();
/**
* Returns the {@link CustomIngredientUnifier} for the given type used in the {@link JsonObject}.
*
* @param type the value of the type property of the {@link JsonObject} as a {@link ResourceLocation}
* @return the {@link CustomIngredientUnifier} or null if no custom unifier is associated with the given type
* @since 1.2.0
*/
@Nullable
CustomIngredientUnifier getCustomIngredientUnifier(ResourceLocation type);
/**
* Fetches all entries of the given {@link RecipeJson} under the specified keys and unifies them as inputs.<br>
* Entries treated as inputs will be converted to tags if possible.

View file

@ -5,6 +5,7 @@ import net.minecraft.resources.ResourceLocation;
import com.almostreliable.unified.AlmostUnifiedCommon;
import com.almostreliable.unified.api.constant.ModConstants;
import com.almostreliable.unified.api.plugin.AlmostUnifiedPlugin;
import com.almostreliable.unified.api.unification.recipe.CustomIngredientUnifierRegistry;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry;
import org.jetbrains.annotations.Nullable;
@ -61,6 +62,10 @@ public final class PluginManager {
INSTANCE = new PluginManager(sortedPlugins);
}
public void registerCustomIngredientUnifiers(CustomIngredientUnifierRegistry registry) {
forEachPlugin(plugin -> plugin.registerCustomIngredientUnifiers(registry));
}
public void registerRecipeUnifiers(RecipeUnifierRegistry registry) {
forEachPlugin(plugin -> plugin.registerRecipeUnifiers(registry));
}

View file

@ -14,6 +14,7 @@ import com.almostreliable.unified.api.unification.TagSubstitutions;
import com.almostreliable.unified.api.unification.UnificationEntry;
import com.almostreliable.unified.api.unification.UnificationLookup;
import com.almostreliable.unified.api.unification.UnificationSettings;
import com.almostreliable.unified.api.unification.recipe.CustomIngredientUnifierRegistry;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry;
import com.almostreliable.unified.compat.PluginManager;
import com.almostreliable.unified.compat.viewer.ItemHider;
@ -25,6 +26,7 @@ import com.almostreliable.unified.config.UnificationConfig;
import com.almostreliable.unified.unification.TagInheritance;
import com.almostreliable.unified.unification.TagSubstitutionsImpl;
import com.almostreliable.unified.unification.UnificationSettingsImpl;
import com.almostreliable.unified.unification.recipe.CustomIngredientUnifierRegistryImpl;
import com.almostreliable.unified.unification.recipe.RecipeTransformer;
import com.almostreliable.unified.unification.recipe.RecipeUnifierRegistryImpl;
import com.almostreliable.unified.utils.DebugHandler;
@ -48,14 +50,16 @@ import java.util.stream.Collectors;
public final class AlmostUnifiedRuntimeImpl implements AlmostUnifiedRuntime {
private final Collection<? extends UnificationSettings> unificationSettings;
private final CustomIngredientUnifierRegistry customIngredientUnifierRegistry;
private final RecipeUnifierRegistry recipeUnifierRegistry;
private final TagSubstitutions tagSubstitutions;
private final Placeholders placeholders;
private final UnificationLookup compositeUnificationLookup;
private final DebugHandler debugHandler;
private AlmostUnifiedRuntimeImpl(Collection<? extends UnificationSettings> unificationSettings, RecipeUnifierRegistry recipeUnifierRegistry, TagSubstitutions tagSubstitutions, Placeholders placeholders, DebugConfig debugConfig) {
private AlmostUnifiedRuntimeImpl(Collection<? extends UnificationSettings> unificationSettings, CustomIngredientUnifierRegistry customIngredientUnifierRegistry, RecipeUnifierRegistry recipeUnifierRegistry, TagSubstitutions tagSubstitutions, Placeholders placeholders, DebugConfig debugConfig) {
this.unificationSettings = unificationSettings;
this.customIngredientUnifierRegistry = customIngredientUnifierRegistry;
this.recipeUnifierRegistry = recipeUnifierRegistry;
this.tagSubstitutions = tagSubstitutions;
this.placeholders = placeholders;
@ -79,6 +83,8 @@ public final class AlmostUnifiedRuntimeImpl implements AlmostUnifiedRuntime {
debugConfig.shouldLogInvalidTags()
);
CustomIngredientUnifierRegistry customIngredientUnifierRegistry = new CustomIngredientUnifierRegistryImpl();
PluginManager.instance().registerCustomIngredientUnifiers(customIngredientUnifierRegistry);
RecipeUnifierRegistry recipeUnifierRegistry = new RecipeUnifierRegistryImpl();
PluginManager.instance().registerRecipeUnifiers(recipeUnifierRegistry);
// TODO: add plugin support for registering config defaults
@ -102,6 +108,7 @@ public final class AlmostUnifiedRuntimeImpl implements AlmostUnifiedRuntime {
return new AlmostUnifiedRuntimeImpl(
unificationSettings,
customIngredientUnifierRegistry,
recipeUnifierRegistry,
tagSubstitutions,
placeholderConfig,
@ -191,7 +198,9 @@ public final class AlmostUnifiedRuntimeImpl implements AlmostUnifiedRuntime {
debugHandler.onRunStart(recipes, compositeUnificationLookup);
debugHandler.measure(() -> {
var transformer = new RecipeTransformer(recipeUnifierRegistry, unificationSettings);
var transformer = new RecipeTransformer(customIngredientUnifierRegistry,
recipeUnifierRegistry,
unificationSettings);
return transformer.transformRecipes(recipes);
});

View file

@ -0,0 +1,27 @@
package com.almostreliable.unified.unification.recipe;
import net.minecraft.resources.ResourceLocation;
import com.almostreliable.unified.api.unification.recipe.CustomIngredientUnifier;
import com.almostreliable.unified.api.unification.recipe.CustomIngredientUnifierRegistry;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
public class CustomIngredientUnifierRegistryImpl implements CustomIngredientUnifierRegistry {
private final Map<ResourceLocation, CustomIngredientUnifier> ingredientUnifiersByType = new HashMap<>();
@Override
public void registerForType(ResourceLocation type, CustomIngredientUnifier customIngredientUnifier) {
ingredientUnifiersByType.put(type, customIngredientUnifier);
}
@Nullable
@Override
public CustomIngredientUnifier getCustomIngredientUnifier(ResourceLocation type) {
return ingredientUnifiersByType.get(type);
}
}

View file

@ -5,6 +5,7 @@ import net.minecraft.resources.ResourceLocation;
import com.almostreliable.unified.AlmostUnifiedCommon;
import com.almostreliable.unified.api.unification.UnificationLookup;
import com.almostreliable.unified.api.unification.UnificationSettings;
import com.almostreliable.unified.api.unification.recipe.CustomIngredientUnifierRegistry;
import com.almostreliable.unified.api.unification.recipe.RecipeJson;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifier;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry;
@ -36,12 +37,14 @@ import java.util.stream.Collectors;
public class RecipeTransformer {
private final CustomIngredientUnifierRegistry customIngredientUnifierRegistry;
private final RecipeUnifierRegistry factory;
private final Collection<? extends UnificationSettings> unificationSettings;
private final DuplicateConfig duplicateConfig;
private final RecipeTypePropertiesLogger propertiesLogger = new RecipeTypePropertiesLogger();
public RecipeTransformer(RecipeUnifierRegistry factory, Collection<? extends UnificationSettings> unificationSettings) {
public RecipeTransformer(CustomIngredientUnifierRegistry customIngredientUnifierRegistry, RecipeUnifierRegistry factory, Collection<? extends UnificationSettings> unificationSettings) {
this.customIngredientUnifierRegistry = customIngredientUnifierRegistry;
this.factory = factory;
this.unificationSettings = unificationSettings;
this.duplicateConfig = Config.load(DuplicateConfig.NAME, DuplicateConfig.SERIALIZER);
@ -188,7 +191,7 @@ public class RecipeTransformer {
continue;
}
UnificationHelperImpl helper = new UnificationHelperImpl(settings);
UnificationHelperImpl helper = new UnificationHelperImpl(customIngredientUnifierRegistry, settings);
RecipeUnifier unifier = factory.getRecipeUnifier(recipe);
unifier.unify(helper, json);
}

View file

@ -7,6 +7,8 @@ import net.minecraft.tags.TagKey;
import com.almostreliable.unified.api.AlmostUnified;
import com.almostreliable.unified.api.constant.RecipeConstants;
import com.almostreliable.unified.api.unification.UnificationLookup;
import com.almostreliable.unified.api.unification.recipe.CustomIngredientUnifier;
import com.almostreliable.unified.api.unification.recipe.CustomIngredientUnifierRegistry;
import com.almostreliable.unified.api.unification.recipe.RecipeJson;
import com.almostreliable.unified.api.unification.recipe.UnificationHelper;
@ -18,7 +20,14 @@ import com.google.gson.JsonPrimitive;
import org.jetbrains.annotations.Nullable;
public record UnificationHelperImpl(UnificationLookup getUnificationLookup) implements UnificationHelper {
public record UnificationHelperImpl(CustomIngredientUnifierRegistry customIngredientUnifierRegistry,
UnificationLookup getUnificationLookup) implements UnificationHelper {
@Nullable
@Override
public CustomIngredientUnifier getCustomIngredientUnifier(ResourceLocation type) {
return customIngredientUnifierRegistry.getCustomIngredientUnifier(type);
}
@Override
public boolean unifyInputs(RecipeJson recipe, String... keys) {
@ -57,6 +66,9 @@ public record UnificationHelperImpl(UnificationLookup getUnificationLookup) impl
@Override
public boolean unifyInputObject(JsonObject jsonObject, String... keys) {
Boolean modified = handleCustomIngredientUnification(jsonObject);
if (modified != null) return modified;
boolean changed = false;
for (String key : keys.length == 0 ? RecipeConstants.DEFAULT_INPUT_INNER_KEYS : keys) {
@ -157,6 +169,9 @@ public record UnificationHelperImpl(UnificationLookup getUnificationLookup) impl
@Override
public boolean unifyOutputObject(JsonObject jsonObject, boolean tagsToItems, String... keys) {
Boolean modified = handleCustomIngredientUnification(jsonObject);
if (modified != null) return modified;
boolean changed = false;
for (String key : keys.length == 0 ? RecipeConstants.DEFAULT_OUTPUT_INNER_KEYS : keys) {
@ -220,4 +235,34 @@ public record UnificationHelperImpl(UnificationLookup getUnificationLookup) impl
if (entry == null || entry.id().equals(item)) return null;
return new JsonPrimitive(entry.id().toString());
}
/**
* Handles custom ingredient unification.
* <p>
* This method checks if the given {@link JsonObject} has a {@code type} property and if a
* {@link CustomIngredientUnifier} is associated with that type. If not, it will return null and the default logic
* should continue. Otherwise, it will return whether the {@link CustomIngredientUnifier} modified the
* {@link JsonObject}.
*
* @param jsonObject the ingredient {@link JsonObject} to unify
* @return true if the ingredient was changed, false otherwise, or null if no unifier was found
*/
@Nullable
private Boolean handleCustomIngredientUnification(JsonObject jsonObject) {
if (!(jsonObject.get("type") instanceof JsonPrimitive jsonPrimitive)) {
return null;
}
ResourceLocation type = ResourceLocation.tryParse(jsonPrimitive.getAsString());
if (type == null) return null;
CustomIngredientUnifier unifier = getCustomIngredientUnifier(type);
if (unifier == null) return null;
jsonObject.remove("type"); // avoids looping when calling helper methods within ingredient unifiers
boolean changed = unifier.unify(this, jsonObject);
jsonObject.addProperty("type", type.toString());
return changed;
}
}

View file

@ -9,6 +9,8 @@ import com.almostreliable.unified.api.unification.ModPriorities;
import com.almostreliable.unified.api.unification.StoneVariants;
import com.almostreliable.unified.api.unification.TagSubstitutions;
import com.almostreliable.unified.api.unification.UnificationLookup;
import com.almostreliable.unified.api.unification.recipe.CustomIngredientUnifier;
import com.almostreliable.unified.api.unification.recipe.CustomIngredientUnifierRegistry;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifier;
import com.almostreliable.unified.api.unification.recipe.UnificationHelper;
import com.almostreliable.unified.unification.ModPrioritiesImpl;
@ -109,7 +111,7 @@ public class TestUtils {
public static UnificationHelper recipeHelper() {
return new UnificationHelperImpl(unificationLookup());
return new UnificationHelperImpl(new CustomIngredientUnifierRegistryImpl(), unificationLookup());
}
public static void assertUnify(RecipeUnifier unifier, String jsonActual, String jsonExpected) {
@ -174,4 +176,16 @@ public class TestUtils {
fail(sb.toString());
}
private static class CustomIngredientUnifierRegistryImpl implements CustomIngredientUnifierRegistry {
@Override
public void registerForType(ResourceLocation type, CustomIngredientUnifier customIngredientUnifier) {}
@Nullable
@Override
public CustomIngredientUnifier getCustomIngredientUnifier(ResourceLocation type) {
return null;
}
}
}