json generator alternator and vibrator (#718)

* json generator alternator and vibrator

* каким хуем ты не закоммитился уебан
This commit is contained in:
Valtos
2024-09-29 01:15:33 +03:00
committed by GitHub
parent 73f82869e4
commit a5cddf1223
17 changed files with 852 additions and 7 deletions

View File

@@ -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

View File

@@ -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));

View File

@@ -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);
}
}

View 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;
}
}

View 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));
}
}

View 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;
}
}
}

View File

@@ -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));
}
}

View 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";
}
}

View File

@@ -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));
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -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));
}
}
}
}

View 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;
}
}

View 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();
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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";
}
}