json generator alternator and vibrator (#718)
* json generator alternator and vibrator * каким хуем ты не закоммитился уебан
This commit is contained in:
@@ -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<IBaseServer>().Shutdown("Data generation done");
|
||||
}
|
||||
else
|
||||
|
||||
@@ -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<ReagentEffect>(),
|
||||
}
|
||||
},
|
||||
NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals
|
||||
};
|
||||
|
||||
file.Write(JsonSerializer.Serialize(reactions, serializeOptions));
|
||||
|
||||
@@ -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<string> Recipes { get; } = new();
|
||||
|
||||
[JsonPropertyName("textColor")]
|
||||
public string TextColor { get; }
|
||||
|
||||
[JsonPropertyName("metabolisms")]
|
||||
public Dictionary<string, ReagentEffectsEntry>? Metabolisms { get; }
|
||||
public Dictionary<string, _White.GuideGenerator.ReagentEffectsEntry>? 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<string, float> Products { get; }
|
||||
|
||||
[JsonPropertyName("mixingCategories")]
|
||||
public List<MixingCategoryEntry> 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<ReagentEffectEntry> ExportEffects { get; } = new();
|
||||
[JsonIgnore]
|
||||
public List<ReagentEffect> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
23
Content.Server/_White/GuideGenerator/EntityEntry.cs
Normal file
23
Content.Server/_White/GuideGenerator/EntityEntry.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
27
Content.Server/_White/GuideGenerator/EntityJsonGenerator.cs
Normal file
27
Content.Server/_White/GuideGenerator/EntityJsonGenerator.cs
Normal file
@@ -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<IPrototypeManager>();
|
||||
var prototypes =
|
||||
prototype
|
||||
.EnumeratePrototypes<EntityPrototype>()
|
||||
.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));
|
||||
}
|
||||
}
|
||||
74
Content.Server/_White/GuideGenerator/GrindRecipeEntry.cs
Normal file
74
Content.Server/_White/GuideGenerator/GrindRecipeEntry.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Id of grindable item
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable name of recipe.
|
||||
/// Should automatically be localized by default
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of recipe
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Item that will be grinded into something
|
||||
/// </summary>
|
||||
[JsonPropertyName("input")]
|
||||
public string Input { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of reagents that entity contains; aka "Recipe Result"
|
||||
/// </summary>
|
||||
[JsonPropertyName("result")]
|
||||
public Dictionary<string, int>? Result { get; } = new Dictionary<string, int>();
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<IPrototypeManager>();
|
||||
|
||||
Dictionary<string, Dictionary<string, Dictionary<string, float>>> healthChangeReagents = new();
|
||||
|
||||
// Сбор данных
|
||||
foreach (var reagent in prototype.EnumeratePrototypes<ReagentPrototype>())
|
||||
{
|
||||
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<string, Dictionary<string, List<string>>> 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<string>.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));
|
||||
}
|
||||
}
|
||||
|
||||
93
Content.Server/_White/GuideGenerator/HeatableRecipeEntry.cs
Normal file
93
Content.Server/_White/GuideGenerator/HeatableRecipeEntry.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Id of recipe
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable name of recipe.
|
||||
/// Should automatically be localized by default
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of recipe
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Temp, required for "input" thing to become "result" thing
|
||||
/// </summary>
|
||||
[JsonPropertyName("minTemp")]
|
||||
public float MinTemp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Item that will be transformed into something with enough temp
|
||||
/// </summary>
|
||||
[JsonPropertyName("input")]
|
||||
public string Input { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Result of a recipe.
|
||||
/// If it is null then recipe does not exist or we could not get recipe info.
|
||||
/// </summary>
|
||||
[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";
|
||||
}
|
||||
}
|
||||
@@ -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<IPrototypeManager>();
|
||||
var entities = prototype.EnumeratePrototypes<EntityPrototype>();
|
||||
var constructable = prototype.EnumeratePrototypes<ConstructionGraphPrototype>();
|
||||
var output = new Dictionary<string, dynamic>();
|
||||
|
||||
var microwaveRecipes =
|
||||
prototype
|
||||
.EnumeratePrototypes<FoodRecipePrototype>()
|
||||
.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(), @"(?<![Cc]rate)[Ff]ood*").Success))
|
||||
.Where(x => x.Components.ContainsKey("Construction"))
|
||||
.ToList();
|
||||
|
||||
var entityGraphs = new Dictionary<string, string>(); // 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<ReactionPrototype>()
|
||||
.Select(x => new ReactionEntry(x))
|
||||
.ToList();
|
||||
|
||||
|
||||
var mixableRecipes = new Dictionary<string, Dictionary<string, string>>(); // 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<string, string>();
|
||||
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));
|
||||
}
|
||||
}
|
||||
71
Content.Server/_White/GuideGenerator/MicrowaveRecipeEntry.cs
Normal file
71
Content.Server/_White/GuideGenerator/MicrowaveRecipeEntry.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Content.Shared.Kitchen;
|
||||
|
||||
namespace Content.Server.GuideGenerator;
|
||||
|
||||
public sealed class MicrowaveRecipeEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Id of recipe
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable name of recipe.
|
||||
/// Should automatically be localized by default
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of recipe
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Time to cook something (for microwave recipes)
|
||||
/// </summary>
|
||||
[JsonPropertyName("time")]
|
||||
public uint Time { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Solids required to cook something
|
||||
/// </summary>
|
||||
[JsonPropertyName("solids")]
|
||||
public Dictionary<string, uint> Solids { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Reagents required to cook something
|
||||
/// </summary>
|
||||
[JsonPropertyName("reagents")]
|
||||
public Dictionary<string, uint> Reagents { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Result of a recipe
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
19
Content.Server/_White/GuideGenerator/MixingCategoryEntry.cs
Normal file
19
Content.Server/_White/GuideGenerator/MixingCategoryEntry.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<MixingCategoryPrototype>]
|
||||
private const string DefaultMixingCategory = "DummyMix";
|
||||
|
||||
private static void AddMixingCategories(Dictionary<String, ReactionEntry> reactions, IPrototypeManager prototype)
|
||||
{
|
||||
foreach (var reaction in reactions)
|
||||
{
|
||||
var reactionPrototype = prototype.Index<ReactionPrototype>(reaction.Key);
|
||||
var mixingCategories = new List<MixingCategoryPrototype>();
|
||||
if (reactionPrototype.MixingCategories != null)
|
||||
{
|
||||
foreach (var category in reactionPrototype.MixingCategories)
|
||||
{
|
||||
mixingCategories.Add(prototype.Index(category));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mixingCategories.Add(prototype.Index<MixingCategoryPrototype>(DefaultMixingCategory));
|
||||
}
|
||||
|
||||
foreach (var mixingCategory in mixingCategories)
|
||||
{
|
||||
reactions[reaction.Key].MixingCategories.Add(new MixingCategoryEntry(mixingCategory));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Content.Server/_White/GuideGenerator/ReagentEffectEntry.cs
Normal file
40
Content.Server/_White/GuideGenerator/ReagentEffectEntry.cs
Normal file
@@ -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<IPrototypeManager>();
|
||||
var entSys = IoCManager.Resolve<IEntitySystemManager>();
|
||||
|
||||
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("<span=") != -1)
|
||||
{
|
||||
var first = guideBookText.IndexOf("<span=") + "<span=".Length - 1;
|
||||
var last = 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;
|
||||
}
|
||||
}
|
||||
20
Content.Server/_White/GuideGenerator/ReagentEffectsEntry.cs
Normal file
20
Content.Server/_White/GuideGenerator/ReagentEffectsEntry.cs
Normal file
@@ -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<ReagentEffectEntry> Effects { get; } = new();
|
||||
|
||||
public ReagentEffectsEntry(Shared.Chemistry.Reagent.ReagentEffectsEntry proto)
|
||||
{
|
||||
MetabolismRate = proto.MetabolismRate;
|
||||
Effects = proto.Effects.Select(x => new ReagentEffectEntry(x)).ToList();
|
||||
}
|
||||
|
||||
}
|
||||
65
Content.Server/_White/GuideGenerator/SliceRecipeEntry.cs
Normal file
65
Content.Server/_White/GuideGenerator/SliceRecipeEntry.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Id of sliceable item
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable name of recipe.
|
||||
/// Should automatically be localized by default
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of recipe
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Item that will be sliced into something
|
||||
/// </summary>
|
||||
[JsonPropertyName("input")]
|
||||
public string Input { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Result of a recipe
|
||||
/// </summary>
|
||||
[JsonPropertyName("result")]
|
||||
public string Result { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Count of result item
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Content.Server/_White/GuideGenerator/TextTools.cs
Normal file
25
Content.Server/_White/GuideGenerator/TextTools.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace Content.Server.GuideGenerator.TextTools;
|
||||
|
||||
public sealed class TextTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Capitalizes first letter of given string.
|
||||
/// </summary>
|
||||
/// <param name="str">String to capitalize</param>
|
||||
/// <returns>String with capitalized first letter</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
96
Content.Server/_White/GuideGenerator/ToolRecipeEntry.cs
Normal file
96
Content.Server/_White/GuideGenerator/ToolRecipeEntry.cs
Normal file
@@ -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 :)
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Id of recipe
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable name of recipe.
|
||||
/// Should automatically be localized by default
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of recipe
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of tool that is used to convert input into result
|
||||
/// </summary>
|
||||
[JsonPropertyName("tool")]
|
||||
public string? Tool { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Item that will be transformed into something with enough temp
|
||||
/// </summary>
|
||||
[JsonPropertyName("input")]
|
||||
public string Input { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Result of a recipe.
|
||||
/// If it is null then recipe does not exist or we could not get recipe info.
|
||||
/// </summary>
|
||||
[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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user