From a48ee365dda5339e7379291925fc3d68c74b7c00 Mon Sep 17 00:00:00 2001 From: "a.o.glazkov" Date: Fri, 26 Dec 2025 21:01:13 +0300 Subject: [PATCH 1/7] Add multiple recipe tree management and batch manipulation Enhance recipe tree functionality with support for multiple trees, allowing addition, removal, and selection. Add combinable costs and progression for aggregated handling. Introduce new tooltips for advanced batch adjustments with `Ctrl` and `Alt`. --- xplat/src/main/java/dev/emi/emi/bom/BoM.java | 89 ++++++- .../main/java/dev/emi/emi/bom/TreeCost.java | 52 +++- .../dev/emi/emi/network/CommandS2CPacket.java | 4 +- .../dev/emi/emi/runtime/EmiFavorites.java | 34 ++- .../java/dev/emi/emi/screen/BoMScreen.java | 245 +++++++++++++++--- .../dev/emi/emi/screen/EmiScreenManager.java | 16 +- .../screen/widget/ResolutionButtonWidget.java | 6 +- .../emi/widget/RecipeTreeButtonWidget.java | 21 +- .../main/resources/assets/emi/lang/en_us.json | 4 +- .../main/resources/assets/emi/lang/es_es.json | 2 + .../main/resources/assets/emi/lang/fi_fi.json | 4 +- .../main/resources/assets/emi/lang/fr_fr.json | 4 +- .../main/resources/assets/emi/lang/ja_jp.json | 2 + .../main/resources/assets/emi/lang/pt_br.json | 4 +- .../main/resources/assets/emi/lang/ru_ru.json | 2 + .../main/resources/assets/emi/lang/tr_tr.json | 7 +- .../main/resources/assets/emi/lang/zh_cn.json | 2 + .../assets/emi/textures/gui/buttons.png | Bin 7074 -> 8798 bytes 18 files changed, 416 insertions(+), 82 deletions(-) diff --git a/xplat/src/main/java/dev/emi/emi/bom/BoM.java b/xplat/src/main/java/dev/emi/emi/bom/BoM.java index 7c000791..0fca2cdb 100644 --- a/xplat/src/main/java/dev/emi/emi/bom/BoM.java +++ b/xplat/src/main/java/dev/emi/emi/bom/BoM.java @@ -1,8 +1,10 @@ package dev.emi.emi.bom; +import java.util.List; import java.util.Map; import java.util.Set; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.gson.JsonArray; @@ -12,6 +14,7 @@ import dev.emi.emi.EmiPort; import dev.emi.emi.api.EmiApi; +import dev.emi.emi.api.recipe.EmiPlayerInventory; import dev.emi.emi.api.recipe.EmiRecipe; import dev.emi.emi.api.recipe.EmiResolutionRecipe; import dev.emi.emi.api.stack.EmiIngredient; @@ -26,7 +29,10 @@ public class BoM { private static RecipeDefaults defaults = new RecipeDefaults(); - public static MaterialTree tree; + public static List trees = Lists.newArrayList(); + public static int treeIndex = -1; + public static TreeCost combinedCost = new TreeCost(); + public static TreeCost combinedProgress = new TreeCost(); public static Map defaultRecipes = Maps.newHashMap(); public static Map addedRecipes = Maps.newHashMap(); public static Set disabledRecipes = Sets.newHashSet(); @@ -166,12 +172,70 @@ public static EmiRecipe getRecipe(EmiIngredient stack) { } public static void setGoal(EmiRecipe recipe) { - tree = new MaterialTree(recipe); + trees.clear(); + trees.add(new MaterialTree(recipe)); + treeIndex = 0; craftingMode = false; + recalculate(); + } + + public static void addGoal(EmiRecipe recipe) { + if (trees.isEmpty()) { + setGoal(recipe); + return; + } + trees.add(new MaterialTree(recipe)); + treeIndex = trees.size() - 1; + recalculate(); + } + + public static MaterialTree getTree() { + if (treeIndex < 0 || treeIndex >= trees.size()) { + return null; + } + return trees.get(treeIndex); + } + + public static List getTrees() { + return trees; + } + + public static void selectTree(int index) { + if (trees.isEmpty()) { + treeIndex = -1; + return; + } + treeIndex = Math.max(0, Math.min(index, trees.size() - 1)); + } + + public static void cycleTree(int delta) { + if (trees.isEmpty()) { + treeIndex = -1; + return; + } + int size = trees.size(); + treeIndex = ((treeIndex + delta) % size + size) % size; + } + + public static void removeTree(int index) { + if (index < 0 || index >= trees.size()) { + return; + } + trees.remove(index); + if (trees.isEmpty()) { + treeIndex = -1; + craftingMode = false; + } else { + treeIndex = Math.max(0, Math.min(treeIndex, trees.size() - 1)); + } + recalculate(); } public static void addResolution(EmiIngredient ingredient, EmiRecipe recipe) { - tree.addResolution(ingredient, recipe); + MaterialTree tree = getTree(); + if (tree != null) { + tree.addResolution(ingredient, recipe); + } } public static boolean isDefaultRecipe(EmiIngredient stack, EmiRecipe recipe) { @@ -227,12 +291,25 @@ public static void removeRecipe(EmiIngredient stack, EmiRecipe recipe) { recalculate(); } - private static void recalculate() { - if (tree != null) { - tree.recalculate(); + public static void calculateCombinedCosts(EmiPlayerInventory inventory) { + combinedProgress.clear(); + for (MaterialTree tree : trees) { + tree.calculateProgress(inventory); + combinedProgress.merge(tree.cost); + } + combinedCost.clear(); + for (MaterialTree tree : trees) { + tree.calculateCost(); + combinedCost.merge(tree.cost); } } + private static void recalculate() { + for (MaterialTree tree : trees) { + tree.recalculate(); + } + } + public static enum DefaultStatus { EMPTY, PARTIAL, diff --git a/xplat/src/main/java/dev/emi/emi/bom/TreeCost.java b/xplat/src/main/java/dev/emi/emi/bom/TreeCost.java index be0c9017..9e29b8fa 100644 --- a/xplat/src/main/java/dev/emi/emi/bom/TreeCost.java +++ b/xplat/src/main/java/dev/emi/emi/bom/TreeCost.java @@ -17,19 +17,61 @@ public class TreeCost { public Map remainders = Maps.newHashMap(); public Map chanceRemainders = Maps.newHashMap(); - public void calculate(MaterialNode node, long batches) { + public void clear() { costs.clear(); chanceCosts.clear(); remainders.clear(); chanceRemainders.clear(); + } + + public void merge(TreeCost other) { + for (FlatMaterialCost cost : other.costs.values()) { + FlatMaterialCost existing = costs.get(cost.ingredient); + if (existing == null) { + costs.put(cost.ingredient, new FlatMaterialCost(cost.ingredient, cost.amount)); + } else { + existing.amount += cost.amount; + } + } + for (ChanceMaterialCost cost : other.chanceCosts.values()) { + ChanceMaterialCost existing = chanceCosts.get(cost.ingredient); + if (existing == null) { + existing = new ChanceMaterialCost(cost.ingredient, cost.amount, cost.chance); + chanceCosts.put(cost.ingredient, existing); + } else { + existing.merge(cost.amount, cost.chance); + } + existing.minBatch(cost.minBatch); + } + for (FlatMaterialCost remainder : other.remainders.values()) { + EmiStack key = (EmiStack) remainder.ingredient; + FlatMaterialCost existing = remainders.get(key); + if (existing == null) { + remainders.put(key, new FlatMaterialCost(key, remainder.amount)); + } else { + existing.amount += remainder.amount; + } + } + for (ChanceMaterialCost remainder : other.chanceRemainders.values()) { + EmiStack key = (EmiStack) remainder.ingredient; + ChanceMaterialCost existing = chanceRemainders.get(key); + if (existing == null) { + existing = new ChanceMaterialCost(key, remainder.amount, remainder.chance); + chanceRemainders.put(key, existing); + } else { + existing.merge(remainder.amount, remainder.chance); + } + existing.minBatch(remainder.minBatch); + } + } + + public void calculate(MaterialNode node, long batches) { + clear(); calculateCost(node, batches * node.amount, ChanceState.DEFAULT, false); } public void calculateProgress(MaterialNode node, long batches, EmiPlayerInventory inventory) { - costs.clear(); - chanceCosts.clear(); - remainders.clear(); - chanceRemainders.clear(); + clear(); for (EmiStack stack : inventory.inventory.values()) { stack = stack.copy(); remainders.put(stack, new FlatMaterialCost(stack, stack.getAmount())); diff --git a/xplat/src/main/java/dev/emi/emi/network/CommandS2CPacket.java b/xplat/src/main/java/dev/emi/emi/network/CommandS2CPacket.java index f27253f7..85085c0d 100644 --- a/xplat/src/main/java/dev/emi/emi/network/CommandS2CPacket.java +++ b/xplat/src/main/java/dev/emi/emi/network/CommandS2CPacket.java @@ -52,9 +52,9 @@ public void apply(PlayerEntity player) { } } else if (type == EmiCommands.TREE_RESOLUTION) { EmiRecipe recipe = EmiApi.getRecipeManager().getRecipe(id); - if (recipe != null && BoM.tree != null) { + if (recipe != null && BoM.getTree() != null) { for (EmiStack stack : recipe.getOutputs()) { - BoM.tree.addResolution(stack, recipe); + BoM.addResolution(stack, recipe); } } } diff --git a/xplat/src/main/java/dev/emi/emi/runtime/EmiFavorites.java b/xplat/src/main/java/dev/emi/emi/runtime/EmiFavorites.java index 21a346ae..a98c61b0 100644 --- a/xplat/src/main/java/dev/emi/emi/runtime/EmiFavorites.java +++ b/xplat/src/main/java/dev/emi/emi/runtime/EmiFavorites.java @@ -22,7 +22,9 @@ import dev.emi.emi.bom.BoM; import dev.emi.emi.bom.ChanceMaterialCost; import dev.emi.emi.bom.FlatMaterialCost; +import dev.emi.emi.bom.MaterialTree; import dev.emi.emi.bom.MaterialNode; +import dev.emi.emi.bom.TreeCost; import it.unimi.dsi.fastutil.objects.Object2LongLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2LongMap; import net.minecraft.util.Identifier; @@ -191,20 +193,32 @@ public static void addFavorite(EmiIngredient stack, EmiRecipe context) { public static void updateSynthetic(EmiPlayerInventory inv) { syntheticFavorites.clear(); - if (BoM.tree != null && BoM.craftingMode) { - BoM.tree.calculateCost(); - Map originalCosts = Maps.newHashMap(BoM.tree.cost.costs); - Map chancedCosts = Maps.newHashMap(BoM.tree.cost.chanceCosts); + List trees = BoM.getTrees(); + if (!trees.isEmpty() && BoM.craftingMode) { + TreeCost originalCost = new TreeCost(); + for (MaterialTree tree : trees) { + tree.calculateCost(); + originalCost.merge(tree.cost); + } + Map originalCosts = Maps.newHashMap(originalCost.costs); + Map chancedCosts = Maps.newHashMap(originalCost.chanceCosts); Object2LongMap originalBatches = new Object2LongLinkedOpenHashMap<>(); Object2LongMap originalAmounts = new Object2LongLinkedOpenHashMap<>(); EmiPlayerInventory emptyInventory = new EmiPlayerInventory(List.of()); emptyInventory.inventory.clear(); - BoM.tree.calculateProgress(emptyInventory); - countRecipes(originalBatches, originalAmounts, BoM.tree.goal); - BoM.tree.calculateProgress(inv); + for (MaterialTree tree : trees) { + tree.calculateProgress(emptyInventory); + countRecipes(originalBatches, originalAmounts, tree.goal); + } + TreeCost remainingCost = new TreeCost(); Object2LongMap batches = new Object2LongLinkedOpenHashMap<>(); Object2LongMap amounts = new Object2LongLinkedOpenHashMap<>(); - countRecipes(batches, amounts, BoM.tree.goal); + for (MaterialTree tree : trees) { + tree.calculateProgress(inv); + countRecipes(batches, amounts, tree.goal); + remainingCost.merge(tree.cost); + } + BoM.calculateCombinedCosts(inv); boolean hasSomething = false; for (Object2LongMap.Entry entry : batches.object2LongEntrySet()) { EmiRecipe recipe = entry.getKey(); @@ -225,12 +239,12 @@ public static void updateSynthetic(EmiPlayerInventory inv) { if (!hasSomething) { BoM.craftingMode = false; } else { - for (FlatMaterialCost cost : BoM.tree.cost.costs.values()) { + for (FlatMaterialCost cost : remainingCost.costs.values()) { if (cost.amount > 0) { syntheticFavorites.add(new EmiFavorite.Synthetic(cost.ingredient, cost.amount, originalCosts.getOrDefault(cost.ingredient, cost).amount)); } } - for (ChanceMaterialCost cost : BoM.tree.cost.chanceCosts.values()) { + for (ChanceMaterialCost cost : remainingCost.chanceCosts.values()) { if (cost.getEffectiveAmount() > 0) { long needed = cost.getEffectiveAmount(); if (chancedCosts.containsKey(cost.ingredient)) { diff --git a/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java b/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java index 727944ca..53148bca 100644 --- a/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java +++ b/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java @@ -32,6 +32,7 @@ import dev.emi.emi.bom.FlatMaterialCost; import dev.emi.emi.bom.FoldState; import dev.emi.emi.bom.MaterialNode; +import dev.emi.emi.bom.MaterialTree; import dev.emi.emi.bom.ProgressState; import dev.emi.emi.bom.TreeCost; import dev.emi.emi.config.EmiConfig; @@ -79,6 +80,13 @@ public class BoMScreen extends Screen { private int nodeHeight = 0; private int lastMouseX, lastMouseY; private double scrollAcc = 0; + private Bounds rootLeft = new Bounds(0, 0, 12, 12); + private Bounds rootRight = new Bounds(0, 0, 12, 12); + private Bounds rootArea = new Bounds(0, 0, 0, 0); + private List rootSlots = Lists.newArrayList(); + private List rootIndices = Lists.newArrayList(); + private List rootAmounts = Lists.newArrayList(); + private int rootScroll = 0; public BoMScreen(HandledScreen old) { super(EmiPort.translatable("screen.emi.recipe_tree")); @@ -86,8 +94,8 @@ public BoMScreen(HandledScreen old) { } public void init() { - if (BoM.tree != null) { - offY = height / -3; + if (BoM.getTree() != null) { + offY = 0; } else { offY = 0; } @@ -96,8 +104,13 @@ public void init() { public void recalculateTree() { help = new Bounds(width - 18, height - 18, 16, 16); - if (BoM.tree != null) { - TreeVolume volume = addNewNodes(BoM.tree.goal, BoM.tree.batches, 1, 0, ChanceState.DEFAULT); + MaterialTree tree = BoM.getTree(); + List roots = BoM.getTrees(); + rootSlots.clear(); + rootIndices.clear(); + rootAmounts.clear(); + if (tree != null) { + TreeVolume volume = addNewNodes(tree.goal, tree.batches, 1, 0, ChanceState.DEFAULT); nodes = volume.nodes; int horizontalOffset = (volume.getMaxRight() + volume.getMinLeft()) / 2; for (Node node : volume.nodes) { @@ -105,25 +118,24 @@ public void recalculateTree() { } if (!volume.nodes.isEmpty()) { Node node = volume.nodes.get(0); - int width = textRenderer.getWidth("x" + BoM.tree.batches); + int width = textRenderer.getWidth("x" + tree.batches); batches = new Bounds(node.x + node.width / 2 + 6, node.y - 10, width + 12, 22); } nodeWidth = volume.getMaxRight() - volume.getMinLeft(); - nodeHeight = getNodeHeight(BoM.tree.goal); + nodeHeight = getNodeHeight(tree.goal); playerInv = EmiPlayerInventory.of(client.player); - BoM.tree.calculateProgress(playerInv); - Map progressCosts = BoM.tree.cost.costs.values().stream() + BoM.calculateCombinedCosts(playerInv); + Map progressCosts = BoM.combinedProgress.costs.values().stream() .collect(Collectors.toMap(c -> c.ingredient, c -> c)); - Map chanceProgressCosts = BoM.tree.cost.chanceCosts.values().stream() + Map chanceProgressCosts = BoM.combinedProgress.chanceCosts.values().stream() .collect(Collectors.toMap(c -> c.ingredient, c -> c)); - + costs.clear(); - BoM.tree.calculateCost(); List treeCosts = Stream.concat( - BoM.tree.cost.costs.values().stream(), - BoM.tree.cost.chanceCosts.values().stream() + BoM.combinedCost.costs.values().stream(), + BoM.combinedCost.chanceCosts.values().stream() ).sorted((a, b) -> Integer.compare( EmiStackList.getIndex(a.ingredient.getEmiStacks().get(0)), EmiStackList.getIndex(b.ingredient.getEmiStacks().get(0)) @@ -163,8 +175,8 @@ public void recalculateTree() { List remainders = Lists.newArrayList(); List remainderCosts = Stream.concat( - BoM.tree.cost.remainders.values().stream(), - BoM.tree.cost.chanceRemainders.values().stream() + BoM.combinedCost.remainders.values().stream(), + BoM.combinedCost.chanceRemainders.values().stream() ).sorted((a, b) -> Integer.compare( EmiStackList.getIndex(a.ingredient.getEmiStacks().get(0)), EmiStackList.getIndex(b.ingredient.getEmiStacks().get(0)) @@ -187,10 +199,62 @@ public void recalculateTree() { hasRemainders = !remainders.isEmpty(); } else { nodes = Lists.newArrayList(); + costs.clear(); + BoM.combinedCost.clear(); + BoM.combinedProgress.clear(); + } + + int rootY = -NODE_VERTICAL_SPACING * 3; + int maxVisible = 7; + rootScroll = Math.max(0, Math.min(rootScroll, Math.max(0, roots.size() - maxVisible))); + if (BoM.treeIndex >= 0) { + if (BoM.treeIndex < rootScroll) { + rootScroll = BoM.treeIndex; + } + if (BoM.treeIndex >= rootScroll + maxVisible) { + rootScroll = BoM.treeIndex - maxVisible + 1; + } } + int visible = Math.min(maxVisible, roots.size()); + int rowWidth = visible > 0 ? ((visible - 1) * 20 + 16) : 0; + int startX = visible > 0 ? -((visible - 1) * 20) / 2 : 0; + rootLeft = new Bounds(startX - 20, rootY - 4, 12, 12); + rootRight = new Bounds(startX + rowWidth + 8, rootY - 4, 12, 12); + rootArea = new Bounds(startX - 24, rootY - 12, rowWidth + 48, 24); + for (int i = 0; i < visible; i++) { + int index = i + rootScroll; + int x = startX + i * 20; + rootSlots.add(new Bounds(x - 8, rootY - 8, 16, 16)); + rootIndices.add(index); + rootAmounts.add(getRootAmount(roots.get(index))); + } + ensureRootVisible(); batcher.repopulate(); } + private void ensureRootVisible() { + if (rootArea == null) { + return; + } + float scale = getScale(); + int scaledHeight = (int) (height / scale); + int margin = 16; + int min = -scaledHeight / 2 + margin - (rootArea.y() + rootArea.height()); + int max = scaledHeight / 2 - margin - rootArea.y(); + offY = MathHelper.clamp(offY, min, max); + } + + private long getRootAmount(MaterialTree tree) { + MaterialNode goal = tree.goal; + if (goal == null) { + return 0; + } + if (goal.catalyst) { + return goal.amount; + } + return goal.amount * tree.batches; + } + @Override public void render(DrawContext raw, int mouseX, int mouseY, float delta) { EmiDrawContext context = EmiDrawContext.wrap(raw); @@ -212,6 +276,8 @@ public void render(DrawContext raw, int mouseX, int mouseY, float delta) { int mx = (int) ((mouseX - width / 2) / scale - offX); int my = (int) ((mouseY - height / 2) / scale - offY); + MaterialTree tree = BoM.getTree(); + List roots = BoM.getTrees(); Matrix4fStack view = RenderSystem.getModelViewStack(); view.pushMatrix(); @@ -219,8 +285,52 @@ public void render(DrawContext raw, int mouseX, int mouseY, float delta) { view.scale(scale, scale, 1); view.translate((float)offX, (float)offY, 0); EmiPort.applyModelViewMatrix(); - if (BoM.tree != null) { + if (tree != null) { batcher.begin(0, 0, 0); + for (int i = 0; i < rootSlots.size(); i++) { + Bounds slot = rootSlots.get(i); + int index = rootIndices.get(i); + MaterialTree rootTree = roots.get(index); + Bounds del = new Bounds(slot.x(), slot.y() - 10, slot.width(), 8); + boolean hoveredSlot = slot.contains(mx, my); + boolean hoveredDelete = del.contains(mx, my); + boolean selected = index == BoM.treeIndex; + int border = selected ? 0xff8099ff : 0xffffffff; + if (!selected && hoveredSlot) { + border = 0xffc0d0ff; + } + int lx = slot.x() - 1; + int ly = slot.y() - 1; + int bw = slot.width() + 2; + int bh = slot.height() + 2; + context.fill(lx, ly, bw, 1, border); + context.fill(lx, ly + bh - 1, bw, 1, border); + context.fill(lx, ly, 1, bh, border); + context.fill(lx + bw - 1, ly, 1, bh, border); + context.setColor(1f, 1f, 1f, 1f); + rootTree.goal.ingredient.render(context.raw(), slot.x(), slot.y(), delta, + ~(EmiIngredient.RENDER_AMOUNT | EmiIngredient.RENDER_REMAINDER)); + if (i < rootAmounts.size()) { + EmiRenderHelper.renderAmount(context, slot.x(), slot.y(), + EmiRenderHelper.getAmountText(rootTree.goal.ingredient, rootAmounts.get(i))); + } + if (hoveredSlot || hoveredDelete) { + context.fill(del.x(), del.y(), del.width(), del.height(), 0x88ff0000); + context.drawCenteredText(EmiPort.literal("X"), del.x() + del.width() / 2, del.y() + 1); + } + } + if (roots.size() > rootSlots.size()) { + if (rootLeft.contains(mx, my)) { + context.setColor(0.5f, 0.6f, 1f, 1f); + } + context.drawCenteredText(EmiPort.literal("<"), rootLeft.x() + rootLeft.width() / 2, rootLeft.y()); + context.setColor(1f, 1f, 1f, 1f); + if (rootRight.contains(mx, my)) { + context.setColor(0.5f, 0.6f, 1f, 1f); + } + context.drawCenteredText(EmiPort.literal(">"), rootRight.x() + rootRight.width() / 2, rootRight.y()); + context.setColor(1f, 1f, 1f, 1f); + } int cy = nodeHeight * NODE_VERTICAL_SPACING * 2; context.drawCenteredText(EmiPort.translatable("emi.total_cost"), 0, cy - 16); if (hasRemainders) { @@ -236,7 +346,7 @@ public void render(DrawContext raw, int mouseX, int mouseY, float delta) { if (batches.contains(mx, my)) { color = 0xff8099ff; } - context.drawTextWithShadow(EmiPort.literal("x" + BoM.tree.batches), + context.drawTextWithShadow(EmiPort.literal("x" + tree.batches), batches.x() + 6, batches.y() + batches.height() / 2 - 4, color); if (mode.contains(mx, my)) { @@ -264,14 +374,16 @@ public void render(DrawContext raw, int mouseX, int mouseY, float delta) { Hover hover = getHoveredStack(mouseX, mouseY); if (hover != null) { hover.drawTooltip(this, context, mouseX, mouseY); - } else if (BoM.tree != null && batches.contains(mx, my)) { + } else if (tree != null && batches.contains(mx, my)) { List list = Lists.newArrayList(); - list.addAll(EmiTooltip.splitTranslate("tooltip.emi.bom.batch_size", BoM.tree.batches)); + list.addAll(EmiTooltip.splitTranslate("tooltip.emi.bom.batch_size", tree.batches)); list.add(EmiTooltipComponents.of(EmiPort.translatable("tooltip.emi.bom.batch_size.ideal", EmiBind.LEFT_CLICK.getBindText()))); + list.add(EmiTooltipComponents.of(EmiPort.translatable("tooltip.emi.bom.batch_size.multiply"))); + list.add(EmiTooltipComponents.of(EmiPort.translatable("tooltip.emi.bom.batch_size.all"))); EmiRenderHelper.drawTooltip(this, context, list, mouseX, mouseY); - } else if (BoM.tree != null && mode.contains(mx, my)) { + } else if (tree != null && mode.contains(mx, my)) { String key = BoM.craftingMode ? "tooltip.emi.bom.mode.craft" : "tooltip.emi.bom.mode.view"; - List list = EmiTooltip.splitTranslate(key, BoM.tree.batches); + List list = EmiTooltip.splitTranslate(key, tree.batches); EmiRenderHelper.drawTooltip(this, context, list, mouseX, mouseY); } else if (help.contains(mouseX, mouseY)) { List list = EmiTooltip.splitTranslate("tooltip.emi.bom.help"); @@ -396,7 +508,9 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { } } } else if (EmiInput.isControlDown() && keyCode == GLFW.GLFW_KEY_C) { - BoM.tree = null; + BoM.getTrees().clear(); + BoM.treeIndex = -1; + BoM.craftingMode = false; init(); } return super.keyPressed(keyCode, scanCode, modifiers); @@ -440,10 +554,38 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { float scale = getScale(); int mx = (int) ((mouseX - width / 2) / scale - offX); int my = (int) ((mouseY - height / 2) / scale - offY); + MaterialTree tree = BoM.getTree(); + List roots = BoM.getTrees(); + for (int i = 0; i < rootSlots.size(); i++) { + Bounds slot = rootSlots.get(i); + int index = rootIndices.get(i); + Bounds del = new Bounds(slot.x(), slot.y() - 10, slot.width(), 8); + if (del.contains(mx, my)) { + BoM.removeTree(index); + recalculateTree(); + return true; + } + if (slot.contains(mx, my)) { + BoM.selectTree(index); + recalculateTree(); + return true; + } + } + if (roots.size() > rootSlots.size()) { + if (rootLeft.contains(mx, my)) { + BoM.cycleTree(-1); + recalculateTree(); + return true; + } else if (rootRight.contains(mx, my)) { + BoM.cycleTree(1); + recalculateTree(); + return true; + } + } if (hover != null) { if (button == 1 && hover.node != null && hover.node.recipe != null) { if (EmiInput.isShiftDown()) { - BoM.tree.addResolution(hover.node.ingredient, null); + BoM.addResolution(hover.node.ingredient, null); } else if (!(hover.node.recipe instanceof EmiResolutionRecipe)) { if (hover.node.state == FoldState.EXPANDED) { hover.node.state = FoldState.COLLAPSED; @@ -456,7 +598,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { } if (hover.stack != null) { if (EmiInput.isShiftDown() && button == 0) { - if (getAutoResolutions(hover, BoM.tree::addResolution)) { + if (getAutoResolutions(hover, BoM::addResolution)) { recalculateTree(); } return true; @@ -481,11 +623,11 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0f)); BoM.craftingMode = !BoM.craftingMode; recalculateTree(); - } else if (batches.contains(mx, my) && BoM.tree != null) { - long ideal = BoM.tree.cost.getIdealBatch(BoM.tree.goal, 1, 1); - if (ideal != BoM.tree.batches) { + } else if (batches.contains(mx, my) && tree != null) { + long ideal = tree.cost.getIdealBatch(tree.goal, 1, 1); + if (ideal != tree.batches) { MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0f)); - BoM.tree.batches = ideal; + tree.batches = ideal; recalculateTree(); } } @@ -505,24 +647,43 @@ public boolean mouseScrolled(double mouseX, double mouseY, double horizontal, do float scale = getScale(); int mx = (int) ((mouseX - width / 2) / scale - offX); int my = (int) ((mouseY - height / 2) / scale - offY); - if (BoM.tree != null && batches.contains(mx, my)) { - long adjustment = (long) amount; - if (EmiInput.isShiftDown()) { - adjustment *= 16; - } else if (EmiInput.isControlDown()) { - if (amount > 0) { - adjustment = BoM.tree.batches; + MaterialTree tree = BoM.getTree(); + List roots = BoM.getTrees(); + if (!roots.isEmpty() && rootArea.contains(mx, my) && amount != 0) { + BoM.cycleTree(-(int) amount); + recalculateTree(); + return true; + } + if (tree != null && batches.contains(mx, my)) { + long scroll = (long) amount; + List targets = EmiInput.isAltDown() ? roots : List.of(tree); + boolean changed = false; + for (MaterialTree target : targets) { + long adjustment = scroll; + if (EmiInput.isShiftDown()) { + adjustment *= 16; + } else if (EmiInput.isControlDown()) { + if (scroll > 0) { + adjustment = target.batches; + } else { + adjustment = -target.batches / 2; + } + } + long newBatches; + if (target.batches == 1 && adjustment > 1) { + newBatches = adjustment; } else { - adjustment = -BoM.tree.batches / 2; + newBatches = target.batches + adjustment; + } + newBatches = Math.max(1, newBatches); + if (newBatches != target.batches) { + target.batches = newBatches; + changed = true; } } - if (BoM.tree.batches == 1 && adjustment > 1) { - BoM.tree.batches = adjustment; - } else { - BoM.tree.batches += adjustment; + if (changed) { + recalculateTree(); } - BoM.tree.batches = Math.max(1, BoM.tree.batches); - recalculateTree(); return true; } zoom += (int) amount; diff --git a/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java b/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java index 2a942e9a..5be3ab5f 100644 --- a/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java +++ b/xplat/src/main/java/dev/emi/emi/screen/EmiScreenManager.java @@ -863,7 +863,7 @@ private static void renderSlotOverlays(EmiDrawContext context, int mouseX, int m } Set ignoredSlots = Sets.newHashSet(); Set synfavs = Sets.newHashSet(); - if (BoM.craftingMode && BoM.tree != null) { + if (BoM.craftingMode && BoM.getTree() != null) { List syntheticFavorites = EmiFavorites.syntheticFavorites; for (EmiFavorite.Synthetic fav : syntheticFavorites) { synfavs.addAll(fav.getEmiStacks()); @@ -892,13 +892,13 @@ private static void renderSlotOverlays(EmiDrawContext context, int mouseX, int m context.push(); context.matrices().translate(0, 0, 300); if (query != null) { - if (!query.test(stack)) { - context.fill(slot.x - 1, slot.y - 1, 18, 18, 0x77000000); - } - } else if (BoM.craftingMode && BoM.tree != null) { - if (!(slot.inventory instanceof PlayerInventory) && !ignoredSlots.contains(slot) && synfavs.contains(stack)) { - context.fill(slot.x - 1, slot.y - 1, 18, 18, 0x7700BBFF); - } + if (!query.test(stack)) { + context.fill(slot.x - 1, slot.y - 1, 18, 18, 0x77000000); + } + } else if (BoM.craftingMode && BoM.getTree() != null) { + if (!(slot.inventory instanceof PlayerInventory) && !ignoredSlots.contains(slot) && synfavs.contains(stack)) { + context.fill(slot.x - 1, slot.y - 1, 18, 18, 0x7700BBFF); + } } context.pop(); } diff --git a/xplat/src/main/java/dev/emi/emi/screen/widget/ResolutionButtonWidget.java b/xplat/src/main/java/dev/emi/emi/screen/widget/ResolutionButtonWidget.java index e4d98a7a..2d036888 100644 --- a/xplat/src/main/java/dev/emi/emi/screen/widget/ResolutionButtonWidget.java +++ b/xplat/src/main/java/dev/emi/emi/screen/widget/ResolutionButtonWidget.java @@ -23,8 +23,10 @@ public class ResolutionButtonWidget extends ButtonWidget { public ResolutionButtonWidget(int x, int y, int width, int height, EmiIngredient stack, Supplier hoveredWidget) { super(x, y, width, height, EmiPort.literal(""), button -> { - BoM.tree.addResolution(stack, null); - EmiHistory.pop(); + if (BoM.getTree() != null) { + BoM.addResolution(stack, null); + EmiHistory.pop(); + } }, s -> s.get()); this.stack = stack; this.hoveredWidget = hoveredWidget; diff --git a/xplat/src/main/java/dev/emi/emi/widget/RecipeTreeButtonWidget.java b/xplat/src/main/java/dev/emi/emi/widget/RecipeTreeButtonWidget.java index a9703e92..257656c0 100644 --- a/xplat/src/main/java/dev/emi/emi/widget/RecipeTreeButtonWidget.java +++ b/xplat/src/main/java/dev/emi/emi/widget/RecipeTreeButtonWidget.java @@ -3,9 +3,13 @@ import java.util.List; import dev.emi.emi.EmiPort; +import dev.emi.emi.EmiRenderHelper; import dev.emi.emi.api.EmiApi; import dev.emi.emi.api.recipe.EmiRecipe; import dev.emi.emi.bom.BoM; +import dev.emi.emi.input.EmiInput; +import dev.emi.emi.runtime.EmiDrawContext; +import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.tooltip.TooltipComponent; public class RecipeTreeButtonWidget extends RecipeButtonWidget { @@ -17,7 +21,7 @@ public RecipeTreeButtonWidget(int x, int y, EmiRecipe recipe) { @Override public int getTextureOffset(int mouseX, int mouseY) { int v = super.getTextureOffset(mouseX, mouseY); - if (BoM.tree != null && BoM.tree.goal.recipe == recipe) { + if (BoM.getTree() != null && BoM.getTree().goal.recipe == recipe) { v += 36; } return v; @@ -30,9 +34,22 @@ public List getTooltip(int mouseX, int mouseY) { @Override public boolean mouseClicked(int mouseX, int mouseY, int button) { - BoM.setGoal(recipe); + if (EmiInput.isShiftDown()) { + BoM.addGoal(recipe); + } else { + BoM.setGoal(recipe); + } this.playButtonSound(); EmiApi.viewRecipeTree(); return true; } + + @Override + public void render(DrawContext raw, int mouseX, int mouseY, float delta) { + EmiDrawContext context = EmiDrawContext.wrap(raw); + context.resetColor(); + int u = EmiInput.isShiftDown() ? 24 : this.u; + int v = this.v + getTextureOffset(mouseX, mouseY); + context.drawTexture(EmiRenderHelper.BUTTONS, x, y, 12, 12, u, v, 12, 12, 256, 256); + } } diff --git a/xplat/src/main/resources/assets/emi/lang/en_us.json b/xplat/src/main/resources/assets/emi/lang/en_us.json index a07d2dcd..4efe1aee 100644 --- a/xplat/src/main/resources/assets/emi/lang/en_us.json +++ b/xplat/src/main/resources/assets/emi/lang/en_us.json @@ -260,8 +260,10 @@ "tooltip.emi.synfav.craft_some": "Press %s to craft as many as possible", "tooltip.emi.synfav.craft_all": "Press %s to craft all §9%s§r batches", - "tooltip.emi.bom.batch_size": "Batch size: %s\nScroll to adjust\nHold §6[shift]§r to adjust by 16", + "tooltip.emi.bom.batch_size": "Batch size: %s\nScroll to adjust\nHold §6[Shift]§r to adjust by 16", "tooltip.emi.bom.batch_size.ideal": "Press %s to get minimal leftovers", + "tooltip.emi.bom.batch_size.multiply": "Hold §6[Ctrl]§r to add multiplier", + "tooltip.emi.bom.batch_size.all": "Hold §6[Alt]§r to apply for all root items", "tooltip.emi.bom.mode.view": "§6Viewing§r recipe tree", "tooltip.emi.bom.mode.craft": "§6Crafting§r recipe tree\nProgress will be shown in recipe tree\n§bSynthetic favorites§r added to sidebar", "tooltip.emi.bom.help": "The recipe tree shows the process and base cost of a recipe\nThe display can be panned and zoomed\n\nLeft click a node to choose a recipe to assign to it\nHold §6[shift]§r to automatically pick based on your inventory\n\nRight click a recipe node to fold it temporarily\nHold §6[shift]§r to clear the recipe\n\nThe initial tree state is controlled by default recipes\nA button next to recipes can set default recipe preferences\n\nThe toggle by total cost can be used to help craft a recipe tree\nWhile crafting, materials that need to be collected will be displayed\n§bSynthetic favorites§r will be added to the favorites sidebar that\nwill show incomplete steps and can be used to craft", diff --git a/xplat/src/main/resources/assets/emi/lang/es_es.json b/xplat/src/main/resources/assets/emi/lang/es_es.json index b5648543..fafe839f 100644 --- a/xplat/src/main/resources/assets/emi/lang/es_es.json +++ b/xplat/src/main/resources/assets/emi/lang/es_es.json @@ -337,6 +337,8 @@ "tooltip.emi.bom.batch_size": "Tamaño del lote: %s\nDesplaza la rueda para ajustar\nMantén §6[shift]§r para ajustar en 16", "tooltip.emi.bom.batch_size.ideal": "Pulsa %s para obtener las mínimas sobras posibles", + "tooltip.emi.bom.batch_size.multiply": "Mantén §6[Ctrl]§r para añadir un multiplicador", + "tooltip.emi.bom.batch_size.all": "Mantén §6[Alt]§r para aplicar a todos los objetos raíz", "tooltip.emi.bom.mode.view": "§6Mostrando§r árbol de recetas", "tooltip.emi.bom.mode.craft": "§6Fabricando§r árbol de recetas\nEl progreso se mostrará en el árbol de recetas\n§bFavoritos sintéticos§r añadidos a la barra lateral", "tooltip.emi.bom.help": "El árbol de recetas muestra la progresión y el coste total de la receta.\nLa vista se puede mover y ampliar.\n\nClick izquierdo en un nodo para seleccionar una receta y asignarla.\nMantén §6[shift]§r para seleccionar automáticamente en función de tu inventario.\n\nClick derecho en un nodo de la receta para plegarlo temporalmente\nMantén §6[shift]§r para vaciar la receta.\n\nEl estado inicial del árbol es controlado por las recetas por defecto.\nUn botón junto a las recetas puede establecer las preferencias por defecto.\n\nEl ajuste por coste total puede usarse para ayudar a crear un árbol.\nMientras esté fabricando, se mostrarán los materiales que hace falta recoger.\nSe añadirán §bFavoritos sintéticos§r a la barra lateral que\nmostrarán los pasos incompletos y podrán usarse para fabricar.", diff --git a/xplat/src/main/resources/assets/emi/lang/fi_fi.json b/xplat/src/main/resources/assets/emi/lang/fi_fi.json index 00b80c46..c443ee82 100644 --- a/xplat/src/main/resources/assets/emi/lang/fi_fi.json +++ b/xplat/src/main/resources/assets/emi/lang/fi_fi.json @@ -226,8 +226,10 @@ "tooltip.emi.synfav.craft_some": "Valmista mahdollisimman monta erää painamalla %s", "tooltip.emi.synfav.craft_all": "Valmista kaikki §9%2$s§r erää painamalla %1$s", - "tooltip.emi.bom.batch_size": "Erän koko: %s\nSäädä vierittämällä\nSäädä 16 kerralla pitämällä pohjassa §6[shift]§r-näppäintä", + "tooltip.emi.bom.batch_size": "Erän koko: %s\nSäädä vierittämällä\nSäädä 16 kerralla pitämällä pohjassa §6[Shift]§r-näppäintä", "tooltip.emi.bom.batch_size.ideal": "Hanki vähäisimmät ylijäämät painamalla %s", + "tooltip.emi.bom.batch_size.multiply": "Pidä §6[Ctrl]§r lisätäksesi kertoimen", + "tooltip.emi.bom.batch_size.all": "Pidä §6[Alt]§r käyttääksesi kaikkiin juurituotteisiin", "tooltip.emi.bom.mode.view": "§6Katsotaan§r reseptipuuta", "tooltip.emi.bom.mode.craft": "§6Valmistetaan§r reseptipuuta\nEdistyminen näkyy reseptipuussa\n§bSynteettiset suosikit§r lisätty sivupalkkiin", "tooltip.emi.bom.help": "Reseptipuu näyttää reseptin prosessin ja pohjakulut\nNäkymää voi siirrellä ja suurentaa\n\nAseta solmukohdan resepti hiiren vasemmalla painikkeella\nValitse automaattisesti tavaraluettelon perusteella §6[shift]§r-näppäimellä\n\nPienennä reseptisolmu väliaikaisesti hiiren oikealla painikkeella\nTyhjennä resepti §6[shift]§r-näppäimellä\n\nPuun lähtötilaa ohjataan oletusresepteillä\nOletusreseptit voi asettaa reseptien viereisellä painikkeella\n\nReseptipuun valmistamisessa voi auttaa kokonaiskulujen kytkeminen\nValmistuksen aikana näytetään materiaalit, jotka täytyy kerätä\nSuosikkipalkkiin lisätään §bsynteettisiä suosikkeja§r, jotka näyttävät\nkeskeneräiset vaiheet ja joita voi käyttää valmistamiseen", diff --git a/xplat/src/main/resources/assets/emi/lang/fr_fr.json b/xplat/src/main/resources/assets/emi/lang/fr_fr.json index 03c31c74..72781bc5 100644 --- a/xplat/src/main/resources/assets/emi/lang/fr_fr.json +++ b/xplat/src/main/resources/assets/emi/lang/fr_fr.json @@ -316,7 +316,9 @@ "tooltip.emi.synfav.partially_craftable": "§dImpossible§r de tout faire\n§6Cliquez§r pour en fabriquer", "tooltip.emi.synfav.fully_craftable": "§aPossible§r de tout faire §a(%1$s)§r\n§6Cliquez§r pour en fabriquer", - "tooltip.emi.bom.batch_size": "Quantité: %s\nScrollez pour changer\nMaintenez §6[shift]§r pour changer par 16", + "tooltip.emi.bom.batch_size": "Quantité: %s\nScrollez pour changer\nMaintenez §6[Shift]§r pour changer par 16", + "tooltip.emi.bom.batch_size.multiply": "Maintenez §6[Ctrl]§r pour ajouter un multiplicateur", + "tooltip.emi.bom.batch_size.all": "Maintenez §6[Alt]§r pour appliquer à tous les objets racine", "tooltip.emi.bom.mode.view": "§6Assistant de fabrication§r\n(Désactivé)\n\nClic gauche pour l'activer", "tooltip.emi.bom.mode.craft": "§6Assistant de fabrication§r\nVotre progression sera\nvisible dans cette liste, des \n§bfavoris temporaires§r seront\najoutés pour vous guider", "tooltip.emi.bom.help": "Le graphe liste les ingrédients et les recettes\nVous pouvez déplacer/zoomer ce graphe\n\nClic gauche sur un noeud permet de modifier une recette\nMaintenir §6[Maj]§r permet de choisir automatiquement une recette\n\nClic droit un un noeud permet de le masquer temporairement\nMaintenir §6[Maj]§r permet de supprimer le noeud\n\nLe graphe est généré selon vos recettes par défaut\n\nLe bouton \"§acoeur§r\" permet de mettre des recettes en favoris\nSi vous utilisez l'§6assistant de fabrication§r alors\n\ndes §bfavoris temporaires§r seront placés dans l'onglet\ndes favoris afin de vous guider dans votre fabrication", diff --git a/xplat/src/main/resources/assets/emi/lang/ja_jp.json b/xplat/src/main/resources/assets/emi/lang/ja_jp.json index 8362a78e..8849196e 100644 --- a/xplat/src/main/resources/assets/emi/lang/ja_jp.json +++ b/xplat/src/main/resources/assets/emi/lang/ja_jp.json @@ -336,6 +336,8 @@ "tooltip.emi.bom.batch_size": "一度にまとめてクラフトする数: %s\nスクロールして調整します\n§6[Shift]§r を押したままにして16ずつ調整します", "tooltip.emi.bom.batch_size.ideal": "%s を押して残り物を最小限にする", + "tooltip.emi.bom.batch_size.multiply": "§6[Ctrl]§r を押しながらで倍率を追加", + "tooltip.emi.bom.batch_size.all": "§6[Alt]§r を押しながらで全てのルート項目に適用", "tooltip.emi.bom.mode.view": "§6閲覧中§r レシピツリー", "tooltip.emi.bom.mode.craft": "§6クラフト§rレシピツリー\n進捗をレシピツリー\n§b合成お気に入り§rをサイドバーに追加しました", "tooltip.emi.bom.help": "レシピ ツリーには, レシピのプロセスとコストが表示されます。\n表示は上下左右へのドラッグや拡大縮小が可能です。\n\nノードを左クリックして, それに割り当てるレシピを選択します。\n§6[Shift]§r を押し続けると, 在庫に基づいて自動的に選択されます。\n\nレシピノードを右クリックして一時的に折りたたむことができ,\n§6[Shift]§r を押し続けるとレシピをクリアできます。\n\nツリーの初期状態はデフォルトのレシピに従います。\nレシピの横にあるボタンでデフォルトのレシピ設定を設定できます。\n\n総コストによる切り替えは, レシピ ツリーの作成に役立ちます。\n製作中は収集が必要な素材が表示されます。\n§b合成お気に入り§r は, お気に入りサイドバーに追加されます。\n不完全な手順が表示され, クラフトに使用できます。", diff --git a/xplat/src/main/resources/assets/emi/lang/pt_br.json b/xplat/src/main/resources/assets/emi/lang/pt_br.json index 9f2f2280..4db7f773 100644 --- a/xplat/src/main/resources/assets/emi/lang/pt_br.json +++ b/xplat/src/main/resources/assets/emi/lang/pt_br.json @@ -339,8 +339,10 @@ "tooltip.emi.synfav.craft_some": "Pressione %s para fabricar o máximo possível", "tooltip.emi.synfav.craft_all": "Pressione %s para fabricar todos os §9%s§r lotes", - "tooltip.emi.bom.batch_size": "Tamanho do lote: %s\nRole para ajustar\nSegure §6[shift]§r para ajustar em 16", + "tooltip.emi.bom.batch_size": "Tamanho do lote: %s\nRole para ajustar\nSegure §6[Shift]§r para ajustar em 16", "tooltip.emi.bom.batch_size.ideal": "Pressione %s para minimizar sobras", + "tooltip.emi.bom.batch_size.multiply": "Segure §6[Ctrl]§r para adicionar multiplicador", + "tooltip.emi.bom.batch_size.all": "Segure §6[Alt]§r para aplicar a todos os itens raiz", "tooltip.emi.bom.mode.view": "§6Visualizando§r árvore de receitas", "tooltip.emi.bom.mode.craft": "§6Criando§r árvore de receitas\nO progresso será mostrado na árvore de receitas\n§bFavoritos sintéticos§r adicionados à barra lateral", "tooltip.emi.bom.help": "A Árvore de Receitas mostra o processo e o custo base de criação de uma receita.\nA exibição da Árvore pode ser movida e ampliada.\n\nClique com o botão esquerdo em um nó para atribuir uma receita a ele.\nSegure §6[shift]§r para escolher automaticamente com base no seu inventário.\n\nClique com o botão direito em um nó de receita para o minimizar temporariamente.\nSegure §6[shift]§r para ocultar a receita.\n\nO estado inicial da árvore é controlado por receitas padrão.\nUse o botão ao lado das receitas para definir preferências de receita padrão.\n\nA mudança por custo total pode ser utilizada para ajudar a criar uma árvore de receitas.\nDurante a fabricação, os materiais que precisam ser coletados serão exibidos.\n§bFavoritos sintéticos§r serão adicionados à barra lateral de favoritos,\nmostrando etapas incompletas e podendo ser usados para fabricar.", diff --git a/xplat/src/main/resources/assets/emi/lang/ru_ru.json b/xplat/src/main/resources/assets/emi/lang/ru_ru.json index f7ecb2d2..55c1f017 100644 --- a/xplat/src/main/resources/assets/emi/lang/ru_ru.json +++ b/xplat/src/main/resources/assets/emi/lang/ru_ru.json @@ -262,6 +262,8 @@ "tooltip.emi.bom.batch_size": "Размер пакета: %s\nПрокрутите, чтобы отрегулировать\nЗажмите §6[shift]§r, чтобы изменять по 16", "tooltip.emi.bom.batch_size.ideal": "Нажмите %s, чтобы получить меньше всего остатков", + "tooltip.emi.bom.batch_size.multiply": "Зажмите §6[Ctrl]§r, чтобы добавить множитель", + "tooltip.emi.bom.batch_size.all": "Зажмите §6[Alt]§r, чтобы применить ко всем корневым предметам", "tooltip.emi.bom.mode.view": "§6Просматриваете§r дерево рецептов", "tooltip.emi.bom.mode.craft": "Дерево рецептов §6создания§r\nПрогресс будет показан в дереве рецептов\n§bSynthetic favorites§r added to sidebar", "tooltip.emi.bom.help": "Дерево рецептов показывает прогресс и базовую стоимость рецепта\nЭкран можно двигать и приближать\n\nНажмите ЛКМ по узлу, чтобы выбрать рецепт, который будет использоваться\nЗажмите §6[shift]§r, чтобы автоматически выбрать, основываясь на вашем инвентаре\n\nНажмите ПКМ по узлу рецептов, чтобы временно его свернуть\nЗажмите §6[shift]§r, чтобы убрать из рецепта\n\nНачальное состояние дерева контролируется рецептами по умолчанию\nКнопка рядом с рецептами может установить предпочтительный стандартный рецепт\n\nПереключатель рядом с общей стоимостью может помочь в создании дерева рецептов\nПри создании дерева материалы, которые ещё предстоит собрать, будут отображены\n\n§bИскусственно избранные предметы§r будут добавлены в боковую панель с избранными предметами.\nОни будут показывать ещё незавершённые шаги к созданию нужного предмета в дереве", diff --git a/xplat/src/main/resources/assets/emi/lang/tr_tr.json b/xplat/src/main/resources/assets/emi/lang/tr_tr.json index b878d288..0cc7b2c8 100644 --- a/xplat/src/main/resources/assets/emi/lang/tr_tr.json +++ b/xplat/src/main/resources/assets/emi/lang/tr_tr.json @@ -400,5 +400,10 @@ "tag.fabric.shovels": "Kürekler", "tag.fabric.hoes": "Çapalar", "tag.fabric.swords": "Kılıçlar", - "tag.fabric.shears": "Makaslar" + "tag.fabric.shears": "Makaslar", + + "tooltip.emi.bom.batch_size": "Üretim miktarı: %s\nAyarlamak için kaydırın\n16'şar ayarlamak için §6[Shift]§r tuşunu basılı tutun", + "tooltip.emi.bom.batch_size.ideal": "Artıkları en aza indirmek için %s tuşuna basın", + "tooltip.emi.bom.batch_size.multiply": "Çarpan eklemek için §6[Ctrl]§r tuşunu basılı tutun", + "tooltip.emi.bom.batch_size.all": "Tüm kök öğelere uygulamak için §6[Alt]§r tuşunu basılı tutun" } diff --git a/xplat/src/main/resources/assets/emi/lang/zh_cn.json b/xplat/src/main/resources/assets/emi/lang/zh_cn.json index b07b4ed9..abeef203 100644 --- a/xplat/src/main/resources/assets/emi/lang/zh_cn.json +++ b/xplat/src/main/resources/assets/emi/lang/zh_cn.json @@ -353,6 +353,8 @@ "tooltip.emi.bom.batch_size": "产物份数: %s\n滚动滚轮调整份数\n按住 §6[Shift]§r 滚动会增减16", "tooltip.emi.bom.batch_size.ideal": "按 %s 将副产物数量降到最低", + "tooltip.emi.bom.batch_size.multiply": "按住 §6[Ctrl]§r 添加倍数", + "tooltip.emi.bom.batch_size.all": "按住 §6[Alt]§r 将调整应用到所有根物品", "tooltip.emi.bom.mode.view": "正在§6查看§r配方树", "tooltip.emi.bom.mode.craft": "正在§6制作§r配方树\n配方树中会显示制作进度\n§b收藏夹§r中可查看并快捷合成", "tooltip.emi.bom.help": "欢迎使用配方树\n这里可以分析物品的制作过程和原材料消耗\n按 §6[Ctrl + R]§r 可展示随机配方树\n本界面可以拖动或缩放\n\n配方树会自动使用默认配方分析制作过程\n配方详情页中可以更改默认配方\n\n左键物品/节点可手动选择配方\n按住 §6[Shift]§r 左键自动选择配方(根据物品栏)\n\n右键节点可以暂时折叠配方\n按住 §6[Shift]§r 右键节点可以清除配方\n\n选中最终产物旁的数可以改变产物份数\n\n总耗材旁的按钮可切换至§6制作§r模式\n在§6制作§r模式下,§b收藏夹§r中会显示需要收集的材料\n和中间产物,这些中间产物可以点击以快速合成", diff --git a/xplat/src/main/resources/assets/emi/textures/gui/buttons.png b/xplat/src/main/resources/assets/emi/textures/gui/buttons.png index 2bf4fa567fc5db6b6fe457790877fbf03d178f48..633951f1ae81c5fc4a94b6ae32ec916455789b4e 100644 GIT binary patch literal 8798 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 O?%mPTD$=ld@xK6q$1#im literal 7074 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 B4%Pqw From f9dc1170593a7483c526ee3d2db816ecbb4d3df5 Mon Sep 17 00:00:00 2001 From: "a.o.glazkov" Date: Fri, 26 Dec 2025 21:54:54 +0300 Subject: [PATCH 2/7] Add initial view adjustment logic and improve node handling Introduce `adjustInitialView` method to dynamically set zoom and vertical offset based on content. --- .../java/dev/emi/emi/screen/BoMScreen.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java b/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java index 53148bca..88db3cf5 100644 --- a/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java +++ b/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java @@ -109,6 +109,7 @@ public void recalculateTree() { rootSlots.clear(); rootIndices.clear(); rootAmounts.clear(); + int cy = 0; if (tree != null) { TreeVolume volume = addNewNodes(tree.goal, tree.batches, 1, 0, ChanceState.DEFAULT); nodes = volume.nodes; @@ -140,7 +141,7 @@ public void recalculateTree() { EmiStackList.getIndex(a.ingredient.getEmiStacks().get(0)), EmiStackList.getIndex(b.ingredient.getEmiStacks().get(0)) )).toList(); - int cy = nodeHeight * NODE_VERTICAL_SPACING * 2; + cy = nodeHeight * NODE_VERTICAL_SPACING * 2; int costX = 0; for (FlatMaterialCost node : treeCosts) { Cost cost = new Cost(node, costX, cy, false); @@ -202,6 +203,7 @@ public void recalculateTree() { costs.clear(); BoM.combinedCost.clear(); BoM.combinedProgress.clear(); + hasRemainders = false; } int rootY = -NODE_VERTICAL_SPACING * 3; @@ -228,10 +230,35 @@ public void recalculateTree() { rootIndices.add(index); rootAmounts.add(getRootAmount(roots.get(index))); } + adjustInitialView(cy); ensureRootVisible(); batcher.repopulate(); } + private void adjustInitialView(int totalCostY) { + int top = rootArea.y() - 12; + int bottom = totalCostY + 16 + (hasRemainders ? 40 : 0); + if (rootArea.height() <= 0) { + return; + } + if (zoom == 0) { + int margin = 16; + int requiredHeight = bottom - top + margin * 2; + int guiScale = (int) this.client.getWindow().getScaleFactor(); + float desiredScale = Math.min(1f, (float) this.height / requiredHeight); + int targetDesired = Math.max(1, Math.round(guiScale * desiredScale)); + int targetZoom = Math.max(-6, Math.min(4, targetDesired - guiScale)); + zoom = targetZoom; + } + float scale = getScale(); + int visibleHeight = (int) (height / scale); + int margin = 16; + int middle = (top + bottom) / 2; + int min = -visibleHeight / 2 + margin - bottom; + int max = visibleHeight / 2 - margin - top; + offY = MathHelper.clamp(-middle, min, max); + } + private void ensureRootVisible() { if (rootArea == null) { return; From f7bcf16269897c7d380e59c0add8c973d4eabc8e Mon Sep 17 00:00:00 2001 From: "a.o.glazkov" Date: Fri, 26 Dec 2025 22:09:47 +0300 Subject: [PATCH 3/7] Fix repeated initial view adjustment by adding `initialViewSet` guard --- xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java b/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java index 88db3cf5..b768deb5 100644 --- a/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java +++ b/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java @@ -87,6 +87,7 @@ public class BoMScreen extends Screen { private List rootIndices = Lists.newArrayList(); private List rootAmounts = Lists.newArrayList(); private int rootScroll = 0; + private boolean initialViewSet = false; public BoMScreen(HandledScreen old) { super(EmiPort.translatable("screen.emi.recipe_tree")); @@ -230,7 +231,10 @@ public void recalculateTree() { rootIndices.add(index); rootAmounts.add(getRootAmount(roots.get(index))); } - adjustInitialView(cy); + if (!initialViewSet && tree != null) { + adjustInitialView(cy); + initialViewSet = true; + } ensureRootVisible(); batcher.repopulate(); } From dd334a5a0440d2928e81f1bb882a6f8fe34a3714 Mon Sep 17 00:00:00 2001 From: "a.o.glazkov" Date: Mon, 29 Dec 2025 15:36:19 +0300 Subject: [PATCH 4/7] Refactor material cost calculation to use shared inventory for accurate progression tracking --- xplat/src/main/java/dev/emi/emi/bom/BoM.java | 29 ++++++++++++++++++- .../dev/emi/emi/runtime/EmiFavorites.java | 29 ++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/xplat/src/main/java/dev/emi/emi/bom/BoM.java b/xplat/src/main/java/dev/emi/emi/bom/BoM.java index 0fca2cdb..6caa5249 100644 --- a/xplat/src/main/java/dev/emi/emi/bom/BoM.java +++ b/xplat/src/main/java/dev/emi/emi/bom/BoM.java @@ -293,9 +293,26 @@ public static void removeRecipe(EmiIngredient stack, EmiRecipe recipe) { public static void calculateCombinedCosts(EmiPlayerInventory inventory) { combinedProgress.clear(); + Map sharedInventory = Maps.newHashMap(); + for (EmiStack stack : inventory.inventory.values()) { + sharedInventory.put(stack, stack.copy()); + } for (MaterialTree tree : trees) { - tree.calculateProgress(inventory); + EmiPlayerInventory shared = createInventoryFromStacks(sharedInventory); + tree.calculateProgress(shared); combinedProgress.merge(tree.cost); + + Map updatedInventory = Maps.newHashMap(); + for (Map.Entry entry : sharedInventory.entrySet()) { + EmiStack key = entry.getKey(); + long before = entry.getValue().getAmount(); + FlatMaterialCost remainder = tree.cost.remainders.get(key); + long after = remainder == null ? 0 : Math.min(before, remainder.amount); + if (after > 0) { + updatedInventory.put(key, entry.getValue().copy().setAmount(after)); + } + } + sharedInventory = updatedInventory; } combinedCost.clear(); for (MaterialTree tree : trees) { @@ -304,6 +321,16 @@ public static void calculateCombinedCosts(EmiPlayerInventory inventory) { } } + private static EmiPlayerInventory createInventoryFromStacks(Map stacks) { + EmiPlayerInventory shared = new EmiPlayerInventory(List.of()); + shared.inventory.clear(); + for (EmiStack stack : stacks.values()) { + EmiStack copy = stack.copy(); + shared.inventory.put(copy, copy); + } + return shared; + } + private static void recalculate() { for (MaterialTree tree : trees) { tree.recalculate(); diff --git a/xplat/src/main/java/dev/emi/emi/runtime/EmiFavorites.java b/xplat/src/main/java/dev/emi/emi/runtime/EmiFavorites.java index a98c61b0..3f338a89 100644 --- a/xplat/src/main/java/dev/emi/emi/runtime/EmiFavorites.java +++ b/xplat/src/main/java/dev/emi/emi/runtime/EmiFavorites.java @@ -213,10 +213,27 @@ public static void updateSynthetic(EmiPlayerInventory inv) { TreeCost remainingCost = new TreeCost(); Object2LongMap batches = new Object2LongLinkedOpenHashMap<>(); Object2LongMap amounts = new Object2LongLinkedOpenHashMap<>(); + Map sharedInventory = Maps.newHashMap(); + for (EmiStack stack : inv.inventory.values()) { + sharedInventory.put(stack, stack.copy()); + } for (MaterialTree tree : trees) { - tree.calculateProgress(inv); + EmiPlayerInventory shared = createInventoryFromStacks(sharedInventory); + tree.calculateProgress(shared); countRecipes(batches, amounts, tree.goal); remainingCost.merge(tree.cost); + + Map updatedInventory = Maps.newHashMap(); + for (Map.Entry entry : sharedInventory.entrySet()) { + EmiStack key = entry.getKey(); + long before = entry.getValue().getAmount(); + FlatMaterialCost remainder = tree.cost.remainders.get(key); + long after = remainder == null ? 0 : Math.min(before, remainder.amount); + if (after > 0) { + updatedInventory.put(key, entry.getValue().copy().setAmount(after)); + } + } + sharedInventory = updatedInventory; } BoM.calculateCombinedCosts(inv); boolean hasSomething = false; @@ -261,6 +278,16 @@ public static void updateSynthetic(EmiPlayerInventory inv) { } } + private static EmiPlayerInventory createInventoryFromStacks(Map stacks) { + EmiPlayerInventory shared = new EmiPlayerInventory(List.of()); + shared.inventory.clear(); + for (EmiStack stack : stacks.values()) { + EmiStack copy = stack.copy(); + shared.inventory.put(copy, copy); + } + return shared; + } + public static void countRecipes(Object2LongMap batches, Object2LongMap amounts, MaterialNode node) { if (node.recipe instanceof EmiResolutionRecipe recipe) { countRecipes(batches, amounts, node.children.get(0)); From 4b21c2d4e02bf427c7a849c0594720445ae8e866 Mon Sep 17 00:00:00 2001 From: "a.o.glazkov" Date: Mon, 29 Dec 2025 15:44:20 +0300 Subject: [PATCH 5/7] Fix incorrect positioning of `rootRightArrow` Bound by adjusting offset calculation --- xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java b/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java index b768deb5..6f92a7b6 100644 --- a/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java +++ b/xplat/src/main/java/dev/emi/emi/screen/BoMScreen.java @@ -222,7 +222,7 @@ public void recalculateTree() { int rowWidth = visible > 0 ? ((visible - 1) * 20 + 16) : 0; int startX = visible > 0 ? -((visible - 1) * 20) / 2 : 0; rootLeft = new Bounds(startX - 20, rootY - 4, 12, 12); - rootRight = new Bounds(startX + rowWidth + 8, rootY - 4, 12, 12); + rootRight = new Bounds(startX + rowWidth - 8, rootY - 4, 12, 12); rootArea = new Bounds(startX - 24, rootY - 12, rowWidth + 48, 24); for (int i = 0; i < visible; i++) { int index = i + rootScroll; From c04789743f510916df2120b683892d2fb7a56f4d Mon Sep 17 00:00:00 2001 From: "a.o.glazkov" Date: Fri, 13 Feb 2026 10:36:15 +0300 Subject: [PATCH 6/7] Review fix fr translation for batch size options --- xplat/src/main/resources/assets/emi/lang/fr_fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xplat/src/main/resources/assets/emi/lang/fr_fr.json b/xplat/src/main/resources/assets/emi/lang/fr_fr.json index 72781bc5..5a76acac 100644 --- a/xplat/src/main/resources/assets/emi/lang/fr_fr.json +++ b/xplat/src/main/resources/assets/emi/lang/fr_fr.json @@ -318,7 +318,7 @@ "tooltip.emi.bom.batch_size": "Quantité: %s\nScrollez pour changer\nMaintenez §6[Shift]§r pour changer par 16", "tooltip.emi.bom.batch_size.multiply": "Maintenez §6[Ctrl]§r pour ajouter un multiplicateur", - "tooltip.emi.bom.batch_size.all": "Maintenez §6[Alt]§r pour appliquer à tous les objets racine", + "tooltip.emi.bom.batch_size.all": "Maintenez §6[Alt]§r pour appliquer à tous les objets principaux", "tooltip.emi.bom.mode.view": "§6Assistant de fabrication§r\n(Désactivé)\n\nClic gauche pour l'activer", "tooltip.emi.bom.mode.craft": "§6Assistant de fabrication§r\nVotre progression sera\nvisible dans cette liste, des \n§bfavoris temporaires§r seront\najoutés pour vous guider", "tooltip.emi.bom.help": "Le graphe liste les ingrédients et les recettes\nVous pouvez déplacer/zoomer ce graphe\n\nClic gauche sur un noeud permet de modifier une recette\nMaintenir §6[Maj]§r permet de choisir automatiquement une recette\n\nClic droit un un noeud permet de le masquer temporairement\nMaintenir §6[Maj]§r permet de supprimer le noeud\n\nLe graphe est généré selon vos recettes par défaut\n\nLe bouton \"§acoeur§r\" permet de mettre des recettes en favoris\nSi vous utilisez l'§6assistant de fabrication§r alors\n\ndes §bfavoris temporaires§r seront placés dans l'onglet\ndes favoris afin de vous guider dans votre fabrication", From cf0ed28a43846398f7d79d0471be23d24c1b4645 Mon Sep 17 00:00:00 2001 From: "a.o.glazkov" Date: Mon, 23 Feb 2026 03:11:53 +0300 Subject: [PATCH 7/7] Review fix: Refactor `TreeCost` and `BoM` to optimize material cost calculation and remainders handling. --- xplat/src/main/java/dev/emi/emi/bom/BoM.java | 53 ++++++++++++++++--- .../main/java/dev/emi/emi/bom/TreeCost.java | 6 +++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/xplat/src/main/java/dev/emi/emi/bom/BoM.java b/xplat/src/main/java/dev/emi/emi/bom/BoM.java index 6caa5249..0f9e9e2d 100644 --- a/xplat/src/main/java/dev/emi/emi/bom/BoM.java +++ b/xplat/src/main/java/dev/emi/emi/bom/BoM.java @@ -303,13 +303,10 @@ public static void calculateCombinedCosts(EmiPlayerInventory inventory) { combinedProgress.merge(tree.cost); Map updatedInventory = Maps.newHashMap(); - for (Map.Entry entry : sharedInventory.entrySet()) { - EmiStack key = entry.getKey(); - long before = entry.getValue().getAmount(); - FlatMaterialCost remainder = tree.cost.remainders.get(key); - long after = remainder == null ? 0 : Math.min(before, remainder.amount); - if (after > 0) { - updatedInventory.put(key, entry.getValue().copy().setAmount(after)); + for (Map.Entry entry : tree.cost.remainders.entrySet()) { + if (entry.getValue().amount > 0) { + EmiStack key = entry.getKey(); + updatedInventory.put(key, key.copy().setAmount(entry.getValue().amount)); } } sharedInventory = updatedInventory; @@ -317,7 +314,47 @@ public static void calculateCombinedCosts(EmiPlayerInventory inventory) { combinedCost.clear(); for (MaterialTree tree : trees) { tree.calculateCost(); - combinedCost.merge(tree.cost); + } + TreeCost running = new TreeCost(); + for (MaterialTree tree : trees) { + running.clear(); + for (Map.Entry e : combinedCost.remainders.entrySet()) { + running.remainders.put(e.getKey(), new FlatMaterialCost(e.getKey(), e.getValue().amount)); + } + for (Map.Entry e : combinedCost.chanceRemainders.entrySet()) { + EmiStack k = e.getKey(); + ChanceMaterialCost v = e.getValue(); + running.chanceRemainders.put(k, new ChanceMaterialCost(k, v.amount, v.chance)); + } + running.calculateWithRemainders(tree.goal, tree.batches); + for (FlatMaterialCost cost : running.costs.values()) { + FlatMaterialCost existing = combinedCost.costs.get(cost.ingredient); + if (existing == null) { + combinedCost.costs.put(cost.ingredient, new FlatMaterialCost(cost.ingredient, cost.amount)); + } else { + existing.amount += cost.amount; + } + } + for (ChanceMaterialCost cost : running.chanceCosts.values()) { + ChanceMaterialCost existing = combinedCost.chanceCosts.get(cost.ingredient); + if (existing == null) { + existing = new ChanceMaterialCost(cost.ingredient, cost.amount, cost.chance); + combinedCost.chanceCosts.put(cost.ingredient, existing); + } else { + existing.merge(cost.amount, cost.chance); + } + existing.minBatch(cost.minBatch); + } + combinedCost.remainders.clear(); + combinedCost.chanceRemainders.clear(); + for (Map.Entry e : running.remainders.entrySet()) { + combinedCost.remainders.put(e.getKey(), new FlatMaterialCost(e.getKey(), e.getValue().amount)); + } + for (Map.Entry e : running.chanceRemainders.entrySet()) { + EmiStack k = e.getKey(); + ChanceMaterialCost v = e.getValue(); + combinedCost.chanceRemainders.put(k, new ChanceMaterialCost(k, v.amount, v.chance)); + } } } diff --git a/xplat/src/main/java/dev/emi/emi/bom/TreeCost.java b/xplat/src/main/java/dev/emi/emi/bom/TreeCost.java index 9e29b8fa..79231888 100644 --- a/xplat/src/main/java/dev/emi/emi/bom/TreeCost.java +++ b/xplat/src/main/java/dev/emi/emi/bom/TreeCost.java @@ -70,6 +70,12 @@ public void calculate(MaterialNode node, long batches) { calculateCost(node, batches * node.amount, ChanceState.DEFAULT, false); } + public void calculateWithRemainders(MaterialNode node, long batches) { + costs.clear(); + chanceCosts.clear(); + calculateCost(node, batches * node.amount, ChanceState.DEFAULT, false); + } + public void calculateProgress(MaterialNode node, long batches, EmiPlayerInventory inventory) { clear(); for (EmiStack stack : inventory.inventory.values()) {