diff --git a/CHANGELOG.md b/CHANGELOG.md index 95a5c03d..69879c4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ So, I may have lied a bit and may or may not have refactored half of the mod's i * Added missing dispenser behavior. +[`misc/recipe_advancements_generation`]: + +* Reworked advancement generation. More recipe types should be supported by default! + [`general`]: * Switched to Codecs for JSON parsing. diff --git a/src/main/java/me/melontini/andromeda/modules/misc/recipe_advancements_generation/Main.java b/src/main/java/me/melontini/andromeda/modules/misc/recipe_advancements_generation/Main.java index c89dfe91..87a5c69d 100644 --- a/src/main/java/me/melontini/andromeda/modules/misc/recipe_advancements_generation/Main.java +++ b/src/main/java/me/melontini/andromeda/modules/misc/recipe_advancements_generation/Main.java @@ -1,8 +1,8 @@ package me.melontini.andromeda.modules.misc.recipe_advancements_generation; import com.google.common.collect.Lists; -import it.unimi.dsi.fastutil.Hash; -import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenCustomHashSet; +import com.google.gson.JsonElement; +import me.melontini.andromeda.common.registries.Keeper; import me.melontini.andromeda.util.AndromedaLog; import me.melontini.dark_matter.api.base.util.MakeSure; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; @@ -12,16 +12,13 @@ import net.minecraft.advancement.AdvancementPositioner; import net.minecraft.advancement.AdvancementRewards; import net.minecraft.advancement.criterion.InventoryChangedCriterion; import net.minecraft.advancement.criterion.RecipeUnlockedCriterion; -import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NbtCompound; import net.minecraft.predicate.entity.LootContextPredicate; import net.minecraft.predicate.item.ItemPredicate; import net.minecraft.recipe.Ingredient; import net.minecraft.recipe.Recipe; import net.minecraft.recipe.RecipeType; import net.minecraft.recipe.SpecialCraftingRecipe; -import net.minecraft.registry.tag.TagKey; import net.minecraft.server.MinecraftServer; import net.minecraft.util.Identifier; import net.minecraft.util.Util; @@ -31,44 +28,26 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiConsumer; +import java.util.function.Function; public class Main { - private static AdvancementGeneration MODULE; + private static final Keeper MODULE = Keeper.create(); + private static final Map, Function> RECIPE_TYPE_HANDLERS = new HashMap<>(); - public static final Hash.Strategy STRATEGY = new Hash.Strategy<>() {//Vanilla ItemStackSet is good, but it uses canCombine() and not areEqual(). It also doesn't exist on 1.18.2-1.19.2 - @Override - public int hashCode(ItemStack itemStack) { - if (itemStack != null) { - NbtCompound nbtCompound = itemStack.getNbt(); - int i = 31 + itemStack.getItem().hashCode(); - return 31 * i + (nbtCompound == null ? 0 : nbtCompound.hashCode()); - } else { - return 0; - } - } - - @Override - public boolean equals(ItemStack itemStack, ItemStack itemStack2) { - return itemStack == itemStack2 - || itemStack != null && itemStack2 != null && itemStack.isEmpty() == itemStack2.isEmpty() && ItemStack.areEqual(itemStack, itemStack2); - } - }; - private static final Map, BiConsumer, Recipe>> RECIPE_TYPE_HANDLERS = new HashMap<>(); - - public static BiConsumer, Recipe> basicConsumer(String typeName) { - return (map, recipe) -> map.put(idFromRecipe(recipe, typeName), createAdvBuilder(recipe.getId(), recipe.getIngredients().get(0))); + public static Function basicConsumer(String typeName) { + return context -> new Return(idFromRecipe(context.id(), typeName), createAdvBuilder(context.id(), context.recipe().getIngredients().get(0))); } - private static Identifier idFromRecipe(Recipe recipe, String typeName) { - return new Identifier(recipe.getId().getNamespace(), "recipes/gen/" + typeName + "/" + recipe.getId().toString().replace(":", "_")); + private static Identifier idFromRecipe(Identifier recipe, String typeName) { + return new Identifier(recipe.getNamespace(), "recipes/gen/" + typeName + "/" + recipe.toString().replace(":", "_")); } - public static void addRecipeTypeHandler(RecipeType type, BiConsumer, Recipe> consumer) { + public static void addRecipeTypeHandler(RecipeType type, Function consumer) { RECIPE_TYPE_HANDLERS.putIfAbsent(type, consumer); } public static void generateRecipeAdvancements(MinecraftServer server) { + AdvancementGeneration module = MODULE.orThrow(); Map advancementBuilders = new ConcurrentHashMap<>(); AtomicInteger count = new AtomicInteger(); @@ -77,16 +56,18 @@ public class Main { for (List> list : lists) { futures.add(CompletableFuture.runAsync(() -> { for (Recipe recipe : list) { - if (MODULE.config().namespaceBlacklist.contains(recipe.getId().getNamespace())) + if (module.config().namespaceBlacklist.contains(recipe.getId().getNamespace())) continue; - if (MODULE.config().recipeBlacklist.contains(recipe.getId().toString())) + if (module.config().recipeBlacklist.contains(recipe.getId().toString())) continue; - if (recipe.isIgnoredInRecipeBook() && MODULE.config().ignoreRecipesHiddenInTheRecipeBook) + if (recipe.isIgnoredInRecipeBook() && module.config().ignoreRecipesHiddenInTheRecipeBook) continue; - if (RECIPE_TYPE_HANDLERS.get(recipe.getType()) != null) { + var handler = RECIPE_TYPE_HANDLERS.get(recipe.getType()); + if (handler != null) { count.getAndIncrement(); - RECIPE_TYPE_HANDLERS.get(recipe.getType()).accept(advancementBuilders, recipe); + var r = handler.apply(new Context(recipe, recipe.getId())); + if (r != null) advancementBuilders.put(r.id(), r.builder()); } else { if (!recipe.getIngredients().isEmpty()) { count.getAndIncrement(); @@ -115,46 +96,45 @@ public class Main { advancementBuilders.clear(); } + static final class CustomPredicate extends ItemPredicate { + private final Ingredient ingredient; + + CustomPredicate(Ingredient ingredient) { + this.ingredient = ingredient; + } + + @Override + public boolean test(ItemStack stack) { + return ingredient.test(stack); + } + + @Override + public JsonElement toJson() { + return ANY.toJson(); + } + } + public static @NotNull Advancement.Builder createAdvBuilder(Identifier id, Ingredient... ingredients) { MakeSure.notEmpty(ingredients);// shouldn't really happen - var builder = Advancement.Builder.create(); + var builder = Advancement.Builder.createUntelemetered(); builder.parent(Identifier.tryParse("minecraft:recipes/root")); List names = new ArrayList<>(); - Set> tags = new HashSet<>(); - Set stacks = new ObjectLinkedOpenCustomHashSet<>(STRATEGY); - + Set elements = new HashSet<>(); for (int i = 0; i < ingredients.length; i++) { - Ingredient ingredient = ingredients[i]; - List predicates = new ArrayList<>(); - for (int j = 0; j < ingredient.entries.length; j++) { - Ingredient.Entry entry = ingredient.entries[j]; - if (entry instanceof Ingredient.StackEntry stackEntry) { - if (!stackEntry.stack.isEmpty()) { - if (stacks.contains(stackEntry.stack)) continue; - stacks.add(stackEntry.stack); - names.add(String.valueOf(i)); - ItemPredicate.Builder predicateBuilder = ItemPredicate.Builder.create() - .items(stackEntry.stack.getItem()); - if (stackEntry.stack.getNbt() != null) predicateBuilder.nbt(stackEntry.stack.getNbt()); - Optional.ofNullable(predicateBuilder.build()).ifPresent(predicates::add); - } - } else if (entry instanceof Ingredient.TagEntry tagEntry) { - if (tags.contains(tagEntry.tag)) continue; - tags.add(tagEntry.tag); - names.add(String.valueOf(i)); - Optional.ofNullable(ItemPredicate.Builder.create().tag(tagEntry.tag).build()) - .ifPresent(predicates::add); - } else { - AndromedaLog.error("unknown ingredient found in {}", id); - } - } - builder.criterion(String.valueOf(i), InventoryChangedCriterion.Conditions.items(predicates.toArray(ItemPredicate[]::new))); + var ingredient = ingredients[i]; + + if (ingredient.isEmpty()) continue; + if (!elements.add(ingredient.toJson())) continue; + + var name = String.valueOf(i); + names.add(name); + builder.criterion(name, InventoryChangedCriterion.Conditions.items(new CustomPredicate(ingredient))); } builder.criterion("has_recipe", new RecipeUnlockedCriterion.Conditions(LootContextPredicate.create(), id)); String[][] reqs; - if (MODULE.config().requireAllItems) { + if (MODULE.orThrow().config().requireAllItems) { reqs = new String[names.size()][2]; for (int i = 0; i < names.size(); i++) { String s = names.get(i); @@ -170,15 +150,14 @@ public class Main { reqs[0][names.size()] = "has_recipe"; } builder.requirements(reqs); - names.clear(); - tags.clear(); - stacks.clear(); Optional.ofNullable(AdvancementRewards.Builder.recipe(id).build()).ifPresent(builder::rewards); return builder; } - Main() { + Main(AdvancementGeneration module) { + Main.MODULE.init(module); + ServerLifecycleEvents.SERVER_STARTED.register(server -> { Main.generateRecipeAdvancements(server); server.getPlayerManager().getPlayerList().forEach(entity -> server.getPlayerManager().getAdvancementTracker(entity).reload(server.getAdvancementLoader())); @@ -189,12 +168,19 @@ public class Main { addRecipeTypeHandler(RecipeType.SMELTING, basicConsumer("smelting")); addRecipeTypeHandler(RecipeType.CAMPFIRE_COOKING, basicConsumer("campfire_cooking")); addRecipeTypeHandler(RecipeType.STONECUTTING, basicConsumer("stonecutting")); - addRecipeTypeHandler(RecipeType.CRAFTING, (map, recipe) -> { - if (!(recipe instanceof SpecialCraftingRecipe)) { - if (!recipe.getIngredients().isEmpty()) { - map.put(idFromRecipe(recipe, "crafting"), createAdvBuilder(recipe.getId(), recipe.getIngredients().toArray(Ingredient[]::new))); + addRecipeTypeHandler(RecipeType.CRAFTING, (context) -> { + if (!(context.recipe() instanceof SpecialCraftingRecipe)) { + if (!context.recipe().getIngredients().isEmpty()) { + return new Return(idFromRecipe(context.id(), "crafting"), createAdvBuilder(context.id(), context.recipe().getIngredients().toArray(Ingredient[]::new))); } } + return null; }); } + + public record Return(Identifier id, Advancement.Builder builder) { + } + + public record Context(Recipe recipe, Identifier id) { + } }