[Features] (Recipe-) Advancement API

This commit is contained in:
Frank 2022-07-31 14:49:15 +02:00
parent e5da06a1e1
commit 18fffafb3a
10 changed files with 490 additions and 10 deletions

View file

@ -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
);
}
}

View file

@ -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<ResourceLocation, Advancement.Builder> ADVANCEMENTS = new HashMap<>();
public static void register(ResourceLocation id, Advancement.Builder builder) {
ADVANCEMENTS.put(id, builder);
}
@ApiStatus.Internal
public static void addAdvancements(Map<ResourceLocation, Advancement.Builder> 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<DisplayBuilder> 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<DisplayBuilder> displayAdapter) {
return create(new ItemStack(item), type, displayAdapter);
}
public static Builder create(ItemStack item, AdvancementType type, Consumer<DisplayBuilder> 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 <C extends Container, T extends Recipe<C>> 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 <C extends Container, T extends Recipe<C>> 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;
}
}
}

View file

@ -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<ResourceLocation, Advancement.Builder> wunder_interceptApply(Map<ResourceLocation, Advancement.Builder> map) {
AdvancementManager.addAdvancements(map);
return map;
}
}

View file

@ -249,9 +249,10 @@ public class AlloyingRecipe implements Recipe<Container>, 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
);
}
}

View file

@ -350,9 +350,10 @@ public class AnvilRecipe implements Recipe<Container>, 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
);
}
}

View file

@ -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 <C extends Container, T extends Recipe<C>> void addRecipeAndCreateAdvancement(
RecipeType<T> type,
T recipe
) {
addRecipe(type, recipe);
registerAndCreateAdvancement(recipe, recipe.getResultItem().getItem() instanceof TieredItem);
}
public static <C extends Container, T extends Recipe<C>> void addRecipeAndCreateAdvancement(
RecipeType<T> type,
T recipe,
boolean isTool
) {
addRecipe(type, recipe);
registerAndCreateAdvancement(recipe, isTool);
}
public static <C extends Container, T extends Recipe<C>> 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 <C extends Container, T extends Recipe<C>> T getRecipe(RecipeType<T> type, ResourceLocation id) {
Map<ResourceLocation, T> map = BCLRecipeManager.<C, T>RECIPES().get(type);
return map != null ? map.get(id) : null;

View file

@ -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);
}
}
}

View file

@ -145,6 +145,6 @@ public class GridRecipe {
result
) : new ShapelessRecipe(id, group, result, materials);
BCLRecipeManager.addRecipe(type, recipe);
BCLRecipeManager.addRecipeAndCreateAdvancement(type, recipe);
}
}

View file

@ -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));
}
}

View file

@ -31,6 +31,7 @@
"RecipeManagerAccessor",
"RecipeManagerMixin",
"RegistryAccessMixin",
"ServerAdvancementManagerMixin",
"ServerLevelMixin",
"ShovelItemAccessor",
"StructuresAccessor",