NameIdentifierSystem.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. using Content.Shared.GameTicking;
  2. using Content.Shared.NameIdentifier;
  3. using Robust.Shared.Collections;
  4. using Robust.Shared.Prototypes;
  5. using Robust.Shared.Random;
  6. namespace Content.Server.NameIdentifier;
  7. /// <summary>
  8. /// Handles unique name identifiers for entities e.g. `monkey (MK-912)`
  9. /// </summary>
  10. public sealed class NameIdentifierSystem : EntitySystem
  11. {
  12. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  13. [Dependency] private readonly IRobustRandom _robustRandom = default!;
  14. [Dependency] private readonly MetaDataSystem _metaData = default!;
  15. /// <summary>
  16. /// Free IDs available per <see cref="NameIdentifierGroupPrototype"/>.
  17. /// </summary>
  18. [ViewVariables]
  19. public readonly Dictionary<string, List<int>> CurrentIds = new();
  20. public override void Initialize()
  21. {
  22. base.Initialize();
  23. SubscribeLocalEvent<NameIdentifierComponent, MapInitEvent>(OnMapInit);
  24. SubscribeLocalEvent<NameIdentifierComponent, ComponentShutdown>(OnComponentShutdown);
  25. SubscribeLocalEvent<RoundRestartCleanupEvent>(CleanupIds);
  26. SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnReloadPrototypes);
  27. InitialSetupPrototypes();
  28. }
  29. private void OnComponentShutdown(EntityUid uid, NameIdentifierComponent component, ComponentShutdown args)
  30. {
  31. if (CurrentIds.TryGetValue(component.Group, out var ids))
  32. {
  33. // Avoid inserting the value right back at the end or shuffling in place:
  34. // just pick a random spot to put it and then move that one to the end.
  35. var randomIndex = _robustRandom.Next(ids.Count);
  36. var random = ids[randomIndex];
  37. ids[randomIndex] = component.Identifier;
  38. ids.Add(random);
  39. }
  40. }
  41. /// <summary>
  42. /// Generates a new unique name/suffix for a given entity and adds it to <see cref="CurrentIds"/>
  43. /// but does not set the entity's name.
  44. /// </summary>
  45. public string GenerateUniqueName(EntityUid uid, ProtoId<NameIdentifierGroupPrototype> proto, out int randomVal)
  46. {
  47. return GenerateUniqueName(uid, _prototypeManager.Index(proto), out randomVal);
  48. }
  49. /// <summary>
  50. /// Generates a new unique name/suffix for a given entity and adds it to <see cref="CurrentIds"/>
  51. /// but does not set the entity's name.
  52. /// </summary>
  53. public string GenerateUniqueName(EntityUid uid, NameIdentifierGroupPrototype proto, out int randomVal)
  54. {
  55. randomVal = 0;
  56. var entityName = Name(uid);
  57. if (!CurrentIds.TryGetValue(proto.ID, out var set))
  58. return entityName;
  59. if (set.Count == 0)
  60. {
  61. // Oh jeez. We're outta numbers.
  62. return entityName;
  63. }
  64. randomVal = set[^1];
  65. set.RemoveAt(set.Count - 1);
  66. return proto.Prefix is not null
  67. ? $"{proto.Prefix}-{randomVal}"
  68. : $"{randomVal}";
  69. }
  70. private void OnMapInit(EntityUid uid, NameIdentifierComponent component, MapInitEvent args)
  71. {
  72. if (!_prototypeManager.TryIndex<NameIdentifierGroupPrototype>(component.Group, out var group))
  73. return;
  74. int id;
  75. string uniqueName;
  76. // If it has an existing valid identifier then use that, otherwise generate a new one.
  77. if (component.Identifier != -1 &&
  78. CurrentIds.TryGetValue(component.Group, out var ids) &&
  79. ids.Remove(component.Identifier))
  80. {
  81. id = component.Identifier;
  82. uniqueName = group.Prefix is not null
  83. ? $"{group.Prefix}-{id}"
  84. : $"{id}";
  85. }
  86. else
  87. {
  88. uniqueName = GenerateUniqueName(uid, group, out id);
  89. component.Identifier = id;
  90. }
  91. component.FullIdentifier = group.FullName
  92. ? uniqueName
  93. : $"({uniqueName})";
  94. var meta = MetaData(uid);
  95. // "DR-1234" as opposed to "drone (DR-1234)"
  96. _metaData.SetEntityName(uid, group.FullName
  97. ? uniqueName
  98. : $"{meta.EntityName} ({uniqueName})", meta);
  99. Dirty(uid, component);
  100. }
  101. private void InitialSetupPrototypes()
  102. {
  103. EnsureIds();
  104. }
  105. private void FillGroup(NameIdentifierGroupPrototype proto, List<int> values)
  106. {
  107. values.Clear();
  108. for (var i = proto.MinValue; i < proto.MaxValue; i++)
  109. {
  110. values.Add(i);
  111. }
  112. _robustRandom.Shuffle(values);
  113. }
  114. private List<int> GetOrCreateIdList(NameIdentifierGroupPrototype proto)
  115. {
  116. if (!CurrentIds.TryGetValue(proto.ID, out var ids))
  117. {
  118. ids = new List<int>(proto.MaxValue - proto.MinValue);
  119. CurrentIds.Add(proto.ID, ids);
  120. }
  121. return ids;
  122. }
  123. private void EnsureIds()
  124. {
  125. foreach (var proto in _prototypeManager.EnumeratePrototypes<NameIdentifierGroupPrototype>())
  126. {
  127. var ids = GetOrCreateIdList(proto);
  128. FillGroup(proto, ids);
  129. }
  130. }
  131. private void OnReloadPrototypes(PrototypesReloadedEventArgs ev)
  132. {
  133. if (!ev.ByType.TryGetValue(typeof(NameIdentifierGroupPrototype), out var set))
  134. return;
  135. var toRemove = new ValueList<string>();
  136. foreach (var proto in CurrentIds.Keys)
  137. {
  138. if (!_prototypeManager.HasIndex<NameIdentifierGroupPrototype>(proto))
  139. {
  140. toRemove.Add(proto);
  141. }
  142. }
  143. foreach (var proto in toRemove)
  144. {
  145. CurrentIds.Remove(proto);
  146. }
  147. foreach (var proto in set.Modified.Values)
  148. {
  149. var name_proto = (NameIdentifierGroupPrototype) proto;
  150. // Only bother adding new ones.
  151. if (CurrentIds.ContainsKey(proto.ID))
  152. continue;
  153. var ids = GetOrCreateIdList(name_proto);
  154. FillGroup(name_proto, ids);
  155. }
  156. }
  157. private void CleanupIds(RoundRestartCleanupEvent ev)
  158. {
  159. EnsureIds();
  160. }
  161. }