Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 110 additions & 6 deletions xplat/src/main/java/dev/emi/emi/bom/BoM.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -26,7 +29,10 @@

public class BoM {
private static RecipeDefaults defaults = new RecipeDefaults();
public static MaterialTree tree;
public static List<MaterialTree> trees = Lists.newArrayList();
public static int treeIndex = -1;
public static TreeCost combinedCost = new TreeCost();
public static TreeCost combinedProgress = new TreeCost();
public static Map<EmiIngredient, EmiRecipe> defaultRecipes = Maps.newHashMap();
public static Map<EmiIngredient, EmiRecipe> addedRecipes = Maps.newHashMap();
public static Set<EmiRecipe> disabledRecipes = Sets.newHashSet();
Expand Down Expand Up @@ -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<MaterialTree> 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) {
Expand Down Expand Up @@ -227,12 +291,52 @@ 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();
Map<EmiStack, EmiStack> sharedInventory = Maps.newHashMap();
for (EmiStack stack : inventory.inventory.values()) {
sharedInventory.put(stack, stack.copy());
}
for (MaterialTree tree : trees) {
EmiPlayerInventory shared = createInventoryFromStacks(sharedInventory);
tree.calculateProgress(shared);
combinedProgress.merge(tree.cost);

Map<EmiStack, EmiStack> updatedInventory = Maps.newHashMap();
for (Map.Entry<EmiStack, EmiStack> 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) {
tree.calculateCost();
combinedCost.merge(tree.cost);
}
}

private static EmiPlayerInventory createInventoryFromStacks(Map<EmiStack, EmiStack> 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();
}
}

public static enum DefaultStatus {
EMPTY,
PARTIAL,
Expand Down
52 changes: 47 additions & 5 deletions xplat/src/main/java/dev/emi/emi/bom/TreeCost.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,61 @@ public class TreeCost {
public Map<EmiStack, FlatMaterialCost> remainders = Maps.newHashMap();
public Map<EmiStack, ChanceMaterialCost> 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()));
Expand Down
4 changes: 2 additions & 2 deletions xplat/src/main/java/dev/emi/emi/network/CommandS2CPacket.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
61 changes: 51 additions & 10 deletions xplat/src/main/java/dev/emi/emi/runtime/EmiFavorites.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -191,20 +193,49 @@ 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<EmiIngredient, FlatMaterialCost> originalCosts = Maps.newHashMap(BoM.tree.cost.costs);
Map<EmiIngredient, ChanceMaterialCost> chancedCosts = Maps.newHashMap(BoM.tree.cost.chanceCosts);
List<MaterialTree> trees = BoM.getTrees();
if (!trees.isEmpty() && BoM.craftingMode) {
TreeCost originalCost = new TreeCost();
for (MaterialTree tree : trees) {
tree.calculateCost();
originalCost.merge(tree.cost);
}
Map<EmiIngredient, FlatMaterialCost> originalCosts = Maps.newHashMap(originalCost.costs);
Map<EmiIngredient, ChanceMaterialCost> chancedCosts = Maps.newHashMap(originalCost.chanceCosts);
Object2LongMap<EmiRecipe> originalBatches = new Object2LongLinkedOpenHashMap<>();
Object2LongMap<EmiRecipe> 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<EmiRecipe> batches = new Object2LongLinkedOpenHashMap<>();
Object2LongMap<EmiRecipe> amounts = new Object2LongLinkedOpenHashMap<>();
countRecipes(batches, amounts, BoM.tree.goal);
Map<EmiStack, EmiStack> sharedInventory = Maps.newHashMap();
for (EmiStack stack : inv.inventory.values()) {
sharedInventory.put(stack, stack.copy());
}
for (MaterialTree tree : trees) {
EmiPlayerInventory shared = createInventoryFromStacks(sharedInventory);
tree.calculateProgress(shared);
countRecipes(batches, amounts, tree.goal);
remainingCost.merge(tree.cost);

Map<EmiStack, EmiStack> updatedInventory = Maps.newHashMap();
for (Map.Entry<EmiStack, EmiStack> 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;
for (Object2LongMap.Entry<EmiRecipe> entry : batches.object2LongEntrySet()) {
EmiRecipe recipe = entry.getKey();
Expand All @@ -225,12 +256,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)) {
Expand All @@ -247,6 +278,16 @@ public static void updateSynthetic(EmiPlayerInventory inv) {
}
}

private static EmiPlayerInventory createInventoryFromStacks(Map<EmiStack, EmiStack> 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<EmiRecipe> batches, Object2LongMap<EmiRecipe> amounts, MaterialNode node) {
if (node.recipe instanceof EmiResolutionRecipe recipe) {
countRecipes(batches, amounts, node.children.get(0));
Expand Down
Loading