From 43a6c31e4e92aa0225ee9d69f7bd6a773439e784 Mon Sep 17 00:00:00 2001 From: Paul Schifferer Date: Thu, 28 Nov 2024 07:30:24 -0800 Subject: [PATCH 1/2] Pre-emptive changelog entry --- CHANGELOG/1.18/current.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG/1.18/current.md b/CHANGELOG/1.18/current.md index e69de29..02501a2 100644 --- a/CHANGELOG/1.18/current.md +++ b/CHANGELOG/1.18/current.md @@ -0,0 +1,2 @@ +- `[NEW]` When an end product's recipe intermediates have multiple variations, the "least expensive" + one is chosen From 8e688a1555f84c5c75418043242b76e00529456f Mon Sep 17 00:00:00 2001 From: Paul Schifferer Date: Fri, 29 Nov 2024 20:09:07 -0800 Subject: [PATCH 2/2] Recipe cost calculation --- .../common/manager/CraftingQueueManager.java | 7 +- .../crafttracker/common/util/RecipeUtil.java | 116 ++++++++++++- .../crafttracker/common/util/TextUtils.java | 2 +- .../crafttracker/common/util/Util.java | 47 +++--- .../crafttracker/data/CTLangProvider.java | 12 +- .../crafttracker/ingredient-costs.properties | 154 ++++++++++++++++++ .../ingredient-overrides.properties | 1 + 7 files changed, 296 insertions(+), 43 deletions(-) create mode 100644 src/main/resources/assets/crafttracker/ingredient-costs.properties create mode 100644 src/main/resources/assets/crafttracker/ingredient-overrides.properties diff --git a/src/main/java/com/sweetrpg/crafttracker/common/manager/CraftingQueueManager.java b/src/main/java/com/sweetrpg/crafttracker/common/manager/CraftingQueueManager.java index 6166200..a2c3de7 100644 --- a/src/main/java/com/sweetrpg/crafttracker/common/manager/CraftingQueueManager.java +++ b/src/main/java/com/sweetrpg/crafttracker/common/manager/CraftingQueueManager.java @@ -133,11 +133,7 @@ public void addProduct(Player player, ResourceLocation itemId, int quantity) { endProducts.compute(itemId, (rl, p) -> p == null ? product : new CraftingQueueProduct(p.getItemId(), p.getRecipes(), p.getQuantity() + quantity)); -// CraftingQueueStorage.get(level).putData(itemId, quantity); - computeAll(); - -// PacketHandler.sendToPlayer(this.player, new UpdateCraftQueueData(this.getEndProducts())); } else { CraftTracker.LOGGER.info("Not adding {} to queue, since there are no recipes for it.", itemId); @@ -307,7 +303,8 @@ public void computeRecipe(Recipe recipe, int recipeQuantity) { }); } - this.computeRecipe(subRecipes.get(0), recipeQuantity); + int subIndex = RecipeUtil.chooseLeastExpensiveOf(subRecipes); + this.computeRecipe(subRecipes.get(subIndex), recipeQuantity); } }); } diff --git a/src/main/java/com/sweetrpg/crafttracker/common/util/RecipeUtil.java b/src/main/java/com/sweetrpg/crafttracker/common/util/RecipeUtil.java index a2192c5..cb6ab08 100644 --- a/src/main/java/com/sweetrpg/crafttracker/common/util/RecipeUtil.java +++ b/src/main/java/com/sweetrpg/crafttracker/common/util/RecipeUtil.java @@ -1,26 +1,67 @@ package com.sweetrpg.crafttracker.common.util; import com.sweetrpg.crafttracker.CraftTracker; +import com.sweetrpg.crafttracker.common.lib.Constants; import net.minecraft.client.Minecraft; import net.minecraft.core.NonNullList; import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.util.Tuple; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.Recipe; -import java.util.Arrays; -import java.util.List; -import java.util.Set; +import java.io.IOException; +import java.util.*; import java.util.stream.Collectors; public class RecipeUtil { + private static Map ingredientCostsByTag = new HashMap<>(); + private static Map ingredientCostOverrides = new HashMap<>(); + + static { + var mgr = Minecraft.getInstance().getResourceManager(); + + var ingCostsResource = new ResourceLocation(Constants.MOD_ID, "ingredient-costs.properties"); + try { + var costs = mgr.getResource(ingCostsResource); + var props = new Properties(); + props.load(costs.getInputStream()); + props.entrySet().forEach(entry -> { + var key = new ResourceLocation((String) entry.getKey()); + var value = Integer.parseInt((String) entry.getValue()); + ingredientCostsByTag.put(key, value); + }); + } + catch (IOException e) { + CraftTracker.LOGGER.error("I/O exception when trying to load ingredient costs file", e); + } + + var ingOverridesResource = new ResourceLocation(Constants.MOD_ID, "ingredient-overrides.properties"); + try { + var costs = mgr.getResource(ingOverridesResource); + var props = new Properties(); + props.load(costs.getInputStream()); + props.entrySet().forEach(entry -> { + var key = new ResourceLocation((String) entry.getKey()); + var value = Integer.parseInt((String) entry.getValue()); + ingredientCostOverrides.put(key, value); + }); + } + catch (IOException e) { + CraftTracker.LOGGER.error("I/O exception when trying to load ingredient overrides file", e); + } + } + public static List> getRecipesFor(ResourceLocation itemId) { CraftTracker.LOGGER.debug("RecipeUtil#getRecipesFor: {}", itemId); var mgr = Minecraft.getInstance().level.getRecipeManager(); var recipes = mgr.getRecipes().stream() .filter(r -> r.getResultItem().getItem().getRegistryName().equals(itemId)) - .collect(Collectors.toUnmodifiableList()); + .toList(); CraftTracker.LOGGER.debug("RecipeUtil#getRecipesFor: recipes {}", recipes); return recipes; @@ -30,12 +71,71 @@ public static boolean areIngredientsSame(NonNullList ingredients) { CraftTracker.LOGGER.debug("RecipeUtil#areIngredientsSame: {}", ingredients); Set ing = ingredients.stream() - .map((i) -> Arrays.asList(i.getItems())) - .filter((l) -> !l.isEmpty()) - .map((l) -> l.get(0)) - .map((i) -> i.getItem().getRegistryName().toString()) + .map(i -> Arrays.asList(i.getItems())) + .filter(l -> !l.isEmpty()) + .map(l -> l.get(0)) + .map(i -> i.getItem().getRegistryName().toString()) .collect(Collectors.toSet()); return ing.size() == 1; } + + public static int calculateRecipeCost(Recipe recipe) { + CraftTracker.LOGGER.debug("RecipeUtil#calculateRecipeCost: {}", recipe); + + return recipe.getIngredients().stream() + .map(RecipeUtil::getIngredientCost) + .reduce(0, Integer::sum); + } + + public static int getIngredientCost(Ingredient ingredient) { + CraftTracker.LOGGER.debug("RecipeUtil#getIngredientCost: {}", ingredient); + + for(ItemStack stack : ingredient.getItems()) { + // is the item in the override list? + var itemId = stack.getItem().getRegistryName(); + if(ingredientCostOverrides.containsKey(itemId)) { + CraftTracker.LOGGER.debug("found item {} in override list", itemId); + return ingredientCostOverrides.get(itemId); + } + + // it's not, so check its tags + if(stack.hasTag()) { + for(TagKey tag : stack.getTags().toList()) { + var tagId = tag.location(); + if(ingredientCostsByTag.containsKey(tagId)) { + CraftTracker.LOGGER.debug("found item {} in tag list", tagId); + return ingredientCostsByTag.get(tagId); + } + } + } + } + + return 1; + } + + public static int chooseLeastExpensiveOf(List> recipes) { + CraftTracker.LOGGER.debug("RecipeUtil#chooseLeastExpensiveOf: {}", recipes); + + List> recipeCosts = new ArrayList<>(); + + for(Recipe recipe : recipes) { + var cost = RecipeUtil.calculateRecipeCost(recipe); + var tuple = new Tuple<>(recipe.getId(), cost); + + recipeCosts.add(tuple); + } + + int lowestCostIndex = 0; + int lowestCost = Integer.MAX_VALUE; + for(int i = 0; i < recipeCosts.size(); i++) { + var cost = recipeCosts.get(i).getB(); + if(cost < lowestCost) { + lowestCostIndex = i; + lowestCost = cost; + } + } + + return lowestCostIndex; + } } diff --git a/src/main/java/com/sweetrpg/crafttracker/common/util/TextUtils.java b/src/main/java/com/sweetrpg/crafttracker/common/util/TextUtils.java index ac346f2..16ef581 100644 --- a/src/main/java/com/sweetrpg/crafttracker/common/util/TextUtils.java +++ b/src/main/java/com/sweetrpg/crafttracker/common/util/TextUtils.java @@ -9,7 +9,7 @@ public class TextUtils { private static final MutableComponent NO_EFFECTS = (new TranslatableComponent("effect.none")).withStyle(ChatFormatting.GRAY); /** - * Syntactic sugar for custom translation keys. Always prefixed with the mod's ID in lang files (e.g. farmersdelight.your.key.here). + * Syntactic sugar for custom translation keys. Always prefixed with the mod's ID in lang files (e.g. crafttracker.your.key.here). */ public static MutableComponent getTranslation(String type, String key, Object... args) { return new TranslatableComponent(type + "." + Constants.MOD_ID + "." + key, args); diff --git a/src/main/java/com/sweetrpg/crafttracker/common/util/Util.java b/src/main/java/com/sweetrpg/crafttracker/common/util/Util.java index f2c06fe..5496402 100644 --- a/src/main/java/com/sweetrpg/crafttracker/common/util/Util.java +++ b/src/main/java/com/sweetrpg/crafttracker/common/util/Util.java @@ -42,7 +42,7 @@ public static float[] rgbIntToFloatArray(int rgbInt) { int g = (rgbInt >> 8) & 255; int b = (rgbInt >> 0) & 255; - return new float[] {r / 255F, g / 255F, b / 255F}; + return new float[]{r / 255F, g / 255F, b / 255F}; } public static int[] rgbIntToIntArray(int rgbInt) { @@ -50,7 +50,7 @@ public static int[] rgbIntToIntArray(int rgbInt) { int g = (rgbInt >> 8) & 255; int b = (rgbInt >> 0) & 255; - return new int[] {r, g, b}; + return new int[]{r, g, b}; } public static int colorDye(int startColor, DyeColor dye) { @@ -63,7 +63,7 @@ public static int colorDye(int startColor, Collection dyes) { .mapToObj(Util::rgbIntToIntArray) .collect(Collectors.toList()); - if (startColor != -1) { + if(startColor != -1) { colors.add(0, rgbIntToIntArray(startColor)); } @@ -74,12 +74,12 @@ public static int colorDye(Collection colors) { int[] temp = new int[3]; int maxCompSum = 0; - for (int[] color : colors) { + for(int[] color : colors) { maxCompSum += Math.max(color[0], Math.max(color[1], color[2])); temp[0] += color[0]; temp[1] += color[1]; temp[2] += color[2]; - } + } int redAve = temp[0] / colors.size(); int greenAve = temp[1] / colors.size(); @@ -88,9 +88,9 @@ public static int colorDye(Collection colors) { float maxAve = (float) maxCompSum / (float) colors.size(); float max = Math.max(redAve, Math.max(greenAve, blueAve)); - redAve = (int)(redAve * maxAve / max); - greenAve = (int)(greenAve * maxAve / max); - blueAve = (int)(blueAve * maxAve / max); + redAve = (int) (redAve * maxAve / max); + greenAve = (int) (greenAve * maxAve / max); + blueAve = (int) (blueAve * maxAve / max); int finalColor = (redAve << 8) + greenAve; finalColor = (finalColor << 8) + blueAve; @@ -122,7 +122,7 @@ public static ResourceLocation mcLoc(String name) { /** * @param modId The namespace - * @param name The path + * @param name The path * @return The total path of the resource e.g "minecraft:air" */ public static String getResourcePath(String modId, String name) { @@ -146,9 +146,10 @@ public static T make(T object, Consumer consumer) { // From net.minecraft.util.Util but for RegistryObject public static > RegistryObject acceptOrElse(RegistryObject opt, Consumer consumer, Runnable orElse) { - if (opt.isPresent()) { + if(opt.isPresent()) { consumer.accept(opt.get()); - } else { + } + else { orElse.run(); } @@ -156,9 +157,10 @@ public static > RegistryObject accep } public static Optional acceptOrElse(Optional opt, Consumer consumer, Runnable orElse) { - if (opt.isPresent()) { + if(opt.isPresent()) { consumer.accept(opt.get()); - } else { + } + else { orElse.run(); } @@ -167,8 +169,8 @@ public static Optional acceptOrElse(Optional opt, Consumer consumer public static boolean allMatch(Iterable input, Predicate matcher) { Objects.requireNonNull(matcher); - for (T e : input) { - if (!matcher.test(e)) { + for(T e : input) { + if(!matcher.test(e)) { return false; } } @@ -177,8 +179,8 @@ public static boolean allMatch(Iterable input, Predicate matcher) { public static boolean anyMatch(Iterable input, Predicate matcher) { Objects.requireNonNull(matcher); - for (T e : input) { - if (matcher.test(e)) { + for(T e : input) { + if(matcher.test(e)) { return true; } } @@ -190,29 +192,28 @@ public static boolean anyMatch(Iterable input, Predicate matcher) { * registry id of the object it is representing */ public static ResourceLocation getRegistryId(Object obj) { - if (obj instanceof ResourceLocation) { + if(obj instanceof ResourceLocation) { return (ResourceLocation) obj; } - if (obj instanceof String) { + if(obj instanceof String) { // Returns null when namespace or path contain invalid // characters return ResourceLocation.tryParse((String) obj); } - if (obj instanceof IForgeRegistryEntry) { + if(obj instanceof IForgeRegistryEntry) { return ((IForgeRegistryEntry) obj).getRegistryName(); } - if (obj instanceof IRegistryDelegate) { + if(obj instanceof IRegistryDelegate) { return ((IRegistryDelegate) obj).name(); } - if (obj instanceof RegistryObject) { + if(obj instanceof RegistryObject) { return ((RegistryObject) obj).getId(); } - return null; } diff --git a/src/main/java/com/sweetrpg/crafttracker/data/CTLangProvider.java b/src/main/java/com/sweetrpg/crafttracker/data/CTLangProvider.java index 6292e19..2493c69 100644 --- a/src/main/java/com/sweetrpg/crafttracker/data/CTLangProvider.java +++ b/src/main/java/com/sweetrpg/crafttracker/data/CTLangProvider.java @@ -53,9 +53,9 @@ private void processENUS() { add(Constants.TRANSLATION_KEY_BINDINGS_OPEN_QMGR_TITLE, "Open Queue Manager"); add(Constants.TRANSLATION_KEY_BINDINGS_POPULATE_SHOPPING_LIST_TITLE, "Populate Shopping List"); add(Constants.TRANSLATION_KEY_BINDINGS_CLEAR_SHOPPING_LIST_TITLE, "Clear Shopping List"); - add(Constants.TRANSLATION_KEY_GUI_MSG_QUEUE_OVERLAY_MODE_HIDE, "The craft list overlay will now be hidden."); - add(Constants.TRANSLATION_KEY_GUI_MSG_QUEUE_OVERLAY_MODE_SHOW, "The craft list overlay will now be shown."); - add(Constants.TRANSLATION_KEY_GUI_MSG_QUEUE_OVERLAY_MODE_DYNAMIC, "The craft list overlay mode is dynamic."); + add(Constants.TRANSLATION_KEY_GUI_MSG_QUEUE_OVERLAY_MODE_HIDE, "The craft queue overlay will now be hidden."); + add(Constants.TRANSLATION_KEY_GUI_MSG_QUEUE_OVERLAY_MODE_SHOW, "The craft queue overlay will now be shown."); + add(Constants.TRANSLATION_KEY_GUI_MSG_QUEUE_OVERLAY_MODE_DYNAMIC, "The craft queue overlay mode is dynamic."); add(Constants.TRANSLATION_KEY_GUI_MSG_SLIST_OVERLAY_MODE_HIDE, "The shopping list overlay will now be hidden."); add(Constants.TRANSLATION_KEY_GUI_MSG_SLIST_OVERLAY_MODE_SHOW, "The shopping list overlay will now be shown."); add(Constants.TRANSLATION_KEY_GUI_MSG_SLIST_OVERLAY_MODE_DYNAMIC, "The shopping list overlay mode is dynamic."); @@ -98,9 +98,9 @@ private void processENGB() { add(Constants.TRANSLATION_KEY_BINDINGS_OPEN_QMGR_TITLE, "Open Queue Manager"); add(Constants.TRANSLATION_KEY_BINDINGS_POPULATE_SHOPPING_LIST_TITLE, "Populate Shopping List"); add(Constants.TRANSLATION_KEY_BINDINGS_CLEAR_SHOPPING_LIST_TITLE, "Clear Shopping List"); - add(Constants.TRANSLATION_KEY_GUI_MSG_QUEUE_OVERLAY_MODE_HIDE, "The craft list overlay will now be hidden."); - add(Constants.TRANSLATION_KEY_GUI_MSG_QUEUE_OVERLAY_MODE_SHOW, "The craft list overlay will now be shown."); - add(Constants.TRANSLATION_KEY_GUI_MSG_QUEUE_OVERLAY_MODE_DYNAMIC, "The craft list overlay mode is dynamic."); + add(Constants.TRANSLATION_KEY_GUI_MSG_QUEUE_OVERLAY_MODE_HIDE, "The craft queue overlay will now be hidden."); + add(Constants.TRANSLATION_KEY_GUI_MSG_QUEUE_OVERLAY_MODE_SHOW, "The craft queue overlay will now be shown."); + add(Constants.TRANSLATION_KEY_GUI_MSG_QUEUE_OVERLAY_MODE_DYNAMIC, "The craft queue overlay mode is dynamic."); add(Constants.TRANSLATION_KEY_GUI_MSG_SLIST_OVERLAY_MODE_HIDE, "The shopping list overlay will now be hidden."); add(Constants.TRANSLATION_KEY_GUI_MSG_SLIST_OVERLAY_MODE_SHOW, "The shopping list overlay will now be shown."); add(Constants.TRANSLATION_KEY_GUI_MSG_SLIST_OVERLAY_MODE_DYNAMIC, "The shopping list overlay mode is dynamic."); diff --git a/src/main/resources/assets/crafttracker/ingredient-costs.properties b/src/main/resources/assets/crafttracker/ingredient-costs.properties new file mode 100644 index 0000000..7f9c414 --- /dev/null +++ b/src/main/resources/assets/crafttracker/ingredient-costs.properties @@ -0,0 +1,154 @@ +minecraft\:anvil=10 +minecraft\:arrows=10 +minecraft\:banners=10 +minecraft\:barrels=10 +minecraft\:barrels_wooden=10 +minecraft\:beds=10 +minecraft\:boats=20 +minecraft\:bones=5 +minecraft\:bookshelves=50 +minecraft\:buttons=10 +minecraft\:candles=10 +minecraft\:carpets=10 +minecraft\:chests=10 +minecraft\:chests_ender=475 +minecraft\:chests_trapped=10 +minecraft\:chests_wooden=10 +minecraft\:coal_ores=20 +minecraft\:coals=20 +minecraft\:cobblestone=5 +minecraft\:cobblestone_deepslate=7 +minecraft\:cobblestone_mossy=10 +minecraft\:cobblestone_normal=5 +minecraft\:copper_ores=50 +minecraft\:crimson_stems=10 +minecraft\:crops=10 +minecraft\:diamond_ores=500 +minecraft\:dirt=1 +minecraft\:doors=10 +minecraft\:dusts=120 +minecraft\:dusts_glowstone=137 +minecraft\:dusts_prismarine=250 +minecraft\:dusts_redstone=137 +minecraft\:dyes=10 +minecraft\:dyes_black=15 +minecraft\:dyes_blue=10 +minecraft\:dyes_brown=10 +minecraft\:dyes_cyan=10 +minecraft\:dyes_gray=10 +minecraft\:dyes_green=15 +minecraft\:dyes_light_blue=10 +minecraft\:dyes_light_gray=10 +minecraft\:dyes_lime=10 +minecraft\:dyes_magenta=10 +minecraft\:dyes_orange=10 +minecraft\:dyes_pink=10 +minecraft\:dyes_purple=10 +minecraft\:dyes_red=10 +minecraft\:dyes_white=10 +minecraft\:dyes_yellow=10 +minecraft\:eggs=10 +minecraft\:emerald_ores=400 +minecraft\:enchanting_fuels=100 +minecraft\:end_stones=150 +minecraft\:ender_pearls=150 +minecraft\:feathers=10 +minecraft\:fence_gates=10 +minecraft\:fence_gates_wooden=10 +minecraft\:fences=10 +minecraft\:fences_nether_brick=10 +minecraft\:fences_wooden=10 +minecraft\:fishes=10 +minecraft\:flowers=5 +minecraft\:gems=150 +minecraft\:gems_amethyst=150 +minecraft\:gems_diamond=500 +minecraft\:gems_emerald=400 +minecraft\:gems_lapis=250 +minecraft\:gems_prismarine=250 +minecraft\:gems_quartz=300 +minecraft\:glass=10 +minecraft\:glass_panes=20 +minecraft\:glass_silica=20 +minecraft\:glass_tinted=20 +minecraft\:gold_ores=250 +minecraft\:gravel=2 +minecraft\:gunpowder=50 +minecraft\:heads=100 +minecraft\:ingots=15 +minecraft\:ingots_brick=16 +minecraft\:ingots_copper=55 +minecraft\:ingots_gold=310 +minecraft\:ingots_iron=110 +minecraft\:ingots_nether_brick=20 +minecraft\:ingots_netherite=750 +minecraft\:iron_ores=110 +minecraft\:lapis_ores=250 +minecraft\:leather=50 +minecraft\:leaves=2 +minecraft\:lectern_books=100 +minecraft\:logs=10 +minecraft\:logs_that_burn=10 +minecraft\:mushrooms=2 +minecraft\:music_discs=10 +minecraft\:nether_stars=900 +minecraft\:netherrack=5 +minecraft\:non_flammable_wood=10 +minecraft\:nuggets=2 +minecraft\:nuggets_gold=33 +minecraft\:nuggets_iron=11 +minecraft\:obsidian=400 +minecraft\:ore_bearing_ground_deepslate=10 +minecraft\:ore_bearing_ground_netherrack=10 +minecraft\:ore_bearing_ground_stone=10 +minecraft\:ores=10 +minecraft\:ores_coal=25 +minecraft\:ores_copper=50 +minecraft\:ores_diamond=500 +minecraft\:ores_emerald=400 +minecraft\:ores_gold=300 +minecraft\:ores_in_ground_deepslate=10 +minecraft\:ores_in_ground_netherrack=10 +minecraft\:ores_in_ground_stone=10 +minecraft\:ores_iron=100 +minecraft\:ores_lapis=250 +minecraft\:ores_netherite_scrap=750 +minecraft\:ores_quartz=300 +minecraft\:ores_redstone=200 +minecraft\:planks=10 +minecraft\:rails=10 +minecraft\:raw_materials=10 +minecraft\:raw_materials_copper=50 +minecraft\:raw_materials_gold=300 +minecraft\:raw_materials_iron=100 +minecraft\:redstone_ores=200 +minecraft\:rods=50 +minecraft\:rods_blaze=450 +minecraft\:rods_wooden=10 +minecraft\:sand=10 +minecraft\:sand_colorless=10 +minecraft\:sand_red=10 +minecraft\:sandstone=15 +minecraft\:saplings=2 +minecraft\:seeds=2 +minecraft\:shears=10 +minecraft\:signs=10 +minecraft\:slimeballs=40 +minecraft\:small_flowers=10 +minecraft\:soul_fire_base_blocks=100 +minecraft\:stained_glass=10 +minecraft\:stained_glass_panes=10 +minecraft\:stone=5 +minecraft\:stone_bricks=5 +minecraft\:string=15 +minecraft\:tall_flowers=5 +minecraft\:terracotta=100 +minecraft\:warped_stems=250 +minecraft\:wooden_buttons=10 +minecraft\:wooden_doors=60 +minecraft\:wooden_fences=10 +minecraft\:wooden_pressure_plates=20 +minecraft\:wooden_slabs=30 +minecraft\:wooden_stairs=60 +minecraft\:wooden_trapdoors=60 +minecraft\:wool=10 diff --git a/src/main/resources/assets/crafttracker/ingredient-overrides.properties b/src/main/resources/assets/crafttracker/ingredient-overrides.properties new file mode 100644 index 0000000..b3dafd4 --- /dev/null +++ b/src/main/resources/assets/crafttracker/ingredient-overrides.properties @@ -0,0 +1 @@ +create\:precision_mechanism=250