From 18fffafb3a1140acbd0ff47790f3d8ddcd93d6b1 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 31 Jul 2022 14:49:15 +0200 Subject: [PATCH] [Features] (Recipe-) Advancement API --- .../AdvancementBuilderElements.java | 43 +++ .../v2/advancement/AdvancementManager.java | 364 ++++++++++++++++++ .../common/ServerAdvancementManagerMixin.java | 23 ++ .../betterx/bclib/recipes/AlloyingRecipe.java | 5 +- .../betterx/bclib/recipes/AnvilRecipe.java | 5 +- .../bclib/recipes/BCLRecipeManager.java | 47 +++ .../betterx/bclib/recipes/FurnaceRecipe.java | 8 +- .../org/betterx/bclib/recipes/GridRecipe.java | 2 +- .../bclib/recipes/SmithingTableRecipe.java | 2 +- src/main/resources/bclib.mixins.common.json | 1 + 10 files changed, 490 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/betterx/bclib/api/v2/advancement/AdvancementBuilderElements.java create mode 100644 src/main/java/org/betterx/bclib/api/v2/advancement/AdvancementManager.java create mode 100644 src/main/java/org/betterx/bclib/mixin/common/ServerAdvancementManagerMixin.java diff --git a/src/main/java/org/betterx/bclib/api/v2/advancement/AdvancementBuilderElements.java b/src/main/java/org/betterx/bclib/api/v2/advancement/AdvancementBuilderElements.java new file mode 100644 index 00000000..e692ad77 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/advancement/AdvancementBuilderElements.java @@ -0,0 +1,43 @@ +package org.betterx.bclib.api.v2.advancement; + +import net.minecraft.advancements.DisplayInfo; +import net.minecraft.advancements.FrameType; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; + +import org.jetbrains.annotations.Nullable; + +class Display { + ItemStack icon; + Component title; + net.minecraft.network.chat.Component description; + @Nullable ResourceLocation background; + FrameType frame; + boolean showToast; + boolean announceChat; + boolean hidden; + + Display() { + } + + Display reset() { + this.icon = null; + this.title = null; + this.description = null; + frame = FrameType.TASK; + background = null; + showToast = true; + announceChat = true; + hidden = false; + return this; + } + + DisplayInfo build() { + return new DisplayInfo( + icon, title, description, + background, frame, showToast, announceChat, hidden + ); + } +} + diff --git a/src/main/java/org/betterx/bclib/api/v2/advancement/AdvancementManager.java b/src/main/java/org/betterx/bclib/api/v2/advancement/AdvancementManager.java new file mode 100644 index 00000000..32137fd6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/advancement/AdvancementManager.java @@ -0,0 +1,364 @@ +package org.betterx.bclib.api.v2.advancement; + +import net.minecraft.advancements.*; +import net.minecraft.advancements.critereon.InventoryChangeTrigger; +import net.minecraft.advancements.critereon.RecipeUnlockedTrigger; +import net.minecraft.core.Registry; +import net.minecraft.data.recipes.RecipeBuilder; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.Container; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.level.ItemLike; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; +import org.jetbrains.annotations.ApiStatus; + +public class AdvancementManager { + private static final Map ADVANCEMENTS = new HashMap<>(); + + public static void register(ResourceLocation id, Advancement.Builder builder) { + ADVANCEMENTS.put(id, builder); + } + + @ApiStatus.Internal + public static void addAdvancements(Map map) { + for (var entry : ADVANCEMENTS.entrySet()) { + if (!map.containsKey(entry.getKey())) { + map.put(entry.getKey(), entry.getValue()); + } + } + } + + public static class RewardsBuilder { + private final Builder calle; + private final AdvancementRewards.Builder builder = new AdvancementRewards.Builder(); + + private RewardsBuilder(Builder calle) { + this.calle = calle; + } + + public RewardsBuilder addExperience(int i) { + builder.addExperience(i); + return this; + } + + + public RewardsBuilder addLootTable(ResourceLocation resourceLocation) { + builder.addLootTable(resourceLocation); + return this; + } + + + public RewardsBuilder addRecipe(ResourceLocation resourceLocation) { + builder.addRecipe(resourceLocation); + return this; + } + + + public RewardsBuilder runs(ResourceLocation resourceLocation) { + builder.runs(resourceLocation); + return this; + } + + public Builder endReward() { + calle.rewards(builder.build()); + return calle; + } + } + + public enum AdvancementType { + REGULAR, + RECIPE_DECORATIONS, + RECIPE_TOOL + } + + public static class Builder { + private static final ThreadLocal DISPLAY_BUILDER = ThreadLocal.withInitial(DisplayBuilder::new); + private static final ResourceLocation RECIPES_ROOT = RecipeBuilder.ROOT_RECIPE_ADVANCEMENT; + + private final Advancement.Builder builder = Advancement.Builder.advancement(); + private final ResourceLocation id; + private final AdvancementType type; + private boolean canBuild = true; + + private Builder(ResourceLocation id, AdvancementType type) { + ResourceLocation ID; + if (type == AdvancementType.RECIPE_DECORATIONS) { + ID = new ResourceLocation(id.getNamespace(), "recipes/decorations/" + id.getPath()); + builder.parent(RECIPES_ROOT); + } else if (type == AdvancementType.RECIPE_TOOL) { + ID = new ResourceLocation(id.getNamespace(), "recipes/tools/" + id.getPath()); + builder.parent(RECIPES_ROOT); + } else { + ID = id; + } + this.id = ID; + this.type = type; + } + + public static Builder create(ResourceLocation id) { + return new Builder(id, AdvancementType.REGULAR); + } + + public static Builder create(ResourceLocation id, AdvancementType type) { + return new Builder(id, type); + } + + public static Builder create(Item item) { + return create(item, AdvancementType.REGULAR); + } + + public static Builder create(ItemStack item) { + return create(item, AdvancementType.REGULAR); + } + + public static Builder create(ItemLike item, AdvancementType type) { + return create(new ItemStack(item), type); + } + + public static Builder create(ItemStack item, AdvancementType type) { + return create(item, type, (displayBuilder) -> { + }); + } + + public static Builder create(Item item, AdvancementType type, Consumer displayAdapter) { + return create(new ItemStack(item), type, displayAdapter); + } + + public static Builder create(ItemStack item, AdvancementType type, Consumer displayAdapter) { + var id = Registry.ITEM.getKey(item.getItem()); + boolean canBuild = true; + if (id == null || item.is(Items.AIR)) { + canBuild = false; + id = Registry.ITEM.getDefaultKey(); + } + + String baseName = "advancements." + id.getNamespace() + "." + id.getPath() + "."; + Builder b = new Builder(id, type); + var displayBuilder = b.startDisplay( + item, + Component.translatable(baseName + "title"), + Component.translatable(baseName + "description") + ); + if (displayAdapter != null) displayAdapter.accept(displayBuilder); + b = displayBuilder.endDisplay(); + b.canBuild = canBuild; + return b; + } + + public static > Builder createRecipe(T recipe, AdvancementType type) { + Item item = recipe.getResultItem().getItem(); + return create(item, type, displayBuilder -> displayBuilder.hideToast().hideFromChat()) + .awardRecipe(item) + .addRecipeUnlockCriterion( + "has_the_recipe", + recipe + ); + } + + public Builder parent(Advancement advancement) { + builder.parent(advancement); + return this; + } + + public Builder parent(ResourceLocation resourceLocation) { + builder.parent(resourceLocation); + return this; + } + + public DisplayBuilder startDisplay(Item icon) { + String baseName = "advancements." + id.getNamespace() + "." + id.getPath() + "."; + return startDisplay( + icon, + Component.translatable(baseName + "title"), + Component.translatable(baseName + "description") + ); + } + + public DisplayBuilder startDisplay( + ItemLike icon, + Component title, + Component description + ) { + return startDisplay(new ItemStack(icon), title, description); + } + + public DisplayBuilder startDisplay( + ItemStack icon, + Component title, + Component description + ) { + if (icon == null) { + canBuild = false; + } else { + var id = Registry.ITEM.getKey(icon.getItem()); + if (id == null) { + canBuild = false; + } + } + DisplayBuilder dp = DISPLAY_BUILDER.get().reset(this); + return dp.icon(icon).title(title).description(description); + } + + Builder display(DisplayInfo displayInfo) { + builder.display(displayInfo); + return this; + } + + public Builder awardRecipe(ItemLike... items) { + var rewardBuilder = startReward(); + for (ItemLike item : items) { + var id = Registry.ITEM.getKey(item.asItem()); + if (id == null) continue; + rewardBuilder.addRecipe(id); + } + return rewardBuilder.endReward(); + } + + public RewardsBuilder startReward() { + return new RewardsBuilder(this); + } + + public Builder rewards(AdvancementRewards advancementRewards) { + builder.rewards(advancementRewards); + return this; + } + + public Builder addCriterion(String string, CriterionTriggerInstance criterionTriggerInstance) { + builder.addCriterion(string, new Criterion(criterionTriggerInstance)); + return this; + } + + public Builder addCriterion(String string, Criterion criterion) { + builder.addCriterion(string, criterion); + return this; + } + + public > Builder addRecipeUnlockCriterion(String name, T recipe) { + return addCriterion( + name, + RecipeUnlockedTrigger.unlocked(recipe.getId()) + ) + .startReward() + .addRecipe(recipe.getId()) + .endReward() + .requirements(RequirementsStrategy.OR); + } + + public Builder addInventoryChangedCriterion(String name, ItemLike... items) { + return addCriterion( + name, + InventoryChangeTrigger.TriggerInstance.hasItems(items) + ); + } + + public Builder requirements(RequirementsStrategy requirementsStrategy) { + builder.requirements(requirementsStrategy); + return this; + } + + public Builder requirements(String[][] strings) { + builder.requirements(strings); + return this; + } + + public ResourceLocation buildAndRegister() { + AdvancementManager.register(id, this.builder); + return this.id; + } + } + + public static class DisplayBuilder { + Builder base; + final Display display = new Display(); + + DisplayBuilder reset(Builder base) { + this.base = base; + this.display.reset(); + return this; + } + + public DisplayBuilder background(ResourceLocation value) { + display.background = value; + return this; + } + + public DisplayBuilder icon(ItemLike value) { + display.icon = new ItemStack(value); + return this; + } + + public DisplayBuilder icon(ItemStack value) { + display.icon = value; + return this; + } + + public DisplayBuilder title(Component value) { + display.title = value; + return this; + } + + public DisplayBuilder description(Component value) { + display.title = value; + return this; + } + + public DisplayBuilder showToast() { + display.showToast = true; + return this; + } + + public DisplayBuilder hideToast() { + display.showToast = false; + return this; + } + + public DisplayBuilder hidden() { + display.hidden = true; + return this; + } + + public DisplayBuilder visible() { + display.hidden = false; + return this; + } + + public DisplayBuilder announceToChat() { + display.announceChat = true; + return this; + } + + public DisplayBuilder hideFromChat() { + display.announceChat = false; + return this; + } + + public DisplayBuilder frame(FrameType type) { + display.frame = type; + return this; + } + + public DisplayBuilder challenge() { + return frame(FrameType.CHALLENGE); + } + + public DisplayBuilder task() { + return frame(FrameType.TASK); + } + + public DisplayBuilder goal() { + return frame(FrameType.GOAL); + } + + public Builder endDisplay() { + base.display(display.build()); + return base; + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/ServerAdvancementManagerMixin.java b/src/main/java/org/betterx/bclib/mixin/common/ServerAdvancementManagerMixin.java new file mode 100644 index 00000000..ce1a6d4a --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/ServerAdvancementManagerMixin.java @@ -0,0 +1,23 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.api.v2.advancement.AdvancementManager; + +import net.minecraft.advancements.Advancement; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.ServerAdvancementManager; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import java.util.Map; + +@Mixin(ServerAdvancementManager.class) +public class ServerAdvancementManagerMixin { + @ModifyArg(method = "apply(Ljava/util/Map;Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/util/profiling/ProfilerFiller;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/advancements/AdvancementList;add(Ljava/util/Map;)V")) + public Map wunder_interceptApply(Map map) { + AdvancementManager.addAdvancements(map); + return map; + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/AlloyingRecipe.java b/src/main/java/org/betterx/bclib/recipes/AlloyingRecipe.java index afda4629..34e5b5ea 100644 --- a/src/main/java/org/betterx/bclib/recipes/AlloyingRecipe.java +++ b/src/main/java/org/betterx/bclib/recipes/AlloyingRecipe.java @@ -249,9 +249,10 @@ public class AlloyingRecipe implements Recipe, UnknownReceipBookCateg BCLib.LOGGER.debug("Can't add Alloying recipe {}! Ingeredient or output not exists.", id); return; } - BCLRecipeManager.addRecipe( + BCLRecipeManager.addRecipeAndCreateAdvancement( TYPE, - new AlloyingRecipe(id, group, primaryInput, secondaryInput, output, experience, smeltTime) + new AlloyingRecipe(id, group, primaryInput, secondaryInput, output, experience, smeltTime), + false ); } } diff --git a/src/main/java/org/betterx/bclib/recipes/AnvilRecipe.java b/src/main/java/org/betterx/bclib/recipes/AnvilRecipe.java index 2e63cd68..d4deebaa 100644 --- a/src/main/java/org/betterx/bclib/recipes/AnvilRecipe.java +++ b/src/main/java/org/betterx/bclib/recipes/AnvilRecipe.java @@ -350,9 +350,10 @@ public class AnvilRecipe implements Recipe, UnknownReceipBookCategory BCLib.LOGGER.debug("Can't add Anvil recipe {}! Ingeredient or output not exists.", id); return; } - BCLRecipeManager.addRecipe( + BCLRecipeManager.addRecipeAndCreateAdvancement( TYPE, - new AnvilRecipe(id, input, output, inputCount, toolLevel, anvilLevel, damage) + new AnvilRecipe(id, input, output, inputCount, toolLevel, anvilLevel, damage), + false ); } } diff --git a/src/main/java/org/betterx/bclib/recipes/BCLRecipeManager.java b/src/main/java/org/betterx/bclib/recipes/BCLRecipeManager.java index bccb8937..ab4cdd7b 100644 --- a/src/main/java/org/betterx/bclib/recipes/BCLRecipeManager.java +++ b/src/main/java/org/betterx/bclib/recipes/BCLRecipeManager.java @@ -1,11 +1,16 @@ package org.betterx.bclib.recipes; +import org.betterx.bclib.api.v2.advancement.AdvancementManager; import org.betterx.bclib.util.CollectionsUtil; import net.minecraft.core.Registry; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.Container; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; +import net.minecraft.world.item.TieredItem; +import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.item.crafting.RecipeSerializer; import net.minecraft.world.item.crafting.RecipeType; @@ -58,6 +63,48 @@ public class BCLRecipeManager { list.put(recipe.getId(), recipe); } + public static > void addRecipeAndCreateAdvancement( + RecipeType type, + T recipe + ) { + addRecipe(type, recipe); + registerAndCreateAdvancement(recipe, recipe.getResultItem().getItem() instanceof TieredItem); + } + + public static > void addRecipeAndCreateAdvancement( + RecipeType type, + T recipe, + boolean isTool + ) { + addRecipe(type, recipe); + registerAndCreateAdvancement(recipe, isTool); + } + + public static > ResourceLocation registerAndCreateAdvancement( + T recipe, + boolean isTool + ) { + AdvancementManager.Builder b = AdvancementManager.Builder.createRecipe( + recipe, + isTool + ? AdvancementManager.AdvancementType.RECIPE_TOOL + : AdvancementManager.AdvancementType.RECIPE_DECORATIONS + ); + + int ct = 0; + for (Ingredient ingredient : recipe.getIngredients()) { + for (ItemStack stack : ingredient.getItems()) { + if (stack.is(Items.AIR)) continue; + + final String name = "has_" + ct++; + Item item = stack.getItem(); + b.addInventoryChangedCriterion(name, item); + } + } + + return b.buildAndRegister(); + } + public static > T getRecipe(RecipeType type, ResourceLocation id) { Map map = BCLRecipeManager.RECIPES().get(type); return map != null ? map.get(id) : null; diff --git a/src/main/java/org/betterx/bclib/recipes/FurnaceRecipe.java b/src/main/java/org/betterx/bclib/recipes/FurnaceRecipe.java index a51d874b..617a6506 100644 --- a/src/main/java/org/betterx/bclib/recipes/FurnaceRecipe.java +++ b/src/main/java/org/betterx/bclib/recipes/FurnaceRecipe.java @@ -84,7 +84,7 @@ public class FurnaceRecipe { xp, time ); - BCLRecipeManager.addRecipe(RecipeType.SMELTING, recipe); + BCLRecipeManager.addRecipeAndCreateAdvancement(RecipeType.SMELTING, recipe, false); if (blasting) { BlastingRecipe recipe2 = new BlastingRecipe( @@ -95,7 +95,7 @@ public class FurnaceRecipe { xp, time / 2 ); - BCLRecipeManager.addRecipe(RecipeType.BLASTING, recipe2); + BCLRecipeManager.addRecipeAndCreateAdvancement(RecipeType.BLASTING, recipe2, false); } if (campfire) { @@ -107,7 +107,7 @@ public class FurnaceRecipe { xp, time * 3 ); - BCLRecipeManager.addRecipe(RecipeType.CAMPFIRE_COOKING, recipe2); + BCLRecipeManager.addRecipeAndCreateAdvancement(RecipeType.CAMPFIRE_COOKING, recipe2, false); } if (smoker) { @@ -119,7 +119,7 @@ public class FurnaceRecipe { xp, time / 2 ); - BCLRecipeManager.addRecipe(RecipeType.SMOKING, recipe2); + BCLRecipeManager.addRecipeAndCreateAdvancement(RecipeType.SMOKING, recipe2, false); } } } diff --git a/src/main/java/org/betterx/bclib/recipes/GridRecipe.java b/src/main/java/org/betterx/bclib/recipes/GridRecipe.java index 7b829abf..855f1587 100644 --- a/src/main/java/org/betterx/bclib/recipes/GridRecipe.java +++ b/src/main/java/org/betterx/bclib/recipes/GridRecipe.java @@ -145,6 +145,6 @@ public class GridRecipe { result ) : new ShapelessRecipe(id, group, result, materials); - BCLRecipeManager.addRecipe(type, recipe); + BCLRecipeManager.addRecipeAndCreateAdvancement(type, recipe); } } diff --git a/src/main/java/org/betterx/bclib/recipes/SmithingTableRecipe.java b/src/main/java/org/betterx/bclib/recipes/SmithingTableRecipe.java index 96d07e78..8f79c63c 100644 --- a/src/main/java/org/betterx/bclib/recipes/SmithingTableRecipe.java +++ b/src/main/java/org/betterx/bclib/recipes/SmithingTableRecipe.java @@ -99,6 +99,6 @@ public class SmithingTableRecipe { return; } - BCLRecipeManager.addRecipe(TYPE, new UpgradeRecipe(id, base, addition, result)); + BCLRecipeManager.addRecipeAndCreateAdvancement(TYPE, new UpgradeRecipe(id, base, addition, result)); } } diff --git a/src/main/resources/bclib.mixins.common.json b/src/main/resources/bclib.mixins.common.json index b80cc49d..5c501db3 100644 --- a/src/main/resources/bclib.mixins.common.json +++ b/src/main/resources/bclib.mixins.common.json @@ -31,6 +31,7 @@ "RecipeManagerAccessor", "RecipeManagerMixin", "RegistryAccessMixin", + "ServerAdvancementManagerMixin", "ServerLevelMixin", "ShovelItemAccessor", "StructuresAccessor",