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,99 @@
using System.Collections.Frozen;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Guidebook;
/// <summary>
/// Used by GuidebookDataSystem to hold data extracted from prototype values,
/// both for storage and for network transmission.
/// </summary>
[Serializable, NetSerializable]
[DataDefinition]
public sealed partial class GuidebookData
{
/// <summary>
/// Total number of data values stored.
/// </summary>
[DataField]
public int Count { get; private set; }
/// <summary>
/// The data extracted by the system.
/// </summary>
/// <remarks>
/// Structured as PrototypeName, ComponentName, FieldName, Value
/// </remarks>
[DataField]
public Dictionary<string, Dictionary<string, Dictionary<string, object?>>> Data = [];
/// <summary>
/// The data extracted by the system, converted to a FrozenDictionary for faster lookup.
/// </summary>
public FrozenDictionary<string, FrozenDictionary<string, FrozenDictionary<string, object?>>> FrozenData;
/// <summary>
/// Has the data been converted to a FrozenDictionary for faster lookup?
/// This should only be done on clients, as FrozenDictionary isn't serializable.
/// </summary>
public bool IsFrozen;
/// <summary>
/// Adds a new value using the given identifiers.
/// </summary>
public void AddData(string prototype, string component, string field, object? value)
{
if (IsFrozen)
throw new InvalidOperationException("Attempted to add data to GuidebookData while it is frozen!");
Data.GetOrNew(prototype).GetOrNew(component).Add(field, value);
Count++;
}
/// <summary>
/// Attempts to retrieve a value using the given identifiers.
/// </summary>
/// <returns>true if the value was retrieved, otherwise false</returns>
public bool TryGetValue(string prototype, string component, string field, out object? value)
{
if (!IsFrozen)
throw new InvalidOperationException("Freeze the GuidebookData before calling TryGetValue!");
// Look in frozen dictionary
if (FrozenData.TryGetValue(prototype, out var p)
&& p.TryGetValue(component, out var c)
&& c.TryGetValue(field, out value))
{
return true;
}
value = null;
return false;
}
/// <summary>
/// Deletes all data.
/// </summary>
public void Clear()
{
Data.Clear();
Count = 0;
IsFrozen = false;
}
public void Freeze()
{
var protos = new Dictionary<string, FrozenDictionary<string, FrozenDictionary<string, object?>>>();
foreach (var (protoId, protoData) in Data)
{
var comps = new Dictionary<string, FrozenDictionary<string, object?>>();
foreach (var (compId, compData) in protoData)
{
comps.Add(compId, FrozenDictionary.ToFrozenDictionary(compData));
}
protos.Add(protoId, FrozenDictionary.ToFrozenDictionary(comps));
}
FrozenData = FrozenDictionary.ToFrozenDictionary(protos);
Data.Clear();
IsFrozen = true;
}
}