Skip to content

Commit

Permalink
Factorio 2.0 / Space Age (#320)
Browse files Browse the repository at this point in the history
Contains the changes necessary to work with Factorio 2.0, as well as
some additions:
- Quality modules can be assigned like other modules, but they don't
have an effect yet
- Rudimentary support for spoilage
- Rudimentary support for agricultural tower

Many features are still missing, but it's enough to start doing some
calculations.
For further details, please see the checklists in the linked issues.
  • Loading branch information
shpaass authored Oct 20, 2024
2 parents c333269 + 4369537 commit 39e328b
Show file tree
Hide file tree
Showing 32 changed files with 435 additions and 359 deletions.
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, false, new ConsoleProgressReport(), errorCollector, "en", false);
project = FactorioDataSource.Parse(factorioPath, "", "", false, new ConsoleProgressReport(), errorCollector, "en", false);
}
catch (Exception ex) {
// Critical errors that make project un-loadable will be thrown as exceptions
Expand Down
3 changes: 0 additions & 3 deletions Yafc.Model.Tests/Data/DataUtils.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
using System;
using System.Globalization;
using System.Threading;
using Xunit;

namespace Yafc.Model.Data.Tests;

public class DataUtilsTests {
public DataUtilsTests() {
Project.current = new();
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
}

[Fact]
Expand Down
2 changes: 1 addition & 1 deletion Yafc.Model.Tests/LuaDependentTestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Could not find an embedded resource named "{targetStreamName}" or "{alternate}".
}
context.Exec(bytes, "*", "");

project = new FactorioDataDeserializer(false, new(1, 1)).LoadData(null, context.data, (LuaTable)context.defines["prototypes"]!, false, helper, new(), false);
project = new FactorioDataDeserializer(new(1, 1)).LoadData(null, context.data, (LuaTable)context.defines["prototypes"]!, false, helper, new(), false);
}

