Add guidebook protodata tag: embed prototype values in guidebook text (#27570)

* First clumsy implementation of guidebook protodata

* Support for Properties, format strings

* Better null

* Rename file

* Documentation and some cleanup

* Handle errors better

* Added note about client-side components

* A couple of examples

* DataFields

* Use attributes like a sane person

* Outdated doc

* string.Empty

* No IComponent?

* No casting

* Use EntityManager.ComponentFactory

* Use FrozenDictionary

* Cache tagged component fields

* Iterate components and check if they're tagged
This commit is contained in:
Tayrtahn
2024-09-12 06:31:56 -04:00
committed by GitHub
parent c8f2ddc729
commit 320135347f
10 changed files with 366 additions and 11 deletions

View File

@@ -0,0 +1,45 @@
using Content.Shared.Guidebook;
namespace Content.Client.Guidebook;
/// <summary>
/// Client system for storing and retrieving values extracted from entity prototypes
/// for display in the guidebook (<see cref="RichText.ProtodataTag"/>).
/// Requests data from the server on <see cref="Initialize"/>.
/// Can also be pushed new data when the server reloads prototypes.
/// </summary>
public sealed class GuidebookDataSystem : EntitySystem
{
private GuidebookData? _data;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<UpdateGuidebookDataEvent>(OnServerUpdated);
// Request data from the server
RaiseNetworkEvent(new RequestGuidebookDataEvent());
}
private void OnServerUpdated(UpdateGuidebookDataEvent args)
{
// Got new data from the server, either in response to our request, or because prototypes reloaded on the server
_data = args.Data;
_data.Freeze();
}
/// <summary>
/// Attempts to retrieve a value using the given identifiers.
/// See <see cref="GuidebookData.TryGetValue"/> for more information.
/// </summary>
public bool TryGetValue(string prototype, string component, string field, out object? value)
{
if (_data == null)
{
value = null;
return false;
}
return _data.TryGetValue(prototype, component, field, out value);
}
}

View File

@@ -0,0 +1,49 @@
using System.Globalization;
using Robust.Client.UserInterface.RichText;
using Robust.Shared.Utility;
namespace Content.Client.Guidebook.RichText;
/// <summary>
/// RichText tag that can display values extracted from entity prototypes.
/// In order to be accessed by this tag, the desired field/property must
/// be tagged with <see cref="Shared.Guidebook.GuidebookDataAttribute"/>.
/// </summary>
public sealed class ProtodataTag : IMarkupTag
{
[Dependency] private readonly ILogManager _logMan = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
public string Name => "protodata";
private ISawmill Log => _log ??= _logMan.GetSawmill("protodata_tag");
private ISawmill? _log;
public string TextBefore(MarkupNode node)
{
// Do nothing with an empty tag
if (!node.Value.TryGetString(out var prototype))
return string.Empty;
if (!node.Attributes.TryGetValue("comp", out var component))
return string.Empty;
if (!node.Attributes.TryGetValue("member", out var member))
return string.Empty;
node.Attributes.TryGetValue("format", out var format);
var guidebookData = _entMan.System<GuidebookDataSystem>();
// Try to get the value
if (!guidebookData.TryGetValue(prototype, component.StringValue!, member.StringValue!, out var value))
{
Log.Error($"Failed to find protodata for {component}.{member} in {prototype}");
return "???";
}
// If we have a format string and a formattable value, format it as requested
if (!string.IsNullOrEmpty(format.StringValue) && value is IFormattable formattable)
return formattable.ToString(format.StringValue, CultureInfo.CurrentCulture);
// No format string given, so just use default ToString
return value?.ToString() ?? "NULL";
}
}