diff --git a/Content.IntegrationTests/Tests/NameIdentifier/NameIdentifierTest.cs b/Content.IntegrationTests/Tests/NameIdentifier/NameIdentifierTest.cs
new file mode 100644
index 0000000000..f85f93d3c9
--- /dev/null
+++ b/Content.IntegrationTests/Tests/NameIdentifier/NameIdentifierTest.cs
@@ -0,0 +1,248 @@
+using System.Collections.Generic;
+using System.Linq;
+using Content.IntegrationTests.Fixtures;
+using Content.IntegrationTests.Fixtures.Attributes;
+using Content.Server.NameIdentifier;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+
+namespace Content.IntegrationTests.Tests.NameIdentifier;
+
+[TestOf(typeof(NameIdentifierSystem))]
+public sealed class NameIdentifierTest : GameTest
+{
+ private const string MaxIds = "5";
+ private const int MaxIdsInt = 5;
+
+ private const string NameTest = "NameTest";
+ private const string NumberTestGroup = "NumberTestGroup";
+ private const string PrefixTestGroup = "PrefixTestGroup";
+ private const string PrefixTest = "PrefixTest";
+ private const string ParenTestEnt = "ParenTestEnt";
+ private const string LocTestEnt = "LocTestEnt";
+
+ [TestPrototypes]
+ private const string Prototypes =
+ $"""
+ - type: nameIdentifierGroup
+ id: {NumberTestGroup}
+ minValue: 1
+ maxValue: {MaxIds}
+
+ - type: entity
+ name: {NameTest}
+ id: {NameTest}
+ components:
+ - type: NameIdentifier
+ group: {NumberTestGroup}
+
+ - type: nameIdentifierGroup
+ parent: {NumberTestGroup}
+ id: {PrefixTestGroup}
+ prefix: true
+
+ - type: entity
+ name: {PrefixTest}
+ id: {PrefixTest}
+ components:
+ - type: NameIdentifier
+ group: {PrefixTestGroup}
+
+ - type: entity
+ name: {ParenTestEnt}
+ id: {ParenTestEnt}
+ components:
+ - type: NameIdentifier
+ group: GenericNumber
+
+ - type: localizedDataset
+ id: NameIdentifierTest
+ values:
+ prefix: name-identifier-test-
+ count: 1
+
+ - type: nameIdentifierGroup
+ id: Localized
+ identifierDataset: NameIdentifierTest
+
+ - type: entity
+ name: {LocTestEnt}
+ id: {LocTestEnt}
+ components:
+ - type: NameIdentifier
+ group: Localized
+ """;
+
+ [SidedDependency(Side.Server)] private NameIdentifierSystem _nameModifier = default!;
+
+ ///
+ ///
+ /// We set false because the client has no unique behavior.
+ /// Skipping client sim saves several seconds.
+ ///
+ public override PoolSettings PoolSettings => new ()
+ {
+ Connected = false,
+ };
+
+ [Test]
+ [Description("Tests that prototypes are loaded into the identifier system properly.")]
+ public async Task ValidatePoolGeneration()
+ {
+ await Server.WaitAssertion(() =>
+ {
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(_nameModifier.CurrentIds.ContainsKey(NumberTestGroup),
+ "Failed to load test prototypes.");
+ Assert.That(_nameModifier.CurrentIds[NumberTestGroup],
+ Has.Count.EqualTo(MaxIdsInt),
+ "Failed to load test prototypes.");
+ }
+ });
+ }
+
+ [Test]
+ [Description("Tests that IDs are removed from the pool and properly returned when deleted.")]
+ public async Task TakeAndReturn()
+ {
+ await Server.WaitAssertion(() =>
+ {
+ // Create an entity that will draw a modifier from the pool
+ var single = SSpawn(NameTest);
+
+ Assert.That(_nameModifier.CurrentIds[NumberTestGroup],
+ Has.Count.EqualTo(MaxIdsInt - 1),
+ "CurrentIds did not decrease.");
+
+ // Delete the entity, which should return the modifier to the pool
+ SDeleteNow(single);
+
+ Assert.That(_nameModifier.CurrentIds[NumberTestGroup],
+ Has.Count.EqualTo(MaxIdsInt),
+ "CurrentIds did not return to max.");
+ });
+ }
+
+ [Test]
+ [Description("Tests that the list of IDs behaves properly when taken to zero values.")]
+ public async Task ExhaustAndRefillList()
+ {
+ var entList = new List();
+ var original = _nameModifier.CurrentIds[NumberTestGroup];
+
+ await Server.WaitAssertion(() =>
+ {
+ // Spawn as many entities as the pool should hold.
+ for (var i = 0; i <= MaxIdsInt; i++)
+ entList.Add(SSpawn(NameTest));
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(_nameModifier.CurrentIds[NumberTestGroup],
+ Has.Count.EqualTo(0),
+ "CurrentIds failed to empty.");
+ }
+
+ // Delete all entities, returning them to the pool.
+ foreach (var ent in entList)
+ SDeleteNow(ent);
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(_nameModifier.CurrentIds[NumberTestGroup],
+ Has.Count.EqualTo(MaxIdsInt),
+ "CurrentIds failed to refill.");
+ Assert.That(original,
+ Is.EqualTo(_nameModifier.CurrentIds[NumberTestGroup]),
+ "List of values was not returned to starting state.");
+ }
+ });
+ }
+
+ [Test]
+ [Description("Tests that IDs are not duplicated and correctly formatted.")]
+ public async Task ValidateDefaultGeneration()
+ {
+ var entList = new List();
+
+ await Server.WaitAssertion(() =>
+ {
+ // Spawn as many entities as the pool should hold.
+ for (var i = 0; i < MaxIdsInt; i++)
+ entList.Add(SSpawn(NameTest));
+
+ var nameList = entList
+ .Select(p => SEntMan.GetComponent(p).EntityName)
+ .ToList();
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(nameList, Is.Unique, "A duplicate name was generated.");
+ Assert.That(nameList, Has.All.Contain(NameTest), "The base name was not preserved.");
+ Assert.That(nameList, Has.All.Match(@"\d+$"), "Created an invalid name.");
+ }
+ });
+ }
+
+ [Test]
+ [Description("Tests that the core parenthesis wrapped modifier works properly.")]
+ public async Task ParenthesisAppend()
+ {
+ await Server.WaitAssertion(() =>
+ {
+ var single = SSpawn(ParenTestEnt);
+ Assert.That(
+ SEntMan.GetComponent(single).EntityName,
+ Does.Match($@"{ParenTestEnt} \(\d+\)$"),
+ "Did not create a valid parenthesis wrapped name."
+ );
+ });
+ }
+
+ [Test]
+ [Description("Tests that nothing is attached to the name when the pool of available names is empty.")]
+ public async Task DrawWhenEmpty()
+ {
+ await Server.WaitAssertion(() =>
+ {
+ // Spawn as many entities as the pool should hold.
+ for (var i = 0; i < MaxIdsInt; i++)
+ SSpawn(NameTest);
+
+ var emptyDraw = SSpawn(NameTest);
+ var name = SEntMan.GetComponent(emptyDraw).EntityName;
+ Assert.That(name, Is.EqualTo(NameTest), "Created an invalid name.");
+ });
+ }
+
+ [Test]
+ [Description("Tests that a localized value is properly fetched and attached.")]
+ public async Task LocalizedIdentifier()
+ {
+ await Server.WaitAssertion(() =>
+ {
+ var single = SSpawn(LocTestEnt);
+ Assert.That(
+ SEntMan.GetComponent(single).EntityName,
+ Is.EqualTo($"{LocTestEnt} TestValue"),
+ "Did not create a valid localized name."
+ );
+ });
+ }
+
+ [Test]
+ [Description("Tests that the prefix setting properly prepends a value.")]
+ public async Task PrefixedIdentifier()
+ {
+ await Server.WaitAssertion(() =>
+ {
+ var single = SSpawn(PrefixTest);
+ Assert.That(
+ SEntMan.GetComponent(single).EntityName,
+ Does.Match(@"^\d+"),
+ "Did not create a valid name with a prefix."
+ );
+ });
+ }
+}
diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
index 3a5fd34eb3..919676c126 100644
--- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
+++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
@@ -428,7 +428,7 @@ public sealed partial class CargoSystem
if (component.Bounties.Count >= component.MaxBounties)
return false;
- _nameIdentifier.GenerateUniqueName(uid, BountyNameIdentifierGroup, out var randomVal);
+ _nameIdentifier.GenerateUniqueNameModifier(BountyNameIdentifierGroup, out var randomVal);
var newBounty = new CargoBountyData(bounty, randomVal);
// This bounty id already exists! Probably because NameIdentifierSystem ran out of ids.
if (component.Bounties.Any(b => b.Id == newBounty.Id))
diff --git a/Content.Server/NameIdentifier/NameIdentifierSystem.cs b/Content.Server/NameIdentifier/NameIdentifierSystem.cs
index c8ab73fdf5..a9a6dfed7e 100644
--- a/Content.Server/NameIdentifier/NameIdentifierSystem.cs
+++ b/Content.Server/NameIdentifier/NameIdentifierSystem.cs
@@ -7,6 +7,7 @@ using Robust.Shared.Random;
namespace Content.Server.NameIdentifier;
+///
public sealed partial class NameIdentifierSystem : SharedNameIdentifierSystem
{
[Dependency] private IRobustRandom _robustRandom = default!;
@@ -18,6 +19,7 @@ public sealed partial class NameIdentifierSystem : SharedNameIdentifierSystem
[ViewVariables]
public readonly Dictionary> CurrentIds = [];
+ ///
public override void Initialize()
{
base.Initialize();
@@ -35,7 +37,17 @@ public sealed partial class NameIdentifierSystem : SharedNameIdentifierSystem
if (ent.Comp.Group is null)
return;
- if (CurrentIds.TryGetValue(ent.Comp.Group, out var ids) && ids.Count > 0)
+ if (!CurrentIds.TryGetValue(ent.Comp.Group, out var ids))
+ {
+ _nameModifier.RefreshNameModifiers(ent.Owner);
+ return;
+ }
+
+ // Not a valid value
+ if (ent.Comp.Identifier == -1)
+ return;
+
+ if (ids.Count > 0)
{
// 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.
@@ -44,44 +56,79 @@ public sealed partial class NameIdentifierSystem : SharedNameIdentifierSystem
ids[randomIndex] = ent.Comp.Identifier;
ids.Add(random);
}
+ else
+ {
+ ids.Add(ent.Comp.Identifier);
+ }
_nameModifier.RefreshNameModifiers(ent.Owner);
}
///
- /// 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, ProtoId proto, out int randomVal)
+ ///
+ /// This overload resolves the ProtoId of the NameIdentifierGroupPrototype first.
+ ///
+ /// A ProtoId that will be resolved and passed on.
+ /// The index value of the randomly selected modifier.
+ public string GenerateUniqueNameModifier(ProtoId proto, out int randomVal)
{
- return GenerateUniqueName(uid, ProtoMan.Index(proto), out randomVal);
+ return GenerateUniqueNameModifier(ProtoMan.Index(proto), out randomVal);
}
///
- /// Generates a new unique name/suffix for a given entity and adds it to
- /// but does not set the entity's name.
+ /// Generates a new unique name modifier for a given entity and removes it from
+ /// but does not set the entity's name.
///
- public string GenerateUniqueName(EntityUid uid, NameIdentifierGroupPrototype proto, out int randomVal)
+ /// The prototype to retrieve from.
+ /// The index value of the randomly selected modifier.
+ /// A formatted and/or localized modifier. Empty string if invalid.
+ public string GenerateUniqueNameModifier(NameIdentifierGroupPrototype proto, out int randomVal)
{
- randomVal = 0;
- var entityName = Name(uid);
+ randomVal = -1;
if (!CurrentIds.TryGetValue(proto.ID, out var set))
- return entityName;
+ return string.Empty;
if (set.Count == 0)
{
// Oh jeez. We're outta numbers.
- return entityName;
+ return string.Empty;
}
randomVal = set[^1];
set.RemoveAt(set.Count - 1);
- return proto.Format is not null
- ? Loc.GetString(proto.Format, ("number", randomVal))
- : $"{randomVal}";
+ return FormatAndLocalize(randomVal, proto);
}
+ ///
+ /// Format and localize the provided integer against the prototype.
+ ///
+ /// The selected value to process.
+ /// The prototype that defines optional localization and formatting.
+ /// A formatted and/or localized string.
+ private string FormatAndLocalize(int value, NameIdentifierGroupPrototype proto)
+ {
+ var formatted = value.ToString();
+
+ if (proto.IdentifierDataset is not null)
+ {
+ var identifiers = ProtoMan.Index(proto.IdentifierDataset);
+ formatted = Loc.GetString(identifiers.Values.Prefix+formatted);
+ }
+
+ return proto.Format is not null
+ ? Loc.GetString(proto.Format, ("number", formatted))
+ : formatted;
+ }
+
+ ///
+ /// Initializes the component when initialized on the map.
+ /// This will use the existing identifier, if present, or generate a new one and update the component appropriately.
+ ///
+ /// The entity component tuple being initialized.
+ /// The arguments for the event. Unused.
private void OnMapInit(Entity ent, ref MapInitEvent args)
{
if (ent.Comp.Group is null)
@@ -90,29 +137,18 @@ public sealed partial class NameIdentifierSystem : SharedNameIdentifierSystem
if (!ProtoMan.Resolve(ent.Comp.Group, out var group))
return;
- int id;
- string uniqueName;
-
// If it has an existing valid identifier then use that, otherwise generate a new one.
if (ent.Comp.Identifier != -1 &&
CurrentIds.TryGetValue(ent.Comp.Group, out var ids) &&
ids.Remove(ent.Comp.Identifier))
{
- id = ent.Comp.Identifier;
- uniqueName = group.Format is not null
- ? Loc.GetString(group.Format, ("number", id))
- : $"{id}";
+ ent.Comp.FullIdentifier = FormatAndLocalize(ent.Comp.Identifier, group);
}
else
{
- uniqueName = GenerateUniqueName(ent, group, out id);
- ent.Comp.Identifier = id;
+ ent.Comp.FullIdentifier = GenerateUniqueNameModifier(group, out ent.Comp.Identifier);
}
- ent.Comp.FullIdentifier = group.FullName
- ? uniqueName
- : $"({uniqueName})";
-
Dirty(ent);
_nameModifier.RefreshNameModifiers(ent.Owner);
}
@@ -122,10 +158,24 @@ public sealed partial class NameIdentifierSystem : SharedNameIdentifierSystem
EnsureIds();
}
+ ///
+ /// Fill a provided list with a range of numbers corresponding to a prototype's defined range.
+ ///
+ /// The prototype to retrieve from.
+ /// Reference to the list where values should be placed.
private void FillGroup(NameIdentifierGroupPrototype proto, List values)
{
values.Clear();
- for (var i = proto.MinValue; i < proto.MaxValue; i++)
+
+ var (max, min) = (proto.MaxValue, proto.MinValue);
+
+ if (proto.IdentifierDataset is not null)
+ {
+ max = ProtoMan.Index(proto.IdentifierDataset).Values.Count;
+ min = 1;
+ }
+
+ for (var i = min; i <= max; i++)
{
values.Add(i);
}
@@ -133,14 +183,23 @@ public sealed partial class NameIdentifierSystem : SharedNameIdentifierSystem
_robustRandom.Shuffle(values);
}
+ ///
+ /// Retrieve the appropriate list or create a new one if one does not already exist.
+ ///
+ /// The prototype to retrieve from.
+ /// The list corresponding to the prototype, a new empty list if not already extant.
private List GetOrCreateIdList(NameIdentifierGroupPrototype proto)
{
- if (!CurrentIds.TryGetValue(proto.ID, out var ids))
- {
- ids = new List(proto.MaxValue - proto.MinValue);
- CurrentIds.Add(proto.ID, ids);
- }
+ // The ID list already exists.
+ if (CurrentIds.TryGetValue(proto.ID, out var ids))
+ return ids;
+ // If we're using a dataset, grab the count. Otherwise, use (max - min).
+ ids = new List(proto.IdentifierDataset is null
+ ? proto.MaxValue - proto.MinValue
+ : ProtoMan.Index(proto.IdentifierDataset).Values.Count);
+
+ CurrentIds.Add(proto.ID, ids);
return ids;
}
@@ -176,18 +235,17 @@ public sealed partial class NameIdentifierSystem : SharedNameIdentifierSystem
foreach (var proto in set.Modified.Values)
{
- var name_proto = (NameIdentifierGroupPrototype)proto;
+ var nameProto = (NameIdentifierGroupPrototype)proto;
// Only bother adding new ones.
if (CurrentIds.ContainsKey(proto.ID))
continue;
- var ids = GetOrCreateIdList(name_proto);
- FillGroup(name_proto, ids);
+ var ids = GetOrCreateIdList(nameProto);
+ FillGroup(nameProto, ids);
}
}
-
private void CleanupIds(RoundRestartCleanupEvent ev)
{
EnsureIds();
diff --git a/Content.Shared/NameIdentifier/NameIdentifierComponent.cs b/Content.Shared/NameIdentifier/NameIdentifierComponent.cs
index f9f9eef49b..c8b42d29db 100644
--- a/Content.Shared/NameIdentifier/NameIdentifierComponent.cs
+++ b/Content.Shared/NameIdentifier/NameIdentifierComponent.cs
@@ -9,6 +9,9 @@ namespace Content.Shared.NameIdentifier;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class NameIdentifierComponent : Component
{
+ ///
+ /// The group prototype that defines the behavior of the identifier.
+ ///
[DataField]
public ProtoId? Group;
diff --git a/Content.Shared/NameIdentifier/NameIdentifierGroupPrototype.cs b/Content.Shared/NameIdentifier/NameIdentifierGroupPrototype.cs
index 933902e222..98e1c7a539 100644
--- a/Content.Shared/NameIdentifier/NameIdentifierGroupPrototype.cs
+++ b/Content.Shared/NameIdentifier/NameIdentifierGroupPrototype.cs
@@ -1,3 +1,4 @@
+using Content.Shared.Dataset;
using Robust.Shared.Prototypes;
namespace Content.Shared.NameIdentifier;
@@ -14,6 +15,12 @@ public sealed partial class NameIdentifierGroupPrototype : IPrototype
[DataField]
public bool FullName = false;
+ ///
+ /// Should the identifier be a prefix, or a suffix?
+ ///
+ [DataField]
+ public bool Prefix = false;
+
///
/// Optional format identifier. If set, the name will be formatted using it (e.g., "MK-500").
/// If not set, only the numeric part will be used (e.g., "500").
@@ -32,4 +39,11 @@ public sealed partial class NameIdentifierGroupPrototype : IPrototype
///
[DataField]
public int MinValue = 0;
+
+ ///
+ /// An optional field that will provide a list of name elements to pull from.
+ /// If non-null, and will be ignored.
+ ///
+ [DataField]
+ public ProtoId? IdentifierDataset;
}
diff --git a/Content.Shared/NameIdentifier/SharedNameIdentifierSystem.cs b/Content.Shared/NameIdentifier/SharedNameIdentifierSystem.cs
index 068e416d77..fa30cf0b89 100644
--- a/Content.Shared/NameIdentifier/SharedNameIdentifierSystem.cs
+++ b/Content.Shared/NameIdentifier/SharedNameIdentifierSystem.cs
@@ -7,18 +7,16 @@ namespace Content.Shared.NameIdentifier;
///
public abstract partial class SharedNameIdentifierSystem : EntitySystem
{
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnRefreshNameModifiers);
- }
-
+ [SubscribeLocalEvent]
private void OnRefreshNameModifiers(Entity ent, ref RefreshNameModifiersEvent args)
{
if (ent.Comp.Group is null)
return;
+ // Don't add whitespace
+ if (string.IsNullOrWhiteSpace(ent.Comp.FullIdentifier))
+ return;
+
// Don't apply the modifier if the component is being removed
if (ent.Comp.LifeStage > ComponentLifeStage.Running)
return;
@@ -26,7 +24,11 @@ public abstract partial class SharedNameIdentifierSystem : EntitySystem
if (!ProtoMan.Resolve(ent.Comp.Group, out var group))
return;
- var format = group.FullName ? "name-identifier-format-full" : "name-identifier-format-append";
+ var format = "name-identifier-format-full";
+
+ if (!group.FullName)
+ format = group.Prefix ? "name-identifier-format-prepend" : "name-identifier-format-append";
+
// We apply the modifier with a low priority to keep it near the base name
// "Beep (Si-4562) the zombie" instead of "Beep the zombie (Si-4562)"
args.AddModifier(format, -10, ("identifier", ent.Comp.FullIdentifier));
diff --git a/Content.Shared/NameModifier/EntitySystems/NameModifierSystem.cs b/Content.Shared/NameModifier/EntitySystems/NameModifierSystem.cs
index eef5261c01..2fb413a2ea 100644
--- a/Content.Shared/NameModifier/EntitySystems/NameModifierSystem.cs
+++ b/Content.Shared/NameModifier/EntitySystems/NameModifierSystem.cs
@@ -9,13 +9,7 @@ public sealed partial class NameModifierSystem : EntitySystem
{
[Dependency] private MetaDataSystem _metaData = default!;
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnEntityRenamed);
- }
-
+ [SubscribeLocalEvent]
private void OnEntityRenamed(Entity ent, ref EntityRenamedEvent args)
{
SetBaseName(ent, args.NewName);
@@ -135,7 +129,7 @@ public sealed class RefreshNameModifiersEvent : IInventoryRelayEvent
///
public string GetModifiedName()
{
- // Start out with the entity's name name
+ // Start out with the entity's base name
var name = BaseName;
// Iterate through all the modifiers in priority order
diff --git a/Resources/Locale/en-US/datasets/adjectives.ftl b/Resources/Locale/en-US/datasets/adjectives.ftl
index be7eecfc59..3dc9175a5e 100644
--- a/Resources/Locale/en-US/datasets/adjectives.ftl
+++ b/Resources/Locale/en-US/datasets/adjectives.ftl
@@ -394,3 +394,29 @@ adjectives-dataset-393 = many
adjectives-dataset-394 = numerous
adjectives-dataset-395 = substantial
adjectives-dataset-396 = capitalist
+adjectives-dataset-397 = zippy
+adjectives-dataset-398 = zesty
+adjectives-dataset-399 = ravenous
+adjectives-dataset-400 = abominable
+adjectives-dataset-401 = man-eating
+adjectives-dataset-402 = sly
+adjectives-dataset-403 = aloof
+adjectives-dataset-404 = sluggish
+adjectives-dataset-405 = wise
+adjectives-dataset-406 = noble
+adjectives-dataset-407 = stealthy
+adjectives-dataset-408 = cowardly
+adjectives-dataset-409 = sniveling
+adjectives-dataset-410 = wriggling
+adjectives-dataset-411 = skittering
+adjectives-dataset-412 = wretched
+adjectives-dataset-413 = creeping
+adjectives-dataset-414 = cunning
+adjectives-dataset-415 = reticulated
+adjectives-dataset-416 = snapping
+adjectives-dataset-417 = giant
+adjectives-dataset-418 = suspicious
+adjectives-dataset-419 = sussy
+adjectives-dataset-420 = blazed
+adjectives-dataset-421 = curmudgeonly
+adjectives-dataset-422 = pudgy
diff --git a/Resources/Locale/en-US/name-identifier.ftl b/Resources/Locale/en-US/name-identifier.ftl
index 31a3986d2b..847fe84a70 100644
--- a/Resources/Locale/en-US/name-identifier.ftl
+++ b/Resources/Locale/en-US/name-identifier.ftl
@@ -7,3 +7,4 @@ name-identifier-format-silicon = Si-{$number}
name-identifier-format-xenoborg = Xi-{$number}
name-identifier-format-station-ai = AI-{$number}
name-identifier-format-telepad = TELE-{$number}
+name-identifier-format-parens = ({$number})
diff --git a/Resources/Locale/en-US/name-identifier/name-identifier.ftl b/Resources/Locale/en-US/name-identifier/name-identifier.ftl
index e0c05e51d3..532dacd991 100644
--- a/Resources/Locale/en-US/name-identifier/name-identifier.ftl
+++ b/Resources/Locale/en-US/name-identifier/name-identifier.ftl
@@ -1,2 +1,5 @@
name-identifier-format-append = {$baseName} {$identifier}
+name-identifier-format-prepend = {$identifier} {$baseName}
name-identifier-format-full = {$identifier}
+
+name-identifier-test-1 = TestValue
diff --git a/Resources/Prototypes/Datasets/adjectives.yml b/Resources/Prototypes/Datasets/adjectives.yml
index a0b5d2fc02..7c669a29af 100644
--- a/Resources/Prototypes/Datasets/adjectives.yml
+++ b/Resources/Prototypes/Datasets/adjectives.yml
@@ -2,4 +2,4 @@
id: Adjectives
values:
prefix: adjectives-dataset-
- count: 396
+ count: 422
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
index 068713b9b2..3c25d1a74a 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
@@ -560,6 +560,8 @@
weightlessAcceleration: 1.5
weightlessFriction: 1
baseWeightlessModifier: 1
+ - type: NameIdentifier
+ group: AdjectivePrefix
- type: Damageable
damageModifierSet: Moth
- type: Bloodstream
@@ -2000,6 +2002,8 @@
- type: MovementSpeedModifier
baseWalkSpeed : 2.5
baseSprintSpeed : 5
+ - type: NameIdentifier
+ group: AdjectivePrefix
- type: DamageStateVisuals
states:
Alive:
diff --git a/Resources/Prototypes/name_identifier_groups.yml b/Resources/Prototypes/name_identifier_groups.yml
index 1928e11281..e461fa8d58 100644
--- a/Resources/Prototypes/name_identifier_groups.yml
+++ b/Resources/Prototypes/name_identifier_groups.yml
@@ -1,4 +1,4 @@
-# Non-fungible apes, anyone?
+# Non-fungible apes, anyone?
- type: nameIdentifierGroup
id: Monkey
format: name-identifier-format-monkey
@@ -48,6 +48,7 @@
# Used to suffix a number to any mob to identify player controlled mob griefing.
- type: nameIdentifierGroup
+ format: name-identifier-format-parens
id: GenericNumber
- type: nameIdentifierGroup
@@ -60,3 +61,8 @@
format: name-identifier-format-telepad
minValue: 0
maxValue: 999
+
+- type: nameIdentifierGroup
+ id: AdjectivePrefix
+ identifierDataset: Adjectives
+ prefix: true