From 17bc6aec14f897450bf3218fd3204c9a54ba28e2 Mon Sep 17 00:00:00 2001 From: Factorio Blueprints Date: Sat, 25 Jul 2026 05:27:48 +0000 Subject: [PATCH 1/3] Read Factorio 2.1 recipe categories when deciding hand craftability. --- .../factorio/prototype/RecipePrototype.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/FactorioDataWrapper/src/com/demod/factorio/prototype/RecipePrototype.java b/FactorioDataWrapper/src/com/demod/factorio/prototype/RecipePrototype.java index 08ed51c..5d86f8f 100644 --- a/FactorioDataWrapper/src/com/demod/factorio/prototype/RecipePrototype.java +++ b/FactorioDataWrapper/src/com/demod/factorio/prototype/RecipePrototype.java @@ -1,7 +1,9 @@ package com.demod.factorio.prototype; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; import org.json.JSONArray; @@ -12,6 +14,7 @@ public class RecipePrototype extends DataPrototype { private final String category; + private final Set categories = new LinkedHashSet<>(); private final Map inputs = new LinkedHashMap<>(); private final Map outputs = new LinkedHashMap<>(); private final double energyRequired; @@ -47,12 +50,16 @@ public RecipePrototype(LuaTable lua) { } energyRequired = lua.get("energy_required").optdouble(0.5); - category = lua.get("category").optjstring("crafting"); + LuaValue categoriesLua = lua.get("categories"); + if (categoriesLua.isnil()) { + categories.add(lua.get("category").optjstring("crafting")); + } else { + Utils.forEach(categoriesLua.checktable(), categoryLua -> categories.add(categoryLua.tojstring())); + } + category = categories.iterator().next(); // FIXME get these from the character prototype - handCraftable = category.equals("crafting") || category.equals("electronics") || category.equals("pressing") - || category.equals("recycling-or-hand-crafing") || category.equals("organic-or-hand-crafing") - || category.equals("organic-or-assembling"); - recycling = category.equals("recycling"); + handCraftable = categories.contains("crafting") || categories.contains("hand-crafting"); + recycling = categories.contains("recycling"); } public String getCategory() { From aa1afc046741faf7995423b0d3b65989ad33ddf7 Mon Sep 17 00:00:00 2001 From: Factorio Blueprints Date: Sun, 26 Jul 2026 17:34:16 +0000 Subject: [PATCH 2/3] Derive recipe hand craftability from the character prototype categories. --- .../src/com/demod/factorio/DataTable.java | 11 +++++++++++ .../src/com/demod/factorio/TotalRawCalculator.java | 10 +++++++--- .../src/com/demod/factorio/apps/FactorioWikiMain.java | 3 ++- .../com/demod/factorio/prototype/RecipePrototype.java | 9 +++------ 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/FactorioDataWrapper/src/com/demod/factorio/DataTable.java b/FactorioDataWrapper/src/com/demod/factorio/DataTable.java index 49d3665..5902389 100644 --- a/FactorioDataWrapper/src/com/demod/factorio/DataTable.java +++ b/FactorioDataWrapper/src/com/demod/factorio/DataTable.java @@ -57,6 +57,7 @@ public class DataTable { private final Map itemSubGroups = new LinkedHashMap<>(); private final Map> craftingCategories = new LinkedHashMap<>(); + private final Set characterCraftingCategories = new LinkedHashSet<>(); private final ListMultimap recipesByInput = MultimapBuilder.hashKeys().arrayListValues() .build(); @@ -135,6 +136,12 @@ public DataTable(TypeHierarchy typeHierarchy, LuaTable rawLua, JSONObject exclud }); } + LuaValue characterCraftingCategoriesLua = entities.get("character").lua().get("crafting_categories"); + if (!characterCraftingCategoriesLua.isnil()) { + Utils.forEach(characterCraftingCategoriesLua.checktable(), + categoryLua -> characterCraftingCategories.add(categoryLua.tojstring())); + } + for (ItemPrototype item : items.values()) { LuaValue luaPlaceResult = item.lua().get("place_result"); if (!luaPlaceResult.isnil()) { @@ -249,6 +256,10 @@ public Map> getCraftingCategories() { return craftingCategories; } + public Set getCharacterCraftingCategories() { + return characterCraftingCategories; + } + public FactorioData getData() { return data; } diff --git a/FactorioDataWrapper/src/com/demod/factorio/TotalRawCalculator.java b/FactorioDataWrapper/src/com/demod/factorio/TotalRawCalculator.java index 1858fcc..c81cb23 100644 --- a/FactorioDataWrapper/src/com/demod/factorio/TotalRawCalculator.java +++ b/FactorioDataWrapper/src/com/demod/factorio/TotalRawCalculator.java @@ -4,16 +4,19 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.Set; import com.demod.factorio.prototype.RecipePrototype; public class TotalRawCalculator { public static final String RAW_TIME = "_TIME_"; private final Map recipes; + private final Set characterCraftingCategories; private final Map> recipeTotalRaws = new LinkedHashMap<>(); - public TotalRawCalculator(Map recipes) { + public TotalRawCalculator(Map recipes, Set characterCraftingCategories) { this.recipes = recipes; + this.characterCraftingCategories = characterCraftingCategories; } public Map compute(RecipePrototype recipe) { @@ -26,7 +29,8 @@ public Map compute(RecipePrototype recipe) { Optional findRecipe = recipes.values().stream() // XXX the nutrients-from-fish filter is here to match the bad Factorio behavior // of picking nutrients from biter eggs - .filter(r -> !r.getName().equals("nutrients-from-fish")).filter(RecipePrototype::isHandCraftable) + .filter(r -> !r.getName().equals("nutrients-from-fish")) + .filter(r -> r.isHandCraftable(characterCraftingCategories)) .filter(r -> r.getOutputs().keySet().stream().anyMatch(i -> { return i.equals(input); })).findFirst(); @@ -45,4 +49,4 @@ public Map compute(RecipePrototype recipe) { return totalRaw; } -} \ No newline at end of file +} diff --git a/FactorioDataWrapper/src/com/demod/factorio/apps/FactorioWikiMain.java b/FactorioDataWrapper/src/com/demod/factorio/apps/FactorioWikiMain.java index 6c00f14..6f4339f 100644 --- a/FactorioDataWrapper/src/com/demod/factorio/apps/FactorioWikiMain.java +++ b/FactorioDataWrapper/src/com/demod/factorio/apps/FactorioWikiMain.java @@ -518,7 +518,8 @@ private static JSONObject wiki_Recipes(DataTable table) { JSONObject json = createOrderedJSONObject(); Map normalRecipes = table.getRecipes(); - TotalRawCalculator normalTotalRawCalculator = new TotalRawCalculator(normalRecipes); + TotalRawCalculator normalTotalRawCalculator = new TotalRawCalculator(normalRecipes, + table.getCharacterCraftingCategories()); normalRecipes.values().stream().filter(r -> !r.isRecycling()) .sorted((r1, r2) -> r1.getName().compareTo(r2.getName())).forEach(recipe -> { diff --git a/FactorioDataWrapper/src/com/demod/factorio/prototype/RecipePrototype.java b/FactorioDataWrapper/src/com/demod/factorio/prototype/RecipePrototype.java index 5d86f8f..e67cfdc 100644 --- a/FactorioDataWrapper/src/com/demod/factorio/prototype/RecipePrototype.java +++ b/FactorioDataWrapper/src/com/demod/factorio/prototype/RecipePrototype.java @@ -18,7 +18,6 @@ public class RecipePrototype extends DataPrototype { private final Map inputs = new LinkedHashMap<>(); private final Map outputs = new LinkedHashMap<>(); private final double energyRequired; - private final boolean handCraftable; private final boolean recycling; public RecipePrototype(LuaTable lua) { @@ -57,8 +56,6 @@ public RecipePrototype(LuaTable lua) { Utils.forEach(categoriesLua.checktable(), categoryLua -> categories.add(categoryLua.tojstring())); } category = categories.iterator().next(); - // FIXME get these from the character prototype - handCraftable = categories.contains("crafting") || categories.contains("hand-crafting"); recycling = categories.contains("recycling"); } @@ -78,8 +75,8 @@ public Map getOutputs() { return outputs; } - public boolean isHandCraftable() { - return handCraftable; + public boolean isHandCraftable(Set characterCraftingCategories) { + return categories.stream().anyMatch(characterCraftingCategories::contains); } public boolean isRecycling() { @@ -89,7 +86,7 @@ public boolean isRecycling() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("Recipe: " + getName() + (!isHandCraftable() ? " (MACHINE ONLY)" : "") + "\n"); + sb.append("Recipe: " + getName() + "\n"); sb.append("\tTIME " + getEnergyRequired() + "\n"); getInputs().forEach((k, v) -> { sb.append("\tIN " + k + " " + v + "\n"); From d77415c44946502dbeae2f77fe7bfe723e0042f0 Mon Sep 17 00:00:00 2001 From: Factorio Blueprints Date: Sun, 26 Jul 2026 18:39:01 +0000 Subject: [PATCH 3/3] Store recipe hand craftability on each prototype. --- .../src/com/demod/factorio/DataTable.java | 12 ++++++------ .../src/com/demod/factorio/TotalRawCalculator.java | 10 +++------- .../com/demod/factorio/apps/FactorioWikiMain.java | 3 +-- .../demod/factorio/prototype/RecipePrototype.java | 14 +++++++++++--- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/FactorioDataWrapper/src/com/demod/factorio/DataTable.java b/FactorioDataWrapper/src/com/demod/factorio/DataTable.java index 5902389..43557aa 100644 --- a/FactorioDataWrapper/src/com/demod/factorio/DataTable.java +++ b/FactorioDataWrapper/src/com/demod/factorio/DataTable.java @@ -118,6 +118,12 @@ public DataTable(TypeHierarchy typeHierarchy, LuaTable rawLua, JSONObject exclud }); }); + LuaValue characterCraftingCategoriesLua = entities.get("character").lua().get("crafting_categories"); + if (!characterCraftingCategoriesLua.isnil()) { + Utils.forEach(characterCraftingCategoriesLua.checktable(), + categoryLua -> characterCraftingCategories.add(categoryLua.tojstring())); + } + for (Map protos : Arrays.asList(items, recipes, entities, fluids, technologies, equipments, tiles, achievements, itemGroups, itemSubGroups)) { protos.values().forEach(p -> { @@ -136,12 +142,6 @@ public DataTable(TypeHierarchy typeHierarchy, LuaTable rawLua, JSONObject exclud }); } - LuaValue characterCraftingCategoriesLua = entities.get("character").lua().get("crafting_categories"); - if (!characterCraftingCategoriesLua.isnil()) { - Utils.forEach(characterCraftingCategoriesLua.checktable(), - categoryLua -> characterCraftingCategories.add(categoryLua.tojstring())); - } - for (ItemPrototype item : items.values()) { LuaValue luaPlaceResult = item.lua().get("place_result"); if (!luaPlaceResult.isnil()) { diff --git a/FactorioDataWrapper/src/com/demod/factorio/TotalRawCalculator.java b/FactorioDataWrapper/src/com/demod/factorio/TotalRawCalculator.java index c81cb23..1858fcc 100644 --- a/FactorioDataWrapper/src/com/demod/factorio/TotalRawCalculator.java +++ b/FactorioDataWrapper/src/com/demod/factorio/TotalRawCalculator.java @@ -4,19 +4,16 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Optional; -import java.util.Set; import com.demod.factorio.prototype.RecipePrototype; public class TotalRawCalculator { public static final String RAW_TIME = "_TIME_"; private final Map recipes; - private final Set characterCraftingCategories; private final Map> recipeTotalRaws = new LinkedHashMap<>(); - public TotalRawCalculator(Map recipes, Set characterCraftingCategories) { + public TotalRawCalculator(Map recipes) { this.recipes = recipes; - this.characterCraftingCategories = characterCraftingCategories; } public Map compute(RecipePrototype recipe) { @@ -29,8 +26,7 @@ public Map compute(RecipePrototype recipe) { Optional findRecipe = recipes.values().stream() // XXX the nutrients-from-fish filter is here to match the bad Factorio behavior // of picking nutrients from biter eggs - .filter(r -> !r.getName().equals("nutrients-from-fish")) - .filter(r -> r.isHandCraftable(characterCraftingCategories)) + .filter(r -> !r.getName().equals("nutrients-from-fish")).filter(RecipePrototype::isHandCraftable) .filter(r -> r.getOutputs().keySet().stream().anyMatch(i -> { return i.equals(input); })).findFirst(); @@ -49,4 +45,4 @@ public Map compute(RecipePrototype recipe) { return totalRaw; } -} +} \ No newline at end of file diff --git a/FactorioDataWrapper/src/com/demod/factorio/apps/FactorioWikiMain.java b/FactorioDataWrapper/src/com/demod/factorio/apps/FactorioWikiMain.java index 6f4339f..6c00f14 100644 --- a/FactorioDataWrapper/src/com/demod/factorio/apps/FactorioWikiMain.java +++ b/FactorioDataWrapper/src/com/demod/factorio/apps/FactorioWikiMain.java @@ -518,8 +518,7 @@ private static JSONObject wiki_Recipes(DataTable table) { JSONObject json = createOrderedJSONObject(); Map normalRecipes = table.getRecipes(); - TotalRawCalculator normalTotalRawCalculator = new TotalRawCalculator(normalRecipes, - table.getCharacterCraftingCategories()); + TotalRawCalculator normalTotalRawCalculator = new TotalRawCalculator(normalRecipes); normalRecipes.values().stream().filter(r -> !r.isRecycling()) .sorted((r1, r2) -> r1.getName().compareTo(r2.getName())).forEach(recipe -> { diff --git a/FactorioDataWrapper/src/com/demod/factorio/prototype/RecipePrototype.java b/FactorioDataWrapper/src/com/demod/factorio/prototype/RecipePrototype.java index e67cfdc..52ce9d6 100644 --- a/FactorioDataWrapper/src/com/demod/factorio/prototype/RecipePrototype.java +++ b/FactorioDataWrapper/src/com/demod/factorio/prototype/RecipePrototype.java @@ -7,6 +7,7 @@ import org.json.JSONArray; +import com.demod.factorio.DataTable; import com.demod.factorio.Utils; import com.demod.factorio.fakelua.LuaTable; import com.demod.factorio.fakelua.LuaValue; @@ -18,6 +19,7 @@ public class RecipePrototype extends DataPrototype { private final Map inputs = new LinkedHashMap<>(); private final Map outputs = new LinkedHashMap<>(); private final double energyRequired; + private boolean handCraftable; private final boolean recycling; public RecipePrototype(LuaTable lua) { @@ -75,18 +77,24 @@ public Map getOutputs() { return outputs; } - public boolean isHandCraftable(Set characterCraftingCategories) { - return categories.stream().anyMatch(characterCraftingCategories::contains); + public boolean isHandCraftable() { + return handCraftable; } public boolean isRecycling() { return recycling; } + @Override + public void setTable(DataTable table) { + super.setTable(table); + handCraftable = categories.stream().anyMatch(table.getCharacterCraftingCategories()::contains); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("Recipe: " + getName() + "\n"); + sb.append("Recipe: " + getName() + (!isHandCraftable() ? " (MACHINE ONLY)" : "") + "\n"); sb.append("\tTIME " + getEnergyRequired() + "\n"); getInputs().forEach((k, v) -> { sb.append("\tIN " + k + " " + v + "\n");