Economy: RecipeData.RoleInputs, Recipes.RoleInputsOf as the one reader of role slots, checks read a role input as any filler and offered recipe-less designs as demanding their roles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1ed3579025
commit
3fb5e0e0e6
3 changed files with 63 additions and 37 deletions
|
|
@ -79,7 +79,8 @@ public static class EconomyChecks
|
||||||
StoredRecipes(catalog).Select(pair => (pair.Key, Sound(pair.Recipe)));
|
StoredRecipes(catalog).Select(pair => (pair.Key, Sound(pair.Recipe)));
|
||||||
|
|
||||||
private static RecipeData Sound(RecipeData recipe) =>
|
private static RecipeData Sound(RecipeData recipe) =>
|
||||||
recipe.Inputs != null && recipe.Upkeep != null && recipe.Fills != null && !recipe.Inputs.Contains(null) && !recipe.Upkeep.Contains(null) && !recipe.Fills.Contains(null)
|
recipe.Inputs != null && recipe.Upkeep != null && recipe.Fills != null && recipe.RoleInputs != null
|
||||||
|
&& !recipe.Inputs.Contains(null) && !recipe.Upkeep.Contains(null) && !recipe.Fills.Contains(null) && !recipe.RoleInputs.Contains(null)
|
||||||
? recipe
|
? recipe
|
||||||
: new RecipeData
|
: new RecipeData
|
||||||
{
|
{
|
||||||
|
|
@ -87,9 +88,28 @@ public static class EconomyChecks
|
||||||
StationKind = recipe.StationKind, LoreHook = recipe.LoreHook,
|
StationKind = recipe.StationKind, LoreHook = recipe.LoreHook,
|
||||||
Inputs = (recipe.Inputs ?? new List<CommodityCount>()).Where(c => c != null).ToList(),
|
Inputs = (recipe.Inputs ?? new List<CommodityCount>()).Where(c => c != null).ToList(),
|
||||||
Upkeep = (recipe.Upkeep ?? new List<CommodityCount>()).Where(c => c != null).ToList(),
|
Upkeep = (recipe.Upkeep ?? new List<CommodityCount>()).Where(c => c != null).ToList(),
|
||||||
Fills = (recipe.Fills ?? new List<string>()).Where(f => f != null).ToList()
|
Fills = (recipe.Fills ?? new List<string>()).Where(f => f != null).ToList(),
|
||||||
|
RoleInputs = (recipe.RoleInputs ?? new List<ItemRole>()).Where(r => r != null).ToList()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The designs some faction offers, but a Station or Turret hull, distinct and ordered by key: the designs that need a recipe.
|
||||||
|
private static List<ItemData> OfferedDesigns(CultCache catalog)
|
||||||
|
{
|
||||||
|
var designs = new Dictionary<string, ItemData>(StringComparer.Ordinal);
|
||||||
|
foreach (var product in catalog.GetAll<FactionProductData>().Where(p => LoadoutGenerator.IsOffered(catalog, p)))
|
||||||
|
{
|
||||||
|
var design = catalog.Get(product.Design);
|
||||||
|
if (design == null || (design is HullData hull && (hull.HullType == HullType.Station || hull.HullType == HullType.Turret))) continue;
|
||||||
|
designs.TryAdd(catalog.RefOf(design).Key.Value, design);
|
||||||
|
}
|
||||||
|
return designs.OrderBy(pair => pair.Key, StringComparer.Ordinal).Select(pair => pair.Value).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The roles some design demands: those of every offered design and of every design a recipe makes, recipe or no.
|
||||||
|
private static HashSet<string> DesignRoles(CultCache catalog) =>
|
||||||
|
new HashSet<string>(OfferedDesigns(catalog).Concat(AllRecipes(catalog).Select(p => catalog.Get(p.Recipe.Output)).Where(o => o != null && !Recipes.IsCommodity(o)))
|
||||||
|
.SelectMany(design => Recipes.RolesOf(design)).Select(role => role.Name), StringComparer.Ordinal);
|
||||||
|
|
||||||
private static IEnumerable<ItemData> Commodities(CultCache catalog) =>
|
private static IEnumerable<ItemData> Commodities(CultCache catalog) =>
|
||||||
catalog.GetAll<ItemData>().Where(Recipes.IsCommodity).OrderBy(item => catalog.RefOf(item).Key.Value, StringComparer.Ordinal);
|
catalog.GetAll<ItemData>().Where(Recipes.IsCommodity).OrderBy(item => catalog.RefOf(item).Key.Value, StringComparer.Ordinal);
|
||||||
|
|
||||||
|
|
@ -101,15 +121,9 @@ public static class EconomyChecks
|
||||||
foreach (var item in Commodities(catalog))
|
foreach (var item in Commodities(catalog))
|
||||||
if (Recipes.RecipeOf(catalog, item) == null)
|
if (Recipes.RecipeOf(catalog, item) == null)
|
||||||
faults.Add(new EconomyFault(LRecipe, catalog.RefOf(item).Key.Value, $"Commodity '{item.Name}' has no recipe.", pending: true));
|
faults.Add(new EconomyFault(LRecipe, catalog.RefOf(item).Key.Value, $"Commodity '{item.Name}' has no recipe.", pending: true));
|
||||||
var seen = new HashSet<string>(StringComparer.Ordinal);
|
foreach (var design in OfferedDesigns(catalog))
|
||||||
foreach (var product in catalog.GetAll<FactionProductData>().Where(p => LoadoutGenerator.IsOffered(catalog, p)))
|
if (Recipes.RecipeOf(catalog, design) == null)
|
||||||
{
|
faults.Add(new EconomyFault(LRecipe, catalog.RefOf(design).Key.Value, $"Offered design '{design.Name}' has no recipe.", pending: true));
|
||||||
var design = catalog.Get(product.Design);
|
|
||||||
if (design == null || (design is HullData hull && (hull.HullType == HullType.Station || hull.HullType == HullType.Turret))) continue;
|
|
||||||
var key = catalog.RefOf(design).Key.Value;
|
|
||||||
if (seen.Add(key) && Recipes.RecipeOf(catalog, design) == null)
|
|
||||||
faults.Add(new EconomyFault(LRecipe, key, $"Offered design '{design.Name}' has no recipe.", pending: true));
|
|
||||||
}
|
|
||||||
foreach (var market in catalog.GetAll<OutsideMarketData>())
|
foreach (var market in catalog.GetAll<OutsideMarketData>())
|
||||||
{
|
{
|
||||||
var marketKey = catalog.RefOf(market).Key.Value;
|
var marketKey = catalog.RefOf(market).Key.Value;
|
||||||
|
|
@ -125,6 +139,8 @@ public static class EconomyChecks
|
||||||
else if (list.Contains(null)) Add(key, $"{name} holds a null entry.");
|
else if (list.Contains(null)) Add(key, $"{name} holds a null entry.");
|
||||||
if (stored.Fills == null) Add(key, "Fills is null; a recipe holds a list, empty when it fills no role.");
|
if (stored.Fills == null) Add(key, "Fills is null; a recipe holds a list, empty when it fills no role.");
|
||||||
else if (stored.Fills.Contains(null)) Add(key, "Fills holds a null entry.");
|
else if (stored.Fills.Contains(null)) Add(key, "Fills holds a null entry.");
|
||||||
|
if (stored.RoleInputs == null) Add(key, "RoleInputs is null; a recipe holds a list, empty when it takes no role.");
|
||||||
|
else if (stored.RoleInputs.Contains(null)) Add(key, "RoleInputs holds a null entry.");
|
||||||
var output = catalog.Get(recipe.Output);
|
var output = catalog.Get(recipe.Output);
|
||||||
if (output == null) { Add(key, "Output names no item this catalog holds."); continue; }
|
if (output == null) { Add(key, "Output names no item this catalog holds."); continue; }
|
||||||
if (key != RecipeData.KeyOf(recipe.Output.Key).Value)
|
if (key != RecipeData.KeyOf(recipe.Output.Key).Value)
|
||||||
|
|
@ -149,8 +165,10 @@ public static class EconomyChecks
|
||||||
if (roles.Count == 0) Add(key, "A design's recipe needs a design with at least one role.");
|
if (roles.Count == 0) Add(key, "A design's recipe needs a design with at least one role.");
|
||||||
}
|
}
|
||||||
if (roles.Count > 0 && recipe.Inputs.Count > 0) Add(key, "A role recipe has no Inputs; its inputs are its design's roles.");
|
if (roles.Count > 0 && recipe.Inputs.Count > 0) Add(key, "A role recipe has no Inputs; its inputs are its design's roles.");
|
||||||
foreach (var role in roles)
|
if (roles.Count > 0 && recipe.RoleInputs.Count > 0) Add(key, "A role recipe has no RoleInputs; its role inputs are its design's roles.");
|
||||||
|
foreach (var role in Recipes.RoleInputsOf(catalog, recipe))
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(role.Name)) { Add(key, "A role input has an empty name."); continue; }
|
||||||
if (role.Count < 1) Add(key, $"Role '{role.Name}' count is below one.");
|
if (role.Count < 1) Add(key, $"Role '{role.Name}' count is below one.");
|
||||||
if (Recipes.Fillers(catalog, role.Name).Length == 0) Add(key, $"Role '{role.Name}' has no commodity whose recipe fills it.");
|
if (Recipes.Fillers(catalog, role.Name).Length == 0) Add(key, $"Role '{role.Name}' has no commodity whose recipe fills it.");
|
||||||
}
|
}
|
||||||
|
|
@ -177,8 +195,8 @@ public static class EconomyChecks
|
||||||
.SelectMany(design => design.Behaviors.OfType<WeaponData>()).Where(weapon => weapon.AmmoType.IsSet())
|
.SelectMany(design => design.Behaviors.OfType<WeaponData>()).Where(weapon => weapon.AmmoType.IsSet())
|
||||||
.Select(weapon => weapon.AmmoType.Key.Value), StringComparer.Ordinal);
|
.Select(weapon => weapon.AmmoType.Key.Value), StringComparer.Ordinal);
|
||||||
|
|
||||||
// Every commodity has a source (a grounded chain, or a gate that sells it) and a sink (a recipe uses it, a role some design
|
// Every commodity has a source (a grounded chain, or a gate that sells it) and a sink (a recipe takes it or a role it fills, a role
|
||||||
// names, a weapon some offered design carries draws it, or a gate that buys it). A consumer good's sink is judged once needs
|
// some design (offered, or made by a recipe) names, a weapon some offered design carries draws it, or a gate that buys it). A consumer good's sink is judged once needs
|
||||||
// tables exist.
|
// tables exist.
|
||||||
public static List<EconomyFault> SourceSink(CultCache catalog)
|
public static List<EconomyFault> SourceSink(CultCache catalog)
|
||||||
{
|
{
|
||||||
|
|
@ -188,8 +206,8 @@ public static class EconomyChecks
|
||||||
var grounded = Depths(catalog, sells);
|
var grounded = Depths(catalog, sells);
|
||||||
var recipes = AllRecipes(catalog).ToList();
|
var recipes = AllRecipes(catalog).ToList();
|
||||||
var used = new HashSet<string>(recipes.SelectMany(p => p.Recipe.Inputs.Concat(p.Recipe.Upkeep)).Select(c => c.Commodity.Key.Value), StringComparer.Ordinal);
|
var used = new HashSet<string>(recipes.SelectMany(p => p.Recipe.Inputs.Concat(p.Recipe.Upkeep)).Select(c => c.Commodity.Key.Value), StringComparer.Ordinal);
|
||||||
var rolesNamed = new HashSet<string>(recipes.Where(p => Recipes.IsRoleRecipe(catalog, p.Recipe))
|
var rolesNamed = DesignRoles(catalog);
|
||||||
.SelectMany(p => Recipes.RolesOf(catalog.Get(p.Recipe.Output))).Select(r => r.Name), StringComparer.Ordinal);
|
rolesNamed.UnionWith(recipes.SelectMany(p => Recipes.RoleInputsOf(catalog, p.Recipe)).Select(r => r.Name));
|
||||||
foreach (var (key, recipe) in recipes)
|
foreach (var (key, recipe) in recipes)
|
||||||
{
|
{
|
||||||
var output = catalog.Get(recipe.Output);
|
var output = catalog.Get(recipe.Output);
|
||||||
|
|
@ -199,7 +217,7 @@ public static class EconomyChecks
|
||||||
faults.Add(new EconomyFault(LSource, key, $"'{output.Name}' has no source: no chain of recipes reaches raw goods and no gate sells it."));
|
faults.Add(new EconomyFault(LSource, key, $"'{output.Name}' has no source: no chain of recipes reaches raw goods and no gate sells it."));
|
||||||
if (Recipes.IsConsumerGood(catalog, recipe)) continue;
|
if (Recipes.IsConsumerGood(catalog, recipe)) continue;
|
||||||
if (!used.Contains(outputKey) && !recipe.Fills.Any(rolesNamed.Contains) && !drawn.Contains(outputKey) && !buys.Contains(outputKey))
|
if (!used.Contains(outputKey) && !recipe.Fills.Any(rolesNamed.Contains) && !drawn.Contains(outputKey) && !buys.Contains(outputKey))
|
||||||
faults.Add(new EconomyFault(LSource, key, $"'{output.Name}' has no sink: no recipe uses it, no design names a role it fills, no weapon draws it, no gate buys it."));
|
faults.Add(new EconomyFault(LSource, key, $"'{output.Name}' has no sink: no recipe takes it or a role it fills, no design names a role it fills, no weapon draws it, no gate buys it."));
|
||||||
}
|
}
|
||||||
return faults;
|
return faults;
|
||||||
}
|
}
|
||||||
|
|
@ -243,7 +261,7 @@ public static class EconomyChecks
|
||||||
if (!depth.TryGetValue(input.Commodity.Key.Value, out var d)) return null;
|
if (!depth.TryGetValue(input.Commodity.Key.Value, out var d)) return null;
|
||||||
deepest = Math.Max(deepest, d);
|
deepest = Math.Max(deepest, d);
|
||||||
}
|
}
|
||||||
foreach (var role in Recipes.RolesOf(catalog.Get(recipe.Output)))
|
foreach (var role in Recipes.RoleInputsOf(catalog, recipe))
|
||||||
{
|
{
|
||||||
int? shallowest = null;
|
int? shallowest = null;
|
||||||
foreach (var filler in Recipes.Fillers(catalog, role.Name))
|
foreach (var filler in Recipes.Fillers(catalog, role.Name))
|
||||||
|
|
@ -254,7 +272,7 @@ public static class EconomyChecks
|
||||||
return deepest + 1;
|
return deepest + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hawkins-Simon over commodities plus one labour good, at the worst single filler of every role slot: the spectral radius
|
// Hawkins-Simon over commodities plus one labour good, at the worst single filler of every role slot (a recipe's role inputs): the spectral radius
|
||||||
// of the requirements matrix stays below one however the roles are filled. The worst fill is the spectral simplex
|
// of the requirements matrix stays below one however the roles are filled. The worst fill is the spectral simplex
|
||||||
// (Nesterov and Protasov 2013) on the matrix nudged strictly positive, then the radius of the chosen matrix is taken exactly.
|
// (Nesterov and Protasov 2013) on the matrix nudged strictly positive, then the radius of the chosen matrix is taken exactly.
|
||||||
public static List<EconomyFault> Productive(CultCache catalog, EconomyLoad load = null)
|
public static List<EconomyFault> Productive(CultCache catalog, EconomyLoad load = null)
|
||||||
|
|
@ -278,7 +296,7 @@ public static class EconomyChecks
|
||||||
foreach (var upkeep in recipe.Upkeep)
|
foreach (var upkeep in recipe.Upkeep)
|
||||||
if (index.TryGetValue(upkeep.Commodity.Key.Value, out var row) && upkeep.Count > 0) fixedTerms[row, j] += upkeep.Count * load.UpkeepFactor / recipe.OutputCount;
|
if (index.TryGetValue(upkeep.Commodity.Key.Value, out var row) && upkeep.Count > 0) fixedTerms[row, j] += upkeep.Count * load.UpkeepFactor / recipe.OutputCount;
|
||||||
fixedTerms[labour, j] = recipe.Workforce * (double) recipe.Cycle * load.WorkforceFactor / recipe.OutputCount;
|
fixedTerms[labour, j] = recipe.Workforce * (double) recipe.Cycle * load.WorkforceFactor / recipe.OutputCount;
|
||||||
foreach (var role in Recipes.RolesOf(catalog.Get(recipe.Output)))
|
foreach (var role in Recipes.RoleInputsOf(catalog, recipe))
|
||||||
{
|
{
|
||||||
var fillers = Recipes.Fillers(catalog, role.Name).Where(k => index.ContainsKey(k.Value)).Select(k => index[k.Value]).ToArray();
|
var fillers = Recipes.Fillers(catalog, role.Name).Where(k => index.ContainsKey(k.Value)).Select(k => index[k.Value]).ToArray();
|
||||||
if (fillers.Length > 0 && role.Count > 0) slots.Add((j, role.Count * load.InputFactor / recipe.OutputCount, fillers));
|
if (fillers.Length > 0 && role.Count > 0) slots.Add((j, role.Count * load.InputFactor / recipe.OutputCount, fillers));
|
||||||
|
|
@ -411,20 +429,19 @@ public static class EconomyChecks
|
||||||
// A processed good owes a consumer use when it is shared (rulings consumer-demand-shared-only, draw-is-one-use). Its uses are
|
// A processed good owes a consumer use when it is shared (rulings consumer-demand-shared-only, draw-is-one-use). Its uses are
|
||||||
// the gear or hull roles, and the rounds some weapon draws, that the goods downstream of it (itself included) fill or are; a
|
// the gear or hull roles, and the rounds some weapon draws, that the goods downstream of it (itself included) fill or are; a
|
||||||
// good used as upkeep is shared too. Shared means two or more such uses, or upkeep. A shared good must lead, through the
|
// good used as upkeep is shared too. Shared means two or more such uses, or upkeep. A shared good must lead, through the
|
||||||
// chain (an input edge or an upkeep edge), into a consumer good or a role one names. A good whose every use is one gear role is exempt like its final assembly.
|
// chain (an input, upkeep or role-input edge: a role input is an edge from each of its fillers), into a consumer good or a role one names. A good whose every use is one gear role is exempt like its final assembly.
|
||||||
// Derived from the recipe graph on every check; depth and distance play no part. Raw goods and the gate count for nothing.
|
// Derived from the recipe graph on every check; depth and distance play no part. Raw goods and the gate count for nothing.
|
||||||
public static List<EconomyFault> ConsumerDemand(CultCache catalog)
|
public static List<EconomyFault> ConsumerDemand(CultCache catalog)
|
||||||
{
|
{
|
||||||
var recipes = AllRecipes(catalog).Where(p => catalog.Get(p.Recipe.Output) != null).ToDictionary(p => p.Recipe.Output.Key.Value, p => p.Recipe, StringComparer.Ordinal);
|
var recipes = AllRecipes(catalog).Where(p => catalog.Get(p.Recipe.Output) != null).ToDictionary(p => p.Recipe.Output.Key.Value, p => p.Recipe, StringComparer.Ordinal);
|
||||||
var drawn = Drawn(catalog);
|
var drawn = Drawn(catalog);
|
||||||
bool IsCommodityRecipe(RecipeData r) => Recipes.IsCommodity(catalog.Get(r.Output));
|
bool IsCommodityRecipe(RecipeData r) => Recipes.IsCommodity(catalog.Get(r.Output));
|
||||||
var consumerRoles = new HashSet<string>(recipes.Values.Where(r => Recipes.IsConsumerGood(catalog, r))
|
var gearRoles = DesignRoles(catalog);
|
||||||
.SelectMany(r => Recipes.RolesOf(catalog.Get(r.Output))).Select(role => role.Name), StringComparer.Ordinal);
|
|
||||||
var gearRoles = new HashSet<string>(recipes.Values.Where(r => !Recipes.IsCommodity(catalog.Get(r.Output)))
|
|
||||||
.SelectMany(r => Recipes.RolesOf(catalog.Get(r.Output))).Select(role => role.Name), StringComparer.Ordinal);
|
|
||||||
var upkeep = new HashSet<string>(recipes.Values.SelectMany(r => r.Upkeep).Select(c => c.Commodity.Key.Value), StringComparer.Ordinal);
|
var upkeep = new HashSet<string>(recipes.Values.SelectMany(r => r.Upkeep).Select(c => c.Commodity.Key.Value), StringComparer.Ordinal);
|
||||||
var takenBy = recipes.Values.Where(IsCommodityRecipe)
|
var takenBy = recipes.Values.Where(IsCommodityRecipe)
|
||||||
.SelectMany(r => r.Inputs.Concat(r.Upkeep).Select(input => (Input: input.Commodity.Key.Value, Output: r.Output.Key.Value)))
|
.SelectMany(r => r.Inputs.Concat(r.Upkeep).Select(input => input.Commodity.Key.Value)
|
||||||
|
.Concat(Recipes.RoleInputsOf(catalog, r).SelectMany(role => Recipes.Fillers(catalog, role.Name)).Select(filler => filler.Value))
|
||||||
|
.Select(input => (Input: input, Output: r.Output.Key.Value)))
|
||||||
.ToLookup(edge => edge.Input, edge => edge.Output, StringComparer.Ordinal);
|
.ToLookup(edge => edge.Input, edge => edge.Output, StringComparer.Ordinal);
|
||||||
var faults = new List<EconomyFault>();
|
var faults = new List<EconomyFault>();
|
||||||
foreach (var key in recipes.Keys.OrderBy(k => k, StringComparer.Ordinal))
|
foreach (var key in recipes.Keys.OrderBy(k => k, StringComparer.Ordinal))
|
||||||
|
|
@ -437,7 +454,7 @@ public static class EconomyChecks
|
||||||
foreach (var next in takenBy[open.Pop()])
|
foreach (var next in takenBy[open.Pop()])
|
||||||
if (reached.Add(next)) open.Push(next);
|
if (reached.Add(next)) open.Push(next);
|
||||||
var members = reached.Select(k => recipes[k]).ToList();
|
var members = reached.Select(k => recipes[k]).ToList();
|
||||||
if (members.Any(r => Recipes.IsConsumerGood(catalog, r) || r.Fills.Any(consumerRoles.Contains))) continue;
|
if (members.Any(r => Recipes.IsConsumerGood(catalog, r))) continue;
|
||||||
var uses = members.SelectMany(r => r.Fills).Where(gearRoles.Contains).Distinct(StringComparer.Ordinal).OrderBy(role => role, StringComparer.Ordinal)
|
var uses = members.SelectMany(r => r.Fills).Where(gearRoles.Contains).Distinct(StringComparer.Ordinal).OrderBy(role => role, StringComparer.Ordinal)
|
||||||
.Concat(reached.Where(drawn.Contains).OrderBy(k => k, StringComparer.Ordinal).Select(k => catalog.Get(recipes[k].Output).Name)).ToList();
|
.Concat(reached.Where(drawn.Contains).OrderBy(k => k, StringComparer.Ordinal).Select(k => catalog.Get(recipes[k].Output).Name)).ToList();
|
||||||
var asUpkeep = reached.Any(upkeep.Contains);
|
var asUpkeep = reached.Any(upkeep.Contains);
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@ using MessagePack;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
// How a good is made: one authored record per output, keyed recipe:<output key>, so a second recipe for one output replaces the
|
// How a good is made: one authored record per output, keyed recipe:<output key>, so a second recipe for one output replaces the
|
||||||
// first. A commodity's recipe says what it takes and which roles it can fill. A role recipe (the output is a design with roles,
|
// first. A commodity's recipe takes named commodities (Inputs) and roles (RoleInputs), each role filled by any commodity whose
|
||||||
// a ship hull, a piece of gear, a consumer good) restates nothing of the design: its inputs are derived from the design's Roles and
|
// recipe Fills it, and says which roles it can fill. A role recipe (the output is a design with roles, a ship hull, a piece of
|
||||||
// their counts, each filled by any commodity whose recipe Fills the role. Station and Turret hulls take no recipe.
|
// gear, a consumer good) restates nothing of the design: its role inputs are the design's Roles and their counts. Station and
|
||||||
|
// Turret hulls take no recipe.
|
||||||
[CultDocument("aetheria.recipedata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
|
[CultDocument("aetheria.recipedata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
|
||||||
public class RecipeData
|
public class RecipeData
|
||||||
{
|
{
|
||||||
|
|
@ -46,6 +47,11 @@ public class RecipeData
|
||||||
[JsonProperty("fills"), Key(8)]
|
[JsonProperty("fills"), Key(8)]
|
||||||
public List<string> Fills = new List<string>();
|
public List<string> Fills = new List<string>();
|
||||||
|
|
||||||
|
// Roles one cycle consumes, any filler will do and the facility picks (ruling ores-fill-metal-roles). Empty on a role recipe;
|
||||||
|
// records written before key 9 read it as empty.
|
||||||
|
[JsonProperty("roleInputs"), Key(9)]
|
||||||
|
public List<ItemRole> RoleInputs = new List<ItemRole>();
|
||||||
|
|
||||||
public static CultRecordKey KeyOf(CultRecordKey outputKey) => new CultRecordKey("recipe:" + outputKey.Value);
|
public static CultRecordKey KeyOf(CultRecordKey outputKey) => new CultRecordKey("recipe:" + outputKey.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,7 +98,8 @@ public class OutsideMarketRow
|
||||||
public float RatePerHour;
|
public float RatePerHour;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The one reader of recipes: which recipe makes a good, which commodities fill a role, and what a good is by its recipe.
|
// The one reader of recipes: which recipe makes a good, a recipe's role slots (RoleInputsOf), which commodities fill a role, and
|
||||||
|
// what a good is by its recipe.
|
||||||
public static class Recipes
|
public static class Recipes
|
||||||
{
|
{
|
||||||
public static RecipeData RecipeOf(CultCache catalog, ItemData item) =>
|
public static RecipeData RecipeOf(CultCache catalog, ItemData item) =>
|
||||||
|
|
@ -110,8 +117,12 @@ public static class Recipes
|
||||||
return output is CraftedItemData && RolesOf(output).Count > 0;
|
return output is CraftedItemData && RolesOf(output).Count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raw: no inputs and not a role recipe. Everything else is processed. Derived, never stored.
|
// The one reader of a recipe's role slots: the design's Roles for a role recipe, else the recipe's RoleInputs.
|
||||||
public static bool IsRaw(CultCache catalog, RecipeData recipe) => recipe.Inputs.Count == 0 && !IsRoleRecipe(catalog, recipe);
|
public static IReadOnlyList<ItemRole> RoleInputsOf(CultCache catalog, RecipeData recipe) =>
|
||||||
|
IsRoleRecipe(catalog, recipe) ? RolesOf(catalog.Get(recipe.Output)) : (IReadOnlyList<ItemRole>) recipe.RoleInputs ?? new List<ItemRole>();
|
||||||
|
|
||||||
|
// Raw: takes no commodity and no role. Everything else is processed. Derived, never stored.
|
||||||
|
public static bool IsRaw(CultCache catalog, RecipeData recipe) => recipe.Inputs.Count == 0 && RoleInputsOf(catalog, recipe).Count == 0;
|
||||||
|
|
||||||
// A commodity made by a role recipe: it is assembled from the same components gear is.
|
// A commodity made by a role recipe: it is assembled from the same components gear is.
|
||||||
public static bool IsConsumerGood(CultCache catalog, RecipeData recipe) =>
|
public static bool IsConsumerGood(CultCache catalog, RecipeData recipe) =>
|
||||||
|
|
|
||||||
|
|
@ -96,10 +96,8 @@ public static class EconomyCommand
|
||||||
output.WriteLine();
|
output.WriteLine();
|
||||||
foreach (var recipe in catalog.GetAll<RecipeData>().OrderBy(r => catalog.RefOf(r).Key.Value, StringComparer.Ordinal))
|
foreach (var recipe in catalog.GetAll<RecipeData>().OrderBy(r => catalog.RefOf(r).Key.Value, StringComparer.Ordinal))
|
||||||
{
|
{
|
||||||
var made = catalog.Get(recipe.Output);
|
var takes = string.Join(", ", (recipe.Inputs ?? new List<CommodityCount>()).Where(c => c != null).Select(c => $"{c.Count} x {c.Commodity.Key.Value}")
|
||||||
var takes = Recipes.IsRoleRecipe(catalog, recipe)
|
.Concat(Recipes.RoleInputsOf(catalog, recipe).Where(r => r != null).Select(r => $"{r.Count} x {r.Name}")));
|
||||||
? string.Join(", ", Recipes.RolesOf(made).Select(r => $"{r.Count} x {r.Name}"))
|
|
||||||
: string.Join(", ", (recipe.Inputs ?? new List<CommodityCount>()).Where(c => c != null).Select(c => $"{c.Count} x {c.Commodity.Key.Value}"));
|
|
||||||
output.WriteLine($"{recipe.Output.Key.Value} <- {(takes.Length == 0 ? "nothing" : takes)} | {catalog.Get(recipe.StationKind)?.Name ?? "no kind"}, cycle {recipe.Cycle}, workforce {recipe.Workforce} | {recipe.LoreHook}");
|
output.WriteLine($"{recipe.Output.Key.Value} <- {(takes.Length == 0 ? "nothing" : takes)} | {catalog.Get(recipe.StationKind)?.Name ?? "no kind"}, cycle {recipe.Cycle}, workforce {recipe.Workforce} | {recipe.LoreHook}");
|
||||||
}
|
}
|
||||||
return report.Faults.Count == 0 ? 0 : 1;
|
return report.Faults.Count == 0 ? 0 : 1;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue