[Feature] EMI Integration for Anvil- and Alloying Recipes

This commit is contained in:
Frank 2022-07-28 18:30:13 +02:00
parent a4205f3440
commit bb15db429d
13 changed files with 492 additions and 9 deletions

View file

@ -1,7 +1,15 @@
package org.betterx.bclib.blocks;
import net.minecraft.client.Minecraft;
import net.minecraft.core.Registry;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.material.MaterialColor;
import java.util.List;
public class LeveledAnvilBlock extends BaseAnvilBlock {
protected final int level;
@ -10,6 +18,36 @@ public class LeveledAnvilBlock extends BaseAnvilBlock {
this.level = level;
}
public static int getAnvilCraftingLevel(Block anvil) {
if (anvil instanceof LeveledAnvilBlock l) return l.getCraftingLevel();
if (anvil == Blocks.ANVIL || anvil == Blocks.CHIPPED_ANVIL || anvil == Blocks.DAMAGED_ANVIL) return 0;
return -1;
}
public static boolean canHandle(Block anvil, int level) {
return getAnvilCraftingLevel(anvil) >= level;
}
public static List<Block> getAnvils() {
return Registry.BLOCK
.stream()
.filter(b -> b instanceof LeveledAnvilBlock || b == Blocks.ANVIL)
.toList();
}
public static List<FormattedCharSequence> getNamesForLevel(int level) {
MutableComponent names = getAnvils()
.stream()
.filter(b -> canHandle(b, level))
.map(Block::getName)
.reduce(
null,
(p, c) -> p == null ? c : p.append(net.minecraft.network.chat.Component.literal(", ")).append(c)
);
if (names == null) return List.of();
return Minecraft.getInstance().font.split(names, 200);
}
public int getCraftingLevel() {
return level;
}

View file

@ -0,0 +1,13 @@
package org.betterx.bclib.integration.emi;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ItemLike;
import dev.emi.emi.api.stack.ItemEmiStack;
public class AnvilEmiStack extends ItemEmiStack {
public AnvilEmiStack(ItemLike itemLike) {
super(new ItemStack(itemLike));
}
}

View file

@ -0,0 +1,82 @@
package org.betterx.bclib.integration.emi;
import org.betterx.bclib.recipes.AlloyingRecipe;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.RecipeManager;
import dev.emi.emi.api.EmiRegistry;
import dev.emi.emi.api.recipe.EmiRecipe;
import dev.emi.emi.api.recipe.EmiRecipeCategory;
import dev.emi.emi.api.render.EmiTexture;
import dev.emi.emi.api.stack.EmiIngredient;
import dev.emi.emi.api.stack.EmiStack;
import dev.emi.emi.api.widget.WidgetHolder;
import java.util.List;
public class EMIAlloyingRecipe implements EmiRecipe {
private final ResourceLocation id;
private final List<EmiIngredient> input;
private final List<EmiStack> output;
public EMIAlloyingRecipe(AlloyingRecipe recipe) {
this.id = recipe.getId();
this.input = List.of(
EmiIngredient.of(recipe.getIngredients().get(0)),
EmiIngredient.of(recipe.getIngredients().get(1))
);
this.output = List.of(EmiStack.of(recipe.getResultItem()));
}
static void addAllRecipes(EmiRegistry emiRegistry, RecipeManager manager) {
for (AlloyingRecipe recipe : manager.getAllRecipesFor(AlloyingRecipe.TYPE)) {
emiRegistry.addRecipe(new EMIAlloyingRecipe(recipe));
}
}
@Override
public EmiRecipeCategory getCategory() {
return EMIPlugin.END_ALLOYING_CATEGORY;
}
@Override
public ResourceLocation getId() {
return id;
}
@Override
public List<EmiIngredient> getInputs() {
return input;
}
@Override
public List<EmiStack> getOutputs() {
return output;
}
@Override
public int getDisplayWidth() {
return 76;
}
@Override
public int getDisplayHeight() {
return 18;
}
@Override
public void addWidgets(WidgetHolder widgets) {
// Add an arrow texture to indicate processing
widgets.addTexture(EmiTexture.EMPTY_ARROW, 46, 1);
// Adds an input slot on the left
widgets.addSlot(input.get(0), 0, 0);
widgets.addSlot(input.get(1), 20, 0);
// Adds an output slot on the right
// Note that output slots need to call `recipeContext` to inform EMI about their recipe context
// This includes being able to resolve recipe trees, favorite stacks with recipe context, and more
widgets.addSlot(output.get(0), 58, 0).recipeContext(this);
}
}

View file

@ -0,0 +1,95 @@
package org.betterx.bclib.integration.emi;
import org.betterx.bclib.recipes.AnvilRecipe;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeManager;
import dev.emi.emi.api.EmiRegistry;
import dev.emi.emi.api.recipe.EmiRecipe;
import dev.emi.emi.api.recipe.EmiRecipeCategory;
import dev.emi.emi.api.render.EmiTexture;
import dev.emi.emi.api.stack.EmiIngredient;
import dev.emi.emi.api.stack.EmiStack;
import dev.emi.emi.api.widget.WidgetHolder;
import java.util.List;
import org.jetbrains.annotations.Nullable;
public class EMIAnvilRecipe implements EmiRecipe {
private final ResourceLocation id;
private final List<EmiIngredient> input;
private final List<EmiStack> output;
private final EmiRecipeCategory category;
public EMIAnvilRecipe(AnvilRecipe recipe, Item hammer) {
this.id = new ResourceLocation(
recipe.getId().getNamespace(),
recipe.getId().getPath() + "_" + hammer.getDescriptionId()
);
this.input = List.of(
EmiIngredient.of(recipe.getMainIngredient(), recipe.getInputCount()),
EmiIngredient.of(Ingredient.of(hammer))
);
this.output = List.of(EmiStack.of(recipe.getResultItem()));
this.category = EMIPlugin.getAnvilCategoryForLevel(recipe.getAnvilLevel());
}
static void addAllRecipes(EmiRegistry emiRegistry, RecipeManager manager) {
Iterable<Holder<Item>> hammers = AnvilRecipe.getAllHammers();
for (AnvilRecipe recipe : manager.getAllRecipesFor(AnvilRecipe.TYPE)) {
for (Holder<Item> hammer : hammers) {
if (recipe.canUse(hammer.value()))
emiRegistry.addRecipe(new EMIAnvilRecipe(recipe, hammer.value()));
}
}
}
@Override
public EmiRecipeCategory getCategory() {
return category;
}
@Override
public @Nullable ResourceLocation getId() {
return id;
}
@Override
public List<EmiIngredient> getInputs() {
return input;
}
@Override
public List<EmiStack> getOutputs() {
return output;
}
@Override
public int getDisplayWidth() {
return 76;
}
@Override
public int getDisplayHeight() {
return 18;
}
@Override
public void addWidgets(WidgetHolder widgetHolder) {
// Add an arrow texture to indicate processing
widgetHolder.addTexture(EmiTexture.EMPTY_ARROW, 46, 1);
// Adds an input slot on the left
widgetHolder.addSlot(input.get(0), 0, 0);
widgetHolder.addSlot(input.get(1), 20, 0);
// Adds an output slot on the right
// Note that output slots need to call `recipeContext` to inform EMI about their recipe context
// This includes being able to resolve recipe trees, favorite stacks with recipe context, and more
widgetHolder.addSlot(output.get(0), 58, 0).recipeContext(this);
}
}

View file

@ -0,0 +1,102 @@
package org.betterx.bclib.integration.emi;
import org.betterx.bclib.blocks.LeveledAnvilBlock;
import org.betterx.bclib.util.RomanNumeral;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.Tesselator;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.FormattedCharSequence;
import com.google.common.collect.Lists;
import dev.emi.emi.EmiPort;
import dev.emi.emi.EmiUtil;
import dev.emi.emi.api.recipe.EmiRecipe;
import dev.emi.emi.api.recipe.EmiRecipeCategory;
import dev.emi.emi.api.render.EmiRenderable;
import dev.emi.emi.api.render.EmiTexture;
import java.util.Comparator;
import java.util.List;
public class EMIAnvilRecipeCategory extends EmiRecipeCategory {
private final int anvilLevel;
private final List<FormattedCharSequence> titleLines;
public EMIAnvilRecipeCategory(ResourceLocation id, EmiRenderable icon, int anvilLevel) {
super(id, icon);
this.anvilLevel = anvilLevel;
titleLines = LeveledAnvilBlock.getNamesForLevel(anvilLevel);
}
public EMIAnvilRecipeCategory(ResourceLocation id, EmiRenderable icon, EmiRenderable simplified, int anvilLevel) {
super(id, icon, simplified);
this.anvilLevel = anvilLevel;
titleLines = LeveledAnvilBlock.getNamesForLevel(anvilLevel);
}
public EMIAnvilRecipeCategory(
ResourceLocation id,
EmiRenderable icon, EmiTexture simplified,
Comparator<EmiRecipe> sorter,
int anvilLevel
) {
super(id, icon, simplified, sorter);
this.anvilLevel = anvilLevel;
titleLines = LeveledAnvilBlock.getNamesForLevel(anvilLevel);
}
@Override
public void renderSimplified(PoseStack stack, int x, int y, float delta) {
super.renderSimplified(stack, x, y, delta);
final Font font = Minecraft.getInstance().font;
final String content = RomanNumeral.toRoman(anvilLevel);
MultiBufferSource.BufferSource bufferSource = MultiBufferSource
.immediate(
Tesselator.getInstance()
.getBuilder()
);
font.drawInBatch(
content,
x + 19 - 2 - font.width(content), y + 6 + 3,
0xFFFFFF,
true,
stack.last().pose(),
bufferSource,
false,
0,
0xF000F0
);
bufferSource.endBatch();
}
public List<ClientTooltipComponent> getTooltip() {
List<ClientTooltipComponent> list = Lists.newArrayList();
if (titleLines.isEmpty()) {
list.add(ClientTooltipComponent.create(EmiPort.ordered(EmiPort.translatable(EmiUtil.translateId(
"emi.category.",
this.getId()
)))));
} else {
for (var line : titleLines)
list.add(ClientTooltipComponent.create(line));
}
list.add(ClientTooltipComponent.create(EmiPort.ordered(EmiPort.literal(
EmiUtil.getModName(this.getId()
.getNamespace()),
new ChatFormatting[]{ChatFormatting.BLUE, ChatFormatting.ITALIC}
))));
return list;
}
}

View file

@ -0,0 +1,116 @@
package org.betterx.bclib.integration.emi;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.blocks.LeveledAnvilBlock;
import org.betterx.bclib.interfaces.AlloyingRecipeWorkstation;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.RecipeManager;
import net.minecraft.world.level.block.Blocks;
import dev.emi.emi.api.EmiPlugin;
import dev.emi.emi.api.EmiRegistry;
import dev.emi.emi.api.recipe.EmiRecipeCategory;
import dev.emi.emi.api.render.EmiTexture;
import dev.emi.emi.api.stack.EmiStack;
import java.util.Comparator;
public class EMIPlugin implements EmiPlugin {
private static boolean didInit = false;
private static int maxAnvilLevel = 1;
public static final ResourceLocation MY_SPRITE_SHEET = BCLib.makeID(
"textures/gui/widgets.png"
);
public static EmiStack END_ALLOYING_WORKSTATION;
public static EmiRecipeCategory END_ALLOYING_CATEGORY;
public static EmiRecipeCategory[] ANVIL_CATEGORIES;
public static EmiStack[] ANVIL_WORKSTATIONS;
public static EmiTexture getSprite(int u, int v) {
return new EmiTexture(MY_SPRITE_SHEET, u, v, 16, 16, 16, 16, 32, 32);
}
public void lazyInit() {
if (!didInit) {
didInit = true;
lazyInitAlloyingCategory();
lazyInitAnvilCategories();
}
}
private void lazyInitAlloyingCategory() {
var workstations = AlloyingRecipeWorkstation.getWorkstationIcon();
if (!workstations.is(Blocks.BARRIER.asItem())) {
END_ALLOYING_WORKSTATION = EmiStack.of(workstations);
END_ALLOYING_CATEGORY = new EmiRecipeCategory(
BCLib.makeID("alloying"),
END_ALLOYING_WORKSTATION,
getSprite(16, 0)
);
}
}
private void lazyInitAnvilCategories() {
if (ANVIL_CATEGORIES == null) {
maxAnvilLevel = Math.max(1, LeveledAnvilBlock
.getAnvils()
.stream()
.map(LeveledAnvilBlock::getAnvilCraftingLevel)
.reduce(0, Math::max)
);
ANVIL_CATEGORIES = new EmiRecipeCategory[maxAnvilLevel + 1];
ANVIL_WORKSTATIONS = new EmiStack[maxAnvilLevel + 1];
for (int anvilLevel = 0; anvilLevel <= maxAnvilLevel; anvilLevel++) {
int finalAnvilLevel = anvilLevel;
ANVIL_WORKSTATIONS[anvilLevel] = new AnvilEmiStack(LeveledAnvilBlock
.getAnvils()
.stream()
.filter(b -> LeveledAnvilBlock.canHandle(b, finalAnvilLevel))
.sorted(Comparator.comparingInt(LeveledAnvilBlock::getAnvilCraftingLevel))
.findFirst().orElse(Blocks.BARRIER)
);
ANVIL_CATEGORIES[anvilLevel] = new EMIAnvilRecipeCategory(
BCLib.makeID("anvil_" + anvilLevel),
ANVIL_WORKSTATIONS[anvilLevel],
getSprite(0, 0),
anvilLevel
);
}
}
}
@Override
public void register(EmiRegistry emiRegistry) {
lazyInit();
final RecipeManager manager = emiRegistry.getRecipeManager();
if (END_ALLOYING_CATEGORY != null && END_ALLOYING_WORKSTATION != null) {
emiRegistry.addCategory(END_ALLOYING_CATEGORY);
emiRegistry.addWorkstation(END_ALLOYING_CATEGORY, END_ALLOYING_WORKSTATION);
EMIAlloyingRecipe.addAllRecipes(emiRegistry, manager);
}
if (ANVIL_CATEGORIES != null && ANVIL_WORKSTATIONS != null && ANVIL_CATEGORIES.length > 0) {
for (int i = 0; i <= maxAnvilLevel; i++) {
emiRegistry.addCategory(ANVIL_CATEGORIES[i]);
emiRegistry.addWorkstation(ANVIL_CATEGORIES[i], ANVIL_WORKSTATIONS[i]);
}
EMIAnvilRecipe.addAllRecipes(emiRegistry, manager);
}
}
static EmiRecipeCategory getAnvilCategoryForLevel(int anvilLevel) {
anvilLevel = Math.max(0, Math.min(ANVIL_CATEGORIES.length - 1, anvilLevel));
return ANVIL_CATEGORIES[anvilLevel];
}
}

View file

@ -6,8 +6,10 @@ import org.betterx.bclib.interfaces.UnknownReceipBookCategory;
import org.betterx.bclib.util.ItemUtil;
import org.betterx.bclib.util.RecipeHelper;
import org.betterx.worlds.together.tag.v3.CommonItemTags;
import org.betterx.worlds.together.world.event.WorldBootstrap;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.core.Holder;
import net.minecraft.core.NonNullList;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
@ -115,6 +117,12 @@ public class AnvilRecipe implements Recipe<Container>, UnknownReceipBookCategory
return this.output.copy();
}
public static Iterable<Holder<Item>> getAllHammers() {
Registry<Item> registry = WorldBootstrap.getLastRegistryAccessOrElseBuiltin()
.registryOrThrow(CommonItemTags.HAMMERS.registry());
return registry.getTagOrEmpty(CommonItemTags.HAMMERS);
}
public static int getHammerSlot(Container c) {
ItemStack h = c.getItem(0);
if (!h.isEmpty() && h.is(CommonItemTags.HAMMERS)) return 0;
@ -182,18 +190,32 @@ public class AnvilRecipe implements Recipe<Container>, UnknownReceipBookCategory
return this.inputCount;
}
public Ingredient getMainIngredient() {
return this.input;
}
public int getAnvilLevel() {
return this.anvilLevel;
}
public boolean canUse(Item tool) {
if (tool instanceof TieredItem ti) {
return ti.getTier().getLevel() >= toolLevel;
}
return false;
}
public static boolean isHammer(Item tool) {
if (tool == null) return false;
return tool.getDefaultInstance().is(CommonItemTags.HAMMERS);
}
@Override
public NonNullList<Ingredient> getIngredients() {
NonNullList<Ingredient> defaultedList = NonNullList.create();
defaultedList.add(Ingredient.of(Registry.ITEM.stream()
.filter(item -> item.builtInRegistryHolder()
.is(CommonItemTags.HAMMERS))
.filter(hammer -> ((TieredItem) hammer).getTier()
.getLevel() >= toolLevel)
.filter(AnvilRecipe::isHammer)
.filter(this::canUse)
.map(ItemStack::new))
);
defaultedList.add(input);