diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index fe2671b542..b69c8eb5bc 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -30,6 +30,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.Shared.Utility; using Content.Server._White; +using Content.Server._White.GuideGenerator; using Content.Server._White.JoinQueue; using Content.Server._White.Jukebox; using Content.Server._White.PandaSocket.Main; @@ -145,6 +146,15 @@ namespace Content.Server.Entry file = resourceManager.UserData.OpenWriteText(resPath.WithName("react_" + dest)); ReactionJsonGenerator.PublishJson(file); file.Flush(); + file = resourceManager.UserData.OpenWriteText(resPath.WithName("entity_" + dest)); + EntityJsonGenerator.PublishJson(file); + file.Flush(); + file = resourceManager.UserData.OpenWriteText(resPath.WithName("mealrecipes_" + dest)); + MealsRecipesJsonGenerator.PublishJson(file); + file.Flush(); + file = resourceManager.UserData.OpenWriteText(resPath.WithName("healthchangereagents_" + dest)); + HealthChangeReagentsJsonGenerator.PublishJson(file); + file.Flush(); IoCManager.Resolve().Shutdown("Data generation done"); } else diff --git a/Content.Server/GuideGenerator/ReactionJsonGenerator.cs b/Content.Server/GuideGenerator/ReactionJsonGenerator.cs index 123dd5f8eb..679d900dff 100644 --- a/Content.Server/GuideGenerator/ReactionJsonGenerator.cs +++ b/Content.Server/GuideGenerator/ReactionJsonGenerator.cs @@ -7,7 +7,7 @@ using Robust.Shared.Prototypes; namespace Content.Server.GuideGenerator; -public sealed class ReactionJsonGenerator +public sealed partial class ReactionJsonGenerator { public static void PublishJson(StreamWriter file) { @@ -19,13 +19,16 @@ public sealed class ReactionJsonGenerator .Select(x => new ReactionEntry(x)) .ToDictionary(x => x.Id, x => x); + if (reactions is not null) AddMixingCategories(reactions, prototype); + var serializeOptions = new JsonSerializerOptions { WriteIndented = true, Converters = { new UniversalJsonConverter(), - } + }, + NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals }; file.Write(JsonSerializer.Serialize(reactions, serializeOptions)); diff --git a/Content.Server/GuideGenerator/ReagentEntry.cs b/Content.Server/GuideGenerator/ReagentEntry.cs index ab9e758206..68af067df7 100644 --- a/Content.Server/GuideGenerator/ReagentEntry.cs +++ b/Content.Server/GuideGenerator/ReagentEntry.cs @@ -1,10 +1,8 @@ using System.Linq; using System.Text.Json.Serialization; -using Content.Server.Body.Components; -using Content.Shared.Body.Prototypes; +using Content.Server._White.GuideGenerator; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; -using Robust.Shared.Prototypes; namespace Content.Server.GuideGenerator; @@ -31,8 +29,11 @@ public sealed class ReagentEntry [JsonPropertyName("recipes")] public List Recipes { get; } = new(); + [JsonPropertyName("textColor")] + public string TextColor { get; } + [JsonPropertyName("metabolisms")] - public Dictionary? Metabolisms { get; } + public Dictionary? Metabolisms { get; } public ReagentEntry(ReagentPrototype proto) { @@ -42,7 +43,13 @@ public sealed class ReagentEntry Description = proto.LocalizedDescription; PhysicalDescription = proto.LocalizedPhysicalDescription; SubstanceColor = proto.SubstanceColor.ToHex(); - Metabolisms = proto.Metabolisms?.ToDictionary(x => x.Key.Id, x => x.Value); + var r = proto.SubstanceColor.R; + var g = proto.SubstanceColor.G; + var b = proto.SubstanceColor.B; + TextColor = (0.2126f * r + 0.7152f * g + 0.0722f * b > 0.5 + ? Color.Black + : Color.White).ToHex(); + Metabolisms = proto.Metabolisms?.ToDictionary(x => x.Key.Id, x => new _White.GuideGenerator.ReagentEffectsEntry(x.Value)); } } @@ -60,7 +67,18 @@ public sealed class ReactionEntry [JsonPropertyName("products")] public Dictionary Products { get; } + [JsonPropertyName("mixingCategories")] + public List MixingCategories { get; } = new(); + [JsonPropertyName("minTemp")] + public float MinTemp { get; } + [JsonPropertyName("maxTemp")] + public float MaxTemp { get; } + [JsonPropertyName("hasMax")] + public bool HasMax { get; } + [JsonPropertyName("effects")] + public List ExportEffects { get; } = new(); + [JsonIgnore] public List Effects { get; } public ReactionEntry(ReactionPrototype proto) @@ -76,6 +94,11 @@ public sealed class ReactionEntry .Select(x => KeyValuePair.Create(x.Key, x.Value.Float())) .ToDictionary(x => x.Key, x => x.Value); Effects = proto.Effects; + + ExportEffects = proto.Effects.Select(x => new ReagentEffectEntry(x)).ToList(); + MinTemp = proto.MinimumTemperature; + MaxTemp = proto.MaximumTemperature; + HasMax = !float.IsPositiveInfinity(MaxTemp); } } diff --git a/Content.Server/_White/GuideGenerator/EntityEntry.cs b/Content.Server/_White/GuideGenerator/EntityEntry.cs new file mode 100644 index 0000000000..8ec1b1536e --- /dev/null +++ b/Content.Server/_White/GuideGenerator/EntityEntry.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; +using Robust.Shared.Prototypes; + +namespace Content.Server.GuideGenerator; + +public sealed class EntityEntry +{ + [JsonPropertyName("id")] + public string Id { get; } + + [JsonPropertyName("name")] + public string Name { get; } + + [JsonPropertyName("desc")] + public string Description { get; } + + public EntityEntry(EntityPrototype proto) + { + Id = proto.ID; + Name = TextTools.TextTools.CapitalizeString(proto.Name); + Description = proto.Description; + } +} diff --git a/Content.Server/_White/GuideGenerator/EntityJsonGenerator.cs b/Content.Server/_White/GuideGenerator/EntityJsonGenerator.cs new file mode 100644 index 0000000000..83669c23cd --- /dev/null +++ b/Content.Server/_White/GuideGenerator/EntityJsonGenerator.cs @@ -0,0 +1,27 @@ +using System.IO; +using System.Linq; +using System.Text.Json; +using Robust.Shared.Prototypes; + +namespace Content.Server.GuideGenerator; + +public sealed class EntityJsonGenerator +{ + public static void PublishJson(StreamWriter file) + { + var prototype = IoCManager.Resolve(); + var prototypes = + prototype + .EnumeratePrototypes() + .Where(x => !x.Abstract) + .Select(x => new EntityEntry(x)) + .ToDictionary(x => x.Id, x => x); + + var serializeOptions = new JsonSerializerOptions + { + WriteIndented = true, + }; + + file.Write(JsonSerializer.Serialize(prototypes, serializeOptions)); + } +} diff --git a/Content.Server/_White/GuideGenerator/GrindRecipeEntry.cs b/Content.Server/_White/GuideGenerator/GrindRecipeEntry.cs new file mode 100644 index 0000000000..6b0cd08434 --- /dev/null +++ b/Content.Server/_White/GuideGenerator/GrindRecipeEntry.cs @@ -0,0 +1,74 @@ +using System.Text.Json.Serialization; +using Robust.Shared.Prototypes; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Kitchen.Components; + +namespace Content.Server.GuideGenerator; + +public sealed class GrindRecipeEntry +{ + /// + /// Id of grindable item + /// + [JsonPropertyName("id")] + public string Id { get; } + + /// + /// Human-readable name of recipe. + /// Should automatically be localized by default + /// + [JsonPropertyName("name")] + public string Name { get; } + + /// + /// Type of recipe + /// + [JsonPropertyName("type")] + public string Type { get; } + + /// + /// Item that will be grinded into something + /// + [JsonPropertyName("input")] + public string Input { get; } + + /// + /// Dictionary of reagents that entity contains; aka "Recipe Result" + /// + [JsonPropertyName("result")] + public Dictionary? Result { get; } = new Dictionary(); + + + public GrindRecipeEntry(EntityPrototype proto) + { + Id = proto.ID; + Name = TextTools.TextTools.CapitalizeString(proto.Name); + Type = "grindableRecipes"; + Input = proto.ID; + var foodSolutionName = "food"; // default to food because everything in prototypes defaults to "food" + + // Now, to become a recipe, entity must: + // A) Have "Extractable" component on it. + // B) Have "SolutionContainerManager" component on it. + // C) Have "GrindableSolution" declared in "SolutionContainerManager" component. + // D) Have solution with name declared in "SolutionContainerManager.GrindableSolution" inside its "SolutionContainerManager" component. + // F) Have "Food" in its name (see Content.Server/_White/GuideGenerator/MealsRecipesJsonGenerator.cs) + if (proto.Components.TryGetComponent("Extractable", out var extractableComp) && proto.Components.TryGetComponent("SolutionContainerManager", out var solutionCompRaw)) + { + var extractable = (ExtractableComponent) extractableComp; + var solutionComp = (SolutionContainerManagerComponent) solutionCompRaw; + foodSolutionName = extractable.GrindableSolution; + + if (solutionComp.Solutions != null && foodSolutionName != null) + { + foreach (ReagentQuantity reagent in solutionComp.Solutions[(string) foodSolutionName].Contents) + { + Result[reagent.Reagent.Prototype] = reagent.Quantity.Int(); + } + } + else + Result = null; + } + } +} diff --git a/Content.Server/_White/GuideGenerator/HealthChangeReagentsJsonGenerator.cs b/Content.Server/_White/GuideGenerator/HealthChangeReagentsJsonGenerator.cs new file mode 100644 index 0000000000..6f514f56db --- /dev/null +++ b/Content.Server/_White/GuideGenerator/HealthChangeReagentsJsonGenerator.cs @@ -0,0 +1,93 @@ +using Content.Server.Chemistry.ReagentEffects; +using Content.Shared.Chemistry.Reagent; +using Robust.Shared.Prototypes; +using System.IO; +using System.Linq; +using System.Text.Json; +using static Content.Server.GuideGenerator.ChemistryJsonGenerator; + +namespace Content.Server._White.GuideGenerator; +public sealed class HealthChangeReagentsJsonGenerator +{ + public static void PublishJson(StreamWriter file) + { + var prototype = IoCManager.Resolve(); + + Dictionary>> healthChangeReagents = new(); + + // Сбор данных + foreach (var reagent in prototype.EnumeratePrototypes()) + { + if (reagent.Metabolisms is null) continue; + + foreach (var metabolism in reagent.Metabolisms) + { + foreach (HealthChange effect in metabolism.Value.Effects.Where(x => x is HealthChange)) + { + foreach (var damage in effect.Damage.DamageDict) + { + var damageType = damage.Key; + var damageChangeType = damage.Value.Float() < 0 ? "health" : "damage"; + + if (!healthChangeReagents.ContainsKey(damageType)) + { + healthChangeReagents.Add(damageType, new()); + } + + if (!healthChangeReagents[damageType].ContainsKey(damageChangeType)) + { + healthChangeReagents[damageType].Add(damageChangeType, new()); + } + + // Берем максимальный показатель (один реагент может наносить разный урон при разных условиях) + var damageChangeValueAbs = Math.Abs(damage.Value.Float() / metabolism.Value.MetabolismRate.Float()); // вычисляем показатель за 1 ед. вещества, а не 1 сек. нахождения я в организме. + if (healthChangeReagents[damageType][damageChangeType].TryGetValue(reagent.ID, out var previousValue)) + { + healthChangeReagents[damageType][damageChangeType][reagent.ID] = Math.Max(previousValue, damageChangeValueAbs); + } + else healthChangeReagents[damageType][damageChangeType].Add(reagent.ID, damageChangeValueAbs); + } + } + } + } + + // Сортировка + Dictionary>> healthChangeReagentsSorted = new(); + + foreach (var damageType in healthChangeReagents) + { + foreach (var damageChangeType in damageType.Value) + { + foreach (var reagent in damageChangeType.Value) + { + if (!healthChangeReagentsSorted.ContainsKey(damageType.Key)) + { + healthChangeReagentsSorted.Add(damageType.Key, new()); + } + + if (!healthChangeReagentsSorted[damageType.Key].ContainsKey(damageChangeType.Key)) + { + healthChangeReagentsSorted[damageType.Key].Add(damageChangeType.Key, new()); + } + + healthChangeReagentsSorted[damageType.Key][damageChangeType.Key].Add(reagent.Key); + + } + + healthChangeReagentsSorted[damageType.Key][damageChangeType.Key].Sort(Comparer.Create((s1, s2) => + -healthChangeReagents[damageType.Key][damageChangeType.Key][s1].CompareTo(healthChangeReagents[damageType.Key][damageChangeType.Key][s2]))); + } + } + + + + var serializeOptions = new JsonSerializerOptions + { + WriteIndented = true, + NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals + }; + + file.Write(JsonSerializer.Serialize(healthChangeReagentsSorted, serializeOptions)); + } +} + diff --git a/Content.Server/_White/GuideGenerator/HeatableRecipeEntry.cs b/Content.Server/_White/GuideGenerator/HeatableRecipeEntry.cs new file mode 100644 index 0000000000..73f22f5e64 --- /dev/null +++ b/Content.Server/_White/GuideGenerator/HeatableRecipeEntry.cs @@ -0,0 +1,93 @@ +using System.Text.Json.Serialization; +using Robust.Shared.Prototypes; +using Content.Shared.Construction; +using Content.Shared.Construction.Prototypes; +using Content.Shared.Construction.Steps; +using Robust.Server.GameObjects; + +namespace Content.Server.GuideGenerator; + +public sealed class HeatableRecipeEntry +{ + /// + /// Id of recipe + /// + [JsonPropertyName("id")] + public string Id { get; } + + /// + /// Human-readable name of recipe. + /// Should automatically be localized by default + /// + [JsonPropertyName("name")] + public string Name { get; } + + /// + /// Type of recipe + /// + [JsonPropertyName("type")] + public string Type { get; } + + /// + /// Temp, required for "input" thing to become "result" thing + /// + [JsonPropertyName("minTemp")] + public float MinTemp { get; } + + /// + /// Item that will be transformed into something with enough temp + /// + [JsonPropertyName("input")] + public string Input { get; } + + /// + /// Result of a recipe. + /// If it is null then recipe does not exist or we could not get recipe info. + /// + [JsonPropertyName("result")] + public string? Result { get; } + + + public HeatableRecipeEntry( + ConstructionGraphPrototype constructionProto, // to get data from construction prototype (minTemp, result) + EntityPrototype entityPrototype // to get entity data (name, input entity id) + ) + { + var graphID = ""; + var startNode = constructionProto.Nodes[constructionProto.Start!]; + if (entityPrototype.Components.TryGetComponent("Construction", out var constructionCompRaw)) // does entity actually has Construction component? + { + foreach (var nodeEdgeRaw in startNode.Edges) // because we don't know what node contains heating step (in case if it is not constructionProto.Start) let's check every node and see if we will get anything + { + var nodeEdge = (ConstructionGraphEdge)nodeEdgeRaw; + foreach (var nodeStepRaw in nodeEdge.Steps) + { + if (nodeStepRaw.GetType().Equals(typeof(TemperatureConstructionGraphStep))) // TemperatureConstructionGraphStep is used only in steaks recipes, so for now we can afford it + { + var nodeStep = (TemperatureConstructionGraphStep)nodeStepRaw; + graphID = nodeEdge.Target; // required to check when we need to leave second loop; this is the best solution, because nodeEdge.Target is marked as required datafield and cannot be null + ServerEntityManager em = new(); + MinTemp = nodeStep.MinTemperature.HasValue ? nodeStep.MinTemperature.Value : 0; + Result = nodeStep.MinTemperature.HasValue ? constructionProto.Nodes[nodeEdge.Target].Entity.GetId(null, null, new GraphNodeEntityArgs(em)) : null; + break; + } + } + if (graphID != "") break; // we're done! let's leave! + } + if (graphID == "") // we've failed to get anything :( + { + MinTemp = 0; + Result = null; + } + } + else // if entity does not have construction component then it cannot be constructed - (c) Jason Statham + { + MinTemp = 0; + Result = null; + } + Input = entityPrototype.ID; + Name = TextTools.TextTools.CapitalizeString(entityPrototype.Name); + Id = entityPrototype.ID; + Type = "heatableRecipes"; + } +} diff --git a/Content.Server/_White/GuideGenerator/MealsRecipesJsonGenerator.cs b/Content.Server/_White/GuideGenerator/MealsRecipesJsonGenerator.cs new file mode 100644 index 0000000000..fde7a38fe2 --- /dev/null +++ b/Content.Server/_White/GuideGenerator/MealsRecipesJsonGenerator.cs @@ -0,0 +1,128 @@ +using System.IO; +using System.Text.RegularExpressions; +using System.Linq; +using System.Text.Json; +using Content.Shared.Chemistry.Reaction; +using Content.Shared.Kitchen; +using Robust.Shared.Prototypes; +using Content.Shared.Construction.Prototypes; +using Content.Server.Construction.Components; +using Content.Server.Chemistry.ReactionEffects; + +namespace Content.Server.GuideGenerator; + +public sealed class MealsRecipesJsonGenerator +{ + public static void PublishJson(StreamWriter file) + { + var prototype = IoCManager.Resolve(); + var entities = prototype.EnumeratePrototypes(); + var constructable = prototype.EnumeratePrototypes(); + var output = new Dictionary(); + + var microwaveRecipes = + prototype + .EnumeratePrototypes() + .Select(x => new MicrowaveRecipeEntry(x)) + .ToDictionary(x => x.Id, x => x); + + + var sliceableRecipes = + entities + .Where(x => x.Components.TryGetComponent("SliceableFood", out var _)) + .Select(x => new SliceRecipeEntry(x)) + .Where(x => x.Result != "") // SOMEONE THOUGHT THAT IT WOULD BE A GREAT IDEA TO PUT COMPONENT ON AN ITEM WITHOUT SPECIFYING THE OUTPUT THING. + .Where(x => x.Count > 0) // Just in case. + .ToDictionary(x => x.Id, x => x); + + + var grindableRecipes = + entities + .Where(x => x.Components.TryGetComponent("Extractable", out var _)) + .Where(x => x.Components.TryGetComponent("SolutionContainerManager", out var _)) + .Where(x => (Regex.Match(x.ID.ToLower().Trim(), @".*[Ff]ood*").Success)) // we dont need some "organ" or "pills" prototypes. + .Select(x => new GrindRecipeEntry(x)) + .Where(x => x.Result != null) + .ToDictionary(x => x.Id, x => x); + + + // construction-related items start + var constructionGraphs = + constructable + .Where(x => (Regex.Match(x.ID.ToLower().Trim(), @".*.*[Bb]acon*|.*[Ss]teak*|[Pp]izza*|[Tt]ortilla*|[Ee]gg*").Success)) // we only need recipes that has "bacon", "steak", "pizza" "tortilla" and "egg" in it, since they are the only "constructable" recipes + .ToDictionary(x => x.ID, x => x); + + var constructableEntities = // list of entities which names match regex and has Construction component + entities + .Where(x => (Regex.Match(x.ID.ToLower().Trim(), @"(? x.Components.ContainsKey("Construction")) + .ToList(); + + var entityGraphs = new Dictionary(); // BFH. Since we cannot get component from another .Where call (because of CS0103), let's keep everything in one temp dictionary. + + foreach (var ent in constructableEntities) + { + if (ent.Components.TryGetComponent("Construction", out var constructionCompRaw)) + { + var constructionComp = (ConstructionComponent)constructionCompRaw; + entityGraphs[ent.ID] = constructionComp.Graph; + } + } + + var constructableHeatableEntities = constructableEntities // let's finally create our heatable recipes list + .Where(x => constructionGraphs.ContainsKey(entityGraphs[x.ID])) + .Select(x => new HeatableRecipeEntry(constructionGraphs[entityGraphs[x.ID]], x)) + .Where(x => (x.Result != null)) + .Where(x => x.Id != x.Result) // sometimes things dupe (for example if someone puts construction component on both inout and output things) + .ToDictionary(x => x.Id, x => x); + + + var constructableToolableEntities = constructableEntities // let's finally create our toolmade recipes list + .Where(x => constructionGraphs.ContainsKey(entityGraphs[x.ID])) + .Select(x => new ToolRecipeEntry(constructionGraphs[entityGraphs[x.ID]], x)) + .Where(x => (x.Result != null)) + .Where(x => x.Id != x.Result) // the same here, things sometimes dupe + .ToDictionary(x => x.Id, x => x); + // construction-related items end + + // reaction-related items start + var reactionPrototypes = + prototype + .EnumeratePrototypes() + .Select(x => new ReactionEntry(x)) + .ToList(); + + + var mixableRecipes = new Dictionary>(); // this is a list because we have https://station14.ru/wiki/Модуль:Chemistry_Lookup that already has everything we need and does everything for us. + + foreach (var react in reactionPrototypes) + { + foreach (var effect in react.Effects) + if (effect.GetType().Equals(typeof(CreateEntityReactionEffect))) + { + var trueEffect = (CreateEntityReactionEffect)effect; + if (Regex.Match(trueEffect.Entity.ToLower().Trim(), @".*[Ff]ood*").Success) if (!mixableRecipes.ContainsKey(react.Id)) + { + mixableRecipes[react.Id] = new Dictionary(); + mixableRecipes[react.Id]["id"] = react.Id; + mixableRecipes[react.Id]["type"] = "mixableRecipes"; + } + } + } + // reaction-related items end + + output["microwaveRecipes"] = microwaveRecipes; + output["sliceableRecipes"] = sliceableRecipes; + output["grindableRecipes"] = grindableRecipes; + output["heatableRecipes"] = constructableHeatableEntities; + output["toolmadeRecipes"] = constructableToolableEntities; + output["mixableRecipes"] = mixableRecipes; + + var serializeOptions = new JsonSerializerOptions + { + WriteIndented = true + }; + + file.Write(JsonSerializer.Serialize(output, serializeOptions)); + } +} diff --git a/Content.Server/_White/GuideGenerator/MicrowaveRecipeEntry.cs b/Content.Server/_White/GuideGenerator/MicrowaveRecipeEntry.cs new file mode 100644 index 0000000000..a38dcfd6c6 --- /dev/null +++ b/Content.Server/_White/GuideGenerator/MicrowaveRecipeEntry.cs @@ -0,0 +1,71 @@ +using System.Linq; +using System.Text.Json.Serialization; +using Content.Shared.Kitchen; + +namespace Content.Server.GuideGenerator; + +public sealed class MicrowaveRecipeEntry +{ + /// + /// Id of recipe + /// + [JsonPropertyName("id")] + public string Id { get; } + + /// + /// Human-readable name of recipe. + /// Should automatically be localized by default + /// + [JsonPropertyName("name")] + public string Name { get; } + + /// + /// Type of recipe + /// + [JsonPropertyName("type")] + public string Type { get; } + + /// + /// Time to cook something (for microwave recipes) + /// + [JsonPropertyName("time")] + public uint Time { get; } + + /// + /// Solids required to cook something + /// + [JsonPropertyName("solids")] + public Dictionary Solids { get; } + + /// + /// Reagents required to cook something + /// + [JsonPropertyName("reagents")] + public Dictionary Reagents { get; } + + /// + /// Result of a recipe + /// + [JsonPropertyName("result")] + public string Result { get; } + + + public MicrowaveRecipeEntry(FoodRecipePrototype proto) + { + Id = proto.ID; + Name = TextTools.TextTools.CapitalizeString(proto.Name); + Type = "microwaveRecipes"; + Time = proto.CookTime; + Solids = proto.IngredientsSolids + .ToDictionary( + sol => sol.Key, + sol => (uint)(int)sol.Value.Int() + ); + Reagents = proto.IngredientsReagents + .ToDictionary( + rea => rea.Key, + rea => (uint)(int)rea.Value.Int() + ); + Result = proto.Result; + } +} diff --git a/Content.Server/_White/GuideGenerator/MixingCategoryEntry.cs b/Content.Server/_White/GuideGenerator/MixingCategoryEntry.cs new file mode 100644 index 0000000000..37162c9b30 --- /dev/null +++ b/Content.Server/_White/GuideGenerator/MixingCategoryEntry.cs @@ -0,0 +1,19 @@ +using Content.Shared.Chemistry.Reaction; +using System.Text.Json.Serialization; + +namespace Content.Server._White.GuideGenerator; + +public sealed class MixingCategoryEntry +{ + [JsonPropertyName("name")] + public string Name { get; } + + [JsonPropertyName("id")] + public string Id { get; } + + public MixingCategoryEntry(MixingCategoryPrototype proto) + { + Name = Loc.GetString(proto.VerbText); + Id = proto.ID; + } +} diff --git a/Content.Server/_White/GuideGenerator/ReactionJsonGenerator.cs b/Content.Server/_White/GuideGenerator/ReactionJsonGenerator.cs new file mode 100644 index 0000000000..9e8c118a0f --- /dev/null +++ b/Content.Server/_White/GuideGenerator/ReactionJsonGenerator.cs @@ -0,0 +1,35 @@ +using Content.Server._White.GuideGenerator; +using Content.Shared.Chemistry.Reaction; +using Robust.Shared.Prototypes; + +namespace Content.Server.GuideGenerator; +public sealed partial class ReactionJsonGenerator +{ + [ValidatePrototypeId] + private const string DefaultMixingCategory = "DummyMix"; + + private static void AddMixingCategories(Dictionary reactions, IPrototypeManager prototype) + { + foreach (var reaction in reactions) + { + var reactionPrototype = prototype.Index(reaction.Key); + var mixingCategories = new List(); + if (reactionPrototype.MixingCategories != null) + { + foreach (var category in reactionPrototype.MixingCategories) + { + mixingCategories.Add(prototype.Index(category)); + } + } + else + { + mixingCategories.Add(prototype.Index(DefaultMixingCategory)); + } + + foreach (var mixingCategory in mixingCategories) + { + reactions[reaction.Key].MixingCategories.Add(new MixingCategoryEntry(mixingCategory)); + } + } + } +} diff --git a/Content.Server/_White/GuideGenerator/ReagentEffectEntry.cs b/Content.Server/_White/GuideGenerator/ReagentEffectEntry.cs new file mode 100644 index 0000000000..3e00c246a6 --- /dev/null +++ b/Content.Server/_White/GuideGenerator/ReagentEffectEntry.cs @@ -0,0 +1,40 @@ +using Content.Shared.Chemistry.Reagent; +using Robust.Shared.Prototypes; +using System.Text.Json.Serialization; + +namespace Content.Server._White.GuideGenerator; +public sealed class ReagentEffectEntry +{ + [JsonPropertyName("id")] + public string Id { get; } + + [JsonPropertyName("description")] + public string Description { get; } + + public ReagentEffectEntry(ReagentEffect proto) + { + var prototype = IoCManager.Resolve(); + var entSys = IoCManager.Resolve(); + + Id = proto.GetType().Name; + Description = GuidebookEffectDescriptionToWeb(proto.GuidebookEffectDescription(prototype, entSys) ?? ""); + } + + private string GuidebookEffectDescriptionToWeb(string guideBookText) + { + guideBookText = guideBookText.Replace("[", "<"); + guideBookText = guideBookText.Replace("]", ">"); + guideBookText = guideBookText.Replace("color", "span"); + + while (guideBookText.IndexOf("", first); + var replacementString = guideBookText.Substring(first, last - first); + var color = replacementString.Substring(1); + guideBookText = guideBookText.Replace(replacementString, string.Format(" style=\"color: {0};\"", color)); + } + + return guideBookText; + } +} diff --git a/Content.Server/_White/GuideGenerator/ReagentEffectsEntry.cs b/Content.Server/_White/GuideGenerator/ReagentEffectsEntry.cs new file mode 100644 index 0000000000..4e3b18ec85 --- /dev/null +++ b/Content.Server/_White/GuideGenerator/ReagentEffectsEntry.cs @@ -0,0 +1,20 @@ +using Content.Shared.FixedPoint; +using System.Linq; +using System.Text.Json.Serialization; + +namespace Content.Server._White.GuideGenerator; +public sealed class ReagentEffectsEntry +{ + [JsonPropertyName("rate")] + public FixedPoint2 MetabolismRate { get; } = FixedPoint2.New(0.5f); + + [JsonPropertyName("effects")] + public List Effects { get; } = new(); + + public ReagentEffectsEntry(Shared.Chemistry.Reagent.ReagentEffectsEntry proto) + { + MetabolismRate = proto.MetabolismRate; + Effects = proto.Effects.Select(x => new ReagentEffectEntry(x)).ToList(); + } + +} diff --git a/Content.Server/_White/GuideGenerator/SliceRecipeEntry.cs b/Content.Server/_White/GuideGenerator/SliceRecipeEntry.cs new file mode 100644 index 0000000000..a4cde7d678 --- /dev/null +++ b/Content.Server/_White/GuideGenerator/SliceRecipeEntry.cs @@ -0,0 +1,65 @@ +using System.Text.Json.Serialization; +using Robust.Shared.Prototypes; +using Content.Server.Nutrition.Components; + +namespace Content.Server.GuideGenerator; + +public sealed class SliceRecipeEntry +{ + /// + /// Id of sliceable item + /// + [JsonPropertyName("id")] + public string Id { get; } + + /// + /// Human-readable name of recipe. + /// Should automatically be localized by default + /// + [JsonPropertyName("name")] + public string Name { get; } + + /// + /// Type of recipe + /// + [JsonPropertyName("type")] + public string Type { get; } + + /// + /// Item that will be sliced into something + /// + [JsonPropertyName("input")] + public string Input { get; } + + /// + /// Result of a recipe + /// + [JsonPropertyName("result")] + public string Result { get; } + + /// + /// Count of result item + /// + [JsonPropertyName("count")] + public int Count { get; } + + + public SliceRecipeEntry(EntityPrototype proto) + { + Id = proto.ID; + Name = TextTools.TextTools.CapitalizeString(proto.Name); + Type = "sliceableRecipes"; + Input = proto.ID; + if (proto.Components.TryGetComponent("SliceableFood", out var comp)) + { + var sliceable = (SliceableFoodComponent) comp; + Result = sliceable.Slice ?? ""; + Count = sliceable.TotalCount; + } + else // just in case something will go wrong and we somehow will not get our component + { + Result = ""; + Count = 0; + } + } +} diff --git a/Content.Server/_White/GuideGenerator/TextTools.cs b/Content.Server/_White/GuideGenerator/TextTools.cs new file mode 100644 index 0000000000..2e441f0567 --- /dev/null +++ b/Content.Server/_White/GuideGenerator/TextTools.cs @@ -0,0 +1,25 @@ +namespace Content.Server.GuideGenerator.TextTools; + +public sealed class TextTools +{ + /// + /// Capitalizes first letter of given string. + /// + /// String to capitalize + /// String with capitalized first letter + public static string CapitalizeString(string str) + { + if (str.Length > 1) + { + return char.ToUpper(str[0]) + str.Remove(0, 1); + } + else if (str.Length == 1) + { + return char.ToUpper(str[0]).ToString(); + } + else + { + return str; + } + } +} diff --git a/Content.Server/_White/GuideGenerator/ToolRecipeEntry.cs b/Content.Server/_White/GuideGenerator/ToolRecipeEntry.cs new file mode 100644 index 0000000000..23362457a6 --- /dev/null +++ b/Content.Server/_White/GuideGenerator/ToolRecipeEntry.cs @@ -0,0 +1,96 @@ +using System.Text.Json.Serialization; +using Robust.Shared.Prototypes; +using Content.Shared.Construction; +using Content.Shared.Construction.Prototypes; +using Content.Shared.Construction.Steps; +using Robust.Server.GameObjects; + +namespace Content.Server.GuideGenerator; + +public sealed class ToolRecipeEntry // because of https://github.com/space-wizards/space-station-14/pull/20624, some recipes can now be cooked using tools +// actually, the code is pretty similar with HeatableRecipeEntry. The only difference is that we need ToolConstructionGraphStep instead of TemperatureConstructionGraphStep +// comments are left untouched :) +{ + + /// + /// Id of recipe + /// + [JsonPropertyName("id")] + public string Id { get; } + + /// + /// Human-readable name of recipe. + /// Should automatically be localized by default + /// + [JsonPropertyName("name")] + public string Name { get; } + + /// + /// Type of recipe + /// + [JsonPropertyName("type")] + public string Type { get; } + + /// + /// Type of tool that is used to convert input into result + /// + [JsonPropertyName("tool")] + public string? Tool { get; } + + /// + /// Item that will be transformed into something with enough temp + /// + [JsonPropertyName("input")] + public string Input { get; } + + /// + /// Result of a recipe. + /// If it is null then recipe does not exist or we could not get recipe info. + /// + [JsonPropertyName("result")] + public string? Result { get; } + + + public ToolRecipeEntry( + ConstructionGraphPrototype constructionProto, // to get data from construction prototype (Tool, result) + EntityPrototype entityPrototype // to get entity data (name, input entity id) + ) + { + var graphID = ""; + var startNode = constructionProto.Nodes[constructionProto.Start!]; + if (entityPrototype.Components.TryGetComponent("Construction", out var constructionCompRaw)) // does entity actually has Construction component? + { + foreach (var nodeEdgeRaw in startNode.Edges) // because we don't know what node contains heating step (in case if it is not constructionProto.Start) let's check every node and see if we will get anything + { + var nodeEdge = (ConstructionGraphEdge)nodeEdgeRaw; + foreach (var nodeStepRaw in nodeEdge.Steps) + { + if (nodeStepRaw.GetType().Equals(typeof(ToolConstructionGraphStep))) // ToolConstructionGraphStep is used only in steaks recipes, so for now we can afford it + { + var nodeStep = (ToolConstructionGraphStep)nodeStepRaw; + graphID = nodeEdge.Target; // required to check when we need to leave second loop; this is the best solution, because nodeEdge.Target is marked as required datafield and cannot be null + ServerEntityManager em = new(); + Tool = nodeStep.Tool; + Result = constructionProto.Nodes[nodeEdge.Target].Entity.GetId(null, null, new GraphNodeEntityArgs(em)); + break; + } + } + if (graphID != "") break; // we're done! let's leave! + } + if (graphID == "") // we've failed to get anything :( + { + Tool = null; + Result = null; + } + } + else // if entity does not have construction component then it cannot be constructed - (c) Jason Statham + { + Tool = null; + Result = null; + } + Input = entityPrototype.ID; + Name = TextTools.TextTools.CapitalizeString(entityPrototype.Name); + Id = entityPrototype.ID; + Type = "toolmadeRecipes"; + } +}