Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calculate item weight and display rocket capacity #387

Merged
merged 4 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions Yafc.Model/Data/DataClasses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,10 @@ public Item() {
/// Gets the time it takes for a base-quality item to spoil, in seconds, or 0 if this item doesn't spoil.
/// </summary>
public float baseSpoilTime => getBaseSpoilTime.Value;

public int weight { get; internal set; }
internal float ingredient_to_weight_coefficient = 0.5f;

/// <summary>
/// Gets the <see cref="Quality"/>-adjusted spoilage time for this item, in seconds, or 0 if this item doesn't spoil.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions Yafc.Model/Data/Database.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public static class Database {
public static FactorioIdRange<Quality> qualities { get; internal set; } = null!;
public static FactorioIdRange<Location> locations { get; internal set; } = null!;
public static int constantCombinatorCapacity { get; internal set; } = 18;
public static int rocketCapacity { get; internal set; }

/// <summary>
/// Returns the set of beacons filtered to only those that can accept at least one module.
Expand Down
12 changes: 6 additions & 6 deletions Yafc.Parser/Data/DataParserUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,18 +76,18 @@ public static T Get<T>(this LuaTable? table, string key, T def) {
return result;
}

public static T Get<T>(this LuaTable table, int key, T def) {
_ = Parse(table[key], out var result, def);
public static T Get<T>(this LuaTable? table, int key, T def) {
_ = Parse(table?[key], out var result, def);
return result;
}

public static T? Get<T>(this LuaTable table, string key) {
_ = Parse(table[key], out T? result);
public static T? Get<T>(this LuaTable? table, string key) {
_ = Parse(table?[key], out T? result);
return result;
}

public static T? Get<T>(this LuaTable table, int key) {
_ = Parse(table[key], out T? result);
public static T? Get<T>(this LuaTable? table, int key) {
_ = Parse(table?[key], out T? result);
return result;
}

Expand Down
88 changes: 86 additions & 2 deletions Yafc.Parser/Data/FactorioDataDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,13 @@ public Project LoadData(string projectPath, LuaTable data, LuaTable prototypes,
allObjects[i].id = (FactorioId)i;
}

rocketCapacity = raw.Get<LuaTable>("utility-constants").Get<LuaTable>("default").Get("rocket_lift_weight", 1000000);
defaultItemWeight = raw.Get<LuaTable>("utility-constants").Get<LuaTable>("default").Get("default_item_weight", 100);
UpdateSplitFluids();
var iconRenderTask = renderIcons ? Task.Run(RenderIcons) : Task.CompletedTask;
UpdateRecipeIngredientFluids(errorCollector);
UpdateRecipeCatalysts();
CalculateItemWeights();
CalculateMaps(netProduction);
ExportBuiltData();
progress.Report(("Post-processing", "Calculating dependencies"));
Expand Down Expand Up @@ -444,6 +447,14 @@ void readTrigger(LuaTable table) {
EnsureLaunchRecipe(item, launchProducts);
}

if (table.Get("weight", out int weight)) {
item.weight = weight;
}

if (table.Get("ingredient_to_weight_coefficient", out float ingredient_to_weight_coefficient)) {
item.ingredient_to_weight_coefficient = ingredient_to_weight_coefficient;
}

if (GetRef(table, "spoil_result", out Item? spoiled)) {
var recipe = CreateSpecialRecipe(item, SpecialNames.SpoilRecipe, "spoiling");
recipe.ingredients = [new Ingredient(item, 1)];
Expand Down Expand Up @@ -482,6 +493,79 @@ void readEffect(LuaTable effect) {
}
}

// This was constructed from educated guesses and https://forums.factorio.com/viewtopic.php?f=23&t=120781.
// It was compared with https://rocketcal.cc/weights.json. Where the results differed, these results were verified in Factorio.
private void CalculateItemWeights() {
Dictionary<Item, List<Item>> dependencies = [];
foreach (Recipe recipe in allObjects.OfType<Recipe>()) {
foreach (Item ingredient in recipe.ingredients.Select(i => i.goods).OfType<Item>()) {
if (!dependencies.TryGetValue(ingredient, out List<Item>? dependents)) {
dependencies[ingredient] = dependents = [];
}
dependents.AddRange(recipe.products.Select(p => p.goods).OfType<Item>());
}
}

Queue<Item> queue = new(allObjects.OfType<Item>());
while (queue.TryDequeue(out Item? item)) {
if (item.weight != 0) {
continue;
}
if (item.stackSize == 0) {
continue; // Py's synthetic items sometimes have a stack size of 0.
}

Recipe? recipe = allObjects.OfType<Recipe>().FirstOrDefault(r => r.name == item.name);
// Hidden recipes appear to be ignored by Factorio; a pistol weighs 100g, not the 200kg that would be expected from its stack size.
if (recipe?.products.Length > 0 && recipe.ingredients.Length > 0 && !recipe.hidden) {
float weight = 0;
foreach (Ingredient ingredient in recipe.ingredients) {
if (ingredient.goods is Item i) {
if (i.weight == 0) {
// Wait to calculate this weight until we have the weights of all its ingredients
goto nextWeightCalculation;
}

weight += i.weight * ingredient.amount;
}
else { // fluid
// Barrels gain 5 kg when filled with 50 units of fluid.
// This matches data.raw.utility-constants.default_item_weight, so that seems like a good value to use.
weight += defaultItemWeight * ingredient.amount;
}
}

item.weight = (int)(weight / recipe.products[0].amount * item.ingredient_to_weight_coefficient);

if (!recipe.allowedEffects.HasFlag(AllowedEffects.Productivity)) {
// When productivity is disallowed, a rocket can never carry more than one stack.
item.weight = Math.Max(item.weight, rocketCapacity / item.stackSize);
}
else if (item.weight * item.stackSize < rocketCapacity) {
// When productivity is allowed, a rocket can carry either < 1 stack or an integer number of stacks.
// Do lots of integer division to floor the stack count.
item.weight = rocketCapacity / (rocketCapacity / item.weight / item.stackSize) / item.stackSize;
}

if (dependencies.TryGetValue(item, out List<Item>? products)) {
foreach (Item product in dependencies[item]) {
if (product.weight == 0) {
queue.Enqueue(product);
}
}
}
}
nextWeightCalculation:;
}

// If it doesn't otherwise have a weight, it gets the default weight.
foreach (Item item in allObjects.OfType<Item>()) {
if (item.weight == 0) {
item.weight = defaultItemWeight;
}
}
}

/// <summary>
/// Creates, or confirms the existence of, a recipe for launching a particular item.
/// </summary>
Expand Down Expand Up @@ -578,10 +662,10 @@ private void DeserializeLocation(LuaTable table, ErrorCollector collector) {
if (mapGen.Get("autoplace_controls", out LuaTable? controls)) {
location.placementControls = controls.ObjectElements.Keys.OfType<string>().ToList();
}
if (mapGen.Get<LuaTable>("autoplace_settings")?.Get<LuaTable>("entity")?.Get<LuaTable>("settings") is LuaTable entities) {
if (mapGen.Get<LuaTable>("autoplace_settings").Get<LuaTable>("entity").Get<LuaTable>("settings") is LuaTable entities) {
location.entitySpawns = entities.ObjectElements.Keys.OfType<string>().ToList();
}
if (mapGen.Get<LuaTable>("autoplace_settings")?.Get<LuaTable>("tile")?.Get<LuaTable>("settings") is LuaTable tiles) {
if (mapGen.Get<LuaTable>("autoplace_settings").Get<LuaTable>("tile").Get<LuaTable>("settings") is LuaTable tiles) {
foreach (string tile in tiles.ObjectElements.Keys.Cast<string>()) {
allObjects.OfType<Tile>().Single(t => t.name == tile).locations.Add(location);
}
Expand Down
4 changes: 4 additions & 0 deletions Yafc.Parser/Data/FactorioDataDeserializer_Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ internal partial class FactorioDataDeserializer {
private readonly EntityEnergy laborEntityEnergy;
private Entity? character;
private readonly Version factorioVersion;
private int rocketCapacity;
private int defaultItemWeight;

private static readonly Version v0_18 = new Version(0, 18);

Expand Down Expand Up @@ -230,6 +232,8 @@ private void ExportBuiltData() {
Database.allInserters = Database.entities.all.OfType<EntityInserter>().ToArray();
Database.allAccumulators = Database.entities.all.OfType<EntityAccumulator>().ToArray();
Database.allContainers = Database.entities.all.OfType<EntityContainer>().ToArray();

Database.rocketCapacity = rocketCapacity;
}

private static bool AreInverseRecipes(Recipe packing, Recipe unpacking) {
Expand Down
2 changes: 1 addition & 1 deletion Yafc.Parser/Data/FactorioDataDeserializer_Entity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ void parseEffect(LuaTable effect) {
}
if (generation.Get("control", out string? control)) {
entity.mapGenerated = true;
entity.autoplaceControl = generation?.Get<string>("control");
entity.autoplaceControl = generation.Get<string>("control");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ private void DeserializeRecipe(LuaTable table, ErrorCollector errorCollector) {
}

private static void DeserializeFlags(LuaTable table, RecipeOrTechnology recipe) {
recipe.hidden = table.Get("hidden", true);
recipe.hidden = table.Get("hidden", false);
recipe.enabled = table.Get("enabled", true);
}

Expand Down
1 change: 1 addition & 0 deletions Yafc/Widgets/ObjectTooltip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ private void BuildGoods(Goods goods, Quality quality, ImGui gui) {

using (gui.EnterGroup(contentPadding)) {
gui.BuildText("Stack size: " + item.stackSize);
gui.BuildText("Rocket capacity: " + DataUtils.FormatAmount(Database.rocketCapacity / item.weight, UnitOfMeasure.None));
}
}
}
Expand Down
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Version: 2.6.0
Date: January 20th 2025
Features:
- Apply productivity, speed, and consumption effects to recipes, and show them in tooltips.
- Display rocket capacity in item tooltips.
Fixes:
- Building emission/absorption (pollution and spores) are displayed in tooltips again.
- Fix a parsing error when using the deprecated data.extend API.
Expand Down