GuidebookDataSystem.cs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. using System.Reflection;
  2. using Content.Shared.Guidebook;
  3. using Robust.Shared.Prototypes;
  4. using Robust.Shared.Utility;
  5. namespace Content.Server.Guidebook;
  6. /// <summary>
  7. /// Server system for identifying component fields/properties to extract values from entity prototypes.
  8. /// Extracted data is sent to clients when they connect or when prototypes are reloaded.
  9. /// </summary>
  10. public sealed class GuidebookDataSystem : EntitySystem
  11. {
  12. [Dependency] private readonly IPrototypeManager _protoMan = default!;
  13. private readonly Dictionary<string, List<MemberInfo>> _tagged = [];
  14. private GuidebookData _cachedData = new();
  15. public override void Initialize()
  16. {
  17. base.Initialize();
  18. SubscribeNetworkEvent<RequestGuidebookDataEvent>(OnRequestRules);
  19. SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
  20. // Build initial cache
  21. GatherData(ref _cachedData);
  22. }
  23. private void OnRequestRules(RequestGuidebookDataEvent ev, EntitySessionEventArgs args)
  24. {
  25. // Send cached data to requesting client
  26. var sendEv = new UpdateGuidebookDataEvent(_cachedData);
  27. RaiseNetworkEvent(sendEv, args.SenderSession);
  28. }
  29. private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
  30. {
  31. // We only care about entity prototypes
  32. if (!args.WasModified<EntityPrototype>())
  33. return;
  34. // The entity prototypes changed! Clear our cache and regather data
  35. RebuildDataCache();
  36. // Send new data to all clients
  37. var ev = new UpdateGuidebookDataEvent(_cachedData);
  38. RaiseNetworkEvent(ev);
  39. }
  40. private void GatherData(ref GuidebookData cache)
  41. {
  42. // Just for debug metrics
  43. var memberCount = 0;
  44. var prototypeCount = 0;
  45. if (_tagged.Count == 0)
  46. {
  47. // Scan component registrations to find members tagged for extraction
  48. foreach (var registration in EntityManager.ComponentFactory.GetAllRegistrations())
  49. {
  50. foreach (var member in registration.Type.GetMembers())
  51. {
  52. if (member.HasCustomAttribute<GuidebookDataAttribute>())
  53. {
  54. // Note this component-member pair for later
  55. _tagged.GetOrNew(registration.Name).Add(member);
  56. memberCount++;
  57. }
  58. }
  59. }
  60. }
  61. // Scan entity prototypes for the component-member pairs we noted
  62. var entityPrototypes = _protoMan.EnumeratePrototypes<EntityPrototype>();
  63. foreach (var prototype in entityPrototypes)
  64. {
  65. foreach (var (component, entry) in prototype.Components)
  66. {
  67. if (!_tagged.TryGetValue(component, out var members))
  68. continue;
  69. prototypeCount++;
  70. foreach (var member in members)
  71. {
  72. // It's dumb that we can't just do member.GetValue, but we can't, so
  73. var value = member switch
  74. {
  75. FieldInfo field => field.GetValue(entry.Component),
  76. PropertyInfo property => property.GetValue(entry.Component),
  77. _ => throw new NotImplementedException("Unsupported member type")
  78. };
  79. // Add it into the data cache
  80. cache.AddData(prototype.ID, component, member.Name, value);
  81. }
  82. }
  83. }
  84. Log.Debug($"Collected {cache.Count} Guidebook Protodata value(s) - {prototypeCount} matched prototype(s), {_tagged.Count} component(s), {memberCount} member(s)");
  85. }
  86. /// <summary>
  87. /// Clears the cached data, then regathers it.
  88. /// </summary>
  89. private void RebuildDataCache()
  90. {
  91. _cachedData.Clear();
  92. GatherData(ref _cachedData);
  93. }
  94. }