using Content.Shared.GameTicking; using Content.Shared.NameIdentifier; using Robust.Shared.Prototypes; using Robust.Shared.Random; namespace Content.Server.NameIdentifier; /// /// Handles unique name identifiers for entities e.g. `monkey (MK-912)` /// public sealed class NameIdentifierSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; [ViewVariables] public Dictionary> CurrentIds = new(); public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnComponentInit); SubscribeLocalEvent(CleanupIds); InitialSetupPrototypes(); _prototypeManager.PrototypesReloaded += OnReloadPrototypes; } public override void Shutdown() { base.Shutdown(); _prototypeManager.PrototypesReloaded -= OnReloadPrototypes; } /// /// Generates a new unique name/suffix for a given entity and adds it to /// but does not set the entity's name. /// public string GenerateUniqueName(EntityUid uid, NameIdentifierGroupPrototype proto) { var entityName = Name(uid); if (!CurrentIds.TryGetValue(proto, out var set)) return entityName; if (set.Count == (proto.MaxValue - proto.MinValue) + 1) { // Oh jeez. We're outta numbers. return entityName; } // This is kind of inefficient with very large amounts of entities but its better than any other method // I could come up with. var randomVal = _robustRandom.Next(proto.MinValue, proto.MaxValue); while (set.Contains(randomVal)) { randomVal = _robustRandom.Next(proto.MinValue, proto.MaxValue); } set.Add(randomVal); return proto.Prefix is not null ? $"{proto.Prefix}-{randomVal}" : $"{randomVal}"; } private void OnComponentInit(EntityUid uid, NameIdentifierComponent component, ComponentInit args) { if (!_prototypeManager.TryIndex(component.Group, out var group)) return; // Generate a new name. var meta = MetaData(uid); var uniqueName = GenerateUniqueName(uid, group); // "DR-1234" as opposed to "drone (DR-1234)" meta.EntityName = group.FullName ? uniqueName : $"{meta.EntityName} ({uniqueName})"; } private void InitialSetupPrototypes() { foreach (var proto in _prototypeManager.EnumeratePrototypes()) { CurrentIds.Add(proto, new()); } } private void OnReloadPrototypes(PrototypesReloadedEventArgs ev) { if (!ev.ByType.TryGetValue(typeof(NameIdentifierGroupPrototype), out var set)) return; foreach (var (_, proto) in set.Modified) { if (proto is not NameIdentifierGroupPrototype group) continue; // Only bother adding new ones. if (CurrentIds.ContainsKey(group)) continue; CurrentIds.Add(group, new()); } } private void CleanupIds(RoundRestartCleanupEvent ev) { foreach (var (_, set) in CurrentIds) { set.Clear(); } } }