using System; using System.Collections.Generic; using System.Linq; using GameCult.Caching; using MessagePack; using Newtonsoft.Json; // How a good is made: one authored record per output, keyed recipe:, 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, // 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 // their counts, each filled by any commodity whose recipe Fills the role. Station and Turret hulls take no recipe. [CultDocument("aetheria.recipedata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)] public class RecipeData { [JsonProperty("output"), Key(0)] public CultRecordRef Output; [JsonProperty("outputCount"), Key(1)] public int OutputCount = 1; // Commodities one cycle consumes. Empty on a raw good and on a role recipe. [JsonProperty("inputs"), Key(2)] public List Inputs = new List(); // Seconds of sim time for one cycle. [JsonProperty("cycle"), Key(3)] public float Cycle = 1; // Crew per running facility; no kind until workforce kinds exist. [JsonProperty("workforce"), Key(4)] public int Workforce; // Commodities one cycle uses up on the side (power, coolant), per cycle. [JsonProperty("upkeep"), Key(5)] public List Upkeep = new List(); // The kind of station that runs this recipe. [JsonProperty("stationKind"), Key(6)] public CultRecordRef StationKind; // Why this good exists in the world, in the operator's reading. Presence is checked, never its meaning. [JsonProperty("loreHook"), Key(7)] public string LoreHook; // The roles of a design this commodity can fill. Only a commodity's recipe has any. [JsonProperty("fills"), Key(8)] public List Fills = new List(); public static CultRecordKey KeyOf(CultRecordKey outputKey) => new CultRecordKey("recipe:" + outputKey.Value); } [MessagePackObject, JsonObject(MemberSerialization.OptIn)] public class CommodityCount { [JsonProperty("commodity"), Key(0)] public CultRecordRef Commodity; [JsonProperty("count"), Key(1)] public int Count = 1; } // A region gate's trade with the world beyond it: what the outside buys and sells, at what price and at most how fast. // The union of all records is the outside; keyed outside-market:. [CultDocument("aetheria.outsidemarketdata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)] public class OutsideMarketData { [CultName, JsonProperty("name"), Key(0)] public string Name; [JsonProperty("rows"), Key(1)] public List Rows = new List(); public static CultRecordKey KeyOf(string id) => new CultRecordKey("outside-market:" + id); } [MessagePackObject, JsonObject(MemberSerialization.OptIn)] public class OutsideMarketRow { [JsonProperty("commodity"), Key(0)] public CultRecordRef Commodity; [JsonProperty("price"), Key(1)] public int Price; [JsonProperty("buys"), Key(2)] public bool Buys; [JsonProperty("sells"), Key(3)] public bool Sells; [JsonProperty("ratePerHour"), Key(4)] 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. public static class Recipes { public static RecipeData RecipeOf(CultCache catalog, ItemData item) => item == null ? null : catalog.Get(RecipeData.KeyOf(catalog.RefOf(item).Key)); public static bool IsCommodity(ItemData item) => item is SimpleCommodityData || item is CompoundCommodityData; public static IReadOnlyList RolesOf(ItemData item) => (item as CraftedItemData)?.Roles ?? new List(); // A recipe whose output is a design with roles: a part list rather than a list of inputs. public static bool IsRoleRecipe(CultCache catalog, RecipeData recipe) { var output = catalog.Get(recipe.Output); return output is CraftedItemData && RolesOf(output).Count > 0; } // Raw: no inputs and not a role recipe. Everything else is processed. Derived, never stored. public static bool IsRaw(CultCache catalog, RecipeData recipe) => recipe.Inputs.Count == 0 && !IsRoleRecipe(catalog, recipe); // A commodity made by a role recipe: it is assembled from the same components gear is. public static bool IsConsumerGood(CultCache catalog, RecipeData recipe) => IsCommodity(catalog.Get(recipe.Output)) && IsRoleRecipe(catalog, recipe); // The commodities whose recipes Fill the role, ordered by key so a choice never depends on store order. public static CultRecordKey[] Fillers(CultCache catalog, string role) => catalog.GetAll() .Where(recipe => recipe.Fills != null && recipe.Fills.Contains(role) && IsCommodity(catalog.Get(recipe.Output))) .Select(recipe => recipe.Output.Key) .OrderBy(key => key.Value, StringComparer.Ordinal) .ToArray(); }