Skip to content

Commit

Permalink
Unlock fixed recipes with their crafters (#278)
Browse files Browse the repository at this point in the history
This fixes #276; I tested it with pY, and with one or both of these
lines
```c#
((dynamic)dataContext.data["raw"]!)["technology"]["rocket-silo"]["effects"][2] = null;
((dynamic)dataContext.data["raw"]!)["recipe"]["rocket-silo"]["enabled"] = null;
```
just before the `deserializer.LoadData` call when loading vanilla. The
first line removes the Recipe.rocket-part unlock from the Rocket silo
technology, and the second makes Recipe.rocket-silo accessible from the
beginning of the game.

The fix should continue to behave correctly if a technology unlocks a
crafter with a fixed recipe, and that fixed recipe creates a different
crafter with a different fixed recipe.
  • Loading branch information
shpaass authored Sep 9, 2024
2 parents ecaad44 + 3cc5796 commit ea66305
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Yafc.Model/Data/DataClasses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public enum FactorioId { }
public abstract class FactorioObject : IFactorioObjectWrapper, IComparable<FactorioObject> {
public string? factorioType { get; internal set; }
public string name { get; internal set; } = null!; // null-forgiving: Initialized to non-null by GetObject.
public string typeDotName { get; internal set; } = null!; // null-forgiving: Initialized to non-null by ExportBuiltData.
public string typeDotName => type + '.' + name;
public string locName { get; internal set; } = null!; // null-forgiving: Copied from name if still null at the end of CalculateMaps
public string? locDescr { get; internal set; }
public FactorioIconPart[]? iconSpec { get; internal set; }
Expand Down
41 changes: 40 additions & 1 deletion Yafc.Parser/Data/FactorioDataDeserializer_Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ private int Skip(int from, FactorioObjectSortOrder sortOrder) {

private void ExportBuiltData() {
Database.rootAccessible = rootAccessible.ToArray();
Database.objectsByTypeName = allObjects.ToDictionary(x => x.typeDotName = x.type + "." + x.name);
Database.objectsByTypeName = allObjects.ToDictionary(x => x.typeDotName);
foreach (var alias in formerAliases) {
_ = Database.objectsByTypeName.TryAdd(alias.Key, alias.Value);
}
Expand Down Expand Up @@ -391,6 +391,45 @@ private void CalculateMaps(bool netProduction) {
}
}

Queue<EntityCrafter> crafters = new(allObjects.OfType<EntityCrafter>());

while (crafters.TryDequeue(out EntityCrafter? crafter)) {
// If this is a crafter with a fixed recipe with data.raw.recipe["fixed-recipe-name"].enabled = false
// (Exclude Mechanics; they aren't recipes in Factorio's fixed_recipe sense.)
if (recipeCrafters.GetRaw(crafter).SingleOrDefault(s => s.StartsWith(SpecialNames.FixedRecipe), false) != null
&& crafter.recipes.SingleOrDefault(r => r.GetType() == typeof(Recipe), false) is Recipe { enabled: false } fixedRecipe) {

bool addedUnlocks = false;
foreach (Recipe itemRecipe in crafter.itemsToPlace.SelectMany(i => i.production)) {
// and (a recipe that creates an item that places) the crafter is accessible
// from the beginning of the game, the fixed recipe is also accessible.
if (itemRecipe.enabled) {
fixedRecipe.enabled = true;
addedUnlocks = true;
break;
}
// otherwise, the recipe is also unlocked by all technologies that
// unlock (a recipe that creates an item that places) the crafter.
else if (itemRecipe.technologyUnlock.Except(fixedRecipe.technologyUnlock).Any()) {
// Add the missing technology/ies
fixedRecipe.technologyUnlock = [.. fixedRecipe.technologyUnlock.Union(itemRecipe.technologyUnlock)];
addedUnlocks = true;
}
}

if (addedUnlocks) {
// If we added unlocks, and the fixed recipe creates (items that place) crafters,
// queue those crafters for a second check, in case they also have fixed recipes.
Item[] products = [.. fixedRecipe.products.Select(p => p.goods).OfType<Item>()];
foreach (EntityCrafter newCrafter in allObjects.OfType<EntityCrafter>()) {
if (newCrafter.itemsToPlace.Intersect(products).Any()) {
crafters.Enqueue(newCrafter);
}
}
}
}
}

foreach (var mechanic in allMechanics) {
mechanic.locName = mechanic.source.locName + " " + mechanic.locName;
mechanic.locDescr = mechanic.source.locDescr;
Expand Down
5 changes: 5 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
// Internal changes:
// Changes to the code that do not affect the behavior of the program.
----------------------------------------------------------------------------------------------------------------------
Version: 0.10.1
Date:
Bugfixes:
- Fixed recipes now become accessible when their crafter does.
----------------------------------------------------------------------------------------------------------------------
Version: 0.10.0
Date:
Feature:
Expand Down

0 comments on commit ea66305

Please sign in to comment.