Skip to content
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
2 changes: 1 addition & 1 deletion CommandLineToolExample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static void Main(string[] args) {
// Empty project path loads default project (with one empty page).
// Project is irrelevant if you just need data, but you need it to perform sheet calculations
// Set to not render any icons
project = FactorioDataSource.Parse(factorioPath, "", "", false, new ConsoleProgressReport(), errorCollector, "en", false);
project = FactorioDataSource.Parse(factorioPath, "", "", false, false, new ConsoleProgressReport(), errorCollector, "en", false);
}
catch (Exception ex) {
// Critical errors that make project un-loadable will be thrown as exceptions
Expand Down
11 changes: 8 additions & 3 deletions Yafc.I18n/FactorioLocalization.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
[assembly: InternalsVisibleTo("Yafc.Model.Tests")]

namespace Yafc.I18n;

public static class FactorioLocalization {
public static partial class FactorioLocalization {
private static readonly Dictionary<string, string> keys = [];

public static void Parse(Stream stream) {
Expand Down Expand Up @@ -48,14 +49,15 @@ private static string CleanupTags(string source) {
while (true) {
int tagStart = source.IndexOf('[');

// Assume 2.0 mods don't have localization strings containing 1.1-style pluralization tags.
if (tagStart < 0) {
return source;
return FindOldPlurals().Replace(source, "__plural_for_parameter__$1__{");
}

int tagEnd = source.IndexOf(']', tagStart);

if (tagEnd < 0) {
return source;
return FindOldPlurals().Replace(source, "__plural_for_parameter__$1__{");
}

source = source.Remove(tagStart, tagEnd - tagStart + 1);
Expand All @@ -82,4 +84,7 @@ internal static void Initialize(Dictionary<string, string> newKeys) {
keys[key] = value;
}
}

[GeneratedRegex("__plural_for_parameter_([0-9]+)_{")]
private static partial Regex FindOldPlurals();
}
3 changes: 2 additions & 1 deletion Yafc.Model.Tests/LuaDependentTestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ internal static Project GetProjectForLua(string targetStreamName = null) {
Project project;
Helper helper = new();
using (LuaContext context = new LuaContext(new(2, 0, 7))) {
byte[] defines = File.ReadAllBytes("Data/Defines2.0.lua");
byte[] bytes = File.ReadAllBytes("Data/Sandbox.lua");
context.Exec(bytes, "*", "");
context.Exec(bytes, "*", "", context.Exec(defines, "*", ""));

using (Stream stream = GetStream(targetStreamName, out string alternate)) {
if (stream == null) {
Expand Down
6 changes: 5 additions & 1 deletion Yafc.Model/Blueprints/Blueprint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,10 @@ public class BlueprintControlFilter {

[Serializable]
public class BlueprintItem {
// For 1.1 blueprints:
public string? item { get; set; }
public int count { get; set; }
// For 2.0 blueprints:
public BlueprintId id { get; } = new();
public BlueprintItemInventory items { get; } = new();
}
Expand All @@ -235,6 +239,6 @@ public class BlueprintItemInventory {

[Serializable]
public class BlueprintInventoryItem {
public int inventory { get; } = 4; // unknown magic number (probably 'modules', needs more investigating)
public int inventory { get; set; }
public int stack { get; set; }
}
8 changes: 6 additions & 2 deletions Yafc.Model/Blueprints/BlueprintUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,12 @@ public static string ExportRecipiesAsBlueprint(string name, IEnumerable<RecipeRo
int idx = 0;
foreach (var (module, count, beacon) in modules) {
if (!beacon) {
BlueprintItem item = new BlueprintItem { id = { name = module.target.name, quality = module.quality.name } };
item.items.inInventory.AddRange(Enumerable.Range(idx, count).Select(i => new BlueprintInventoryItem { stack = i }));
BlueprintItem item = new BlueprintItem {
item = module.target.name,
count = count,
id = { name = module.target.name, quality = module.quality.name }
};
item.items.inInventory.AddRange(Enumerable.Range(idx, count).Select(i => new BlueprintInventoryItem { inventory = recipe.entity.target.BlueprintModuleInventory, stack = i }));
entity.items.Add(item);
idx += count;
}
Expand Down
13 changes: 13 additions & 0 deletions Yafc.Model/Data/DataClasses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,11 @@ public static bool CanAcceptModule(ModuleSpecification module, AllowedEffects ef
return true;
}

/// <summary>
/// Gets the index of this entity's module inventory, as if from defines.inventory.{something}_modules
/// </summary>
public abstract int BlueprintModuleInventory { get; }

public bool CanAcceptModule<T>(IObjectWithQuality<T> module) where T : Module => CanAcceptModule(module.target.moduleSpecification);
public bool CanAcceptModule(ModuleSpecification module) => CanAcceptModule(module, allowedEffects, allowedModuleCategories);
}
Expand All @@ -689,6 +694,12 @@ public virtual float baseCraftingSpeed {
}
public virtual float CraftingSpeed(Quality quality) => factorioType is "agricultural-tower" or "electric-energy-interface" ? baseCraftingSpeed : quality.ApplyStandardBonus(baseCraftingSpeed);
public EffectReceiver effectReceiver { get; internal set; } = null!;

public override int BlueprintModuleInventory => factorioType switch {
"mining-drill" => 2, /* defines.inventory.mining_drill_modules */
"lab" => 3, /* defines.inventory.lab_modules */
_ => 4 /* defines.inventory.crafter_modules */
};
}

public class EntityAttractor : EntityCrafter {
Expand Down Expand Up @@ -938,6 +949,8 @@ public float GetProfile(int numberOfBeacons) {

return profile[Math.Min(numberOfBeacons, profile.Length) - 1];
}

public override int BlueprintModuleInventory => 1 /*defines.inventory.beacon_modules*/;
}

public class EntityContainer : Entity {
Expand Down
1 change: 1 addition & 0 deletions Yafc.Model/Data/DataUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public int Compare(T? x, T? y) {

public static string dataPath { get; internal set; } = "";
public static string modsPath { get; internal set; } = "";
public static bool expensiveRecipes { get; internal set; }

/// <summary>
/// If <see langword="true"/>, recipe selection windows will only display recipes that provide net production or consumption of the <see cref="Goods"/> in question.
Expand Down
70 changes: 53 additions & 17 deletions Yafc.Parser/Data/FactorioDataDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,8 @@ private static float ParseEnergy(string? energy) {
float energyBase = float.Parse(energy[..^2]);

switch (energyMul) {
case 'k': return energyBase * 1e-3f;
// 2.0 only allows k; 1.1 allows either. Assume 2.0 mods don't rely on whatever Factorio does with 'K'.
case 'k' or 'K': return energyBase * 1e-3f;
case 'M': return energyBase;
case 'G': return energyBase * 1e3f;
case 'T': return energyBase * 1e6f;
Expand All @@ -418,13 +419,18 @@ private static float ParseEnergy(string? energy) {
return float.Parse(energy[..^1]) * 1e-6f;
}

private static Effect ParseEffect(LuaTable table) => new Effect {
consumption = table.Get("consumption", 0f),
speed = table.Get("speed", 0f),
productivity = table.Get("productivity", 0f),
pollution = table.Get("pollution", 0f),
quality = table.Get("quality", 0f),
};
private static Effect ParseEffect(LuaTable table) {
return new Effect {
consumption = load(table, "consumption"),
speed = load(table, "speed"),
productivity = load(table, "productivity"),
pollution = load(table, "pollution"),
quality = load(table, "quality"),
};

// table[effect].bonus in 1.1; table[effect] in 2.0. Assume [effect] is not a table in 2.0.
static float load(LuaTable table, string effect) => table.Get<LuaTable>(effect)?.Get("bonus", 0f) ?? table.Get(effect, 0f);
}

private static EffectReceiver ParseEffectReceiver(LuaTable? table) {
if (table == null) {
Expand Down Expand Up @@ -498,15 +504,21 @@ void readTrigger(LuaTable table) {
}
}

if (table.Get("send_to_orbit_mode", "not-sendable") != "not-sendable" || item.factorioType == "space-platform-starter-pack") {
Product[] launchProducts;
if (table.Get("rocket_launch_products", out LuaTable? products)) {
Product[]? launchProducts = null;
if (table.Get("send_to_orbit_mode", "not-sendable") != "not-sendable" || item.factorioType == "space-platform-starter-pack"
|| factorioVersion < v2_0) {

if (table.Get("rocket_launch_product", out LuaTable? product)) {
launchProducts = [LoadProduct("rocket_launch_product", item.stackSize)(product)];
}
else if (table.Get("rocket_launch_products", out LuaTable? products)) {
launchProducts = [.. products.ArrayElements<LuaTable>().Select(LoadProduct(item.typeDotName, item.stackSize))];
}
else {
else if (factorioVersion >= v2_0) {
launchProducts = [];
}

}
if (launchProducts != null) {
EnsureLaunchRecipe(item, launchProducts);
}

Expand Down Expand Up @@ -559,6 +571,10 @@ 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() {
if (factorioVersion < v2_0) {
return;
}

Dictionary<Item, List<Item>> dependencies = [];
foreach (Recipe recipe in allObjects.OfType<Recipe>()) {
foreach (Item ingredient in recipe.ingredients.Select(i => i.goods).OfType<Item>()) {
Expand Down Expand Up @@ -656,11 +672,12 @@ private void CalculateItemWeights() {
/// the existing launch products of a preexisting recipe, or set no products for a new recipe.</param>
private void EnsureLaunchRecipe(Item item, Product[]? launchProducts) {
Recipe recipe = CreateSpecialRecipe(item, SpecialNames.RocketLaunch, LSs.SpecialRecipeLaunched);
// When this is called in 2.0, we don't know the item weight or the rocket capacity.
// CalculateItemWeights will scale this ingredient and the products appropriately (but not the launch slot), only in 2.0.
int ingredientCount = factorioVersion < v2_0 ? item.stackSize : 1;
recipe.ingredients =
[
// When this is called, we don't know the item weight or the rocket capacity.
// CalculateItemWeights will scale this ingredient and the products appropriately (but not the launch slot)
new Ingredient(item, 1),
new Ingredient(item, ingredientCount),
new Ingredient(rocketLaunch, 1),
];
recipe.products = launchProducts ?? recipe.products ?? [];
Expand Down Expand Up @@ -694,6 +711,8 @@ private void DeserializeTile(LuaTable table, ErrorCollector _) {

string recipeCategory = SpecialNames.PumpingRecipe + "tile";
Recipe recipe = CreateSpecialRecipe(pumpingFluid, recipeCategory, LSs.SpecialRecipePumping);
// We changed the names of pumping recipes when adding support for 2.0.
formerAliases[$"Mechanics.pump.{pumpingFluid.name}.{pumpingFluid.name}"] = recipe;

if (recipe.products == null) {
recipe.products = [new Product(pumpingFluid, 1200f)]; // set to Factorio default pump amounts - looks nice in tooltip
Expand Down Expand Up @@ -746,6 +765,18 @@ private void DeserializeFluid(LuaTable table, ErrorCollector _) {
return GetObject<Item>(researchItem);
}
}
else if (factorioVersion < v2_0) {
// In 1.1, type is optional, and an item ingredient/product can be represented as { [1] = "name", [2] = amount }
if (table.Get("name", out string? name)) {
return GetObject<Item>(name);
}

if (table.Get(1, out name)) {
// Copy the amount from [2] to ["amount"] to translate for later code.
table["amount"] = table.Get<int>(2);
return GetObject<Item>(name);
}
}
return null;
}

Expand Down Expand Up @@ -809,12 +840,17 @@ private void DeserializeLocation(LuaTable table, ErrorCollector collector) {
// These are the only expected sizes for icons we render.
// New classes of icons (e.g. achievements) will need new cases to make IconParts with default scale render correctly.
// https://lua-api.factorio.com/latest/types/IconData.html
Technology => 256,
Technology => 256, // I think this should be 512 in 1.1, but that doesn't fix vanilla icons, and makes SE icons worse.
_ => 64
};

FactorioIconPart part = new(path) { size = x.Get("icon_size", 64) };
part.scale = x.Get("scale", expectedSize / 2f / part.size);
if (factorioVersion < v2_0) {
// Mystery adjustment that makes 1.1 technology icons render correctly, and doesn't appear to mess up the recipe icons.
// (Checked many of vanilla/SE's tech icons and the se-simulation-* recipes.)
part.scale *= part.size / 64f;
}

if (x.Get("shift", out LuaTable? shift)) {
part.x = shift.Get<float>(1);
Expand Down
22 changes: 17 additions & 5 deletions Yafc.Parser/Data/FactorioDataDeserializer_Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ internal partial class FactorioDataDeserializer {
private int rocketCapacity;
private int defaultItemWeight;

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

public FactorioDataDeserializer(Version factorioVersion) {
this.factorioVersion = factorioVersion;
Expand Down Expand Up @@ -84,11 +85,22 @@ Item createSpecialItem(string name, string locName, string locDescr, string icon
voidEnergy.showInExplorers = false;
rootAccessible.Add(voidEnergy);

rocketLaunch = createSpecialObject(false, SpecialNames.RocketLaunch, LSs.SpecialObjectLaunchSlot,
LSs.SpecialObjectLaunchSlotDescription, "__base__/graphics/entity/rocket-silo/rocket-static-pod.png", "signal-R");
if (factorioVersion >= v2_0) {
rocketLaunch = createSpecialObject(false, SpecialNames.RocketLaunch, LSs.SpecialObjectLaunchSlot,
LSs.SpecialObjectLaunchSlotDescription, "__base__/graphics/entity/rocket-silo/rocket-static-pod.png", "signal-R");

science = GetObject<Item>("science");
science.showInExplorers = false;
}
else {
rocketLaunch = createSpecialObject(false, SpecialNames.RocketLaunch, LSs.SpecialObjectLaunchSlot,
LSs.SpecialObjectLaunchSlotDescription, "__base__/graphics/entity/rocket-silo/02-rocket.png", "signal-R");

science = createSpecialItem("science", LSs.LegacyScience, LSs.LegacyScienceDesc, "__base__/graphics/icons/compilatron.png");
rootAccessible.Remove(science);
science.isLinkable = true;
}

science = GetObject<Item>("science");
science.showInExplorers = false;
Analysis.ExcludeFromAnalysis<CostAnalysis>(science);
formerAliases["Special.research-unit"] = science;

Expand Down
26 changes: 19 additions & 7 deletions Yafc.Parser/Data/FactorioDataDeserializer_Entity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,18 @@ private void ReadEnergySource(LuaTable? energySource, Entity entity, float defau

EntityEnergy energy = new EntityEnergy();
entity.energy = energy;
LuaTable? table = energySource.Get<LuaTable>("emissions_per_minute");
List<(string, float)> emissions = [];
foreach (var (key, value) in table?.ObjectElements ?? []) {
if (key is string k && value is double v) {
emissions.Add((k, (float)v));
// emissions_per_minute is a table in 2.0, and a number in 1.1.
if (energySource.Get("emissions_per_minute", out LuaTable? table) && factorioVersion >= v2_0) {
foreach (var (key, value) in table?.ObjectElements ?? []) {
if (key is string k && value is double v) {
emissions.Add((k, (float)v));
}
}
}
else if (energySource.Get("emissions_per_minute", out float emission) && factorioVersion < v2_0) {
emissions.Add(("pollution", emission));
}
energy.emissions = emissions.AsReadOnly();
energy.effectivity = energySource.Get("effectivity", 1f);

Expand All @@ -104,6 +109,11 @@ private void ReadEnergySource(LuaTable? energySource, Entity entity, float defau
fuelUsers.Add(entity, cat);
}
}
else {
// fuel_category is not used in 2.0. Assume it's not present for 2.0 mods. Use this to load either the 1.1 value (or default),
// or the { "chemical" } default value for 2.0's fuel_categories.
fuelUsers.Add(entity, energySource.Get("fuel_category", "chemical"));
}

break;
case "heat":
Expand Down Expand Up @@ -148,7 +158,8 @@ private static void ParseModules(LuaTable table, EntityWithModules entity, Allow
entity.allowedModuleCategories = [.. categories.ArrayElements<string>()];
}

entity.moduleSlots = table.Get("module_slots", 0);
// table.module_specification.module_slots in 1.1; table.module_slots in 2.0. Assume module_specification is not present for 2.0 mods.
entity.moduleSlots = table.Get<LuaTable>("module_specification")?.Get<int>("module_slots") ?? table.Get("module_slots", 0);
}

private Recipe CreateLaunchRecipe(EntityCrafter entity, Recipe recipe, int partsRequired, int outputCount) {
Expand Down Expand Up @@ -383,7 +394,7 @@ private void DeserializeEntity(LuaTable table, ErrorCollector errorCollector) {

if (factorioType == "rocket-silo") {
bool launchToSpacePlatforms = table.Get("launch_to_space_platforms", false);
int rocketInventorySize = table.Get("to_be_inserted_to_rocket_inventory_size", 0);
int rocketInventorySize = table.Get("to_be_inserted_to_rocket_inventory_size", factorioVersion < v2_0 ? 1 : 0);

if (rocketInventorySize > 0) {
_ = table.Get("rocket_parts_required", out int partsRequired, 100);
Expand Down Expand Up @@ -411,7 +422,8 @@ private void DeserializeEntity(LuaTable table, ErrorCollector errorCollector) {
case "inserter":
var inserter = GetObject<EntityInserter>(table);
inserter.inserterSwingTime = 1f / (table.Get("rotation_speed", 1f) * 60);
inserter.isBulkInserter = table.Get("bulk", false);
// Assume mods don't declare bulk(2.0)/stack(1.1) inconsistently.
inserter.isBulkInserter = table.Get("bulk", false) || table.Get("stack", false);
break;
case "lab":
var lab = GetObject<EntityCrafter>(table);
Expand Down
Loading