DataUtils.SetupForProject(project);
Expand Down
14 changes: 7 additions & 7 deletions Yafc.Model.Tests/Model/ProductionTableContentTests.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ data = {
crafting_speed = 0.5,
energy_source = {
type = "burner",
fuel_category = "chemical",
fuel_categories = { "chemical" },
},
energy_usage = "75kW",
crafting_categories = { "crafting" },
Expand All @@ -62,7 +62,7 @@ data = {
crafting_speed = 0.75,
energy_source = {
type = "burner",
fuel_category = "chemical",
fuel_categories = { "chemical" },
},
energy_usage = "150kW",
module_specification = {
Expand All @@ -82,7 +82,7 @@ data = {
crafting_speed = 1.25,
energy_source = {
type = "burner",
fuel_category = "chemical",
fuel_categories = { "chemical" },
},
energy_usage = "250kW",
module_specification = {
Expand Down Expand Up @@ -294,13 +294,13 @@ data = {
type = "recipe",
name = "recipe",
ingredients = {
{ "dummy_1", 5 },
{ "dummy_2", 10 },
{ type = "item", name = "dummy_1", amount = 5 },
{ type = "item", name = "dummy_2", amount = 10 },
},
energy_required = 5,
results = {
{ "dummy_3", 5 },
{ "ash", 3 },
{ type = "item", name = "dummy_3", amount = 5 },
{ type = "item", name = "ash", amount = 3 },
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ data = {
name = "water",
default_temperature = 15,
max_temperature = 1000,
heat_capacity = "0.2KJ",
heat_capacity = "0.2kJ",
icon = "",
},
steam = {
type = "fluid",
name = "steam",
default_temperature = 15,
max_temperature = 1000,
heat_capacity = "0.2KJ",
heat_capacity = "0.2kJ",
gas_temperature = 15,
auto_barrel = False,
icon = "",
Expand All @@ -88,7 +88,7 @@ data = {
energy_consumption = "1.8MW",
energy_source = {
type = "burner",
fuel_category = "chemical",
fuel_categories = { "chemical" },
},
},
["heat-exchanger"] = {
Expand Down
4 changes: 4 additions & 0 deletions Yafc.Model.Tests/Model/RecipeParametersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public void FluidBoilingRecipes_HaveCorrectConsumption() {
table.Solve((ProjectPage)table.owner).Wait(); // Initial Solve to set RecipeRow.Ingredients

for (int i = 0; i < 3; i++) {
if (i != 0) {
// boiler has changed in 2.0 and doesn't work yet
continue;
}
boiler.ChangeVariant(boiler.Ingredients.Single().Goods, water[i]);
heatExchanger.ChangeVariant(boiler.Ingredients.Single().Goods, water[i]);

Expand Down
4 changes: 2 additions & 2 deletions Yafc.Model.Tests/Model/SelectableVariantsTests.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ data = {
name = "steam",
default_temperature = 15,
max_temperature = 1000,
heat_capacity = "0.2KJ",
heat_capacity = "0.2kJ",
gas_temperature = 15,
auto_barrel = False,
icon = "",
Expand All @@ -39,7 +39,7 @@ data = {
energy_consumption = "1.8MW",
energy_source = {
type = "burner",
fuel_category = "chemical",
fuel_categories = { "chemical" },
},
},
["heat-exchanger"] = {
Expand Down
16 changes: 16 additions & 0 deletions Yafc.Model.Tests/PrepareForTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Globalization;
using System.Threading;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using Yafc.Model.Tests;

[assembly: TestFramework("Yafc.Model.Tests." + nameof(PrepareForTests), "Yafc.Model.Tests")]

namespace Yafc.Model.Tests;
public class PrepareForTests : XunitTestFramework {
public PrepareForTests(IMessageSink messageSink)
: base(messageSink) {
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
}
}
89 changes: 56 additions & 33 deletions Yafc.Model/Data/DataClasses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ internal enum FactorioObjectSortOrder {
Recipes,
Mechanics,
Technologies,
Entities
Entities,
Tiles
}

public enum FactorioId { }
Expand Down Expand Up @@ -82,14 +83,12 @@ public enum RecipeFlags {
UsesMiningProductivity = 1 << 0,
UsesFluidTemperature = 1 << 2,
ScaleProductionWithPower = 1 << 3,
LimitedByTickRate = 1 << 4,
}

public abstract class RecipeOrTechnology : FactorioObject {
public EntityCrafter[] crafters { get; internal set; } = null!; // null-forgiving: Initialized by CalculateMaps
public Ingredient[] ingredients { get; internal set; } = null!; // null-forgiving: Initialized by LoadRecipeData, LoadTechnologyData, and after all calls to CreateSpecialRecipe
public Product[] products { get; internal set; } = null!; // null-forgiving: Initialized by LoadRecipeData, LoadTechnologyData, and after all calls to CreateSpecialRecipe
public Module[] modules { get; internal set; } = [];
public Entity? sourceEntity { get; internal set; }
public Goods? mainProduct { get; internal set; }
public float time { get; internal set; }
Expand Down Expand Up @@ -151,6 +150,8 @@ public enum FactorioObjectSpecialType {
}

public class Recipe : RecipeOrTechnology {
public AllowedEffects allowedEffects { get; internal set; }
public string[]? allowedModuleCategories { get; internal set; }
public Technology[] technologyUnlock { get; internal set; } = [];
public bool HasIngredientVariants() {
foreach (var ingredient in ingredients) {
Expand All @@ -170,17 +171,7 @@ public override void GetDependencies(IDependencyCollector collector, List<Factor
}
}

public bool IsProductivityAllowed() {
foreach (var module in modules) {
if (module.moduleSpecification.productivity != 0f) {
return true;
}
}

return false;
}

public override bool CanAcceptModule(Item module) => modules.Contains(module);
public override bool CanAcceptModule(Item module) => EntityWithModules.CanAcceptModule(((Module)module).moduleSpecification, allowedEffects, allowedModuleCategories);
}

public class Mechanics : Recipe {
Expand Down Expand Up @@ -323,6 +314,7 @@ public class Item : Goods {
public Item? fuelResult { get; internal set; }
public int stackSize { get; internal set; }
public Entity? placeResult { get; internal set; }
public Entity? plantResult { get; internal set; }
public override bool isPower => false;
public override string type => "Item";
internal override FactorioObjectSortOrder sortingOrder => FactorioObjectSortOrder.Items;
Expand Down Expand Up @@ -383,11 +375,22 @@ public enum AllowedEffects {
Productivity = 1 << 1,
Consumption = 1 << 2,
Pollution = 1 << 3,
Quality = 1 << 4,

All = Speed | Productivity | Consumption | Pollution,
All = Speed | Productivity | Consumption | Pollution | Quality,
None = 0
}

public class Tile : FactorioObject {
public Fluid? Fluid { get; internal set; }

internal override FactorioObjectSortOrder sortingOrder => FactorioObjectSortOrder.Tiles;
public override string type => "Tile";

public override void GetDependencies(IDependencyCollector collector, List<FactorioObject> temp) {
}
}

public class Entity : FactorioObject {
public Product[] loot { get; internal set; } = [];
public bool mapGenerated { get; internal set; }
Expand All @@ -414,42 +417,38 @@ public override void GetDependencies(IDependencyCollector collector, List<Factor

public abstract class EntityWithModules : Entity {
public AllowedEffects allowedEffects { get; internal set; } = AllowedEffects.None;
public string[]? allowedModuleCategories { get; internal set; }
public int moduleSlots { get; internal set; }

public static bool CanAcceptModule(ModuleSpecification module, AllowedEffects effects) {
// Check most common cases first
if (effects == AllowedEffects.All) {
return true;
}

if (effects == (AllowedEffects.Consumption | AllowedEffects.Pollution | AllowedEffects.Speed)) {
return module.productivity == 0f;
public static bool CanAcceptModule(ModuleSpecification module, AllowedEffects effects, string[]? allowedModuleCategories) {
if (module.productivity > 0f && (effects & AllowedEffects.Productivity) == 0) {
return false;
}

if (effects == AllowedEffects.None) {
if (module.consumption < 0f && (effects & AllowedEffects.Consumption) == 0) {
return false;
}
// Check the rest
if (module.productivity != 0f && (effects & AllowedEffects.Productivity) == 0) {

if (module.pollution < 0f && (effects & AllowedEffects.Pollution) == 0) {
return false;
}

if (module.consumption != 0f && (effects & AllowedEffects.Consumption) == 0) {
if (module.speed > 0f && (effects & AllowedEffects.Speed) == 0) {
return false;
}

if (module.pollution != 0f && (effects & AllowedEffects.Pollution) == 0) {
if (module.quality > 0f && (effects & AllowedEffects.Quality) == 0) {
return false;
}

if (module.speed != 0f && (effects & AllowedEffects.Speed) == 0) {
if (allowedModuleCategories?.Contains(module.category) == false) {
return false;
}

return true;
}

public bool CanAcceptModule(ModuleSpecification module) => CanAcceptModule(module, allowedEffects);
public bool CanAcceptModule(ModuleSpecification module) => CanAcceptModule(module, allowedEffects, allowedModuleCategories);
}

public class EntityCrafter : EntityWithModules {
Expand All @@ -463,11 +462,26 @@ public float craftingSpeed {
get => _craftingSpeed * (1 + (factorioType == "lab" ? Project.current.settings.researchSpeedBonus : 0));
internal set => _craftingSpeed = value;
}
public EffectReceiver? effectReceiver { get; internal set; } = null!;
}

public class Effect {
public float consumption { get; internal set; }
public float speed { get; internal set; }
public float productivity { get; internal set; }
public float pollution { get; internal set; }
public float quality { get; internal set; }
}

public class EffectReceiver {
public Effect baseEffect { get; internal set; } = null!;
public bool usesModuleEffects { get; internal set; }
public bool usesBeaconEffects { get; internal set; }
public bool usesSurfaceEffects { get; internal set; }
}

public class EntityInserter : Entity {
public bool isStackInserter { get; internal set; }
public bool isBulkInserter { get; internal set; }
public float inserterSwingTime { get; internal set; }
}

Expand All @@ -485,6 +499,15 @@ public class EntityReactor : EntityCrafter {

public class EntityBeacon : EntityWithModules {
public float beaconEfficiency { get; internal set; }
public float[] profile { get; internal set; } = null!;

public float GetProfile(int numberOfBeacons) {
if (numberOfBeacons == 0) {
return 1f;
}

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

public class EntityContainer : Entity {
Expand Down Expand Up @@ -534,12 +557,12 @@ public class EntityEnergy {
}

public class ModuleSpecification {
public string category { get; internal set; } = null!;
public float consumption { get; internal set; }
public float speed { get; internal set; }
public float productivity { get; internal set; }
public float pollution { get; internal set; }
public Recipe[]? limitation { get; internal set; }
public Recipe[]? limitation_blacklist { get; internal set; }
public float quality { get; internal set; }
}

public struct TemperatureRange(int min, int max) {
Expand Down
1 change: 0 additions & 1 deletion Yafc.Model/Data/DataUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ public static Bits GetMilestoneOrder(FactorioId id) {

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
2 changes: 1 addition & 1 deletion Yafc.Model/Data/Database.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public static class Database {
/// <param name="module">A module that can be placed in that beacon, if such a module exists.</param>
/// <returns><see langword="true"/> if a module could be found, or <see langword="false"/> if the supplied beacon does not accept any modules or was <see langword="null"/>.</returns>
public static bool GetDefaultModuleFor(EntityBeacon? beacon, [NotNullWhen(true)] out Module? module) {
module = allModules.FirstOrDefault(m => EntityWithModules.CanAcceptModule(m.moduleSpecification, beacon?.allowedEffects ?? AllowedEffects.None));
module = allModules.FirstOrDefault(m => EntityWithModules.CanAcceptModule(m.moduleSpecification, beacon?.allowedEffects ?? AllowedEffects.None, null));
return module != null;
}

Expand Down
6 changes: 3 additions & 3 deletions Yafc.Model/Model/ModuleFillerParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public BeaconConfiguration GetBeaconsForCrafter(EntityCrafter? crafter) {
internal void AutoFillBeacons(RecipeOrTechnology recipe, EntityCrafter entity, ref ModuleEffects effects, ref UsedModule used) {
BeaconConfiguration beaconsToUse = GetBeaconsForCrafter(entity);
if (!recipe.flags.HasFlags(RecipeFlags.UsesMiningProductivity) && beaconsToUse.beacon is EntityBeacon beacon && beaconsToUse.beaconModule != null) {
effects.AddModules(beaconsToUse.beaconModule.moduleSpecification, beaconsToUse.beaconCount * beacon.beaconEfficiency * beacon.moduleSlots, entity.allowedEffects);
effects.AddModules(beaconsToUse.beaconModule.moduleSpecification, beaconsToUse.beaconCount * beacon.beaconEfficiency * beacon.GetProfile(beaconsToUse.beaconCount) * beacon.moduleSlots, entity.allowedEffects);
used.beacon = beacon;
used.beaconCount = beaconsToUse.beaconCount;
}
Expand Down Expand Up @@ -161,8 +161,8 @@ The payback time is calculated as the module cost divided by the economy gain pe
float bestEconomy = 0f;
Module? usedModule = null;

foreach (var module in recipe.modules) {
if (module.IsAccessibleWithCurrentMilestones() && entity.CanAcceptModule(module.moduleSpecification)) {
foreach (var module in Database.allModules) {
if (module.IsAccessibleWithCurrentMilestones() && entity.CanAcceptModule(module.moduleSpecification) && recipe.CanAcceptModule(module)) {
float economy = module.moduleSpecification.productivity * productivityEconomy
+ module.moduleSpecification.speed * speedEconomy
- module.moduleSpecification.consumption * effectivityEconomy;
Expand Down
Loading

0 comments on commit 39e328b

Please sign in to comment.