Skip to content

Commit d48f577

Browse files
authored
Restore 1.1 support (#526)
This restores support for Factorio 1.1, and incidentally fixes #507. We could just abandon 1.1, but I've never really been happy with that idea, given that many mods (e.g. SE, Angel's) still aren't updated, and others (e.g. IR3) will explicitly never be updated. I used a customized version of my dump-data branch (e.g. suppressing properties that didn't exist in 0.9.1) to compare the objects loaded by 0.9.1 and this branch.
2 parents 14e3c99 + 3fa79b2 commit d48f577

26 files changed

+304
-70
lines changed

CommandLineToolExample/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static void Main(string[] args) {
2222
// Empty project path loads default project (with one empty page).
2323
// Project is irrelevant if you just need data, but you need it to perform sheet calculations
2424
// Set to not render any icons
25-
project = FactorioDataSource.Parse(factorioPath, "", "", false, new ConsoleProgressReport(), errorCollector, "en", false);
25+
project = FactorioDataSource.Parse(factorioPath, "", "", false, false, new ConsoleProgressReport(), errorCollector, "en", false);
2626
}
2727
catch (Exception ex) {
2828
// Critical errors that make project un-loadable will be thrown as exceptions

Yafc.I18n/FactorioLocalization.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
using System.Runtime.CompilerServices;
2+
using System.Text.RegularExpressions;
23
[assembly: InternalsVisibleTo("Yafc.Model.Tests")]
34

45
namespace Yafc.I18n;
56

6-
public static class FactorioLocalization {
7+
public static partial class FactorioLocalization {
78
private static readonly Dictionary<string, string> keys = [];
89

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

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

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

5759
if (tagEnd < 0) {
58-
return source;
60+
return FindOldPlurals().Replace(source, "__plural_for_parameter__$1__{");
5961
}
6062

6163
source = source.Remove(tagStart, tagEnd - tagStart + 1);
@@ -82,4 +84,7 @@ internal static void Initialize(Dictionary<string, string> newKeys) {
8284
keys[key] = value;
8385
}
8486
}
87+
88+
[GeneratedRegex("__plural_for_parameter_([0-9]+)_{")]
89+
private static partial Regex FindOldPlurals();
8590
}

Yafc.Model.Tests/LuaDependentTestHelper.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ internal static Project GetProjectForLua(string targetStreamName = null) {
6464
Project project;
6565
Helper helper = new();
6666
using (LuaContext context = new LuaContext(new(2, 0, 7))) {
67+
byte[] defines = File.ReadAllBytes("Data/Defines2.0.lua");
6768
byte[] bytes = File.ReadAllBytes("Data/Sandbox.lua");
68-
context.Exec(bytes, "*", "");
69+
context.Exec(bytes, "*", "", context.Exec(defines, "*", ""));
6970

7071
using (Stream stream = GetStream(targetStreamName, out string alternate)) {
7172
if (stream == null) {

Yafc.Model/Blueprints/Blueprint.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,10 @@ public class BlueprintControlFilter {
218218

219219
[Serializable]
220220
public class BlueprintItem {
221+
// For 1.1 blueprints:
222+
public string? item { get; set; }
223+
public int count { get; set; }
224+
// For 2.0 blueprints:
221225
public BlueprintId id { get; } = new();
222226
public BlueprintItemInventory items { get; } = new();
223227
}
@@ -235,6 +239,6 @@ public class BlueprintItemInventory {
235239

236240
[Serializable]
237241
public class BlueprintInventoryItem {
238-
public int inventory { get; } = 4; // unknown magic number (probably 'modules', needs more investigating)
242+
public int inventory { get; set; }
239243
public int stack { get; set; }
240244
}

Yafc.Model/Blueprints/BlueprintUtilities.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,12 @@ public static string ExportRecipiesAsBlueprint(string name, IEnumerable<RecipeRo
180180
int idx = 0;
181181
foreach (var (module, count, beacon) in modules) {
182182
if (!beacon) {
183-
BlueprintItem item = new BlueprintItem { id = { name = module.target.name, quality = module.quality.name } };
184-
item.items.inInventory.AddRange(Enumerable.Range(idx, count).Select(i => new BlueprintInventoryItem { stack = i }));
183+
BlueprintItem item = new BlueprintItem {
184+
item = module.target.name,
185+
count = count,
186+
id = { name = module.target.name, quality = module.quality.name }
187+
};
188+
item.items.inInventory.AddRange(Enumerable.Range(idx, count).Select(i => new BlueprintInventoryItem { inventory = recipe.entity.target.BlueprintModuleInventory, stack = i }));
185189
entity.items.Add(item);
186190
idx += count;
187191
}

Yafc.Model/Data/DataClasses.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,11 @@ public static bool CanAcceptModule(ModuleSpecification module, AllowedEffects ef
668668
return true;
669669
}
670670

671+
/// <summary>
672+
/// Gets the index of this entity's module inventory, as if from defines.inventory.{something}_modules
673+
/// </summary>
674+
public abstract int BlueprintModuleInventory { get; }
675+
671676
public bool CanAcceptModule<T>(IObjectWithQuality<T> module) where T : Module => CanAcceptModule(module.target.moduleSpecification);
672677
public bool CanAcceptModule(ModuleSpecification module) => CanAcceptModule(module, allowedEffects, allowedModuleCategories);
673678
}
@@ -689,6 +694,12 @@ public virtual float baseCraftingSpeed {
689694
}
690695
public virtual float CraftingSpeed(Quality quality) => factorioType is "agricultural-tower" or "electric-energy-interface" ? baseCraftingSpeed : quality.ApplyStandardBonus(baseCraftingSpeed);
691696
public EffectReceiver effectReceiver { get; internal set; } = null!;
697+
698+
public override int BlueprintModuleInventory => factorioType switch {
699+
"mining-drill" => 2, /* defines.inventory.mining_drill_modules */
700+
"lab" => 3, /* defines.inventory.lab_modules */
701+
_ => 4 /* defines.inventory.crafter_modules */
702+
};
692703
}
693704

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

939950
return profile[Math.Min(numberOfBeacons, profile.Length) - 1];
940951
}
952+
953+
public override int BlueprintModuleInventory => 1 /*defines.inventory.beacon_modules*/;
941954
}
942955

943956
public class EntityContainer : Entity {

Yafc.Model/Data/DataUtils.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ public int Compare(T? x, T? y) {
119119

120120
public static string dataPath { get; internal set; } = "";
121121
public static string modsPath { get; internal set; } = "";
122+
public static bool expensiveRecipes { get; internal set; }
122123

123124
/// <summary>
124125
/// If <see langword="true"/>, recipe selection windows will only display recipes that provide net production or consumption of the <see cref="Goods"/> in question.

Yafc.Parser/Data/FactorioDataDeserializer.cs

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,8 @@ private static float ParseEnergy(string? energy) {
403403
float energyBase = float.Parse(energy[..^2]);
404404

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

421-
private static Effect ParseEffect(LuaTable table) => new Effect {
422-
consumption = table.Get("consumption", 0f),
423-
speed = table.Get("speed", 0f),
424-
productivity = table.Get("productivity", 0f),
425-
pollution = table.Get("pollution", 0f),
426-
quality = table.Get("quality", 0f),
427-
};
422+
private static Effect ParseEffect(LuaTable table) {
423+
return new Effect {
424+
consumption = load(table, "consumption"),
425+
speed = load(table, "speed"),
426+
productivity = load(table, "productivity"),
427+
pollution = load(table, "pollution"),
428+
quality = load(table, "quality"),
429+
};
430+
431+
// table[effect].bonus in 1.1; table[effect] in 2.0. Assume [effect] is not a table in 2.0.
432+
static float load(LuaTable table, string effect) => table.Get<LuaTable>(effect)?.Get("bonus", 0f) ?? table.Get(effect, 0f);
433+
}
428434

429435
private static EffectReceiver ParseEffectReceiver(LuaTable? table) {
430436
if (table == null) {
@@ -498,15 +504,21 @@ void readTrigger(LuaTable table) {
498504
}
499505
}
500506

501-
if (table.Get("send_to_orbit_mode", "not-sendable") != "not-sendable" || item.factorioType == "space-platform-starter-pack") {
502-
Product[] launchProducts;
503-
if (table.Get("rocket_launch_products", out LuaTable? products)) {
507+
Product[]? launchProducts = null;
508+
if (table.Get("send_to_orbit_mode", "not-sendable") != "not-sendable" || item.factorioType == "space-platform-starter-pack"
509+
|| factorioVersion < v2_0) {
510+
511+
if (table.Get("rocket_launch_product", out LuaTable? product)) {
512+
launchProducts = [LoadProduct("rocket_launch_product", item.stackSize)(product)];
513+
}
514+
else if (table.Get("rocket_launch_products", out LuaTable? products)) {
504515
launchProducts = [.. products.ArrayElements<LuaTable>().Select(LoadProduct(item.typeDotName, item.stackSize))];
505516
}
506-
else {
517+
else if (factorioVersion >= v2_0) {
507518
launchProducts = [];
508519
}
509-
520+
}
521+
if (launchProducts != null) {
510522
EnsureLaunchRecipe(item, launchProducts);
511523
}
512524

@@ -559,6 +571,10 @@ void readEffect(LuaTable effect) {
559571
// This was constructed from educated guesses and https://forums.factorio.com/viewtopic.php?f=23&t=120781.
560572
// It was compared with https://rocketcal.cc/weights.json. Where the results differed, these results were verified in Factorio.
561573
private void CalculateItemWeights() {
574+
if (factorioVersion < v2_0) {
575+
return;
576+
}
577+
562578
Dictionary<Item, List<Item>> dependencies = [];
563579
foreach (Recipe recipe in allObjects.OfType<Recipe>()) {
564580
foreach (Item ingredient in recipe.ingredients.Select(i => i.goods).OfType<Item>()) {
@@ -656,11 +672,12 @@ private void CalculateItemWeights() {
656672
/// the existing launch products of a preexisting recipe, or set no products for a new recipe.</param>
657673
private void EnsureLaunchRecipe(Item item, Product[]? launchProducts) {
658674
Recipe recipe = CreateSpecialRecipe(item, SpecialNames.RocketLaunch, LSs.SpecialRecipeLaunched);
675+
// When this is called in 2.0, we don't know the item weight or the rocket capacity.
676+
// CalculateItemWeights will scale this ingredient and the products appropriately (but not the launch slot), only in 2.0.
677+
int ingredientCount = factorioVersion < v2_0 ? item.stackSize : 1;
659678
recipe.ingredients =
660679
[
661-
// When this is called, we don't know the item weight or the rocket capacity.
662-
// CalculateItemWeights will scale this ingredient and the products appropriately (but not the launch slot)
663-
new Ingredient(item, 1),
680+
new Ingredient(item, ingredientCount),
664681
new Ingredient(rocketLaunch, 1),
665682
];
666683
recipe.products = launchProducts ?? recipe.products ?? [];
@@ -694,6 +711,8 @@ private void DeserializeTile(LuaTable table, ErrorCollector _) {
694711

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

698717
if (recipe.products == null) {
699718
recipe.products = [new Product(pumpingFluid, 1200f)]; // set to Factorio default pump amounts - looks nice in tooltip
@@ -746,6 +765,18 @@ private void DeserializeFluid(LuaTable table, ErrorCollector _) {
746765
return GetObject<Item>(researchItem);
747766
}
748767
}
768+
else if (factorioVersion < v2_0) {
769+
// In 1.1, type is optional, and an item ingredient/product can be represented as { [1] = "name", [2] = amount }
770+
if (table.Get("name", out string? name)) {
771+
return GetObject<Item>(name);
772+
}
773+
774+
if (table.Get(1, out name)) {
775+
// Copy the amount from [2] to ["amount"] to translate for later code.
776+
table["amount"] = table.Get<int>(2);
777+
return GetObject<Item>(name);
778+
}
779+
}
749780
return null;
750781
}
751782

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

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

819855
if (x.Get("shift", out LuaTable? shift)) {
820856
part.x = shift.Get<float>(1);

Yafc.Parser/Data/FactorioDataDeserializer_Context.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ internal partial class FactorioDataDeserializer {
3939
private int rocketCapacity;
4040
private int defaultItemWeight;
4141

42-
private static readonly Version v0_18 = new Version(0, 18);
42+
internal static readonly Version v0_18 = new Version(0, 18);
43+
internal static readonly Version v2_0 = new Version(2, 0);
4344

4445
public FactorioDataDeserializer(Version factorioVersion) {
4546
this.factorioVersion = factorioVersion;
@@ -84,11 +85,22 @@ Item createSpecialItem(string name, string locName, string locDescr, string icon
8485
voidEnergy.showInExplorers = false;
8586
rootAccessible.Add(voidEnergy);
8687

87-
rocketLaunch = createSpecialObject(false, SpecialNames.RocketLaunch, LSs.SpecialObjectLaunchSlot,
88-
LSs.SpecialObjectLaunchSlotDescription, "__base__/graphics/entity/rocket-silo/rocket-static-pod.png", "signal-R");
88+
if (factorioVersion >= v2_0) {
89+
rocketLaunch = createSpecialObject(false, SpecialNames.RocketLaunch, LSs.SpecialObjectLaunchSlot,
90+
LSs.SpecialObjectLaunchSlotDescription, "__base__/graphics/entity/rocket-silo/rocket-static-pod.png", "signal-R");
91+
92+
science = GetObject<Item>("science");
93+
science.showInExplorers = false;
94+
}
95+
else {
96+
rocketLaunch = createSpecialObject(false, SpecialNames.RocketLaunch, LSs.SpecialObjectLaunchSlot,
97+
LSs.SpecialObjectLaunchSlotDescription, "__base__/graphics/entity/rocket-silo/02-rocket.png", "signal-R");
98+
99+
science = createSpecialItem("science", LSs.LegacyScience, LSs.LegacyScienceDesc, "__base__/graphics/icons/compilatron.png");
100+
rootAccessible.Remove(science);
101+
science.isLinkable = true;
102+
}
89103

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

Yafc.Parser/Data/FactorioDataDeserializer_Entity.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,18 @@ private void ReadEnergySource(LuaTable? energySource, Entity entity, float defau
8080

8181
EntityEnergy energy = new EntityEnergy();
8282
entity.energy = energy;
83-
LuaTable? table = energySource.Get<LuaTable>("emissions_per_minute");
8483
List<(string, float)> emissions = [];
85-
foreach (var (key, value) in table?.ObjectElements ?? []) {
86-
if (key is string k && value is double v) {
87-
emissions.Add((k, (float)v));
84+
// emissions_per_minute is a table in 2.0, and a number in 1.1.
85+
if (energySource.Get("emissions_per_minute", out LuaTable? table) && factorioVersion >= v2_0) {
86+
foreach (var (key, value) in table?.ObjectElements ?? []) {
87+
if (key is string k && value is double v) {
88+
emissions.Add((k, (float)v));
89+
}
8890
}
8991
}
92+
else if (energySource.Get("emissions_per_minute", out float emission) && factorioVersion < v2_0) {
93+
emissions.Add(("pollution", emission));
94+
}
9095
energy.emissions = emissions.AsReadOnly();
9196
energy.effectivity = energySource.Get("effectivity", 1f);
9297

@@ -104,6 +109,11 @@ private void ReadEnergySource(LuaTable? energySource, Entity entity, float defau
104109
fuelUsers.Add(entity, cat);
105110
}
106111
}
112+
else {
113+
// 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),
114+
// or the { "chemical" } default value for 2.0's fuel_categories.
115+
fuelUsers.Add(entity, energySource.Get("fuel_category", "chemical"));
116+
}
107117

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

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

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

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

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

0 commit comments

Comments
 (0)