diff --git a/Content.Server/NameIdentifier/NameIdentifierComponent.cs b/Content.Server/NameIdentifier/NameIdentifierComponent.cs index 0a98eee60b..ff3d1a8898 100644 --- a/Content.Server/NameIdentifier/NameIdentifierComponent.cs +++ b/Content.Server/NameIdentifier/NameIdentifierComponent.cs @@ -8,4 +8,10 @@ public sealed class NameIdentifierComponent : Component { [DataField("group", required: true, customTypeSerializer:typeof(PrototypeIdSerializer))] public string Group = string.Empty; + + /// + /// The randomly generated ID for this entity. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("identifier")] + public int Identifier = -1; } diff --git a/Content.Server/NameIdentifier/NameIdentifierSystem.cs b/Content.Server/NameIdentifier/NameIdentifierSystem.cs index 2b5bc4515b..82875acec5 100644 --- a/Content.Server/NameIdentifier/NameIdentifierSystem.cs +++ b/Content.Server/NameIdentifier/NameIdentifierSystem.cs @@ -1,7 +1,10 @@ -using Content.Shared.GameTicking; +using System.Linq; +using Content.Shared.GameTicking; using Content.Shared.NameIdentifier; +using Robust.Shared.Collections; using Robust.Shared.Prototypes; using Robust.Shared.Random; +using Robust.Shared.Utility; namespace Content.Server.NameIdentifier; @@ -13,20 +16,37 @@ public sealed class NameIdentifierSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; + /// + /// Free IDs available per . + /// [ViewVariables] - public Dictionary> CurrentIds = new(); + public Dictionary> CurrentIds = new(); public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnComponentShutdown); SubscribeLocalEvent(CleanupIds); InitialSetupPrototypes(); _prototypeManager.PrototypesReloaded += OnReloadPrototypes; } + private void OnComponentShutdown(EntityUid uid, NameIdentifierComponent component, ComponentShutdown args) + { + if (CurrentIds.TryGetValue(component.Group, out var ids)) + { + // Avoid inserting the value right back at the end or shuffling in place: + // just pick a random spot to put it and then move that one to the end. + var randomIndex = _robustRandom.Next(ids.Count); + var random = ids[randomIndex]; + ids[randomIndex] = component.Identifier; + ids.Add(random); + } + } + public override void Shutdown() { base.Shutdown(); @@ -38,43 +58,52 @@ public sealed class NameIdentifierSystem : EntitySystem /// 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) + public string GenerateUniqueName(EntityUid uid, NameIdentifierGroupPrototype proto, out int randomVal) { + randomVal = 0; var entityName = Name(uid); - if (!CurrentIds.TryGetValue(proto, out var set)) + if (!CurrentIds.TryGetValue(proto.ID, out var set)) return entityName; - if (set.Count == (proto.MaxValue - proto.MinValue) + 1) + if (set.Count == 0) { // 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); + randomVal = set[^1]; + set.RemoveAt(set.Count - 1); return proto.Prefix is not null ? $"{proto.Prefix}-{randomVal}" : $"{randomVal}"; } - private void OnComponentInit(EntityUid uid, NameIdentifierComponent component, ComponentInit args) + private void OnMapInit(EntityUid uid, NameIdentifierComponent component, MapInitEvent args) { if (!_prototypeManager.TryIndex(component.Group, out var group)) return; - // Generate a new name. - var meta = MetaData(uid); - var uniqueName = GenerateUniqueName(uid, group); + int id; + string uniqueName; + // If it has an existing valid identifier then use that, otherwise generate a new one. + if (component.Identifier != -1 && + CurrentIds.TryGetValue(component.Group, out var ids) && + ids.Remove(component.Identifier)) + { + id = component.Identifier; + uniqueName = group.Prefix is not null + ? $"{group.Prefix}-{id}" + : $"{id}"; + } + else + { + uniqueName = GenerateUniqueName(uid, group, out id); + component.Identifier = id; + } + + var meta = MetaData(uid); // "DR-1234" as opposed to "drone (DR-1234)" meta.EntityName = group.FullName ? uniqueName @@ -85,33 +114,58 @@ public sealed class NameIdentifierSystem : EntitySystem { foreach (var proto in _prototypeManager.EnumeratePrototypes()) { - CurrentIds.Add(proto, new()); + AddGroup(proto); } } + private void AddGroup(NameIdentifierGroupPrototype proto) + { + var values = new List(proto.MaxValue - proto.MinValue); + + for (var i = proto.MinValue; i < proto.MaxValue; i++) + { + values.Add(i); + } + + _robustRandom.Shuffle(values); + CurrentIds.Add(proto.ID, values); + } + private void OnReloadPrototypes(PrototypesReloadedEventArgs ev) { if (!ev.ByType.TryGetValue(typeof(NameIdentifierGroupPrototype), out var set)) return; - foreach (var (_, proto) in set.Modified) + var toRemove = new ValueList(); + + foreach (var proto in CurrentIds.Keys) { - if (proto is not NameIdentifierGroupPrototype group) - continue; + if (!_prototypeManager.HasIndex(proto)) + { + toRemove.Add(proto); + } + } + foreach (var proto in toRemove) + { + CurrentIds.Remove(proto); + } + + foreach (var proto in set.Modified.Values) + { // Only bother adding new ones. - if (CurrentIds.ContainsKey(group)) + if (CurrentIds.ContainsKey(proto.ID)) continue; - CurrentIds.Add(group, new()); + AddGroup((NameIdentifierGroupPrototype) proto); } } private void CleanupIds(RoundRestartCleanupEvent ev) { - foreach (var (_, set) in CurrentIds) + foreach (var values in CurrentIds.Values) { - set.Clear(); + _robustRandom.Shuffle(values); } } } diff --git a/Content.Shared/NameIdentifier/NameIdentifierGroupPrototype.cs b/Content.Shared/NameIdentifier/NameIdentifierGroupPrototype.cs index 5d1b5848f9..dd9a4e91ff 100644 --- a/Content.Shared/NameIdentifier/NameIdentifierGroupPrototype.cs +++ b/Content.Shared/NameIdentifier/NameIdentifierGroupPrototype.cs @@ -18,7 +18,7 @@ public sealed class NameIdentifierGroupPrototype : IPrototype public string? Prefix; [DataField("maxValue")] - public int MaxValue = 999; + public int MaxValue = 1000; [DataField("minValue")] public int MinValue = 0;