diff --git a/src/main/java/net/liukrast/schematicdisplay/clipboard/ClipboardScreenUtils.java b/src/main/java/net/liukrast/schematicdisplay/clipboard/ClipboardScreenUtils.java index 2ded86c..3b5ac8e 100644 --- a/src/main/java/net/liukrast/schematicdisplay/clipboard/ClipboardScreenUtils.java +++ b/src/main/java/net/liukrast/schematicdisplay/clipboard/ClipboardScreenUtils.java @@ -1,10 +1,16 @@ package net.liukrast.schematicdisplay.clipboard; +import com.simibubi.create.AllBlocks; import com.simibubi.create.content.equipment.clipboard.ClipboardEntry; +import dev.emi.emi.api.EmiApi; +import dev.emi.emi.api.stack.Comparison; import dev.emi.emi.api.stack.EmiStack; -import dev.emi.emi.runtime.EmiFavorite; -import dev.emi.emi.runtime.EmiFavorites; +import dev.emi.emi.bom.BoM; +import net.liukrast.schematicdisplay.EMICreateSchematics; +import net.minecraft.client.Minecraft; import net.minecraft.core.component.DataComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.CustomData; @@ -14,37 +20,93 @@ public final class ClipboardScreenUtils { public static final String REAL_COUNT_KEY = "CreateEMISchematicsData"; + public static final String VIRTUAL_RECIPE_KEY = "CreateEMISchematicsVirtualRecipe"; + private static long emi_create_schematics$recipeCounter = 0; + private ClipboardScreenUtils() {} public static void load(List> pages) { - List allEntries = new ArrayList<>(); - for(var page : pages) - allEntries.addAll(page); - allEntries.sort(Comparator.comparingInt((ClipboardEntry entry) -> entry.itemAmount).reversed()); - - var copiedList = new ArrayList<>(EmiFavorites.favorites); - - copiedList.stream() - .filter(fav -> { - if(!(fav.getStack() instanceof EmiStack stack)) return false; - var data = stack.getItemStack().getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); - - if(data.isEmpty()) return false; - return data.contains(REAL_COUNT_KEY); - }) - .forEach(EmiFavorites::removeFavorite); - - for(var entry : allEntries) { - ItemStack stack1 = entry.icon.copy(); - stack1.update(DataComponents.CUSTOM_DATA, CustomData.EMPTY, existingData -> - existingData.update(tag -> tag.putLong(REAL_COUNT_KEY, entry.itemAmount)) - ); - EmiStack emiStack = EmiStack.of(stack1); - emiStack.setAmount(1); + List remainingEntries = collectRemainingEntries(pages); + if (remainingEntries.isEmpty()) { + notifyEmptyTree(); + return; + } + + List inputs = new ArrayList<>(remainingEntries.size()); + for (EntryAmount entry : remainingEntries) { + inputs.add(EmiStack.of(entry.stack.copy()).setAmount(entry.amount)); + } + + ClipboardTreeRecipe recipe = new ClipboardTreeRecipe( + inputs, + createVirtualOutput(), + nextVirtualRecipeId() + ); + + BoM.setGoal(recipe); + BoM.craftingMode = true; + + Minecraft minecraft = Minecraft.getInstance(); + minecraft.setScreen(null); + EmiApi.viewRecipeTree(); + } + + private static List collectRemainingEntries(List> pages) { + List mergedEntries = new ArrayList<>(); + for (List page : pages) { + for (ClipboardEntry entry : page) { + if (entry.checked || entry.icon.isEmpty() || entry.itemAmount <= 0) { + continue; + } + mergeEntry(mergedEntries, entry.icon, entry.itemAmount); + } + } + mergedEntries.sort(Comparator.comparingLong((EntryAmount entry) -> entry.amount).reversed()); + return mergedEntries; + } + + private static void mergeEntry(List mergedEntries, ItemStack stack, long amount) { + for (EntryAmount entry : mergedEntries) { + if (ItemStack.isSameItemSameComponents(entry.stack, stack)) { + entry.amount += amount; + return; + } + } + mergedEntries.add(new EntryAmount(stack.copy(), amount)); + } + + private static EmiStack createVirtualOutput() { + ItemStack stack = AllBlocks.CLIPBOARD.asStack(); + stack.set(DataComponents.CUSTOM_NAME, Component.translatable("gui." + EMICreateSchematics.MOD_ID + ".clipboard.tree_output")); + stack.update(DataComponents.CUSTOM_DATA, CustomData.EMPTY, + existingData -> existingData.update(tag -> tag.putBoolean(VIRTUAL_RECIPE_KEY, true))); + return EmiStack.of(stack).comparison(Comparison.compareComponents()); + } + private static ResourceLocation nextVirtualRecipeId() { + return ResourceLocation.fromNamespaceAndPath( + EMICreateSchematics.MOD_ID, + "/clipboard/" + emi_create_schematics$recipeCounter++ + ); + } + + private static void notifyEmptyTree() { + Minecraft minecraft = Minecraft.getInstance(); + if (minecraft.player != null) { + minecraft.player.displayClientMessage( + Component.translatable("gui." + EMICreateSchematics.MOD_ID + ".clipboard.tree_empty"), + true + ); + } + } + private static final class EntryAmount { + private final ItemStack stack; + private long amount; - if(!entry.checked) EmiFavorites.addFavorite(emiStack); + private EntryAmount(ItemStack stack, long amount) { + this.stack = stack; + this.amount = amount; } } -} \ No newline at end of file +} diff --git a/src/main/java/net/liukrast/schematicdisplay/clipboard/ClipboardTreeRecipe.java b/src/main/java/net/liukrast/schematicdisplay/clipboard/ClipboardTreeRecipe.java new file mode 100644 index 0000000..4f0729e --- /dev/null +++ b/src/main/java/net/liukrast/schematicdisplay/clipboard/ClipboardTreeRecipe.java @@ -0,0 +1,76 @@ +package net.liukrast.schematicdisplay.clipboard; + +import dev.emi.emi.api.recipe.BasicEmiRecipe; +import dev.emi.emi.api.recipe.EmiRecipe; +import dev.emi.emi.api.recipe.VanillaEmiRecipeCategories; +import dev.emi.emi.api.render.EmiTexture; +import dev.emi.emi.api.stack.EmiStack; +import dev.emi.emi.api.widget.WidgetHolder; +import net.liukrast.schematicdisplay.EMICreateSchematics; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.List; + +public final class ClipboardTreeRecipe extends BasicEmiRecipe { + private static final int DISPLAYED_INPUTS = 9; + private final Component overflowLabel; + private boolean completed; + + public ClipboardTreeRecipe(List inputs, EmiStack output, ResourceLocation id) { + super(VanillaEmiRecipeCategories.CRAFTING, id, 118, 72); + this.inputs.addAll(inputs); + this.outputs.add(output); + + int hiddenInputs = Math.max(0, inputs.size() - DISPLAYED_INPUTS); + overflowLabel = hiddenInputs > 0 + ? Component.translatable("gui." + EMICreateSchematics.MOD_ID + ".clipboard.tree_more", hiddenInputs) + : Component.empty(); + } + + public static boolean isVirtual(EmiRecipe recipe) { + return recipe instanceof ClipboardTreeRecipe; + } + + public static ClipboardTreeRecipe getVirtual(EmiRecipe recipe) { + return recipe instanceof ClipboardTreeRecipe virtual ? virtual : null; + } + + public boolean isCompleted() { + return completed; + } + + public void markCompleted() { + completed = true; + } + + @Override + public boolean hideCraftable() { + return true; + } + + @Override + public void addWidgets(WidgetHolder widgets) { + widgets.addText( + Component.translatable("gui." + EMICreateSchematics.MOD_ID + ".clipboard.tree_recipe"), + 0, + 0, + 0x404040, + false + ); + widgets.addTexture(EmiTexture.EMPTY_ARROW, 60, 22); + + for (int i = 0; i < DISPLAYED_INPUTS; i++) { + if (i < inputs.size()) { + widgets.addSlot(inputs.get(i), i % 3 * 18, 12 + i / 3 * 18); + } else { + widgets.addSlot(i % 3 * 18, 12 + i / 3 * 18); + } + } + + widgets.addSlot(outputs.get(0), 92, 18).large(true).recipeContext(this); + if (!overflowLabel.getString().isEmpty()) { + widgets.addText(overflowLabel, 0, 62, 0x787878, false); + } + } +} diff --git a/src/main/java/net/liukrast/schematicdisplay/mixin/BoMScreenMixin.java b/src/main/java/net/liukrast/schematicdisplay/mixin/BoMScreenMixin.java new file mode 100644 index 0000000..8a3f954 --- /dev/null +++ b/src/main/java/net/liukrast/schematicdisplay/mixin/BoMScreenMixin.java @@ -0,0 +1,68 @@ +package net.liukrast.schematicdisplay.mixin; + +import dev.emi.emi.bom.BoM; +import dev.emi.emi.bom.MaterialNode; +import dev.emi.emi.bom.ProgressState; +import dev.emi.emi.screen.BoMScreen; +import net.liukrast.schematicdisplay.clipboard.ClipboardTreeRecipe; +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(BoMScreen.class) +public abstract class BoMScreenMixin { + @Inject(method = "recalculateTree", at = @At("TAIL"), remap = false) + private void emi_create_schematics$markVirtualRootProgress(CallbackInfo ci) { + if (BoM.tree == null) { + return; + } + + ClipboardTreeRecipe virtualRecipe = ClipboardTreeRecipe.getVirtual(BoM.tree.goal.recipe); + if (virtualRecipe == null || BoM.tree.goal.children == null) { + return; + } + + if (!virtualRecipe.isCompleted() && Minecraft.getInstance().player != null + && dev.emi.emi.api.recipe.EmiPlayerInventory.of(Minecraft.getInstance().player).canCraft(virtualRecipe)) { + virtualRecipe.markCompleted(); + BoM.craftingMode = false; + } + + if (virtualRecipe.isCompleted()) { + emi_create_schematics$completeNode(BoM.tree.goal); + return; + } + + boolean anyProgress = false; + boolean allComplete = !BoM.tree.goal.children.isEmpty(); + + for (MaterialNode child : BoM.tree.goal.children) { + if (child.progress != ProgressState.UNSTARTED) { + anyProgress = true; + } + if (child.progress != ProgressState.COMPLETED) { + allComplete = false; + } + } + + if (allComplete) { + BoM.tree.goal.progress = ProgressState.COMPLETED; + } else if (anyProgress) { + BoM.tree.goal.progress = ProgressState.PARTIAL; + } else { + BoM.tree.goal.progress = ProgressState.UNSTARTED; + } + } + + private void emi_create_schematics$completeNode(MaterialNode node) { + node.progress = ProgressState.COMPLETED; + if (node.children == null) { + return; + } + for (MaterialNode child : node.children) { + emi_create_schematics$completeNode(child); + } + } +} diff --git a/src/main/java/net/liukrast/schematicdisplay/mixin/ClipboardScreenMixin.java b/src/main/java/net/liukrast/schematicdisplay/mixin/ClipboardScreenMixin.java index 08d60d7..add8f6e 100644 --- a/src/main/java/net/liukrast/schematicdisplay/mixin/ClipboardScreenMixin.java +++ b/src/main/java/net/liukrast/schematicdisplay/mixin/ClipboardScreenMixin.java @@ -33,8 +33,8 @@ protected ClipboardScreenMixin(final Component ignored) { private void init(CallbackInfo ci) { final int x = guiLeft; final int y = guiTop - 8; - final IconButton customButton = new IconButton(x + 234, y + 197, AllIcons.I_WHITELIST).withCallback(() -> ClipboardScreenUtils.load(pages)); + final IconButton customButton = new IconButton(x + 234, y + 197, AllIcons.I_SCHEMATIC).withCallback(() -> ClipboardScreenUtils.load(pages)); customButton.setToolTip(emi_create_schematics$TOOLTIP); this.addRenderableWidget(customButton); } -} \ No newline at end of file +} diff --git a/src/main/java/net/liukrast/schematicdisplay/mixin/EmiFavoritesMixin.java b/src/main/java/net/liukrast/schematicdisplay/mixin/EmiFavoritesMixin.java new file mode 100644 index 0000000..c1075dc --- /dev/null +++ b/src/main/java/net/liukrast/schematicdisplay/mixin/EmiFavoritesMixin.java @@ -0,0 +1,37 @@ +package net.liukrast.schematicdisplay.mixin; + +import dev.emi.emi.api.recipe.EmiPlayerInventory; +import dev.emi.emi.bom.BoM; +import dev.emi.emi.runtime.EmiFavorites; +import net.liukrast.schematicdisplay.clipboard.ClipboardTreeRecipe; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(EmiFavorites.class) +public abstract class EmiFavoritesMixin { + @Inject(method = "updateSynthetic", at = @At("TAIL"), remap = false) + private static void emi_create_schematics$hideVirtualRootRecipe(EmiPlayerInventory inv, CallbackInfo ci) { + if (BoM.tree == null) { + return; + } + + ClipboardTreeRecipe virtualRecipe = ClipboardTreeRecipe.getVirtual(BoM.tree.goal.recipe); + if (virtualRecipe == null) { + return; + } + + if (!virtualRecipe.isCompleted() && inv.canCraft(virtualRecipe)) { + virtualRecipe.markCompleted(); + BoM.craftingMode = false; + } + + if (virtualRecipe.isCompleted()) { + EmiFavorites.syntheticFavorites.clear(); + return; + } + + EmiFavorites.syntheticFavorites.removeIf(favorite -> favorite.getRecipe() == virtualRecipe); + } +} diff --git a/src/main/resources/assets/emi_create_schematics/lang/en_us.json b/src/main/resources/assets/emi_create_schematics/lang/en_us.json index b72345c..4deed8c 100644 --- a/src/main/resources/assets/emi_create_schematics/lang/en_us.json +++ b/src/main/resources/assets/emi_create_schematics/lang/en_us.json @@ -1,3 +1,7 @@ { - "gui.emi_create_schematics.clipboard.favourite": "Transfer all items to EMI Favourites" -} \ No newline at end of file + "gui.emi_create_schematics.clipboard.favourite": "Open the EMI recipe tree from this clipboard", + "gui.emi_create_schematics.clipboard.tree_empty": "No unchecked item requirements were found on this clipboard", + "gui.emi_create_schematics.clipboard.tree_output": "Schematic Materials", + "gui.emi_create_schematics.clipboard.tree_recipe": "Clipboard Checklist", + "gui.emi_create_schematics.clipboard.tree_more": "+%s more" +} diff --git a/src/main/resources/assets/emi_create_schematics/lang/it_it.json b/src/main/resources/assets/emi_create_schematics/lang/it_it.json index 2b931fb..c792612 100644 --- a/src/main/resources/assets/emi_create_schematics/lang/it_it.json +++ b/src/main/resources/assets/emi_create_schematics/lang/it_it.json @@ -1,3 +1,7 @@ { - "gui.emi_create_schematics.clipboard.favourite": "Trasferisci tutti gli item nei preferiti di EMI" -} \ No newline at end of file + "gui.emi_create_schematics.clipboard.favourite": "Apri l'albero ricette di EMI da questa clipboard", + "gui.emi_create_schematics.clipboard.tree_empty": "Nessun requisito oggetto non spuntato trovato su questa clipboard", + "gui.emi_create_schematics.clipboard.tree_output": "Materiali dello schema", + "gui.emi_create_schematics.clipboard.tree_recipe": "Checklist della clipboard", + "gui.emi_create_schematics.clipboard.tree_more": "+%s altri" +} diff --git a/src/main/resources/emi_create_schematics.mixins.json b/src/main/resources/emi_create_schematics.mixins.json index e4196f5..91e9859 100644 --- a/src/main/resources/emi_create_schematics.mixins.json +++ b/src/main/resources/emi_create_schematics.mixins.json @@ -8,7 +8,9 @@ "ItemEmiStackMixin" ], "client": [ - "ClipboardScreenMixin" + "BoMScreenMixin", + "ClipboardScreenMixin", + "EmiFavoritesMixin" ], "injectors": { "defaultRequire": 1