diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 5c3711fe19..20bc61f63c 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -19,6 +19,7 @@ namespace Content.Client.Entry "WarpPoint", "EmitSoundOnUse", "EmitSoundOnLand", + "NameIdentifier", "EmitSoundOnActivate", "FootstepModifier", "HeatResistance", diff --git a/Content.Server/NameIdentifier/NameIdentifierComponent.cs b/Content.Server/NameIdentifier/NameIdentifierComponent.cs new file mode 100644 index 0000000000..0a98eee60b --- /dev/null +++ b/Content.Server/NameIdentifier/NameIdentifierComponent.cs @@ -0,0 +1,11 @@ +using Content.Shared.NameIdentifier; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.NameIdentifier; + +[RegisterComponent] +public sealed class NameIdentifierComponent : Component +{ + [DataField("group", required: true, customTypeSerializer:typeof(PrototypeIdSerializer))] + public string Group = string.Empty; +} diff --git a/Content.Server/NameIdentifier/NameIdentifierSystem.cs b/Content.Server/NameIdentifier/NameIdentifierSystem.cs new file mode 100644 index 0000000000..590e15e2a9 --- /dev/null +++ b/Content.Server/NameIdentifier/NameIdentifierSystem.cs @@ -0,0 +1,118 @@ +using System.Linq; +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(); + } + } +} diff --git a/Content.Shared/NameIdentifier/NameIdentifierGroupPrototype.cs b/Content.Shared/NameIdentifier/NameIdentifierGroupPrototype.cs new file mode 100644 index 0000000000..5e25f215a0 --- /dev/null +++ b/Content.Shared/NameIdentifier/NameIdentifierGroupPrototype.cs @@ -0,0 +1,25 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.NameIdentifier; + +[Prototype("nameIdentifierGroup")] +public sealed class NameIdentifierGroupPrototype : IPrototype +{ + [DataField("id", required: true)] + public string ID { get; } = default!; + + /// + /// Should the identifier become the full name, or just append? + /// + [DataField("fullName")] + public bool FullName = false; + + [DataField("prefix")] + public string? Prefix; + + [DataField("maxValue")] + public int MaxValue = 999; + + [DataField("minValue")] + public int MinValue = 0; +} diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index d573fefb12..d7ff9e84ba 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -551,6 +551,8 @@ parent: SimpleMobBase description: New church of neo-darwinists actually believe that EVERY animal evolved from a monkey. Tastes like pork, and killing them is both fun and relaxing. components: + - type: NameIdentifier + group: Monkey - type: GhostTakeoverAvailable makeSentient: true name: monkey diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 89395786c5..744439318a 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -63,6 +63,8 @@ - id: PowerDrill - id: JawsOfLife - id: WelderExperimental + - type: NameIdentifier + group: Drone - type: GhostTakeoverAvailable makeSentient: true name: Maintenance Drone diff --git a/Resources/Prototypes/name_identifier_groups.yml b/Resources/Prototypes/name_identifier_groups.yml new file mode 100644 index 0000000000..82b512c0eb --- /dev/null +++ b/Resources/Prototypes/name_identifier_groups.yml @@ -0,0 +1,11 @@ +# Non-fungible apes, anyone? +- type: nameIdentifierGroup + id: Monkey + prefix: MK + +- type: nameIdentifierGroup + id: Drone + prefix: DR + fullName: true + minValue: 10000 + maxValue: 99999