Chem guidebook (#17123)
* im good at atomizing. welcome to half-finished chem guides. * wagh * e * save work * aa * woweee UI * finishing the last of it * don't actually update the engine :( --------- Co-authored-by: moonheart08 <moonheart08@users.noreply.github.com>
This commit is contained in:
@@ -7,5 +7,8 @@ namespace Content.Shared.Body.Prototypes
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[DataField("name", required: true)]
|
||||
public string Name { get; } = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Localizations;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Shared.Chemistry.Reagent
|
||||
@@ -23,6 +26,10 @@ namespace Content.Shared.Chemistry.Reagent
|
||||
[DataField("conditions")]
|
||||
public ReagentEffectCondition[]? Conditions;
|
||||
|
||||
public virtual string ReagentEffectFormat => "guidebook-reagent-effect-description";
|
||||
|
||||
protected abstract string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys); // => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
|
||||
|
||||
/// <summary>
|
||||
/// What's the chance, from 0 to 1, that this effect will occur?
|
||||
/// </summary>
|
||||
@@ -42,6 +49,23 @@ namespace Content.Shared.Chemistry.Reagent
|
||||
public virtual bool ShouldLog { get; } = false;
|
||||
|
||||
public abstract void Effect(ReagentEffectArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Produces a localized, bbcode'd guidebook description for this effect.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string? GuidebookEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
{
|
||||
var effect = ReagentEffectGuidebookText(prototype, entSys);
|
||||
if (effect is null)
|
||||
return null;
|
||||
|
||||
return Loc.GetString(ReagentEffectFormat, ("effect", effect), ("chance", Probability),
|
||||
("conditionCount", Conditions?.Length ?? 0),
|
||||
("conditions",
|
||||
ContentLocalizationManager.FormatList(Conditions?.Select(x => x.GuidebookExplanation(prototype)).ToList() ??
|
||||
new List<string>())));
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReagentEffectExt
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Chemistry.Reagent
|
||||
{
|
||||
@@ -10,5 +11,12 @@ namespace Content.Shared.Chemistry.Reagent
|
||||
[JsonPropertyName("id")] private protected string _id => this.GetType().Name;
|
||||
|
||||
public abstract bool Condition(ReagentEffectArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Effect explanations are of the form "[chance to] [action] when [condition] and [condition]"
|
||||
/// </summary>
|
||||
/// <param name="prototype"></param>
|
||||
/// <returns></returns>
|
||||
public abstract string GuidebookExplanation(IPrototypeManager prototype);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Body.Prototypes;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
@@ -9,6 +10,7 @@ using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
@@ -157,6 +159,23 @@ namespace Content.Shared.Chemistry.Reagent
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public struct ReagentGuideEntry
|
||||
{
|
||||
public string ReagentPrototype;
|
||||
|
||||
public Dictionary<string, ReagentEffectsGuideEntry>? GuideEntries;
|
||||
|
||||
public ReagentGuideEntry(ReagentPrototype proto, IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
{
|
||||
ReagentPrototype = proto.ID;
|
||||
GuideEntries = proto.Metabolisms?
|
||||
.Select(x => (x.Key, x.Value.MakeGuideEntry(prototype, entSys)))
|
||||
.ToDictionary(x => x.Key, x => x.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[DataDefinition]
|
||||
public sealed class ReagentEffectsEntry
|
||||
{
|
||||
@@ -173,6 +192,30 @@ namespace Content.Shared.Chemistry.Reagent
|
||||
[JsonPropertyName("effects")]
|
||||
[DataField("effects", required: true)]
|
||||
public ReagentEffect[] Effects = default!;
|
||||
|
||||
public ReagentEffectsGuideEntry MakeGuideEntry(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
{
|
||||
return new ReagentEffectsGuideEntry(MetabolismRate,
|
||||
Effects
|
||||
.Select(x => x.GuidebookEffectDescription(prototype, entSys)) // hate.
|
||||
.Where(x => x is not null)
|
||||
.Select(x => x!)
|
||||
.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public struct ReagentEffectsGuideEntry
|
||||
{
|
||||
public FixedPoint2 MetabolismRate;
|
||||
|
||||
public string[] EffectDescriptions;
|
||||
|
||||
public ReagentEffectsGuideEntry(FixedPoint2 metabolismRate, string[] effectDescriptions)
|
||||
{
|
||||
MetabolismRate = metabolismRate;
|
||||
EffectDescriptions = effectDescriptions;
|
||||
}
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
|
||||
42
Content.Shared/Chemistry/SharedChemistryGuideDataSystem.cs
Normal file
42
Content.Shared/Chemistry/SharedChemistryGuideDataSystem.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Chemistry;
|
||||
|
||||
/// <summary>
|
||||
/// This handles the chemistry guidebook and caching it.
|
||||
/// </summary>
|
||||
public abstract class SharedChemistryGuideDataSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
||||
|
||||
protected readonly Dictionary<string, ReagentGuideEntry> Registry = new();
|
||||
|
||||
public IReadOnlyDictionary<string, ReagentGuideEntry> ReagentGuideRegistry => Registry;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ReagentGuideRegistryChangedEvent : EntityEventArgs
|
||||
{
|
||||
public ReagentGuideChangeset Changeset;
|
||||
|
||||
public ReagentGuideRegistryChangedEvent(ReagentGuideChangeset changeset)
|
||||
{
|
||||
Changeset = changeset;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ReagentGuideChangeset
|
||||
{
|
||||
public Dictionary<string,ReagentGuideEntry> GuideEntries;
|
||||
|
||||
public HashSet<string> Removed;
|
||||
|
||||
public ReagentGuideChangeset(Dictionary<string, ReagentGuideEntry> guideEntries, HashSet<string> removed)
|
||||
{
|
||||
GuideEntries = guideEntries;
|
||||
Removed = removed;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Localizations
|
||||
{
|
||||
@@ -31,13 +34,96 @@ namespace Content.Shared.Localizations
|
||||
_loc.AddFunction(culture, "UNITS", FormatUnits);
|
||||
_loc.AddFunction(culture, "TOSTRING", args => FormatToString(culture, args));
|
||||
_loc.AddFunction(culture, "LOC", FormatLoc);
|
||||
_loc.AddFunction(culture, "NATURALFIXED", FormatNaturalFixed);
|
||||
_loc.AddFunction(culture, "NATURALPERCENT", FormatNaturalPercent);
|
||||
|
||||
|
||||
/*
|
||||
* The following language functions are specific to the english localization. When working on your own
|
||||
* localization you should NOT modify these, instead add new functions specific to your language/culture.
|
||||
* This ensures the english translations continue to work as expected when fallbacks are needed.
|
||||
*/
|
||||
var cultureEn = new CultureInfo("en-US");
|
||||
|
||||
_loc.AddFunction(cultureEn, "MAKEPLURAL", FormatMakePlural);
|
||||
_loc.AddFunction(cultureEn, "MANY", FormatMany);
|
||||
}
|
||||
|
||||
private ILocValue FormatMany(LocArgs args)
|
||||
{
|
||||
var count = ((LocValueNumber) args.Args[1]).Value;
|
||||
|
||||
if (Math.Abs(count - 1) < 0.0001f)
|
||||
{
|
||||
return (LocValueString) args.Args[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
return (LocValueString) FormatMakePlural(args);
|
||||
}
|
||||
}
|
||||
|
||||
private ILocValue FormatNaturalPercent(LocArgs args)
|
||||
{
|
||||
var number = ((LocValueNumber) args.Args[0]).Value * 100;
|
||||
var maxDecimals = (int)Math.Floor(((LocValueNumber) args.Args[1]).Value);
|
||||
var formatter = (NumberFormatInfo)NumberFormatInfo.GetInstance(CultureInfo.GetCultureInfo(Culture)).Clone();
|
||||
formatter.NumberDecimalDigits = maxDecimals;
|
||||
return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd('.') + "%");
|
||||
}
|
||||
|
||||
private ILocValue FormatNaturalFixed(LocArgs args)
|
||||
{
|
||||
var number = ((LocValueNumber) args.Args[0]).Value;
|
||||
var maxDecimals = (int)Math.Floor(((LocValueNumber) args.Args[1]).Value);
|
||||
var formatter = (NumberFormatInfo)NumberFormatInfo.GetInstance(CultureInfo.GetCultureInfo(Culture)).Clone();
|
||||
formatter.NumberDecimalDigits = maxDecimals;
|
||||
return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd('.'));
|
||||
}
|
||||
|
||||
private static readonly Regex PluralEsRule = new("^.*(s|sh|ch|x|z)$");
|
||||
|
||||
private ILocValue FormatMakePlural(LocArgs args)
|
||||
{
|
||||
var text = ((LocValueString) args.Args[0]).Value;
|
||||
var split = text.Split(" ", 1);
|
||||
var firstWord = split[0];
|
||||
if (PluralEsRule.IsMatch(firstWord))
|
||||
{
|
||||
if (split.Length == 1)
|
||||
return new LocValueString($"{firstWord}es");
|
||||
else
|
||||
return new LocValueString($"{firstWord}es {split[1]}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (split.Length == 1)
|
||||
return new LocValueString($"{firstWord}s");
|
||||
else
|
||||
return new LocValueString($"{firstWord}s {split[1]}");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: allow fluent to take in lists of strings so this can be a format function like it should be.
|
||||
/// <summary>
|
||||
/// Formats a list as per english grammar rules.
|
||||
/// </summary>
|
||||
public static string FormatList(List<string> list)
|
||||
{
|
||||
return list.Count switch
|
||||
{
|
||||
<= 0 => string.Empty,
|
||||
1 => list[0],
|
||||
2 => $"{list[0]} and {list[1]}",
|
||||
> 2 => $"{string.Join(", ", list.GetRange(0, list.Count - 2))}, and {list[^1]}"
|
||||
};
|
||||
}
|
||||
|
||||
private static ILocValue FormatLoc(LocArgs args)
|
||||
{
|
||||
var id = ((LocValueString)args.Args[0]).Value;
|
||||
var id = ((LocValueString) args.Args[0]).Value;
|
||||
|
||||
return new LocValueString(Loc.GetString(id));
|
||||
return new LocValueString(Loc.GetString(id, args.Options.Select(x => (x.Key, x.Value.Value!)).ToArray()));
|
||||
}
|
||||
|
||||
private static ILocValue FormatToString(CultureInfo culture, LocArgs args)
|
||||
@@ -115,8 +201,8 @@ namespace Content.Shared.Localizations
|
||||
//
|
||||
// Note that the closing brace isn't replaced so that format specifiers can be applied.
|
||||
var res = String.Format(
|
||||
fmtstr.Replace("{UNIT", "{" + $"{fargs.Length - 1}"),
|
||||
fargs
|
||||
fmtstr.Replace("{UNIT", "{" + $"{fargs.Length - 1}"),
|
||||
fargs
|
||||
);
|
||||
|
||||
return new LocValueString(res);
|
||||
|
||||
Reference in New Issue
Block a user