]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Cleans up tag system (#28272)
authorTornado Tech <54727692+Tornado-Technology@users.noreply.github.com>
Sun, 2 Jun 2024 01:11:19 +0000 (11:11 +1000)
committerGitHub <noreply@github.com>
Sun, 2 Jun 2024 01:11:19 +0000 (11:11 +1000)
* Updated tag system

* Added params methods

* Fixed tag integration tests

* Fixed params methods recursion

* Revert has All/Any tag one argument realisation

* Updated tag integration tests

* Shit happens

* Added individual List/HashSet methods, docs, tests

18 files changed:
Content.Client/Verbs/VerbSystem.cs
Content.IntegrationTests/Tests/Tag/TagTest.cs
Content.Server/Administration/Toolshed/TagCommand.cs
Content.Server/Mech/Systems/MechAssemblySystem.cs
Content.Server/Procedural/DungeonJob.PostGen.cs
Content.Server/Procedural/DungeonJob.cs
Content.Server/Procedural/DungeonSystem.cs
Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs
Content.Server/Spreader/SpreaderSystem.cs
Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs
Content.Shared/Construction/Conditions/NoWindowsInTile.cs
Content.Shared/Construction/EntitySystems/AnchorableSystem.cs
Content.Shared/Construction/Steps/MultipleTagsConstructionGraphStep.cs
Content.Shared/Pinpointer/SharedNavMapSystem.cs
Content.Shared/Tag/TagComponent.cs
Content.Shared/Tag/TagComponentState.cs [deleted file]
Content.Shared/Tag/TagPrototype.cs
Content.Shared/Tag/TagSystem.cs

index 2e6c5f58099ed32f79dc8f93166b4486e188afe0..5f1f49e5fd08b3cfd81354b6f38d862fb9fa2b2e 100644 (file)
@@ -123,7 +123,6 @@ namespace Content.Client.Verbs
             if ((visibility & MenuVisibility.Invisible) == 0)
             {
                 var spriteQuery = GetEntityQuery<SpriteComponent>();
-                var tagQuery = GetEntityQuery<TagComponent>();
 
                 for (var i = entities.Count - 1; i >= 0; i--)
                 {
@@ -131,7 +130,7 @@ namespace Content.Client.Verbs
 
                     if (!spriteQuery.TryGetComponent(entity, out var spriteComponent) ||
                         !spriteComponent.Visible ||
-                        _tagSystem.HasTag(entity, "HideContextMenu", tagQuery))
+                        _tagSystem.HasTag(entity, "HideContextMenu"))
                     {
                         entities.RemoveSwap(i);
                     }
index cbcdd1c6c6231de5b8932509a6746a2cb6e7551a..e6cd2accaf56cc06343a4333a6091e16a2d607ae 100644 (file)
@@ -53,11 +53,13 @@ namespace Content.IntegrationTests.Tests.Tag
 
             EntityUid sTagDummy = default;
             TagComponent sTagComponent = null!;
+            Entity<TagComponent> sTagEntity = default;
 
             await server.WaitPost(() =>
             {
                 sTagDummy = sEntityManager.SpawnEntity(TagEntityId, MapCoordinates.Nullspace);
                 sTagComponent = sEntityManager.GetComponent<TagComponent>(sTagDummy);
+                sTagEntity = new Entity<TagComponent>(sTagDummy, sTagComponent);
             });
 
             await server.WaitAssertion(() =>
@@ -130,49 +132,64 @@ namespace Content.IntegrationTests.Tests.Tag
                 Assert.Multiple(() =>
                 {
                     // Cannot add the starting tag again
-                    Assert.That(tagSystem.AddTag(sTagDummy, sTagComponent, StartingTag), Is.False);
-                    Assert.That(tagSystem.AddTags(sTagDummy, sTagComponent, StartingTag, StartingTag), Is.False);
-                    Assert.That(tagSystem.AddTags(sTagDummy, sTagComponent, new List<string> { StartingTag, StartingTag }), Is.False);
+                    Assert.That(tagSystem.AddTag(sTagEntity, StartingTag), Is.False);
+
+                    Assert.That(tagSystem.AddTags(sTagEntity, StartingTag, StartingTag), Is.False);
+                    Assert.That(tagSystem.AddTags(sTagEntity, new List<ProtoId<TagPrototype>> { StartingTag, StartingTag }), Is.False);
+                    Assert.That(tagSystem.AddTags(sTagEntity, new HashSet<ProtoId<TagPrototype>> { StartingTag, StartingTag }), Is.False);
 
                     // Has the starting tag
                     Assert.That(tagSystem.HasTag(sTagComponent, StartingTag), Is.True);
+
                     Assert.That(tagSystem.HasAllTags(sTagComponent, StartingTag, StartingTag), Is.True);
-                    Assert.That(tagSystem.HasAllTags(sTagComponent, new List<string> { StartingTag, StartingTag }), Is.True);
+                    Assert.That(tagSystem.HasAllTags(sTagComponent, new List<ProtoId<TagPrototype>> { StartingTag, StartingTag }), Is.True);
+                    Assert.That(tagSystem.HasAllTags(sTagComponent, new HashSet<ProtoId<TagPrototype>> { StartingTag, StartingTag }), Is.True);
+
                     Assert.That(tagSystem.HasAnyTag(sTagComponent, StartingTag, StartingTag), Is.True);
-                    Assert.That(tagSystem.HasAnyTag(sTagComponent, new List<string> { StartingTag, StartingTag }), Is.True);
+                    Assert.That(tagSystem.HasAnyTag(sTagComponent, new List<ProtoId<TagPrototype>> { StartingTag, StartingTag }), Is.True);
+                    Assert.That(tagSystem.HasAnyTag(sTagComponent, new HashSet<ProtoId<TagPrototype>> { StartingTag, StartingTag }), Is.True);
 
                     // Does not have the added tag yet
                     Assert.That(tagSystem.HasTag(sTagComponent, AddedTag), Is.False);
+
                     Assert.That(tagSystem.HasAllTags(sTagComponent, AddedTag, AddedTag), Is.False);
-                    Assert.That(tagSystem.HasAllTags(sTagComponent, new List<string> { AddedTag, AddedTag }), Is.False);
+                    Assert.That(tagSystem.HasAllTags(sTagComponent, new List<ProtoId<TagPrototype>> { AddedTag, AddedTag }), Is.False);
+                    Assert.That(tagSystem.HasAllTags(sTagComponent, new HashSet<ProtoId<TagPrototype>> { AddedTag, AddedTag }), Is.False);
+
                     Assert.That(tagSystem.HasAnyTag(sTagComponent, AddedTag, AddedTag), Is.False);
-                    Assert.That(tagSystem.HasAnyTag(sTagComponent, new List<string> { AddedTag, AddedTag }), Is.False);
+                    Assert.That(tagSystem.HasAnyTag(sTagComponent, new List<ProtoId<TagPrototype>> { AddedTag, AddedTag }), Is.False);
+                    Assert.That(tagSystem.HasAnyTag(sTagComponent, new HashSet<ProtoId<TagPrototype>> { AddedTag, AddedTag }), Is.False);
 
                     // Has a combination of the two tags
                     Assert.That(tagSystem.HasAnyTag(sTagComponent, StartingTag, AddedTag), Is.True);
-                    Assert.That(tagSystem.HasAnyTag(sTagComponent, new List<string> { StartingTag, AddedTag }), Is.True);
+                    Assert.That(tagSystem.HasAnyTag(sTagComponent, new List<ProtoId<TagPrototype>> { StartingTag, AddedTag }), Is.True);
+                    Assert.That(tagSystem.HasAnyTag(sTagComponent, new HashSet<ProtoId<TagPrototype>> { StartingTag, AddedTag }), Is.True);
 
                     // Does not have both tags
                     Assert.That(tagSystem.HasAllTags(sTagComponent, StartingTag, AddedTag), Is.False);
-                    Assert.That(tagSystem.HasAllTags(sTagComponent, new List<string> { StartingTag, AddedTag }), Is.False);
+                    Assert.That(tagSystem.HasAllTags(sTagComponent, new List<ProtoId<TagPrototype>> { StartingTag, AddedTag }), Is.False);
+                    Assert.That(tagSystem.HasAllTags(sTagComponent, new HashSet<ProtoId<TagPrototype>> { StartingTag, AddedTag }), Is.False);
 
                     // Cannot remove a tag that does not exist
-                    Assert.That(tagSystem.RemoveTag(sTagDummy, sTagComponent, AddedTag), Is.False);
-                    Assert.That(tagSystem.RemoveTags(sTagDummy, sTagComponent, AddedTag, AddedTag), Is.False);
-                    Assert.That(tagSystem.RemoveTags(sTagDummy, sTagComponent, new List<string> { AddedTag, AddedTag }), Is.False);
+                    Assert.That(tagSystem.RemoveTag(sTagEntity, AddedTag), Is.False);
+
+                    Assert.That(tagSystem.RemoveTags(sTagEntity, AddedTag, AddedTag), Is.False);
+                    Assert.That(tagSystem.RemoveTags(sTagEntity, new List<ProtoId<TagPrototype>> { AddedTag, AddedTag }), Is.False);
+                    Assert.That(tagSystem.RemoveTags(sTagEntity, new HashSet<ProtoId<TagPrototype>> { AddedTag, AddedTag }), Is.False);
                 });
 
                 // Can add the new tag
-                Assert.That(tagSystem.AddTag(sTagDummy, sTagComponent, AddedTag), Is.True);
+                Assert.That(tagSystem.AddTag(sTagEntity, AddedTag), Is.True);
 
                 Assert.Multiple(() =>
                 {
                     // Cannot add it twice
-                    Assert.That(tagSystem.AddTag(sTagDummy, sTagComponent, AddedTag), Is.False);
+                    Assert.That(tagSystem.AddTag(sTagEntity, AddedTag), Is.False);
 
                     // Cannot add existing tags
-                    Assert.That(tagSystem.AddTags(sTagDummy, sTagComponent, StartingTag, AddedTag), Is.False);
-                    Assert.That(tagSystem.AddTags(sTagDummy, sTagComponent, new List<string> { StartingTag, AddedTag }), Is.False);
+                    Assert.That(tagSystem.AddTags(sTagEntity, StartingTag, AddedTag), Is.False);
+                    Assert.That(tagSystem.AddTags(sTagEntity, new List<ProtoId<TagPrototype>> { StartingTag, AddedTag }), Is.False);
+                    Assert.That(tagSystem.AddTags(sTagEntity, new HashSet<ProtoId<TagPrototype>> { StartingTag, AddedTag }), Is.False);
 
                     // Now has two tags
                     Assert.That(sTagComponent.Tags, Has.Count.EqualTo(2));
@@ -180,65 +197,103 @@ namespace Content.IntegrationTests.Tests.Tag
                     // Has both tags
                     Assert.That(tagSystem.HasTag(sTagComponent, StartingTag), Is.True);
                     Assert.That(tagSystem.HasTag(sTagComponent, AddedTag), Is.True);
+
                     Assert.That(tagSystem.HasAllTags(sTagComponent, StartingTag, StartingTag), Is.True);
                     Assert.That(tagSystem.HasAllTags(sTagComponent, AddedTag, StartingTag), Is.True);
-                    Assert.That(tagSystem.HasAllTags(sTagComponent, new List<string> { StartingTag, AddedTag }), Is.True);
-                    Assert.That(tagSystem.HasAllTags(sTagComponent, new List<string> { AddedTag, StartingTag }), Is.True);
+                    Assert.That(tagSystem.HasAllTags(sTagComponent, new List<ProtoId<TagPrototype>> { StartingTag, AddedTag }), Is.True);
+                    Assert.That(tagSystem.HasAllTags(sTagComponent, new List<ProtoId<TagPrototype>> { AddedTag, StartingTag }), Is.True);
+                    Assert.That(tagSystem.HasAllTags(sTagComponent, new HashSet<ProtoId<TagPrototype>> { StartingTag, AddedTag }), Is.True);
+                    Assert.That(tagSystem.HasAllTags(sTagComponent, new HashSet<ProtoId<TagPrototype>> { AddedTag, StartingTag }), Is.True);
+
                     Assert.That(tagSystem.HasAnyTag(sTagComponent, StartingTag, AddedTag), Is.True);
                     Assert.That(tagSystem.HasAnyTag(sTagComponent, AddedTag, StartingTag), Is.True);
+                    Assert.That(tagSystem.HasAnyTag(sTagComponent, new List<ProtoId<TagPrototype>> { StartingTag, AddedTag }), Is.True);
+                    Assert.That(tagSystem.HasAnyTag(sTagComponent, new List<ProtoId<TagPrototype>> { AddedTag, StartingTag }), Is.True);
+                    Assert.That(tagSystem.HasAnyTag(sTagComponent, new HashSet<ProtoId<TagPrototype>> { StartingTag, AddedTag }), Is.True);
+                    Assert.That(tagSystem.HasAnyTag(sTagComponent, new HashSet<ProtoId<TagPrototype>> { AddedTag, StartingTag }), Is.True);
                 });
 
                 Assert.Multiple(() =>
                 {
                     // Remove the existing starting tag
-                    Assert.That(tagSystem.RemoveTag(sTagDummy, sTagComponent, StartingTag), Is.True);
+                    Assert.That(tagSystem.RemoveTag(sTagEntity, StartingTag), Is.True);
 
                     // Remove the existing added tag
-                    Assert.That(tagSystem.RemoveTags(sTagDummy, sTagComponent, AddedTag, AddedTag), Is.True);
+                    Assert.That(tagSystem.RemoveTags(sTagEntity, AddedTag, AddedTag), Is.True);
                 });
 
                 Assert.Multiple(() =>
                 {
                     // No tags left to remove
-                    Assert.That(tagSystem.RemoveTags(sTagDummy, sTagComponent, new List<string> { StartingTag, AddedTag }), Is.False);
+                    Assert.That(tagSystem.RemoveTags(sTagEntity, new List<ProtoId<TagPrototype>> { StartingTag, AddedTag }), Is.False);
 
                     // No tags left in the component
                     Assert.That(sTagComponent.Tags, Is.Empty);
                 });
 
-#if !DEBUG
-                return;
+                // It is run only in DEBUG build,
+                // as the checks are performed only in DEBUG build.
+#if DEBUG
+                // Has single
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasTag(sTagDummy, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasTag(sTagComponent, UnregisteredTag); });
+
+                // HasAny entityUid methods
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAnyTag(sTagDummy, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAnyTag(sTagDummy, UnregisteredTag, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAnyTag(sTagDummy, new List<ProtoId<TagPrototype>> { UnregisteredTag }); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAnyTag(sTagDummy, new HashSet<ProtoId<TagPrototype>> { UnregisteredTag }); });
+
+                // HasAny component methods
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAnyTag(sTagComponent, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAnyTag(sTagComponent, UnregisteredTag, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAnyTag(sTagComponent, new List<ProtoId<TagPrototype>> { UnregisteredTag }); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAnyTag(sTagComponent, new HashSet<ProtoId<TagPrototype>> { UnregisteredTag }); });
+
+                // HasAll entityUid methods
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAllTags(sTagDummy, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAllTags(sTagDummy, UnregisteredTag, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAllTags(sTagDummy, new List<ProtoId<TagPrototype>> { UnregisteredTag }); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAllTags(sTagDummy, new HashSet<ProtoId<TagPrototype>> { UnregisteredTag }); });
+
+                // HasAll component methods
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAllTags(sTagComponent, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAllTags(sTagComponent, UnregisteredTag, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAllTags(sTagComponent, new List<ProtoId<TagPrototype>> { UnregisteredTag }); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.HasAllTags(sTagComponent, new HashSet<ProtoId<TagPrototype>> { UnregisteredTag }); });
+
+                // RemoveTag single
+                Assert.Throws<DebugAssertException>(() => { tagSystem.RemoveTag(sTagDummy, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.RemoveTag(sTagEntity, UnregisteredTag); });
+
+                // RemoveTags entityUid methods
+                Assert.Throws<DebugAssertException>(() => { tagSystem.RemoveTags(sTagDummy, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.RemoveTags(sTagDummy, UnregisteredTag, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.RemoveTags(sTagDummy, new List<ProtoId<TagPrototype>> { UnregisteredTag }); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.RemoveTags(sTagDummy, new HashSet<ProtoId<TagPrototype>> { UnregisteredTag }); });
+
+                // RemoveTags entity methods
+                Assert.Throws<DebugAssertException>(() => { tagSystem.RemoveTags(sTagEntity, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.RemoveTags(sTagEntity, UnregisteredTag, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.RemoveTags(sTagEntity, new List<ProtoId<TagPrototype>> { UnregisteredTag }); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.RemoveTags(sTagEntity, new HashSet<ProtoId<TagPrototype>> { UnregisteredTag }); });
+
+                // AddTag single
+                Assert.Throws<DebugAssertException>(() => { tagSystem.AddTag(sTagDummy, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.AddTag(sTagEntity, UnregisteredTag); });
+
+                // AddTags entityUid methods
+                Assert.Throws<DebugAssertException>(() => { tagSystem.AddTags(sTagDummy, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.AddTags(sTagDummy, UnregisteredTag, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.AddTags(sTagDummy, new List<ProtoId<TagPrototype>> { UnregisteredTag }); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.AddTags(sTagDummy, new HashSet<ProtoId<TagPrototype>> { UnregisteredTag }); });
+
+                // AddTags entity methods
+                Assert.Throws<DebugAssertException>(() => { tagSystem.AddTags(sTagEntity, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.AddTags(sTagEntity, UnregisteredTag, UnregisteredTag); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.AddTags(sTagEntity, new List<ProtoId<TagPrototype>> { UnregisteredTag }); });
+                Assert.Throws<DebugAssertException>(() => { tagSystem.AddTags(sTagEntity, new HashSet<ProtoId<TagPrototype>> { UnregisteredTag }); });
 #endif
-
-                // Single
-                Assert.Throws<DebugAssertException>(() =>
-                {
-                    tagSystem.HasTag(sTagDummy, UnregisteredTag);
-                });
-                Assert.Throws<DebugAssertException>(() =>
-                {
-                    tagSystem.HasTag(sTagComponent, UnregisteredTag);
-                });
-
-                // Any
-                Assert.Throws<DebugAssertException>(() =>
-                {
-                    tagSystem.HasAnyTag(sTagDummy, UnregisteredTag);
-                });
-                Assert.Throws<DebugAssertException>(() =>
-                {
-                    tagSystem.HasAnyTag(sTagComponent, UnregisteredTag);
-                });
-
-                // All
-                Assert.Throws<DebugAssertException>(() =>
-                {
-                    tagSystem.HasAllTags(sTagDummy, UnregisteredTag);
-                });
-                Assert.Throws<DebugAssertException>(() =>
-                {
-                    tagSystem.HasAllTags(sTagComponent, UnregisteredTag);
-                });
             });
             await pair.CleanReturnAsync();
         }
index 1af27797660dad12d833564bc2dab13fa74eda44..e1cf53e1b1abf7cb7e35ce8a7e275ff22fbfaa5e 100644 (file)
@@ -1,6 +1,7 @@
 using System.Linq;
 using Content.Shared.Administration;
 using Content.Shared.Tag;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Toolshed;
 using Robust.Shared.Toolshed.Syntax;
 using Robust.Shared.Toolshed.TypeParsers;
@@ -13,14 +14,14 @@ public sealed class TagCommand : ToolshedCommand
     private TagSystem? _tag;
 
     [CommandImplementation("list")]
-    public IEnumerable<string> List([PipedArgument] IEnumerable<EntityUid> ent)
+    public IEnumerable<ProtoId<TagPrototype>> List([PipedArgument] IEnumerable<EntityUid> ent)
     {
         return ent.SelectMany(x =>
         {
             if (TryComp<TagComponent>(x, out var tags))
                 // Note: Cast is required for C# to figure out the type signature.
-                return (IEnumerable<string>)tags.Tags;
-            return Array.Empty<string>();
+                return (IEnumerable<ProtoId<TagPrototype>>)tags.Tags;
+            return Array.Empty<ProtoId<TagPrototype>>();
         });
     }
 
@@ -72,7 +73,7 @@ public sealed class TagCommand : ToolshedCommand
     )
     {
         _tag ??= GetSys<TagSystem>();
-        _tag.AddTags(input, @ref.Evaluate(ctx)!);
+        _tag.AddTags(input, (IEnumerable<ProtoId<TagPrototype>>)@ref.Evaluate(ctx)!);
         return input;
     }
 
@@ -92,7 +93,7 @@ public sealed class TagCommand : ToolshedCommand
     )
     {
         _tag ??= GetSys<TagSystem>();
-        _tag.RemoveTags(input, @ref.Evaluate(ctx)!);
+        _tag.RemoveTags(input, (IEnumerable<ProtoId<TagPrototype>>)@ref.Evaluate(ctx)!);
         return input;
     }
 
index e5b7bfaac3c89d209b64f16f2b961c63332b9050..c1fff819b4c9f573f607e964b8de65fe6a820aea 100644 (file)
@@ -14,6 +14,7 @@ namespace Content.Server.Mech.Systems;
 public sealed class MechAssemblySystem : EntitySystem
 {
     [Dependency] private readonly ContainerSystem _container = default!;
+    [Dependency] private readonly TagSystem _tag = default!;
 
     /// <inheritdoc/>
     public override void Initialize()
@@ -44,7 +45,7 @@ public sealed class MechAssemblySystem : EntitySystem
 
         foreach (var (tag, val) in component.RequiredParts)
         {
-            if (!val && tagComp.Tags.Contains(tag))
+            if (!val && _tag.HasTag(tagComp, tag))
             {
                 component.RequiredParts[tag] = true;
                 _container.Insert(args.Used, component.PartsContainer);
index b326bcc378f645db615ef90c40ddd99cb82b2e4d..cb9e64f04e256cc04c0db430a019254ae16343fb 100644 (file)
@@ -13,6 +13,7 @@ using Robust.Shared.Collections;
 using Robust.Shared.Map;
 using Robust.Shared.Map.Components;
 using Robust.Shared.Physics.Components;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
 using Robust.Shared.Utility;
 
@@ -24,13 +25,15 @@ public sealed partial class DungeonJob
      * Run after the main dungeon generation
      */
 
+    private static readonly ProtoId<TagPrototype> WallTag = "Wall";
+
     private bool HasWall(MapGridComponent grid, Vector2i tile)
     {
         var anchored = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile);
 
         while (anchored.MoveNext(out var uid))
         {
-            if (_tagQuery.TryGetComponent(uid, out var tagComp) && tagComp.Tags.Contains("Wall"))
+            if (_tag.HasTag(uid.Value, WallTag))
                 return true;
         }
 
index 8fecf1c9e8e5579b62d8beff9e024deb5480606b..bf2822ff42374596c0c627d5b922a5af7dbb80ac 100644 (file)
@@ -28,10 +28,10 @@ public sealed partial class DungeonJob : Job<Dungeon>
     private readonly DecalSystem _decals;
     private readonly DungeonSystem _dungeon;
     private readonly EntityLookupSystem _lookup;
+    private readonly TagSystem _tag;
     private readonly TileSystem _tile;
     private readonly SharedMapSystem _maps;
     private readonly SharedTransformSystem _transform;
-    private EntityQuery<TagComponent> _tagQuery;
 
     private readonly DungeonConfigPrototype _gen;
     private readonly int _seed;
@@ -53,6 +53,7 @@ public sealed partial class DungeonJob : Job<Dungeon>
         DecalSystem decals,
         DungeonSystem dungeon,
         EntityLookupSystem lookup,
+        TagSystem tag,
         TileSystem tile,
         SharedTransformSystem transform,
         DungeonConfigPrototype gen,
@@ -72,10 +73,10 @@ public sealed partial class DungeonJob : Job<Dungeon>
         _decals = decals;
         _dungeon = dungeon;
         _lookup = lookup;
+        _tag = tag;
         _tile = tile;
         _maps = _entManager.System<SharedMapSystem>();
         _transform = transform;
-        _tagQuery = _entManager.GetEntityQuery<TagComponent>();
 
         _gen = gen;
         _grid = grid;
index 069508bcbbba4c79afc55a36d3a6e5e1999565fc..36009896a2cb599081c9e54d37746812551220aa 100644 (file)
@@ -10,6 +10,7 @@ using Content.Shared.GameTicking;
 using Content.Shared.Maps;
 using Content.Shared.Physics;
 using Content.Shared.Procedural;
+using Content.Shared.Tag;
 using Robust.Server.GameObjects;
 using Robust.Shared.Configuration;
 using Robust.Shared.Console;
@@ -31,6 +32,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
     [Dependency] private readonly AnchorableSystem _anchorable = default!;
     [Dependency] private readonly DecalSystem _decals = default!;
     [Dependency] private readonly EntityLookupSystem _lookup = default!;
+    [Dependency] private readonly TagSystem _tag = default!;
     [Dependency] private readonly TileSystem _tile = default!;
     [Dependency] private readonly MapLoaderSystem _loader = default!;
     [Dependency] private readonly SharedMapSystem _maps = default!;
@@ -199,6 +201,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
             _decals,
             this,
             _lookup,
+            _tag,
             _tile,
             _transform,
             gen,
@@ -231,6 +234,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
             _decals,
             this,
             _lookup,
+            _tag,
             _tile,
             _transform,
             gen,
index fd7d15ea5dceb61febd7f15848d065927a2fa76a..670c64577a480f15b441b17b816976b9973132d3 100644 (file)
@@ -246,7 +246,7 @@ public sealed partial class RevenantSystem
         foreach (var ent in lookup)
         {
             //break windows
-            if (tags.HasComponent(ent) && _tag.HasAnyTag(ent, "Window"))
+            if (tags.HasComponent(ent) && _tag.HasTag(ent, "Window"))
             {
                 //hardcoded damage specifiers til i die.
                 var dspec = new DamageSpecifier();
index fe14d86aa1dadab8298418283f20c48b657a83c0..7de8a43d354ff4eae4e18dfc1d4345e00ee55943 100644 (file)
@@ -21,6 +21,7 @@ public sealed class SpreaderSystem : EntitySystem
     [Dependency] private readonly IPrototypeManager _prototype = default!;
     [Dependency] private readonly IRobustRandom _robustRandom = default!;
     [Dependency] private readonly SharedMapSystem _map = default!;
+    [Dependency] private readonly TagSystem _tag = default!;
 
     /// <summary>
     /// Cached maximum number of updates per spreader prototype. This is applied per-grid.
@@ -37,8 +38,7 @@ public sealed class SpreaderSystem : EntitySystem
 
     public const float SpreadCooldownSeconds = 1;
 
-    [ValidatePrototypeId<TagPrototype>]
-    private const string IgnoredTag = "SpreaderIgnore";
+    private static readonly ProtoId<TagPrototype> IgnoredTag = "SpreaderIgnore";
 
     /// <inheritdoc/>
     public override void Initialize()
@@ -189,7 +189,6 @@ public sealed class SpreaderSystem : EntitySystem
         var airtightQuery = GetEntityQuery<AirtightComponent>();
         var dockQuery = GetEntityQuery<DockingComponent>();
         var xformQuery = GetEntityQuery<TransformComponent>();
-        var tagQuery = GetEntityQuery<TagComponent>();
         var blockedAtmosDirs = AtmosDirection.Invalid;
 
         // Due to docking ports they may not necessarily be opposite directions.
@@ -212,7 +211,7 @@ public sealed class SpreaderSystem : EntitySystem
 
             // If we're on a blocked tile work out which directions we can go.
             if (!airtightQuery.TryGetComponent(ent, out var airtight) || !airtight.AirBlocked ||
-                tagQuery.TryGetComponent(ent, out var tags) && tags.Tags.Contains(IgnoredTag))
+                _tag.HasTag(ent.Value, IgnoredTag))
             {
                 continue;
             }
@@ -250,8 +249,7 @@ public sealed class SpreaderSystem : EntitySystem
 
             while (directionEnumerator.MoveNext(out var ent))
             {
-                if (!airtightQuery.TryGetComponent(ent, out var airtight) || !airtight.AirBlocked ||
-                    tagQuery.TryGetComponent(ent, out var tags) && tags.Tags.Contains(IgnoredTag))
+                if (!airtightQuery.TryGetComponent(ent, out var airtight) || !airtight.AirBlocked || _tag.HasTag(ent.Value, IgnoredTag))
                 {
                     continue;
                 }
index a447a54df17086e7eb304ddfac0e4457fed9a109..fced03bfabfc98e059b322771cc8a93227e5ed25 100644 (file)
@@ -12,9 +12,10 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
 {
     [Dependency] private readonly IComponentFactory _factory = default!;
     [Dependency] private readonly IPrototypeManager _proto = default!;
-    [Dependency] private readonly SharedItemSystem _itemSystem = default!;
     [Dependency] private readonly ClothingSystem _clothingSystem = default!;
     [Dependency] private readonly MetaDataSystem _metaData = default!;
+    [Dependency] private readonly SharedItemSystem _itemSystem = default!;
+    [Dependency] private readonly TagSystem _tag = default!;
 
     public override void Initialize()
     {
@@ -81,7 +82,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
             return false;
 
         // check if it is marked as valid chameleon target
-        if (!proto.TryGetComponent(out TagComponent? tags, _factory) || !tags.Tags.Contains("WhitelistChameleon"))
+        if (!proto.TryGetComponent(out TagComponent? tag, _factory) || !_tag.HasTag(tag, "WhitelistChameleon"))
             return false;
 
         // check if it's valid clothing
index be6bc2cfacbb279be2e19ccacff532fd70e23caf..3ae3b59362788bedf365603a01b28a15ec404e5c 100644 (file)
@@ -12,13 +12,12 @@ namespace Content.Shared.Construction.Conditions
         public bool Condition(EntityUid user, EntityCoordinates location, Direction direction)
         {
             var entManager = IoCManager.Resolve<IEntityManager>();
-            var tagQuery = entManager.GetEntityQuery<TagComponent>();
             var sysMan = entManager.EntitySysManager;
             var tagSystem = sysMan.GetEntitySystem<TagSystem>();
 
             foreach (var entity in location.GetEntitiesInTile(LookupFlags.Static))
             {
-                if (tagSystem.HasTag(entity, "Window", tagQuery))
+                if (tagSystem.HasTag(entity, "Window"))
                     return false;
             }
 
index c041cf1ba0698da70b54be01aeabe3b0278e48e9..e9ef053f629fd6fb856062581bb65df3bd4c9a06 100644 (file)
@@ -15,6 +15,7 @@ using Robust.Shared.Map;
 using Robust.Shared.Map.Components;
 using Robust.Shared.Physics.Components;
 using Content.Shared.Tag;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
 using Robust.Shared.Utility;
 using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;
@@ -32,16 +33,14 @@ public sealed partial class AnchorableSystem : EntitySystem
     [Dependency] private   readonly TagSystem _tagSystem = default!;
 
     private EntityQuery<PhysicsComponent> _physicsQuery;
-    private EntityQuery<TagComponent> _tagQuery;
 
-    public const string Unstackable = "Unstackable";
+    public readonly ProtoId<TagPrototype> Unstackable = "Unstackable";
 
     public override void Initialize()
     {
         base.Initialize();
 
         _physicsQuery = GetEntityQuery<PhysicsComponent>();
-        _tagQuery = GetEntityQuery<TagComponent>();
 
         SubscribeLocalEvent<AnchorableComponent, InteractUsingEvent>(OnInteractUsing,
             before: new[] { typeof(ItemSlotsSystem) }, after: new[] { typeof(SharedConstructionSystem) });
@@ -312,7 +311,7 @@ public sealed partial class AnchorableSystem : EntitySystem
         DebugTools.Assert(!Transform(uid).Anchored);
 
         // If we are unstackable, iterate through any other entities anchored on the current square
-        return _tagSystem.HasTag(uid, Unstackable, _tagQuery) && AnyUnstackablesAnchoredAt(location);
+        return _tagSystem.HasTag(uid, Unstackable) && AnyUnstackablesAnchoredAt(location);
     }
 
     public bool AnyUnstackablesAnchoredAt(EntityCoordinates location)
@@ -327,10 +326,8 @@ public sealed partial class AnchorableSystem : EntitySystem
         while (enumerator.MoveNext(out var entity))
         {
             // If we find another unstackable here, return true.
-            if (_tagSystem.HasTag(entity.Value, Unstackable, _tagQuery))
-            {
+            if (_tagSystem.HasTag(entity.Value, Unstackable))
                 return true;
-            }
         }
 
         return false;
index 668952dac23ddc97f8a30a0b4460a2204381acac..07ba46946a6180e332b1f3223f41b07207678af1 100644 (file)
@@ -1,14 +1,15 @@
 using Content.Shared.Tag;
+using Robust.Shared.Prototypes;
 
 namespace Content.Shared.Construction.Steps
 {
     public sealed partial class MultipleTagsConstructionGraphStep : ArbitraryInsertConstructionGraphStep
     {
         [DataField("allTags")]
-        private List<string>? _allTags;
+        private List<ProtoId<TagPrototype>>? _allTags;
 
         [DataField("anyTags")]
-        private List<string>? _anyTags;
+        private List<ProtoId<TagPrototype>>? _anyTags;
 
         private static bool IsNullOrEmpty<T>(ICollection<T>? list)
         {
@@ -21,16 +22,12 @@ namespace Content.Shared.Construction.Steps
             if (IsNullOrEmpty(_allTags) && IsNullOrEmpty(_anyTags))
                 return false; // Step is somehow invalid, we return.
 
-            // No tags at all.
-            if (!entityManager.TryGetComponent(uid, out TagComponent? tags))
-                return false;
-
             var tagSystem = entityManager.EntitySysManager.GetEntitySystem<TagSystem>();
 
-            if (_allTags != null && !tagSystem.HasAllTags(tags, _allTags))
+            if (_allTags != null && !tagSystem.HasAllTags(uid, _allTags))
                 return false; // We don't have all the tags needed.
 
-            if (_anyTags != null && !tagSystem.HasAnyTag(tags, _anyTags))
+            if (_anyTags != null && !tagSystem.HasAnyTag(uid, _anyTags))
                 return false; // We don't have any of the tags needed.
 
             // This entity is valid!
index 7c12321b5db6c60e8e713372d3c466ed82b04c42..3ced5f3c9ed52966cb71fbeb092421321f46a230 100644 (file)
@@ -3,6 +3,7 @@ using System.Numerics;
 using System.Runtime.CompilerServices;
 using Content.Shared.Tag;
 using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
@@ -24,7 +25,7 @@ public abstract class SharedNavMapSystem : EntitySystem
 
     [Robust.Shared.IoC.Dependency] private readonly TagSystem _tagSystem = default!;
 
-    private readonly string[] _wallTags = ["Wall", "Window"];
+    private static readonly ProtoId<TagPrototype>[] WallTags = {"Wall", "Window"};
     private EntityQuery<NavMapDoorComponent> _doorQuery;
 
     public override void Initialize()
@@ -58,7 +59,7 @@ public abstract class SharedNavMapSystem : EntitySystem
         if (_doorQuery.HasComp(uid))
             return  NavMapChunkType.Airlock;
 
-        if (_tagSystem.HasAnyTag(uid, _wallTags))
+        if (_tagSystem.HasAnyTag(uid, WallTags))
             return NavMapChunkType.Wall;
 
         return NavMapChunkType.Invalid;
index b5b8a48a4415da3a69a7e656e409b6d6dbbad711..ad4240ba06c72422d540f10444809231477cf1a8 100644 (file)
@@ -1,13 +1,11 @@
 using Robust.Shared.GameStates;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
+using Robust.Shared.Prototypes;
 
-namespace Content.Shared.Tag
+namespace Content.Shared.Tag;
+
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(TagSystem))]
+public sealed partial class TagComponent : Component
 {
-    [RegisterComponent, NetworkedComponent, Access(typeof(TagSystem))]
-    public sealed partial class TagComponent : Component
-    {
-        [DataField("tags", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<TagPrototype>))]
-        [Access(typeof(TagSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
-        public HashSet<string> Tags = new();
-    }
+    [DataField, ViewVariables, AutoNetworkedField]
+    public HashSet<ProtoId<TagPrototype>> Tags = new();
 }
diff --git a/Content.Shared/Tag/TagComponentState.cs b/Content.Shared/Tag/TagComponentState.cs
deleted file mode 100644 (file)
index 9919aba..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Tag
-{
-    [Serializable, NetSerializable]
-    public sealed class TagComponentState : ComponentState
-    {
-        public TagComponentState(string[] tags)
-        {
-            Tags = tags;
-        }
-
-        public string[] Tags { get; }
-    }
-}
index 2a06e22cf907e5a8d53407f3bc31fa2ed5c52e30..97f8c1af7d44632637ad71f634f7ee1eee5dbc0c 100644 (file)
@@ -1,17 +1,15 @@
 using Robust.Shared.Prototypes;
 
-namespace Content.Shared.Tag
+namespace Content.Shared.Tag;
+
+/// <summary>
+/// Prototype representing a tag in YAML.
+/// Meant to only have an ID property, as that is the only thing that
+/// gets saved in TagComponent.
+/// </summary>
+[Prototype("Tag")]
+public sealed partial class TagPrototype : IPrototype
 {
-    /// <summary>
-    ///     Prototype representing a tag in YAML.
-    ///     Meant to only have an ID property, as that is the only thing that
-    ///     gets saved in TagComponent.
-    /// </summary>
-    [Prototype("Tag")]
-    public sealed partial class TagPrototype : IPrototype
-    {
-        [ViewVariables]
-        [IdDataField]
-        public string ID { get; private set; } = default!;
-    }
+    [IdDataField, ViewVariables]
+    public string ID { get; } = string.Empty;
 }
index 7bcb887a410add321621354358bd667db943bec4..f1f620a69494e0eee9bbf6a0e51592814efc1430 100644 (file)
@@ -1,11 +1,19 @@
-using System.Diagnostics;
-using System.Linq;
-using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Utility;
 
 namespace Content.Shared.Tag;
 
+/// <summary>
+/// The system that is responsible for working with tags.
+/// Checking the existence of the <see cref="TagPrototype"/> only happens in DEBUG builds,
+/// to improve performance, so don't forget to check it.
+/// </summary>
+/// <summary>
+/// The methods to add or remove a list of tags have only an implementation with the <see cref="IEnumerable{T}"/> type,
+/// it's not much, but it takes away performance,
+/// if you need to use them often, it's better to make a proper implementation,
+/// you can read more <a href="https://github.com/space-wizards/space-station-14/pull/28272">HERE</a>.
+/// </summary>
 public sealed class TagSystem : EntitySystem
 {
     [Dependency] private readonly IPrototypeManager _proto = default!;
@@ -15,541 +23,514 @@ public sealed class TagSystem : EntitySystem
     public override void Initialize()
     {
         base.Initialize();
+
         _tagQuery = GetEntityQuery<TagComponent>();
-        SubscribeLocalEvent<TagComponent, ComponentGetState>(OnTagGetState);
-        SubscribeLocalEvent<TagComponent, ComponentHandleState>(OnTagHandleState);
 
 #if DEBUG
         SubscribeLocalEvent<TagComponent, ComponentInit>(OnTagInit);
+#endif
     }
 
+#if DEBUG
     private void OnTagInit(EntityUid uid, TagComponent component, ComponentInit args)
     {
         foreach (var tag in component.Tags)
         {
             AssertValidTag(tag);
         }
-#endif
-    }
-
-
-    private void OnTagHandleState(EntityUid uid, TagComponent component, ref ComponentHandleState args)
-    {
-        if (args.Current is not TagComponentState state)
-            return;
-
-        component.Tags.Clear();
-
-        foreach (var tag in state.Tags)
-        {
-            AssertValidTag(tag);
-            component.Tags.Add(tag);
-        }
-    }
-
-    private static void OnTagGetState(EntityUid uid, TagComponent component, ref ComponentGetState args)
-    {
-        var tags = new string[component.Tags.Count];
-        var i = 0;
-
-        foreach (var tag in component.Tags)
-        {
-            tags[i] = tag;
-            i++;
-        }
-
-        args.State = new TagComponentState(tags);
-    }
-
-    private void AssertValidTag(string id)
-    {
-        DebugTools.Assert(_proto.HasIndex<TagPrototype>(id), $"Unknown tag: {id}");
     }
+#endif
 
     /// <summary>
-    ///     Tries to add a tag to an entity if the tag doesn't already exist.
+    /// Tries to add a tag to an entity if the tag doesn't already exist.
     /// </summary>
-    /// <param name="entity">The entity to add the tag to.</param>
-    /// <param name="id">The tag to add.</param>
     /// <returns>
-    ///     true if it was added, false otherwise even if it already existed.
+    /// true if it was added, false otherwise even if it already existed.
     /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if no <see cref="TagPrototype"/> exists with the given id.
+    /// Thrown if no <see cref="TagPrototype"/> exists with the given id.
     /// </exception>
-    public bool AddTag(EntityUid entity, string id)
+    public bool AddTag(EntityUid entityUid, ProtoId<TagPrototype> tag)
     {
-        return AddTag(entity, EnsureComp<TagComponent>(entity), id);
+        return AddTag((entityUid, EnsureComp<TagComponent>(entityUid)), tag);
     }
 
     /// <summary>
-    ///     Tries to add the given tags to an entity if the tags don't already exist.
+    /// Tries to add the given tags to an entity if the tags don't already exist.
     /// </summary>
-    /// <param name="entity">The entity to add the tag to.</param>
-    /// <param name="ids">The tags to add.</param>
     /// <returns>
-    ///     true if any tags were added, false otherwise even if they all already existed.
+    /// true if any tags were added, false otherwise even if they all already existed.
     /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool AddTags(EntityUid entity, params string[] ids)
+    public bool AddTags(EntityUid entityUid, params ProtoId<TagPrototype>[] tags)
     {
-        return AddTags(entity, EnsureComp<TagComponent>(entity), ids);
+        return AddTags(entityUid,  (IEnumerable<ProtoId<TagPrototype>>)tags);
     }
 
     /// <summary>
-    ///     Tries to add the given tags to an entity if the tags don't already exist.
+    /// Tries to add the given tags to an entity if the tags don't already exist.
     /// </summary>
-    /// <param name="entity">The entity to add the tag to.</param>
-    /// <param name="ids">The tags to add.</param>
     /// <returns>
-    ///     true if any tags were added, false otherwise even if they all already existed.
+    /// true if any tags were added, false otherwise even if they all already existed.
     /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool AddTags(EntityUid entity, IEnumerable<string> ids)
+    public bool AddTags(EntityUid entityUid, IEnumerable<ProtoId<TagPrototype>> tags)
     {
-        return AddTags(entity, EnsureComp<TagComponent>(entity), ids);
+        return AddTags((entityUid, EnsureComp<TagComponent>(entityUid)), tags);
     }
 
     /// <summary>
-    ///     Tries to add a tag to an entity if it has a <see cref="TagComponent"/>
-    ///     and the tag doesn't already exist.
+    /// Tries to add a tag to an entity if it has a <see cref="TagComponent"/>
+    /// and the tag doesn't already exist.
     /// </summary>
-    /// <param name="entity">The entity to add the tag to.</param>
-    /// <param name="id">The tag to add.</param>
     /// <returns>
-    ///     true if it was added, false otherwise even if it already existed.
+    /// true if it was added, false otherwise even if it already existed.
     /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if no <see cref="TagPrototype"/> exists with the given id.
+    /// Thrown if no <see cref="TagPrototype"/> exists with the given id.
     /// </exception>
-    public bool TryAddTag(EntityUid entity, string id)
+    public bool TryAddTag(EntityUid entityUid, ProtoId<TagPrototype> tag)
     {
-        return _tagQuery.TryComp(entity, out var component) &&
-               AddTag(entity, component, id);
+        return _tagQuery.TryComp(entityUid, out var component) &&
+               AddTag((entityUid, component), tag);
     }
 
     /// <summary>
-    ///     Tries to add the given tags to an entity if it has a
-    ///     <see cref="TagComponent"/> and the tags don't already exist.
+    /// Tries to add the given tags to an entity if it has a
+    /// <see cref="TagComponent"/> and the tags don't already exist.
     /// </summary>
-    /// <param name="entity">The entity to add the tag to.</param>
-    /// <param name="ids">The tags to add.</param>
     /// <returns>
-    ///     true if any tags were added, false otherwise even if they all already existed.
+    /// true if any tags were added, false otherwise even if they all already existed.
     /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool TryAddTags(EntityUid entity, params string[] ids)
+    public bool TryAddTags(EntityUid entityUid, params ProtoId<TagPrototype>[] tags)
     {
-        return _tagQuery.TryComp(entity, out var component) &&
-               AddTags(entity, component, ids);
+        return TryAddTags(entityUid, (IEnumerable<ProtoId<TagPrototype>>)tags);
     }
 
     /// <summary>
-    ///     Tries to add the given tags to an entity if it has a
-    ///     <see cref="TagComponent"/> and the tags don't already exist.
+    /// Tries to add the given tags to an entity if it has a
+    /// <see cref="TagComponent"/> and the tags don't already exist.
     /// </summary>
-    /// <param name="entity">The entity to add the tag to.</param>
-    /// <param name="ids">The tags to add.</param>
     /// <returns>
-    ///     true if any tags were added, false otherwise even if they all already existed.
+    /// true if any tags were added, false otherwise even if they all already existed.
     /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool TryAddTags(EntityUid entity, IEnumerable<string> ids)
+    public bool TryAddTags(EntityUid entityUid, IEnumerable<ProtoId<TagPrototype>> tags)
     {
-        return _tagQuery.TryComp(entity, out var component) &&
-               AddTags(entity, component, ids);
+        return _tagQuery.TryComp(entityUid, out var component) &&
+               AddTags((entityUid, component), tags);
     }
 
     /// <summary>
-    ///     Checks if a tag has been added to an entity.
+    /// Checks if a tag has been added to an entity.
     /// </summary>
-    /// <param name="entity">The entity to check.</param>
-    /// <param name="id">The tag to check for.</param>
-    /// <returns>true if it exists, false otherwise.</returns>
+    /// <returns>
+    /// true if it exists, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if no <see cref="TagPrototype"/> exists with the given id.
+    /// Thrown if no <see cref="TagPrototype"/> exists with the given id.
     /// </exception>
-    public bool HasTag(EntityUid entity, string id)
-    {
-        return _tagQuery.TryComp(entity, out var component) &&
-               HasTag(component, id);
-    }
-
-    /// <summary>
-    ///     Checks if a tag has been added to an entity.
-    /// </summary>
-    [Obsolete]
-    public bool HasTag(EntityUid entity, string id, EntityQuery<TagComponent> tagQuery)
+    public bool HasTag(EntityUid entityUid, ProtoId<TagPrototype> tag)
     {
-        return tagQuery.TryGetComponent(entity, out var component) &&
-               HasTag(component, id);
+        return _tagQuery.TryComp(entityUid, out var component) &&
+               HasTag(component, tag);
     }
 
     /// <summary>
-    ///     Checks if all of the given tags have been added to an entity.
+    /// Checks if a tag has been added to an entity.
     /// </summary>
-    /// <param name="entity">The entity to check.</param>
-    /// <param name="id">The tags to check for.</param>
-    /// <returns>true if they all exist, false otherwise.</returns>
+    /// <returns>
+    /// true if it exists, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if no <see cref="TagPrototype"/> exists with the given id.
     /// </exception>
-    public bool HasAllTags(EntityUid entity, string id) => HasTag(entity, id);
+    public bool HasAllTags(EntityUid entityUid, ProtoId<TagPrototype> tag) =>
+        HasTag(entityUid, tag);
 
     /// <summary>
-    ///     Checks if all of the given tags have been added to an entity.
+    /// Checks if all of the given tags have been added to an entity.
     /// </summary>
-    /// <param name="entity">The entity to check.</param>
-    /// <param name="ids">The tags to check for.</param>
-    /// <returns>true if they all exist, false otherwise.</returns>
+    /// <returns>
+    /// true if they all exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool HasAllTags(EntityUid entity, List<string> ids)
+    public bool HasAllTags(EntityUid entityUid, params ProtoId<TagPrototype>[] tags)
     {
-        return _tagQuery.TryComp(entity, out var component) &&
-               HasAllTags(component, ids);
+        return _tagQuery.TryComp(entityUid, out var component) &&
+               HasAllTags(component, tags);
     }
 
     /// <summary>
-    ///     Checks if all of the given tags have been added to an entity.
+    /// Checks if all of the given tags have been added to an entity.
     /// </summary>
-    /// <param name="entity">The entity to check.</param>
-    /// <param name="ids">The tags to check for.</param>
-    /// <returns>true if they all exist, false otherwise.</returns>
+    /// <returns>
+    /// true if they all exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool HasAllTags(EntityUid entity, IEnumerable<string> ids)
+    public bool HasAllTags(EntityUid entityUid, HashSet<ProtoId<TagPrototype>> tags)
     {
-        return _tagQuery.TryComp(entity, out var component) &&
-               HasAllTags(component, ids);
+        return _tagQuery.TryComp(entityUid, out var component) &&
+               HasAllTags(component, tags);
     }
 
     /// <summary>
-    ///     Checks if all of the given tags have been added to an entity.
+    /// Checks if all of the given tags have been added to an entity.
     /// </summary>
-    /// <param name="entity">The entity to check.</param>
-    /// <param name="ids">The tags to check for.</param>
-    /// <returns>true if they all exist, false otherwise.</returns>
+    /// <returns>
+    /// true if they all exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool HasAllTags(EntityUid entity, List<ProtoId<TagPrototype>> ids)
+    public bool HasAllTags(EntityUid entityUid, List<ProtoId<TagPrototype>> tags)
     {
-        return _tagQuery.TryComp(entity, out var component) &&
-               HasAllTags(component, ids);
+        return _tagQuery.TryComp(entityUid, out var component) &&
+               HasAllTags(component, tags);
     }
 
     /// <summary>
-    ///     Checks if any of the given tags have been added to an entity.
+    /// Checks if all of the given tags have been added to an entity.
     /// </summary>
-    /// <param name="entity">The entity to check.</param>
-    /// <param name="ids">The tags to check for.</param>
-    /// <returns>true if any of them exist, false otherwise.</returns>
+    /// <returns>
+    /// true if they all exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool HasAnyTag(EntityUid entity, params string[] ids)
+    public bool HasAllTags(EntityUid entityUid, IEnumerable<ProtoId<TagPrototype>> tags)
     {
-        return _tagQuery.TryComp(entity, out var component) &&
-               HasAnyTag(component, ids);
+        return _tagQuery.TryComp(entityUid, out var component) &&
+               HasAllTags(component, tags);
     }
 
     /// <summary>
-    ///     Checks if any of the given tags have been added to an entity.
+    /// Checks if a tag has been added to an entity.
     /// </summary>
-    /// <param name="entity">The entity to check.</param>
-    /// <param name="id">The tag to check for.</param>
-    /// <returns>true if any of them exist, false otherwise.</returns>
+    /// <returns>
+    /// true if it exists, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if no <see cref="TagPrototype"/> exists with the given id.
     /// </exception>
-    public bool HasAnyTag(EntityUid entity, string id) => HasTag(entity, id);
+    public bool HasAnyTag(EntityUid entityUid, ProtoId<TagPrototype> tag) =>
+        HasTag(entityUid, tag);
 
     /// <summary>
-    ///     Checks if any of the given tags have been added to an entity.
+    /// Checks if any of the given tags have been added to an entity.
     /// </summary>
-    /// <param name="entity">The entity to check.</param>
-    /// <param name="ids">The tags to check for.</param>
-    /// <returns>true if any of them exist, false otherwise.</returns>
+    /// <returns>
+    /// true if any of them exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool HasAnyTag(EntityUid entity, List<string> ids)
+    public bool HasAnyTag(EntityUid entityUid, params ProtoId<TagPrototype>[] tags)
     {
-        return _tagQuery.TryComp(entity, out var component) &&
-               HasAnyTag(component, ids);
+        return _tagQuery.TryComp(entityUid, out var component) &&
+               HasAnyTag(component, tags);
     }
 
     /// <summary>
-    ///     Checks if any of the given tags have been added to an entity.
+    /// Checks if any of the given tags have been added to an entity.
     /// </summary>
-    /// <param name="entity">The entity to check.</param>
-    /// <param name="ids">The tags to check for.</param>
-    /// <returns>true if any of them exist, false otherwise.</returns>
+    /// <returns>
+    /// true if any of them exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool HasAnyTag(EntityUid entity, List<ProtoId<TagPrototype>> ids)
+    public bool HasAnyTag(EntityUid entityUid, HashSet<ProtoId<TagPrototype>> tags)
     {
-        return TryComp<TagComponent>(entity, out var component) &&
-               HasAnyTag(component, ids);
+        return _tagQuery.TryComp(entityUid, out var component) &&
+               HasAnyTag(component, tags);
     }
 
     /// <summary>
-    ///     Checks if any of the given tags have been added to an entity.
+    /// Checks if any of the given tags have been added to an entity.
     /// </summary>
-    /// <param name="entity">The entity to check.</param>
-    /// <param name="ids">The tags to check for.</param>
-    /// <returns>true if any of them exist, false otherwise.</returns>
+    /// <returns>
+    /// true if any of them exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool HasAnyTag(EntityUid entity, IEnumerable<string> ids)
+    public bool HasAnyTag(EntityUid entityUid, List<ProtoId<TagPrototype>> tags)
     {
-        return _tagQuery.TryComp(entity, out var component) &&
-               HasAnyTag(component, ids);
+        return _tagQuery.TryComp(entityUid, out var component) &&
+               HasAnyTag(component, tags);
     }
 
     /// <summary>
-    ///     Tries to remove a tag from an entity if it exists.
+    /// Checks if any of the given tags have been added to an entity.
     /// </summary>
-    /// <param name="entity">The entity to remove the tag from.</param>
-    /// <param name="id">The tag to remove.</param>
     /// <returns>
-    ///     true if it was removed, false otherwise even if it didn't exist.
+    /// true if any of them exist, false otherwise.
     /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if no <see cref="TagPrototype"/> exists with the given id.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool RemoveTag(EntityUid entity, string id)
+    public bool HasAnyTag(EntityUid entityUid, IEnumerable<ProtoId<TagPrototype>> tags)
     {
-        return _tagQuery.TryComp(entity, out var component) &&
-               RemoveTag(entity, component, id);
+        return _tagQuery.TryComp(entityUid, out var component) &&
+               HasAnyTag(component, tags);
     }
 
     /// <summary>
-    ///     Tries to remove a tag from an entity if it exists.
+    /// Checks if a tag has been added to an component.
     /// </summary>
-    /// <param name="entity">The entity to remove the tag from.</param>
-    /// <param name="ids">The tag to remove.</param>
     /// <returns>
-    ///     true if it was removed, false otherwise even if it didn't exist.
+    /// true if it exists, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if no <see cref="TagPrototype"/> exists with the given id.
     /// </exception>
-    /// </returns>
-    public bool RemoveTags(EntityUid entity, params string[] ids)
+    public bool HasTag(TagComponent component, ProtoId<TagPrototype> tag)
     {
-        return _tagQuery.TryComp(entity, out var component) &&
-               RemoveTags(entity, component, ids);
+#if DEBUG
+        AssertValidTag(tag);
+#endif
+        return component.Tags.Contains(tag);
     }
 
     /// <summary>
-    ///     Tries to remove a tag from an entity if it exists.
+    /// Checks if a tag has been added to an component.
     /// </summary>
-    /// <param name="entity">The entity to remove the tag from.</param>
-    /// <param name="ids">The tag to remove.</param>
     /// <returns>
-    ///     true if it was removed, false otherwise even if it didn't exist.
+    /// true if it exists, false otherwise.
     /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if no <see cref="TagPrototype"/> exists with the given id.
     /// </exception>
-    public bool RemoveTags(EntityUid entity, IEnumerable<string> ids)
-    {
-        return _tagQuery.TryComp(entity, out var component) &&
-               RemoveTags(entity, component, ids);
-    }
+    public bool HasAllTags(TagComponent component, ProtoId<TagPrototype> tag) =>
+        HasTag(component, tag);
 
     /// <summary>
-    ///     Tries to add a tag if it doesn't already exist.
+    /// Checks if all of the given tags have been added to an component.
     /// </summary>
-    /// <param name="id">The tag to add.</param>
-    /// <returns>true if it was added, false if it already existed.</returns>
+    /// <returns>
+    /// true if they all exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if no <see cref="TagPrototype"/> exists with the given id.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool AddTag(EntityUid uid, TagComponent component, string id)
+    public bool HasAllTags(TagComponent component, params ProtoId<TagPrototype>[] tags)
     {
-        AssertValidTag(id);
-        var added = component.Tags.Add(id);
-
-        if (added)
+        foreach (var tag in tags)
         {
-            Dirty(uid, component);
-            return true;
+#if DEBUG
+            AssertValidTag(tag);
+#endif
+            if (!component.Tags.Contains(tag))
+                return false;
         }
 
-        return false;
+        return true;
     }
 
     /// <summary>
-    ///     Tries to add the given tags if they don't already exist.
+    /// Checks if all of the given tags have been added to an component.
     /// </summary>
-    /// <param name="ids">The tags to add.</param>
-    /// <returns>true if any tags were added, false if they all already existed.</returns>
+    /// <returns>
+    /// true if they all exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool AddTags(EntityUid uid, TagComponent component, params string[] ids)
+    public bool HasAllTagsArray(TagComponent component, ProtoId<TagPrototype>[] tags)
     {
-        return AddTags(uid, component, ids.AsEnumerable());
+        foreach (var tag in tags)
+        {
+#if DEBUG
+            AssertValidTag(tag);
+#endif
+            if (!component.Tags.Contains(tag))
+                return false;
+        }
+
+        return true;
     }
 
     /// <summary>
-    ///     Tries to add the given tags if they don't already exist.
+    /// Checks if all of the given tags have been added to an component.
     /// </summary>
-    /// <param name="ids">The tags to add.</param>
-    /// <returns>true if any tags were added, false if they all already existed.</returns>
+    /// <returns>
+    /// true if they all exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool AddTags(EntityUid uid, TagComponent component, IEnumerable<string> ids)
+    public bool HasAllTags(TagComponent component, List<ProtoId<TagPrototype>> tags)
     {
-        var count = component.Tags.Count;
-
-        foreach (var id in ids)
-        {
-            AssertValidTag(id);
-            component.Tags.Add(id);
-        }
-
-        if (component.Tags.Count > count)
+        foreach (var tag in tags)
         {
-            Dirty(uid, component);
-            return true;
+#if DEBUG
+            AssertValidTag(tag);
+#endif
+            if (!component.Tags.Contains(tag))
+                return false;
         }
 
-        return false;
+        return true;
     }
 
     /// <summary>
-    ///     Checks if a tag has been added.
+    /// Checks if all of the given tags have been added to an component.
     /// </summary>
-    /// <param name="id">The tag to check for.</param>
-    /// <returns>true if it exists, false otherwise.</returns>
+    /// <returns>
+    /// true if they all exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if no <see cref="TagPrototype"/> exists with the given id.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool HasTag(TagComponent component, string id)
+    public bool HasAllTags(TagComponent component, HashSet<ProtoId<TagPrototype>> tags)
     {
-        AssertValidTag(id);
-        return component.Tags.Contains(id);
+        foreach (var tag in tags)
+        {
+#if DEBUG
+            AssertValidTag(tag);
+#endif
+            if (!component.Tags.Contains(tag))
+                return false;
+        }
+
+        return true;
     }
 
     /// <summary>
-    ///     Checks if all of the given tags have been added.
+    /// Checks if all of the given tags have been added to an component.
     /// </summary>
-    /// <param name="ids">The tags to check for.</param>
-    /// <returns>true if they all exist, false otherwise.</returns>
+    /// <returns>
+    /// true if they all exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool HasAllTags(TagComponent component, params string[] ids)
+    public bool HasAllTags(TagComponent component, IEnumerable<ProtoId<TagPrototype>> tags)
     {
-        return HasAllTags(component, ids.AsEnumerable());
+        foreach (var tag in tags)
+        {
+#if DEBUG
+            AssertValidTag(tag);
+#endif
+            if (!component.Tags.Contains(tag))
+                return false;
+        }
+
+        return true;
     }
 
     /// <summary>
-    ///     Checks if all of the given tags have been added.
+    /// Checks if a tag has been added to an component.
     /// </summary>
-    /// <param name="id">The tag to check for.</param>
-    /// <returns>true if they all exist, false otherwise.</returns>
+    /// <returns>
+    /// true if it exists, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if no <see cref="TagPrototype"/> exists with the given id.
     /// </exception>
-    public bool HasAllTags(TagComponent component, string id) => HasTag(component, id);
+    public bool HasAnyTag(TagComponent component, ProtoId<TagPrototype> tag) =>
+        HasTag(component, tag);
 
     /// <summary>
-    ///     Checks if all of the given tags have been added.
+    /// Checks if any of the given tags have been added to an component.
     /// </summary>
-    /// <param name="ids">The tags to check for.</param>
-    /// <returns>true if they all exist, false otherwise.</returns>
+    /// <returns>
+    /// true if any of them exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool HasAllTags(TagComponent component, List<string> ids)
+    public bool HasAnyTag(TagComponent component, params ProtoId<TagPrototype>[] tags)
     {
-        foreach (var id in ids)
+        foreach (var tag in tags)
         {
-            AssertValidTag(id);
-
-            if (!component.Tags.Contains(id))
-                return false;
+#if DEBUG
+            AssertValidTag(tag);
+#endif
+            if (component.Tags.Contains(tag))
+                return true;
         }
 
-        return true;
+        return false;
     }
 
     /// <summary>
-    ///     Checks if all of the given tags have been added.
+    /// Checks if any of the given tags have been added to an component.
     /// </summary>
-    /// <param name="ids">The tags to check for.</param>
-    /// <returns>true if they all exist, false otherwise.</returns>
+    /// <returns>
+    /// true if any of them exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool HasAllTags(TagComponent component, IEnumerable<string> ids)
+    public bool HasAnyTag(TagComponent component, HashSet<ProtoId<TagPrototype>> tags)
     {
-        foreach (var id in ids)
+        foreach (var tag in tags)
         {
-            AssertValidTag(id);
-
-            if (!component.Tags.Contains(id))
-                return false;
-
+#if DEBUG
+            AssertValidTag(tag);
+#endif
+            if (component.Tags.Contains(tag))
+                return true;
         }
 
-        return true;
+        return false;
     }
 
     /// <summary>
-    ///     Checks if all of the given tags have been added.
+    /// Checks if any of the given tags have been added to an component.
     /// </summary>
-    /// <param name="ids">The tags to check for.</param>
-    /// <returns>true if they all exist, false otherwise.</returns>
+    /// <returns>
+    /// true if any of them exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool HasAllTags(TagComponent component, List<ProtoId<TagPrototype>> ids)
+    public bool HasAnyTag(TagComponent component, List<ProtoId<TagPrototype>> tags)
     {
-        foreach (var id in ids)
+        foreach (var tag in tags)
         {
-            AssertValidTag(id);
-
-            if (!component.Tags.Contains(id))
-                return false;
-
+#if DEBUG
+            AssertValidTag(tag);
+#endif
+            if (component.Tags.Contains(tag))
+                return true;
         }
 
-        return true;
+        return false;
     }
 
     /// <summary>
-    ///     Checks if any of the given tags have been added.
+    /// Checks if any of the given tags have been added to an component.
     /// </summary>
-    /// <param name="ids">The tags to check for.</param>
-    /// <returns>true if any of them exist, false otherwise.</returns>
+    /// <returns>
+    /// true if any of them exist, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool HasAnyTag(TagComponent component, params string[] ids)
+    public bool HasAnyTag(TagComponent component, IEnumerable<ProtoId<TagPrototype>> tags)
     {
-        foreach (var id in ids)
+        foreach (var tag in tags)
         {
-            AssertValidTag(id);
-
-            if (component.Tags.Contains(id))
+#if DEBUG
+            AssertValidTag(tag);
+#endif
+            if (component.Tags.Contains(tag))
                 return true;
         }
 
@@ -557,143 +538,178 @@ public sealed class TagSystem : EntitySystem
     }
 
     /// <summary>
-    ///     Checks if any of the given tags have been added.
+    /// Tries to remove a tag from an entity if it exists.
     /// </summary>
-    /// <param name="id">The tag to check for.</param>
-    /// <returns>true if any of them exist, false otherwise.</returns>
+    /// <returns>
+    /// true if it was removed, false otherwise even if it didn't exist.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if no <see cref="TagPrototype"/> exists with the given id.
     /// </exception>
-    public bool HasAnyTag(TagComponent component, string id) => HasTag(component, id);
+    public bool RemoveTag(EntityUid entityUid, ProtoId<TagPrototype> tag)
+    {
+        return _tagQuery.TryComp(entityUid, out var component) &&
+               RemoveTag((entityUid, component), tag);
+    }
 
     /// <summary>
-    ///     Checks if any of the given tags have been added.
+    /// Tries to remove a tag from an entity if it exists.
     /// </summary>
-    /// <param name="ids">The tags to check for.</param>
-    /// <returns>true if any of them exist, false otherwise.</returns>
+    /// <returns>
+    /// true if it was removed, false otherwise even if it didn't exist.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool HasAnyTag(TagComponent component, List<string> ids)
+    public bool RemoveTags(EntityUid entityUid, params ProtoId<TagPrototype>[] tags)
     {
-        foreach (var id in ids)
-        {
-            AssertValidTag(id);
-
-            if (component.Tags.Contains(id))
-            {
-                return true;
-            }
-        }
+        return RemoveTags(entityUid, (IEnumerable<ProtoId<TagPrototype>>)tags);
+    }
 
-        return false;
+    /// <summary>
+    /// Tries to remove a tag from an entity if it exists.
+    /// </summary>
+    /// <returns>
+    /// true if it was removed, false otherwise even if it didn't exist.
+    /// </returns>
+    /// <exception cref="UnknownPrototypeException">
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// </exception>
+    public bool RemoveTags(EntityUid entityUid, IEnumerable<ProtoId<TagPrototype>> tags)
+    {
+        return _tagQuery.TryComp(entityUid, out var component) &&
+               RemoveTags((entityUid, component), tags);
     }
 
     /// <summary>
-    ///     Checks if any of the given tags have been added.
+    /// Tries to add a tag if it doesn't already exist.
     /// </summary>
-    /// <param name="ids">The tags to check for.</param>
-    /// <returns>true if any of them exist, false otherwise.</returns>
+    /// <returns>
+    /// true if it was added, false if it already existed.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if no <see cref="TagPrototype"/> exists with the given id.
     /// </exception>
-    public bool HasAnyTag(TagComponent component, IEnumerable<string> ids)
+    public bool AddTag(Entity<TagComponent> entity, ProtoId<TagPrototype> tag)
     {
-        foreach (var id in ids)
-        {
-            AssertValidTag(id);
+#if DEBUG
+        AssertValidTag(tag);
+#endif
+        if (!entity.Comp.Tags.Add(tag))
+            return false;
 
-            if (component.Tags.Contains(id))
-            {
-                return true;
-            }
-        }
+        Dirty(entity);
+        return true;
+    }
 
-        return false;
+    /// <summary>
+    /// Tries to add the given tags if they don't already exist.
+    /// </summary>
+    /// <returns>
+    /// true if any tags were added, false if they all already existed.
+    /// </returns>
+    /// <exception cref="UnknownPrototypeException">
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// </exception>
+    public bool AddTags(Entity<TagComponent> entity, params ProtoId<TagPrototype>[] tags)
+    {
+        return AddTags(entity, (IEnumerable<ProtoId<TagPrototype>>)tags);
     }
 
     /// <summary>
-    ///     Checks if any of the given tags have been added.
+    /// Tries to add the given tags if they don't already exist.
     /// </summary>
-    /// <param name="ids">The tags to check for.</param>
-    /// <returns>true if any of them exist, false otherwise.</returns>
+    /// <returns>
+    /// true if any tags were added, false if they all already existed.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool HasAnyTag(TagComponent comp, List<ProtoId<TagPrototype>> ids)
+    public bool AddTags(Entity<TagComponent> entity, IEnumerable<ProtoId<TagPrototype>> tags)
     {
-        foreach (var id in ids)
+        var update = false;
+        foreach (var tag in tags)
         {
-            AssertValidTag(id);
-
-            if (comp.Tags.Contains(id))
-                return true;
+#if DEBUG
+            AssertValidTag(tag);
+#endif
+            if (entity.Comp.Tags.Add(tag) && !update)
+                update = true;
         }
 
-        return false;
+        if (!update)
+            return false;
+
+        Dirty(entity);
+        return true;
     }
 
     /// <summary>
-    ///     Tries to remove a tag if it exists.
+    /// Tries to remove a tag if it exists.
     /// </summary>
     /// <returns>
-    ///     true if it was removed, false otherwise even if it didn't exist.
+    /// true if it was removed, false otherwise even if it didn't exist.
     /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if no <see cref="TagPrototype"/> exists with the given id.
+    /// Thrown if no <see cref="TagPrototype"/> exists with the given id.
     /// </exception>
-    public bool RemoveTag(EntityUid uid, TagComponent component, string id)
+    public bool RemoveTag(Entity<TagComponent> entity, ProtoId<TagPrototype> tag)
     {
-        AssertValidTag(id);
+#if DEBUG
+        AssertValidTag(tag);
+#endif
 
-        if (component.Tags.Remove(id))
-        {
-            Dirty(uid, component);
-            return true;
-        }
+        if (!entity.Comp.Tags.Remove(tag))
+            return false;
 
-        return false;
+        Dirty(entity);
+        return true;
     }
 
     /// <summary>
-    ///     Tries to remove all of the given tags if they exist.
+    /// Tries to remove all of the given tags if they exist.
     /// </summary>
-    /// <param name="ids">The tags to remove.</param>
     /// <returns>
-    ///     true if it was removed, false otherwise even if they didn't exist.
+    /// true if any tag was removed, false otherwise.
     /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool RemoveTags(EntityUid uid, TagComponent component, params string[] ids)
+    public bool RemoveTags(Entity<TagComponent> entity, params ProtoId<TagPrototype>[] tags)
     {
-        return RemoveTags(uid, component, ids.AsEnumerable());
+        return RemoveTags(entity, (IEnumerable<ProtoId<TagPrototype>>)tags);
     }
 
     /// <summary>
-    ///     Tries to remove all of the given tags if they exist.
+    /// Tries to remove all of the given tags if they exist.
     /// </summary>
-    /// <param name="ids">The tags to remove.</param>
-    /// <returns>true if any tag was removed, false otherwise.</returns>
+    /// <returns>
+    /// true if any tag was removed, false otherwise.
+    /// </returns>
     /// <exception cref="UnknownPrototypeException">
-    ///     Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
+    /// Thrown if one of the ids represents an unregistered <see cref="TagPrototype"/>.
     /// </exception>
-    public bool RemoveTags(EntityUid uid, TagComponent component, IEnumerable<string> ids)
+    public bool RemoveTags(Entity<TagComponent> entity, IEnumerable<ProtoId<TagPrototype>> tags)
     {
-        var count = component.Tags.Count;
-
-        foreach (var id in ids)
+        var update = false;
+        foreach (var tag in tags)
         {
-            AssertValidTag(id);
-            component.Tags.Remove(id);
+#if DEBUG
+            AssertValidTag(tag);
+#endif
+            if (entity.Comp.Tags.Remove(tag) && !update)
+                update = true;
         }
 
-        if (component.Tags.Count < count)
-        {
-            Dirty(uid, component);
-            return true;
-        }
+        if (!update)
+            return false;
 
-        return false;
+        Dirty(entity);
+        return true;
+    }
+
+    private void AssertValidTag(string id)
+    {
+        DebugTools.Assert(_proto.HasIndex<TagPrototype>(id), $"Unknown tag: {id}");
     }
 }