From 54e453628790ea2fbdc4ad7d0e8b10ef32a9201d Mon Sep 17 00:00:00 2001
From: Tim <timfalken@hotmail.com>
Date: Mon, 20 Jan 2025 20:33:21 +0100
Subject: [PATCH 01/14] Allow sorting into teams

---
 .../NpcFactionSpriteStateSetterSystem.cs      |  28 ++++++++
 .../NPC/Systems/NpcFactionSystem.cs           |   6 ++
 .../NPC/Events/NpcFactionChangedEvent.cs      |  25 ++++++++
 .../NPC/NpcFactionSelectorComponent.cs        |  14 ++++
 .../NPC/NpcFactionSelectorSystem.cs           |  60 ++++++++++++++++++
 .../NpcFactionSpriteStateSetterComponent.cs   |   7 ++
 Content.Shared/Verbs/VerbCategory.cs          |   2 +
 Resources/Locale/en-US/npc/factions.ftl       |   1 +
 Resources/Locale/en-US/verbs/verb-system.ftl  |   2 +
 .../Prototypes/Recipes/Crafting/bots.yml      |   2 +-
 Resources/Prototypes/ai_factions.yml          |  37 +++++++++++
 .../Bots/gladiabot.rsi/GladiabotBlue.png      | Bin 0 -> 1218 bytes
 .../{gladiabot.png => GladiabotFFA.png}       | Bin
 .../Bots/gladiabot.rsi/GladiabotGreen.png     | Bin 0 -> 1218 bytes
 .../Bots/gladiabot.rsi/GladiabotRed.png       | Bin 0 -> 1218 bytes
 .../Bots/gladiabot.rsi/GladiabotYellow.png    | Bin 0 -> 1218 bytes
 .../Mobs/Silicon/Bots/gladiabot.rsi/meta.json |  38 ++++++++++-
 17 files changed, 220 insertions(+), 2 deletions(-)
 create mode 100644 Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
 create mode 100644 Content.Shared/NPC/Events/NpcFactionChangedEvent.cs
 create mode 100644 Content.Shared/NPC/NpcFactionSelectorComponent.cs
 create mode 100644 Content.Shared/NPC/NpcFactionSelectorSystem.cs
 create mode 100644 Content.Shared/NPC/NpcFactionSpriteStateSetterComponent.cs
 create mode 100644 Resources/Locale/en-US/npc/factions.ftl
 create mode 100644 Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/GladiabotBlue.png
 rename Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/{gladiabot.png => GladiabotFFA.png} (100%)
 create mode 100644 Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/GladiabotGreen.png
 create mode 100644 Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/GladiabotRed.png
 create mode 100644 Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/GladiabotYellow.png

diff --git a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
new file mode 100644
index 00000000000..1e57d917bec
--- /dev/null
+++ b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
@@ -0,0 +1,28 @@
+
+using Content.Shared.NPC.Components;
+using Content.Shared.NPC.Events;
+using Robust.Client.GameObjects;
+using Robust.Shared.Reflection;
+
+namespace Content.Client.NPC.Systems;
+public sealed partial class NpcFactionSpriteStateSetterSystem : EntitySystem
+{
+    [Dependency] private readonly SpriteSystem _spriteSystem = default!;
+    [Dependency] private readonly EntityManager _entityManager = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<NpcFactionMemberComponent, NpcFactionAddedEvent>(OnFactionAdded);
+    }
+
+    private void OnFactionAdded(Entity<NpcFactionMemberComponent > entity, ref NpcFactionAddedEvent args)
+    {
+        if (!_entityManager.HasComponent(entity, typeof(NpcFactionSpriteStateSetterComponent)))
+            return;
+
+        SpriteComponent spriteComponent = _entityManager.GetComponent<SpriteComponent>(entity);
+        spriteComponent.LayerSetState(0, new Robust.Client.Graphics.RSI.StateId(args.FactionID));
+    }
+}
diff --git a/Content.Server/NPC/Systems/NpcFactionSystem.cs b/Content.Server/NPC/Systems/NpcFactionSystem.cs
index 663dbac9c77..a8a5121b250 100644
--- a/Content.Server/NPC/Systems/NpcFactionSystem.cs
+++ b/Content.Server/NPC/Systems/NpcFactionSystem.cs
@@ -1,3 +1,4 @@
+using Content.Shared.NPC.Events;
 using System.Collections.Frozen;
 using System.Linq;
 using Content.Server.NPC.Components;
@@ -76,6 +77,8 @@ public void AddFaction(EntityUid uid, string faction, bool dirty = true)
         if (!comp.Factions.Add(faction))
             return;
 
+        RaiseLocalEvent(ent.Owner, new NpcFactionAddedEvent(faction));
+
         if (dirty)
         {
             RefreshFactions(comp);
@@ -99,6 +102,9 @@ public void RemoveFaction(EntityUid uid, string faction, bool dirty = true)
         if (!component.Factions.Remove(faction))
             return;
 
+        RaiseLocalEvent(ent.Owner, new NpcFactionRemovedEvent(faction));
+
+
         if (dirty)
         {
             RefreshFactions(component);
diff --git a/Content.Shared/NPC/Events/NpcFactionChangedEvent.cs b/Content.Shared/NPC/Events/NpcFactionChangedEvent.cs
new file mode 100644
index 00000000000..dc43017af68
--- /dev/null
+++ b/Content.Shared/NPC/Events/NpcFactionChangedEvent.cs
@@ -0,0 +1,25 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.NPC.Events;
+
+/// <summary>
+/// Raised from client to server to notify a faction was added to an NPC.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class NpcFactionAddedEvent : EntityEventArgs
+{
+    public string FactionID;
+
+    public NpcFactionAddedEvent(string factionId) => FactionID = factionId;
+}
+
+/// <summary>
+/// Raised from client to server to notify a faction was removed from an NPC.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class NpcFactionRemovedEvent : EntityEventArgs
+{
+    public string FactionID;
+
+    public NpcFactionRemovedEvent(string factionId) => FactionID = factionId;
+}
diff --git a/Content.Shared/NPC/NpcFactionSelectorComponent.cs b/Content.Shared/NPC/NpcFactionSelectorComponent.cs
new file mode 100644
index 00000000000..7eb5701373b
--- /dev/null
+++ b/Content.Shared/NPC/NpcFactionSelectorComponent.cs
@@ -0,0 +1,14 @@
+using Content.Shared.NPC.Systems;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+
+namespace Content.Shared.NPC.Components;
+
+[RegisterComponent, NetworkedComponent, Access(typeof(NpcFactionSelectorSystem))]
+public sealed partial class NpcFactionSelectorComponent : Component
+{
+    [DataField(customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
+    public List<string> SelectableFactions = new();
+}
+
diff --git a/Content.Shared/NPC/NpcFactionSelectorSystem.cs b/Content.Shared/NPC/NpcFactionSelectorSystem.cs
new file mode 100644
index 00000000000..c3fef47da47
--- /dev/null
+++ b/Content.Shared/NPC/NpcFactionSelectorSystem.cs
@@ -0,0 +1,60 @@
+using Content.Shared.Database;
+using Content.Shared.NPC.Components;
+using Content.Shared.NPC.Prototypes;
+using Content.Shared.Popups;
+using Content.Shared.Verbs;
+using Robust.Shared.Prototypes;
+using System.Linq;
+
+namespace Content.Shared.NPC.Systems;
+public sealed partial class NpcFactionSelectorSystem : EntitySystem
+{
+
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly IPrototypeManager _prototype = default!;
+    [Dependency] private readonly NpcFactionSystem _factionSystem = default!;
+    [Dependency] private readonly EntityManager _entityManager = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<NpcFactionSelectorComponent, GetVerbsEvent<Verb>>(OnGetVerb);
+    }
+
+    private void OnGetVerb(Entity<NpcFactionSelectorComponent> entity, ref GetVerbsEvent<Verb> args)
+    {
+        if (!args.CanAccess || !args.CanInteract || args.Hands == null)
+            return;
+
+        NpcFactionSelectorComponent factionSelectorComponent = _entityManager.GetComponent<NpcFactionSelectorComponent>(entity);
+
+        if (factionSelectorComponent.SelectableFactions.Count < 2)
+            return;
+
+        foreach (var type in factionSelectorComponent.SelectableFactions)
+        {
+            var proto = _prototype.Index<NpcFactionPrototype>(type);
+
+            var v = new Verb
+            {
+                Priority = 1,
+                Category = VerbCategory.SelectFaction,
+                Text = proto.ID,
+                Impact = LogImpact.Medium,
+                DoContactInteraction = true,
+                Act = () =>
+                {
+                    _popup.PopupPredicted(Loc.GetString("npcfaction-component-faction-set", ("faction", proto.ID)), entity.Owner, null);
+                    foreach (var type in factionSelectorComponent.SelectableFactions)
+                    {
+                        _factionSystem.RemoveFaction(entity.Owner, type);
+                    }
+
+                    _factionSystem.AddFaction(entity.Owner, proto.ID);
+                }
+            };
+            args.Verbs.Add(v);
+        }
+    }
+}
diff --git a/Content.Shared/NPC/NpcFactionSpriteStateSetterComponent.cs b/Content.Shared/NPC/NpcFactionSpriteStateSetterComponent.cs
new file mode 100644
index 00000000000..ec5d66ff03e
--- /dev/null
+++ b/Content.Shared/NPC/NpcFactionSpriteStateSetterComponent.cs
@@ -0,0 +1,7 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.NPC.Components;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class NpcFactionSpriteStateSetterComponent : Component {}
+
diff --git a/Content.Shared/Verbs/VerbCategory.cs b/Content.Shared/Verbs/VerbCategory.cs
index 2d2a6044dfc..97ddb5c5cde 100644
--- a/Content.Shared/Verbs/VerbCategory.cs
+++ b/Content.Shared/Verbs/VerbCategory.cs
@@ -84,6 +84,8 @@ public VerbCategory(string text, string? icon, bool iconsOnly = false)
 
         public static readonly VerbCategory SelectType = new("verb-categories-select-type", null);
 
+        public static readonly VerbCategory SelectFaction = new("verb-categories-select-faction", null);
+
         public static readonly VerbCategory PowerLevel = new("verb-categories-power-level", null);
 
         public static readonly VerbCategory Interaction = new("verb-categories-interaction", null);
diff --git a/Resources/Locale/en-US/npc/factions.ftl b/Resources/Locale/en-US/npc/factions.ftl
new file mode 100644
index 00000000000..8aaa2b6e8f3
--- /dev/null
+++ b/Resources/Locale/en-US/npc/factions.ftl
@@ -0,0 +1 @@
+npcfaction-component-faction-set = Faction set to: {$faction}
diff --git a/Resources/Locale/en-US/verbs/verb-system.ftl b/Resources/Locale/en-US/verbs/verb-system.ftl
index dfb4e621dca..d579f045c5f 100644
--- a/Resources/Locale/en-US/verbs/verb-system.ftl
+++ b/Resources/Locale/en-US/verbs/verb-system.ftl
@@ -26,9 +26,11 @@ verb-categories-set-sensor = Sensor
 verb-categories-timer = Set Delay
 verb-categories-lever = Lever
 verb-categories-select-type = Select Type
+verb-categories-select-faction = Select Faction
 verb-categories-fax = Set Destination
 verb-categories-power-level = Power Level
 verb-categories-interaction = Interact
+verb-categories-blood-cult = Blood Cult
 
 verb-common-toggle-light = Toggle light
 verb-common-close = Close
diff --git a/Resources/Prototypes/Recipes/Crafting/bots.yml b/Resources/Prototypes/Recipes/Crafting/bots.yml
index 64106f663f1..8f67905b85b 100644
--- a/Resources/Prototypes/Recipes/Crafting/bots.yml
+++ b/Resources/Prototypes/Recipes/Crafting/bots.yml
@@ -87,4 +87,4 @@
   description: This bot fights for honour and glory!
   icon:
     sprite: Mobs/Silicon/Bots/gladiabot.rsi
-    state: gladiabot
+    state: GladiabotFFA
diff --git a/Resources/Prototypes/ai_factions.yml b/Resources/Prototypes/ai_factions.yml
index 0e66069f15d..fef9c2bd7f8 100644
--- a/Resources/Prototypes/ai_factions.yml
+++ b/Resources/Prototypes/ai_factions.yml
@@ -131,3 +131,40 @@
   id: GladiabotFFA
   hostile:
     - GladiabotFFA
+    - GladiabotRed
+    - GladiabotGreen
+    - GladiabotBlue
+    - GladiabotYellow
+
+- type: npcFaction
+  id: GladiabotRed
+  hostile:
+    - GladiabotFFA
+    - GladiabotGreen
+    - GladiabotBlue
+    - GladiabotYellow
+
+- type: npcFaction
+  id: GladiabotGreen
+  hostile:
+    - GladiabotFFA
+    - GladiabotRed
+    - GladiabotBlue
+    - GladiabotYellow
+
+- type: npcFaction
+  id: GladiabotBlue
+  hostile:
+    - GladiabotFFA
+    - GladiabotRed
+    - GladiabotGreen
+    - GladiabotYellow
+
+- type: npcFaction
+  id: GladiabotYellow
+  hostile:
+    - GladiabotFFA
+    - GladiabotRed
+    - GladiabotGreen
+    - GladiabotBlue
+
diff --git a/Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/GladiabotBlue.png b/Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/GladiabotBlue.png
new file mode 100644
index 0000000000000000000000000000000000000000..800edcb860771b5198492675364c8834177e7c08
GIT binary patch
literal 1218
zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3-pQ0$S%VFfg`eIy(n=Iy)-_6y>L7=A<$(
zXq=xq(c0s1fJoc_WnD_4g4P`+Cj#c`bm+^dcDZf{xW%>BYQ~)}tiDo{M8w?>Zhi0|
zdE=`GkJhYiUai1k=WvHr-Bn!k<DDfJltMlncv|=8`#<~V^SC*-3SC~)SLkp#*I01s
zk&?8~H&w0twGszRoQyX2L=+jeZu?s5zkTI9B^&PltK&?w4G*Xboi{wZrPI74-7L5H
zvt8i?(Uhq_=2V<n!qzFjb6v%sW0vnX9WZWA(=zmzQ4#yByJm}l_6(_yB83rGOHPSj
zl3P7zyG8a=uIw)-<0VeK()L_;nM<)t^@u<_2dk(^U{f#0TPsa5p%T5VANhCxv)Cz8
zBzo|~#s(vk6YJ#v@YkKccFNbU!jqBnMNQ$He!&IV;w!>spZ~s5_)5LuzvPZfv;P(G
z&iJdc_tup|?hgOW3JULDnPU5(h+U$}C?&W|)m~R|I`h5rFJ>DZc%XY>^Ru&S3;Qm!
zE4Xfs+Q-Pp*Lcw;b^Z3r^77Yr{_S~uj{g$>My{d*+D{*YVkk4fC&X1s)>K)|-NmcZ
z!lvNyyUz<Z?=PJ&GqGuAK==Y5FRy8nTi3RP+`e_bEID~$W8<_UPjd@14V}Q5C0;Y9
zPN}FY+rE8!xwQ1nn>RZ%oXexFtR2dqJ$u%aY+n#+mf>%#YZz^3XID^|2ej?T2QCR9
zrCbu^7yO?fU`W$!o(5FTS>O>_%)r2R1cVu<YV%101tm&cBT9nv(@M${i&7Z^5;OBk
z^!!{y6ioFD^^AV+VcrH*(~}zEnda%K#lQjNurf$7vNA9NSzbUa4P}GEK!cGPEY1XE
z8!|F62mt9QAkJ)O0gGn=*&x7E&dBfrn3!QSnxzat{S(+3Sb!=GjEoH!7eGw?{~xTG
zK{Hb^=d9rEN?<N%_H=O!@$h~-<#t(<fq<(q+by>I3tFKUl0N^JziDi2=zAjQ_pNn#
zF->R8IHwyQvwy(P)h6VmDthLSn$DIfTvNDJ)Ym(OhL*RkS~ve4XXsxYCpV>xyO+J$
zEX7;gUfadQAJI*8h%miTp|EM%wbx(Mn)PLNKMiADw|&L-l$G5RO4q+T`%+|e+4Wy#
z?%Qs=?{axi|9->93*X-_c>jH_wTy5h@0vu3z$xb~c3f5zwFx`e#KXXP=;IVl2ls$u
z2Y2Ogv1LEdWMI+x6vHK=$N0d={#=#3l1c4-j!nXe9We%%U4j|d)$F=YEDq<3vC}`<
zH<Qy&Bx$bz*FV;$?M&{|>sy|ywCq3Sx2XGZ^j}4n&t<#hxfaH8{qb_l$U2*MQ{LiU
z+u^leH`y2L;cb`RD4f`~Wcu<9iy1mk{wi`Yu+8|+-npI2;J_Yxk3G!~N?W$rE9{YZ
zzxu$Fzl_-)%LG3CV`zLF+qa<Oz{hi~YwRERi8)=9f3&&d%<twoKCPAY7bbs_{jv1#
a!Qc9s5=WA6nR6w9BFEF!&t;ucLK6U?SonMZ

literal 0
HcmV?d00001

diff --git a/Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/gladiabot.png b/Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/GladiabotFFA.png
similarity index 100%
rename from Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/gladiabot.png
rename to Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/GladiabotFFA.png
diff --git a/Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/GladiabotGreen.png b/Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/GladiabotGreen.png
new file mode 100644
index 0000000000000000000000000000000000000000..3e82302e6cfbf6a25805042be647351adddffbc7
GIT binary patch
literal 1218
zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3-pQ0$S%VFfg`eIy(n=Iy)-_6y>L7=A<$(
zXq=xq(c0s1fJoc_WnD_4g4P`+Cj#c`bm+^dcDZf{xW%>BYQ~)}tiDo{M8w?>Zhi0|
zdE=`GkJhYiUai1k=WvHr-Bn!k<DDfJltMlncv|=8`#<~V^SC*-3SC~)SLkp#*I01s
zk&?8~H&w0twGszRoQyX2L=+jeZu?s5zkTI9B^&PltK&?w4G*Xboi{wZrPI74-7L5H
zvt8i?(Uhq_=2V<n!qzFjb6v%sW0vnX9WZWA(=zmzQ4#yByJm}l_6(_yB83rGOHPSj
zl3P7zyG8a=uIw)-<0VeK()L_;nM<)t^@u<_2dk(^U{f#0TPsa5p%T5VANhCxv)Cz8
zBzo|~#s(vk6YJ#v@YkKccFNbU!jqBnMNQ$He!&IV;w!>spZ~s5_)5LuzvPZfv;P(G
z&iJdc_tup|?hgOW3JULDnPU5(h+U$}C?&W|)m~R|I`h5rFJ>DZc%XY>^Ru&S3;Qm!
zE4Xfs+Q-Pp*Lcw;b^Z3r^77Yr{_S~uj{g$>My{d*+D{*YVkk4fC&X3CR94wt&85@J
zqQK_yyUz<Z?=PJ&GqGuAz=CieFRy8nTi3RP+`e_bEID~$W8<_UPjd@1jX<55C0;Y9
zPN}FY+rE8!xwQ1nn>RZ%oXexFtjisqJ$u%aY+n#+mf>%#8*ON3XID^|2egeZ#oZf7
zDVGHK1^*`q7}7MGrva677I;J!12rE3VaBQ2e9}Nci4xa{lHmNblJdl&REB`W%)Amk
zKi3ciQ$0gHqu+a&w*l4kq(*qAd3tIwZ~!^13{s4&42(dQ7Z6KB*`P4cU}Of1GXdF#
zj7$syKspMDGuv6f;#oj82=J6MGQ0pLW*CiTDFaad1a<}%ph^QHV*|zo5L5sE2Ww`~
z%v8)dD|ovSm<yUcT^vI^yx&f_UDjkE;A+fvi!J|xR_KMK&;R9b8XFt>o(TGVYh7MU
z(-||)>Bh(GAMkUv2|1~Xo;jqZvt<g`6mAvu^-iIo<*lpM&40%k`d7!vO)2B<Wp6f1
z@fNq&b}{isbQ2vSOm9>uY?^lM_1Cm!eVN@)!&ujCU$H%9W%q>A_3zHU6j@z%{a2a$
zw%hKzTprZF->~t*_xB6lf1hhDBizWlCQ%}A%6W?&mlZ{A!VWg^Ft8r_IEB-}J>b~E
zT{&EA*$*@sSad$caEa(KJ}|OBS0%4xQhT3clW<~3jKO7>U<P(IyY3T<!}((D^iTH9
z<g^n>+AF~IkM(Igll%1gmgg!h`%n2T>V6#kSJCBj*)Dmmg>hVeyc{#K&gR{ex474K
zc<t9s_62)*+od-OC$=q_zC6QXhR&0}id+nAGrqHTZs#&Mu*cqGPxFJ)mM!)Qdt~0P
zKJer(W46aKflvP!8Xw2@E$BG#@m%W~`v-nvPS@lgZSFYpyLpaJYi0d~$)99@Ed6`%
Zw|=I?k>p$ETuGqF@pScbS?83{1OR)$_o@H@

literal 0
HcmV?d00001

diff --git a/Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/GladiabotRed.png b/Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/GladiabotRed.png
new file mode 100644
index 0000000000000000000000000000000000000000..68d0e24929bd221076c1154b9d8d2449a3a5afac
GIT binary patch
literal 1218
zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3-pQ0$S%VFfg`eIy(n=Iy)-_6y>L7=A<$(
zXq=xq(c0s1fJoc_WnD_4g4P`+Cj#c`bm+^dcDZf{xW%>BYQ~)}tiDo{M8w?>Zhi0|
zdE=`GkJhYiUai1k=WvHr-Bn!k<DDfJltMlncv|=8`#<~V^SC*-3SC~)SLkp#*I01s
zk&?8~H&w0twGszRoQyX2L=+jeZu?s5zkTI9B^&PltK&?w4G*Xboi{wZrPI74-7L5H
zvt8i?(Uhq_=2V<n!qzFjb6v%sW0vnX9WZWA(=zmzQ4#yByJm}l_6(_yB83rGOHPSj
zl3P7zyG8a=uIw)-<0VeK()L_;nM<)t^@u<_2dk(^U{f#0TPsa5p%T5VANhCxv)Cz8
zBzo|~#s(vk6YJ#v@YkKccFNbU!jqBnMNQ$He!&IV;w!>spZ~s5_)5LuzvPZfv;P(G
z&iJdc_tup|?hgOW3JULDnPU5(h+U$}C?&W|)m~R|I`h5rFJ>DZc%XY>^Ru&S3;Qm!
zE4Xfs+Q-Pp*Lcw;b^Z3r^77Yr{_S~uj{g$>My{d*+D{*YVkk4fC&blMO3GbXxzoj^
zz{2A3yUz<Z?=PJ&GqGvrf`9-YFRy8nTi3RP+`e_bEID~$W8<_UPjd^iKn;zVC0;Y9
zPN}FY+rE8!xwQ1nn>RZ%oXexF%B`)RJ$u%aY+n#+mf>$4t*dKiXID^|2ehr*Tay(?
zDVGHK1^*`q7}7MGrva677I;J!GcfQS0b$0e+I-SLL5ULAh?3y^w370~qEv=}#LT=B
zJwMkF1yemkJ)_@yn70Ad^rS|3rg?g5F>nAmtPE0&tPG4mmKP99L)oA(&|qW+i!%Y)
zhKx)M0zf(nh%?(+z~WgzHVE*PGcvpYCT19oW+?+u{{(gh7NAN4BVz-`1rSsJ{|9Sk
z(9BfKIV*U(5||5`JzX3_JiOmdxn0&|AmD1uc8e|lf>!8-q|g85ZyFmL`kn~-eQRA_
zOw$=N&gsU->>u!RwFx<?ik>;7rn6-V*A#9Q_4Q7nq2;Zs*3Ey%8Twbp$xSKa?qzQ_
zOYs)B*LE@SM|2Y%B1~^oC~TT`?e*8RW__96Ps3Q(ZC|lHWo7q-()I7oz7$zqcKuhG
z`?lNeyIdaBzu&O&!uR(J-hZEKEhF5>yCzX0aLRd$9hVhFZNd&V@i4F+`Z$Ht!9C#E
z!Cg6AY}pSq8CY~a#c+w}F+MP|KUXEMWKw&dW0P=VM~uN`mtY2VHM{N;i^KV1?DS9e
z&E&KbN!lyG^^f&wJCpnL`j+P^E&EUTE$V(8{a4ZDbJ;F=u7z=2f4m$svd-q+l()Fo
zc6jaAP4)$Qc-y5n3MaNLnZ7*3VusF>zlvN8Y%{*IcW&o0IIzdwV^8yg(v~gu3VUSU
zuRiePFJrdHGJ#M37#bhP_ATf*@bO&h8v6%+VoulOA8qb9^SgPDPitlUg~^{}e=Plb
a@V9=Z#F6A%=3Ggj$nkXbb6Mw<&;$S$ZTE%%

literal 0
HcmV?d00001

diff --git a/Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/GladiabotYellow.png b/Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/GladiabotYellow.png
new file mode 100644
index 0000000000000000000000000000000000000000..abdef3b926ff71c930310a9fa66e1cdf0dc41d26
GIT binary patch
literal 1218
zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3-pQ0$S%VFfg`eIy(n=Iy)-_6y>L7=A<$(
zXq=xq(c0s1fJoc_WnD_4g4P`+Cj#c`bm+^dcDZf{xW%>BYQ~)}tiDo{M8w?>Zhi0|
zdE=`GkJhYiUai1k=WvHr-Bn!k<DDfJltMlncv|=8`#<~V^SC*-3SC~)SLkp#*I01s
zk&?8~H&w0twGszRoQyX2L=+jeZu?s5zkTI9B^&PltK&?w4G*Xboi{wZrPI74-7L5H
zvt8i?(Uhq_=2V<n!qzFjb6v%sW0vnX9WZWA(=zmzQ4#yByJm}l_6(_yB83rGOHPSj
zl3P7zyG8a=uIw)-<0VeK()L_;nM<)t^@u<_2dk(^U{f#0TPsa5p%T5VANhCxv)Cz8
zBzo|~#s(vk6YJ#v@YkKccFNbU!jqBnMNQ$He!&IV;w!>spZ~s5_)5LuzvPZfv;P(G
z&iJdc_tup|?hgOW3JULDnPU5(h+U$}C?&W|)m~R|I`h5rFJ>DZc%XY>^Ru&S3;Qm!
zE4Xfs+Q-Pp*Lcw;b^Z3r^77Yr{_S~uj{g$>My{d*+D{*YVkk4fC&bm%Sjyc+xwF}&
zAlu^cyUz<Z?=PJ&GqGvrf|&t6US88Cx2|moxqa(=S#t8i#>Qzyp5_*2fqoh@OT1=I
zol;R*wtf5da%t(CH*a=kIG0CTl^0n*d-kj;*}fpuEW_V8I$YPz&aR*^4`|!^)U9$r
zO1UJ+FZe$}z>ucdJPoLvv%n*=n1O-s2naJy)#j513QCl?MwA5Sr<If^7Ns%-BxdH7
z==r&ZD46OQ>KXms!@LcsrYAMRGtJXei-7~kVP%kFWMyCkvb=y;8p;NRfd(TpSeyyS
zHe_UC5CGCqK%Cjm0v68#vO$2SoRQ%LFfqeuG)ozP`X{h6umDvW7#SNdE`XT&|36qW
zgJz~;&RN0RmB3uk?CIhd;^F;v%I&fy0|8fKwp(oZ7qmhzBz^ubf7965(Dy{p?_2Bg
zVw%pFaZWcrX8(Ynt4+vBRrJguHJvR}xTbKcsIPYl4J~h7wQl}9&d|R)PHsvWcQ1Rh
zS&Fx~y|#;qKcbuH5Mg?wLSfUiYp=hiHS5dlej3KQZu^SuDJ#1tl&*hw_NB<`vg^Of
z+_&9!-{tb4{{4oH7rwt=@c#Q;YZ>82-ZhC5fm6;~?6|BbY7=&_iHCvp(8np94(<WR
z4(`g~V#|J@$-tuXDTYf#kMV(#{kbZ6C6n6w9Gip_J7Nqjy96_^tJ!s*SRBq5W2b+z
zZziXmNYY*bu79jg+nL;_*S9=ZY1x0uZ&CN-=)a0CpUZa1b1jVH`s3x8k##ojro6?y
zw!>?`Zn7`f!`m*sQ8=+}$@JwJ7Bh67{8i*)V4Lxsy>mO4!GS&Y9($S}l(uZKSJ)%-
ze)WMTe;KnqmI-|N$I$pVwr@elfsf}}*VsSs6LY#I|7df^ncvNGd|E5(FHHU<`(x?f
agTM7NC5|NDGUrMHMUJPdpUXO@geCyAmHFiW

literal 0
HcmV?d00001

diff --git a/Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/meta.json b/Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/meta.json
index cc898e53cd6..d3ac6b30cf6 100644
--- a/Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/meta.json
+++ b/Resources/Textures/Mobs/Silicon/Bots/gladiabot.rsi/meta.json
@@ -8,7 +8,43 @@
     "copyright": "Tim Falken",
     "states": [
         {
-            "name": "gladiabot",
+            "name": "GladiabotFFA",
+            "delays": [
+                [
+                    0.5,
+                    0.2
+                ]
+            ]
+        },
+        {
+            "name": "GladiabotRed",
+            "delays": [
+                [
+                    0.5,
+                    0.2
+                ]
+            ]
+        },
+        {
+            "name": "GladiabotGreen",
+            "delays": [
+                [
+                    0.5,
+                    0.2
+                ]
+            ]
+        },
+        {
+            "name": "GladiabotBlue",
+            "delays": [
+                [
+                    0.5,
+                    0.2
+                ]
+            ]
+        },
+        {
+            "name": "GladiabotYellow",
             "delays": [
                 [
                     0.5,

From ddb30d711b922b158622e524df5baa51eeab6078 Mon Sep 17 00:00:00 2001
From: Tim <timfalken@hotmail.com>
Date: Mon, 20 Jan 2025 21:11:54 +0100
Subject: [PATCH 02/14] Use another component to subscribe to the event

---
 .../NPC/Systems/NpcFactionSpriteStateSetterSystem.cs          | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
index 1e57d917bec..376590abc71 100644
--- a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
+++ b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
@@ -14,10 +14,10 @@ public override void Initialize()
     {
         base.Initialize();
 
-        SubscribeLocalEvent<NpcFactionMemberComponent, NpcFactionAddedEvent>(OnFactionAdded);
+        SubscribeLocalEvent<NpcFactionSelectorComponent, NpcFactionAddedEvent>(OnFactionAdded);
     }
 
-    private void OnFactionAdded(Entity<NpcFactionMemberComponent > entity, ref NpcFactionAddedEvent args)
+    private void OnFactionAdded(Entity<NpcFactionSelectorComponent> entity, ref NpcFactionAddedEvent args)
     {
         if (!_entityManager.HasComponent(entity, typeof(NpcFactionSpriteStateSetterComponent)))
             return;

From ebbf1683fc55f20c975a327f9cb2114238b92b67 Mon Sep 17 00:00:00 2001
From: Tim <timfalken@hotmail.com>
Date: Mon, 20 Jan 2025 21:12:48 +0100
Subject: [PATCH 03/14] removed using which won't work in Floof

copied form EE but the Floof implementation of the NpcFactionSystem seems to be slightly outdated
---
 Content.Shared/NPC/NpcFactionSelectorSystem.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/Content.Shared/NPC/NpcFactionSelectorSystem.cs b/Content.Shared/NPC/NpcFactionSelectorSystem.cs
index c3fef47da47..a1889605564 100644
--- a/Content.Shared/NPC/NpcFactionSelectorSystem.cs
+++ b/Content.Shared/NPC/NpcFactionSelectorSystem.cs
@@ -1,6 +1,5 @@
 using Content.Shared.Database;
 using Content.Shared.NPC.Components;
-using Content.Shared.NPC.Prototypes;
 using Content.Shared.Popups;
 using Content.Shared.Verbs;
 using Robust.Shared.Prototypes;

From 00c302297ac960830075ab355227954e275ae6f8 Mon Sep 17 00:00:00 2001
From: Tim <timfalken@hotmail.com>
Date: Mon, 20 Jan 2025 22:21:42 +0100
Subject: [PATCH 04/14] Move faction selector to Server

---
 .../Systems/NpcFactionSpriteStateSetterSystem.cs   |  4 ++--
 .../NPC/Components/NpcFactionSelectorComponent.cs  | 12 ++++++++++++
 .../NPC/Systems}/NpcFactionSelectorSystem.cs       |  8 ++++----
 Content.Server/NPC/Systems/NpcFactionSystem.cs     |  4 ++--
 Content.Shared/NPC/NpcFactionSelectorComponent.cs  | 14 --------------
 5 files changed, 20 insertions(+), 22 deletions(-)
 create mode 100644 Content.Server/NPC/Components/NpcFactionSelectorComponent.cs
 rename {Content.Shared/NPC => Content.Server/NPC/Systems}/NpcFactionSelectorSystem.cs (87%)
 delete mode 100644 Content.Shared/NPC/NpcFactionSelectorComponent.cs

diff --git a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
index 376590abc71..e409436b695 100644
--- a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
+++ b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
@@ -14,10 +14,10 @@ public override void Initialize()
     {
         base.Initialize();
 
-        SubscribeLocalEvent<NpcFactionSelectorComponent, NpcFactionAddedEvent>(OnFactionAdded);
+        SubscribeLocalEvent<NpcFactionSpriteStateSetterComponent, NpcFactionAddedEvent>(OnFactionAdded);
     }
 
-    private void OnFactionAdded(Entity<NpcFactionSelectorComponent> entity, ref NpcFactionAddedEvent args)
+    private void OnFactionAdded(Entity<NpcFactionSpriteStateSetterComponent> entity, ref NpcFactionAddedEvent args)
     {
         if (!_entityManager.HasComponent(entity, typeof(NpcFactionSpriteStateSetterComponent)))
             return;
diff --git a/Content.Server/NPC/Components/NpcFactionSelectorComponent.cs b/Content.Server/NPC/Components/NpcFactionSelectorComponent.cs
new file mode 100644
index 00000000000..ff12f8c77cd
--- /dev/null
+++ b/Content.Server/NPC/Components/NpcFactionSelectorComponent.cs
@@ -0,0 +1,12 @@
+using Content.Server.NPC.Systems;
+using Robust.Shared.GameStates;
+
+namespace Content.Server.NPC.Components;
+
+[RegisterComponent, NetworkedComponent, Access(typeof(NpcFactionSelectorSystem))]
+public sealed partial class NpcFactionSelectorComponent : Component
+{
+    [DataField]
+    public List<string> SelectableFactions = new();
+}
+
diff --git a/Content.Shared/NPC/NpcFactionSelectorSystem.cs b/Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs
similarity index 87%
rename from Content.Shared/NPC/NpcFactionSelectorSystem.cs
rename to Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs
index a1889605564..29c9fd11713 100644
--- a/Content.Shared/NPC/NpcFactionSelectorSystem.cs
+++ b/Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs
@@ -1,11 +1,11 @@
+using Content.Server.NPC.Components;
 using Content.Shared.Database;
-using Content.Shared.NPC.Components;
 using Content.Shared.Popups;
 using Content.Shared.Verbs;
 using Robust.Shared.Prototypes;
 using System.Linq;
 
-namespace Content.Shared.NPC.Systems;
+namespace Content.Server.NPC.Systems;
 public sealed partial class NpcFactionSelectorSystem : EntitySystem
 {
 
@@ -18,10 +18,10 @@ public override void Initialize()
     {
         base.Initialize();
 
-        SubscribeLocalEvent<NpcFactionSelectorComponent, GetVerbsEvent<Verb>>(OnGetVerb);
+        SubscribeLocalEvent<NpcFactionMemberComponent, GetVerbsEvent<Verb>>(OnGetVerb);
     }
 
-    private void OnGetVerb(Entity<NpcFactionSelectorComponent> entity, ref GetVerbsEvent<Verb> args)
+    private void OnGetVerb(Entity<NpcFactionMemberComponent> entity, ref GetVerbsEvent<Verb> args)
     {
         if (!args.CanAccess || !args.CanInteract || args.Hands == null)
             return;
diff --git a/Content.Server/NPC/Systems/NpcFactionSystem.cs b/Content.Server/NPC/Systems/NpcFactionSystem.cs
index a8a5121b250..864ad5a4980 100644
--- a/Content.Server/NPC/Systems/NpcFactionSystem.cs
+++ b/Content.Server/NPC/Systems/NpcFactionSystem.cs
@@ -77,7 +77,7 @@ public void AddFaction(EntityUid uid, string faction, bool dirty = true)
         if (!comp.Factions.Add(faction))
             return;
 
-        RaiseLocalEvent(ent.Owner, new NpcFactionAddedEvent(faction));
+        RaiseLocalEvent(uid, new NpcFactionAddedEvent(faction));
 
         if (dirty)
         {
@@ -102,7 +102,7 @@ public void RemoveFaction(EntityUid uid, string faction, bool dirty = true)
         if (!component.Factions.Remove(faction))
             return;
 
-        RaiseLocalEvent(ent.Owner, new NpcFactionRemovedEvent(faction));
+        RaiseLocalEvent(uid, new NpcFactionRemovedEvent(faction));
 
 
         if (dirty)
diff --git a/Content.Shared/NPC/NpcFactionSelectorComponent.cs b/Content.Shared/NPC/NpcFactionSelectorComponent.cs
deleted file mode 100644
index 7eb5701373b..00000000000
--- a/Content.Shared/NPC/NpcFactionSelectorComponent.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Content.Shared.NPC.Systems;
-using Robust.Shared.GameStates;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-
-namespace Content.Shared.NPC.Components;
-
-[RegisterComponent, NetworkedComponent, Access(typeof(NpcFactionSelectorSystem))]
-public sealed partial class NpcFactionSelectorComponent : Component
-{
-    [DataField(customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
-    public List<string> SelectableFactions = new();
-}
-

From 622aabd9262abf9ddf9317820b9987b5ca94a6d0 Mon Sep 17 00:00:00 2001
From: Tim <timfalken@hotmail.com>
Date: Tue, 21 Jan 2025 19:21:57 +0100
Subject: [PATCH 05/14] remove networked attributes where they're not neede

---
 Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs | 2 --
 Content.Server/NPC/Components/NpcFactionSelectorComponent.cs    | 2 +-
 Content.Shared/NPC/NpcFactionSpriteStateSetterComponent.cs      | 2 +-
 3 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
index e409436b695..11e9d326d35 100644
--- a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
+++ b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
@@ -2,12 +2,10 @@
 using Content.Shared.NPC.Components;
 using Content.Shared.NPC.Events;
 using Robust.Client.GameObjects;
-using Robust.Shared.Reflection;
 
 namespace Content.Client.NPC.Systems;
 public sealed partial class NpcFactionSpriteStateSetterSystem : EntitySystem
 {
-    [Dependency] private readonly SpriteSystem _spriteSystem = default!;
     [Dependency] private readonly EntityManager _entityManager = default!;
 
     public override void Initialize()
diff --git a/Content.Server/NPC/Components/NpcFactionSelectorComponent.cs b/Content.Server/NPC/Components/NpcFactionSelectorComponent.cs
index ff12f8c77cd..b5572db4db1 100644
--- a/Content.Server/NPC/Components/NpcFactionSelectorComponent.cs
+++ b/Content.Server/NPC/Components/NpcFactionSelectorComponent.cs
@@ -3,7 +3,7 @@
 
 namespace Content.Server.NPC.Components;
 
-[RegisterComponent, NetworkedComponent, Access(typeof(NpcFactionSelectorSystem))]
+[RegisterComponent]
 public sealed partial class NpcFactionSelectorComponent : Component
 {
     [DataField]
diff --git a/Content.Shared/NPC/NpcFactionSpriteStateSetterComponent.cs b/Content.Shared/NPC/NpcFactionSpriteStateSetterComponent.cs
index ec5d66ff03e..4c90340fb84 100644
--- a/Content.Shared/NPC/NpcFactionSpriteStateSetterComponent.cs
+++ b/Content.Shared/NPC/NpcFactionSpriteStateSetterComponent.cs
@@ -2,6 +2,6 @@
 
 namespace Content.Shared.NPC.Components;
 
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent]
 public sealed partial class NpcFactionSpriteStateSetterComponent : Component {}
 

From c30fdf3fafcb6d29b8ed5ca040d420e494878826 Mon Sep 17 00:00:00 2001
From: Tim <timfalken@hotmail.com>
Date: Tue, 21 Jan 2025 19:21:57 +0100
Subject: [PATCH 06/14] remove networked attributes where they're not neede

---
 .../NPC/Systems/NpcFactionSpriteStateSetterSystem.cs         | 2 --
 Content.Server/NPC/Components/NpcFactionSelectorComponent.cs | 5 +----
 Content.Shared/NPC/NpcFactionSpriteStateSetterComponent.cs   | 2 +-
 3 files changed, 2 insertions(+), 7 deletions(-)

diff --git a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
index e409436b695..11e9d326d35 100644
--- a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
+++ b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
@@ -2,12 +2,10 @@
 using Content.Shared.NPC.Components;
 using Content.Shared.NPC.Events;
 using Robust.Client.GameObjects;
-using Robust.Shared.Reflection;
 
 namespace Content.Client.NPC.Systems;
 public sealed partial class NpcFactionSpriteStateSetterSystem : EntitySystem
 {
-    [Dependency] private readonly SpriteSystem _spriteSystem = default!;
     [Dependency] private readonly EntityManager _entityManager = default!;
 
     public override void Initialize()
diff --git a/Content.Server/NPC/Components/NpcFactionSelectorComponent.cs b/Content.Server/NPC/Components/NpcFactionSelectorComponent.cs
index ff12f8c77cd..cf42f186226 100644
--- a/Content.Server/NPC/Components/NpcFactionSelectorComponent.cs
+++ b/Content.Server/NPC/Components/NpcFactionSelectorComponent.cs
@@ -1,9 +1,6 @@
-using Content.Server.NPC.Systems;
-using Robust.Shared.GameStates;
-
 namespace Content.Server.NPC.Components;
 
-[RegisterComponent, NetworkedComponent, Access(typeof(NpcFactionSelectorSystem))]
+[RegisterComponent]
 public sealed partial class NpcFactionSelectorComponent : Component
 {
     [DataField]
diff --git a/Content.Shared/NPC/NpcFactionSpriteStateSetterComponent.cs b/Content.Shared/NPC/NpcFactionSpriteStateSetterComponent.cs
index ec5d66ff03e..4c90340fb84 100644
--- a/Content.Shared/NPC/NpcFactionSpriteStateSetterComponent.cs
+++ b/Content.Shared/NPC/NpcFactionSpriteStateSetterComponent.cs
@@ -2,6 +2,6 @@
 
 namespace Content.Shared.NPC.Components;
 
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent]
 public sealed partial class NpcFactionSpriteStateSetterComponent : Component {}
 

From 33eb9e446b7601699218dfb402521437c96aae0e Mon Sep 17 00:00:00 2001
From: Tim <timfalken@hotmail.com>
Date: Tue, 21 Jan 2025 20:00:47 +0100
Subject: [PATCH 07/14] Update prototype

---
 Resources/Prototypes/Entities/Mobs/NPCs/gladiabot.yml | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/gladiabot.yml b/Resources/Prototypes/Entities/Mobs/NPCs/gladiabot.yml
index 2ad231acb6c..78faa6f6cb6 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/gladiabot.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/gladiabot.yml
@@ -6,7 +6,7 @@
   components:
   - type: Sprite
     sprite: Mobs/Silicon/Bots/gladiabot.rsi
-    state: gladiabot
+    state: GladiabotFFA
   - type: Inventory
     templateId: gladiabot
   - type: InventorySlots
@@ -26,6 +26,14 @@
   - type: NpcFactionMember
     factions:
     - GladiabotFFA
+  - type: NpcFactionSelector
+    selectableFactions:
+    - GladiabotFFA
+    - GladiabotRed
+    - GladiabotBlue
+    - GladiabotGreen
+    - GladiabotYellow
+  - type: NpcFactionSpriteStateSetter
   - type: CombatMode
   - type: MeleeWeapon
     altDisarm: false

From 9ff0a3323b44c102f87e87a764325608cd23c7c4 Mon Sep 17 00:00:00 2001
From: Tim <timfalken@hotmail.com>
Date: Tue, 21 Jan 2025 22:03:12 +0100
Subject: [PATCH 08/14] Fix events between server and client so the bot changes
 colour

---
 .../NpcFactionSpriteStateSetterSystem.cs       | 15 +++++++++------
 Content.Server/NPC/Systems/NpcFactionSystem.cs |  9 +++++----
 .../NPC/Events/NpcFactionChangedEvent.cs       | 18 ++++++++++++++----
 3 files changed, 28 insertions(+), 14 deletions(-)

diff --git a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
index 11e9d326d35..8a6e08ae28a 100644
--- a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
+++ b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
@@ -12,15 +12,18 @@ public override void Initialize()
     {
         base.Initialize();
 
-        SubscribeLocalEvent<NpcFactionSpriteStateSetterComponent, NpcFactionAddedEvent>(OnFactionAdded);
+        SubscribeNetworkEvent<NpcFactionAddedEvent>(OnFactionAdded);
     }
 
-    private void OnFactionAdded(Entity<NpcFactionSpriteStateSetterComponent> entity, ref NpcFactionAddedEvent args)
+    private void OnFactionAdded(NpcFactionAddedEvent ev)
     {
-        if (!_entityManager.HasComponent(entity, typeof(NpcFactionSpriteStateSetterComponent)))
-            return;
+        if (_entityManager.TryGetEntity(ev.EntityUid, out var entity))
+        {
+            if (!_entityManager.HasComponent(entity.Value, typeof(NpcFactionSpriteStateSetterComponent)))
+                return;
 
-        SpriteComponent spriteComponent = _entityManager.GetComponent<SpriteComponent>(entity);
-        spriteComponent.LayerSetState(0, new Robust.Client.Graphics.RSI.StateId(args.FactionID));
+            SpriteComponent spriteComponent = _entityManager.GetComponent<SpriteComponent>(entity.Value);
+            spriteComponent.LayerSetState(0, new Robust.Client.Graphics.RSI.StateId(ev.FactionID));
+        }
     }
 }
diff --git a/Content.Server/NPC/Systems/NpcFactionSystem.cs b/Content.Server/NPC/Systems/NpcFactionSystem.cs
index 864ad5a4980..faa72d1e6fe 100644
--- a/Content.Server/NPC/Systems/NpcFactionSystem.cs
+++ b/Content.Server/NPC/Systems/NpcFactionSystem.cs
@@ -9,7 +9,7 @@ namespace Content.Server.NPC.Systems;
 
 /// <summary>
 ///     Outlines faction relationships with each other.
-///     part of psionics rework was making this a partial class. Should've already been handled upstream, based on the linter. 
+///     part of psionics rework was making this a partial class. Should've already been handled upstream, based on the linter.
 /// </summary>
 public sealed partial class NpcFactionSystem : EntitySystem
 {
@@ -77,7 +77,8 @@ public void AddFaction(EntityUid uid, string faction, bool dirty = true)
         if (!comp.Factions.Add(faction))
             return;
 
-        RaiseLocalEvent(uid, new NpcFactionAddedEvent(faction));
+        if(_lookup.TryGetNetEntity(uid, out var netEntity))
+            RaiseNetworkEvent(new NpcFactionAddedEvent(netEntity.Value, faction));
 
         if (dirty)
         {
@@ -102,8 +103,8 @@ public void RemoveFaction(EntityUid uid, string faction, bool dirty = true)
         if (!component.Factions.Remove(faction))
             return;
 
-        RaiseLocalEvent(uid, new NpcFactionRemovedEvent(faction));
-
+        if(_lookup.TryGetNetEntity(uid, out var netEntity))
+            RaiseNetworkEvent(new NpcFactionRemovedEvent(netEntity.Value, faction));
 
         if (dirty)
         {
diff --git a/Content.Shared/NPC/Events/NpcFactionChangedEvent.cs b/Content.Shared/NPC/Events/NpcFactionChangedEvent.cs
index dc43017af68..4b85345f326 100644
--- a/Content.Shared/NPC/Events/NpcFactionChangedEvent.cs
+++ b/Content.Shared/NPC/Events/NpcFactionChangedEvent.cs
@@ -8,9 +8,14 @@ namespace Content.Shared.NPC.Events;
 [Serializable, NetSerializable]
 public sealed class NpcFactionAddedEvent : EntityEventArgs
 {
-    public string FactionID;
+    public readonly string FactionID;
+    public readonly NetEntity EntityUid;
 
-    public NpcFactionAddedEvent(string factionId) => FactionID = factionId;
+    public NpcFactionAddedEvent(NetEntity entity, string factionId)
+    {
+        FactionID = factionId;
+        EntityUid = entity;
+    }
 }
 
 /// <summary>
@@ -19,7 +24,12 @@ public sealed class NpcFactionAddedEvent : EntityEventArgs
 [Serializable, NetSerializable]
 public sealed class NpcFactionRemovedEvent : EntityEventArgs
 {
-    public string FactionID;
+    public readonly string FactionID;
+    public readonly NetEntity EntityUid;
 
-    public NpcFactionRemovedEvent(string factionId) => FactionID = factionId;
+    public NpcFactionRemovedEvent(NetEntity entity, string factionId)
+    {
+        FactionID = factionId;
+        EntityUid = entity;
+    }
 }

From 785eb3b38bd2367185134f0003c7ce004553fbdf Mon Sep 17 00:00:00 2001
From: Tim <timfalken@hotmail.com>
Date: Wed, 22 Jan 2025 00:00:26 +0100
Subject: [PATCH 09/14] Don't crash when selecting NPC's that don't have the
 factionselector component

---
 Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs b/Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs
index 29c9fd11713..de026b5db61 100644
--- a/Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs
+++ b/Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs
@@ -18,10 +18,10 @@ public override void Initialize()
     {
         base.Initialize();
 
-        SubscribeLocalEvent<NpcFactionMemberComponent, GetVerbsEvent<Verb>>(OnGetVerb);
+        SubscribeLocalEvent<NpcFactionSelectorComponent, GetVerbsEvent<Verb>>(OnGetVerb);
     }
 
-    private void OnGetVerb(Entity<NpcFactionMemberComponent> entity, ref GetVerbsEvent<Verb> args)
+    private void OnGetVerb(Entity<NpcFactionSelectorComponent> entity, ref GetVerbsEvent<Verb> args)
     {
         if (!args.CanAccess || !args.CanInteract || args.Hands == null)
             return;

From e3499c48bd0a1ae45a9f160b98fbdc1d0a2c08d2 Mon Sep 17 00:00:00 2001
From: Timfa <timfalken@hotmail.com>
Date: Wed, 22 Jan 2025 00:20:49 +0100
Subject: [PATCH 10/14] Apply suggestions from code review

Co-authored-by: Mnemotechnican <69920617+Mnemotechnician@users.noreply.github.com>
---
 .../NPC/Systems/NpcFactionSpriteStateSetterSystem.cs  | 11 +++--------
 .../NPC/Systems/NpcFactionSelectorSystem.cs           |  4 +---
 2 files changed, 4 insertions(+), 11 deletions(-)

diff --git a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
index 8a6e08ae28a..328c2622b11 100644
--- a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
+++ b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
@@ -6,7 +6,6 @@
 namespace Content.Client.NPC.Systems;
 public sealed partial class NpcFactionSpriteStateSetterSystem : EntitySystem
 {
-    [Dependency] private readonly EntityManager _entityManager = default!;
 
     public override void Initialize()
     {
@@ -17,13 +16,9 @@ public override void Initialize()
 
     private void OnFactionAdded(NpcFactionAddedEvent ev)
     {
-        if (_entityManager.TryGetEntity(ev.EntityUid, out var entity))
-        {
-            if (!_entityManager.HasComponent(entity.Value, typeof(NpcFactionSpriteStateSetterComponent)))
-                return;
+        if (!TryGetEntity(ev.EntityUid, out var entity) || !TryComp<SpriteComponent>(entity.Value, out var sprite))
+            return;
 
-            SpriteComponent spriteComponent = _entityManager.GetComponent<SpriteComponent>(entity.Value);
-            spriteComponent.LayerSetState(0, new Robust.Client.Graphics.RSI.StateId(ev.FactionID));
-        }
+        sprite.LayerSetState(0, new Robust.Client.Graphics.RSI.State(0, new Robust.Client.Graphics.RSI.StateId(ev.FactionID));
     }
 }
diff --git a/Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs b/Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs
index de026b5db61..4d8eec1374b 100644
--- a/Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs
+++ b/Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs
@@ -26,9 +26,7 @@ private void OnGetVerb(Entity<NpcFactionSelectorComponent> entity, ref GetVerbsE
         if (!args.CanAccess || !args.CanInteract || args.Hands == null)
             return;
 
-        NpcFactionSelectorComponent factionSelectorComponent = _entityManager.GetComponent<NpcFactionSelectorComponent>(entity);
-
-        if (factionSelectorComponent.SelectableFactions.Count < 2)
+        if (!TryComp<NpcFactionSelectorComponent>(entity, out var factionSelectorComponent) || factionSelectorComponent.SelectableFactions.Count < 2)
             return;
 
         foreach (var type in factionSelectorComponent.SelectableFactions)

From be337147cb0ae02c6cdada4a26adcb9b628805bc Mon Sep 17 00:00:00 2001
From: Tim <timfalken@hotmail.com>
Date: Wed, 22 Jan 2025 00:46:12 +0100
Subject: [PATCH 11/14] Apply //Floofstation comments

---
 Content.Server/NPC/Systems/NpcFactionSystem.cs | 2 +-
 Content.Shared/Verbs/VerbCategory.cs           | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/Content.Server/NPC/Systems/NpcFactionSystem.cs b/Content.Server/NPC/Systems/NpcFactionSystem.cs
index faa72d1e6fe..5af25647155 100644
--- a/Content.Server/NPC/Systems/NpcFactionSystem.cs
+++ b/Content.Server/NPC/Systems/NpcFactionSystem.cs
@@ -77,7 +77,7 @@ public void AddFaction(EntityUid uid, string faction, bool dirty = true)
         if (!comp.Factions.Add(faction))
             return;
 
-        if(_lookup.TryGetNetEntity(uid, out var netEntity))
+        if(TryGetNetEntity(uid, out var netEntity)) // Floofstation
             RaiseNetworkEvent(new NpcFactionAddedEvent(netEntity.Value, faction));
 
         if (dirty)
diff --git a/Content.Shared/Verbs/VerbCategory.cs b/Content.Shared/Verbs/VerbCategory.cs
index 97ddb5c5cde..72580d7a086 100644
--- a/Content.Shared/Verbs/VerbCategory.cs
+++ b/Content.Shared/Verbs/VerbCategory.cs
@@ -84,12 +84,12 @@ public VerbCategory(string text, string? icon, bool iconsOnly = false)
 
         public static readonly VerbCategory SelectType = new("verb-categories-select-type", null);
 
-        public static readonly VerbCategory SelectFaction = new("verb-categories-select-faction", null);
-
         public static readonly VerbCategory PowerLevel = new("verb-categories-power-level", null);
 
         public static readonly VerbCategory Interaction = new("verb-categories-interaction", null);
 
         public static readonly VerbCategory Vore = new("verb-categories-vore", null); // Floofstation
+
+        public static readonly VerbCategory SelectFaction = new("verb-categories-select-faction", null); // Floofstation
     }
 }

From b1a22daf7bb0a7a0b5911a9551e7e5f5d4f1ebd9 Mon Sep 17 00:00:00 2001
From: Tim <timfalken@hotmail.com>
Date: Wed, 22 Jan 2025 00:46:39 +0100
Subject: [PATCH 12/14] Fix accidentally using an internal constructor that
 didn't seem to be necessary

---
 Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
index 328c2622b11..73eb69a7004 100644
--- a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
+++ b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
@@ -19,6 +19,6 @@ private void OnFactionAdded(NpcFactionAddedEvent ev)
         if (!TryGetEntity(ev.EntityUid, out var entity) || !TryComp<SpriteComponent>(entity.Value, out var sprite))
             return;
 
-        sprite.LayerSetState(0, new Robust.Client.Graphics.RSI.State(0, new Robust.Client.Graphics.RSI.StateId(ev.FactionID));
+        sprite.LayerSetState(0, new (ev.FactionID));
     }
 }

From 45ac05ab1589d2332892ab4d62e1e5ed003aff06 Mon Sep 17 00:00:00 2001
From: Tim <timfalken@hotmail.com>
Date: Wed, 22 Jan 2025 02:35:11 +0100
Subject: [PATCH 13/14] ensure we're only applying the event on entities with
 the setter component

---
 Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
index 73eb69a7004..c897a332cb5 100644
--- a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
+++ b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
@@ -16,7 +16,7 @@ public override void Initialize()
 
     private void OnFactionAdded(NpcFactionAddedEvent ev)
     {
-        if (!TryGetEntity(ev.EntityUid, out var entity) || !TryComp<SpriteComponent>(entity.Value, out var sprite))
+        if (!TryGetEntity(ev.EntityUid, out var entity) || !TryComp<SpriteComponent>(entity.Value, out var sprite) || !TryComp<NpcFactionSpriteStateSetterComponent>(entity.Value, out var _))
             return;
 
         sprite.LayerSetState(0, new (ev.FactionID));

From 8b764d456f0f798a6e4638c46fd0614013a6cfb8 Mon Sep 17 00:00:00 2001
From: Tim <timfalken@hotmail.com>
Date: Wed, 22 Jan 2025 17:10:20 +0100
Subject: [PATCH 14/14] Ensure a set faction is relevant to an entity that will
 change its sprite because of it

---
 .../NPC/Systems/NpcFactionSpriteStateSetterSystem.cs         | 5 +++--
 Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs       | 2 ++
 .../NPC}/NpcFactionSelectorComponent.cs                      | 2 +-
 3 files changed, 6 insertions(+), 3 deletions(-)
 rename {Content.Server/NPC/Components => Content.Shared/NPC}/NpcFactionSelectorComponent.cs (79%)

diff --git a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
index c897a332cb5..9767c87bf0d 100644
--- a/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
+++ b/Content.Client/NPC/Systems/NpcFactionSpriteStateSetterSystem.cs
@@ -16,9 +16,10 @@ public override void Initialize()
 
     private void OnFactionAdded(NpcFactionAddedEvent ev)
     {
-        if (!TryGetEntity(ev.EntityUid, out var entity) || !TryComp<SpriteComponent>(entity.Value, out var sprite) || !TryComp<NpcFactionSpriteStateSetterComponent>(entity.Value, out var _))
+        if (!TryGetEntity(ev.EntityUid, out var entity) || !TryComp<SpriteComponent>(entity.Value, out var sprite) || !TryComp<NpcFactionSpriteStateSetterComponent>(entity.Value, out var _)|| !TryComp<NpcFactionSelectorComponent>(entity.Value, out var factionSelector))
             return;
 
-        sprite.LayerSetState(0, new (ev.FactionID));
+        if(factionSelector.SelectableFactions.Contains(ev.FactionID))
+            sprite.LayerSetState(0, new (ev.FactionID));
     }
 }
diff --git a/Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs b/Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs
index 4d8eec1374b..c17207028a0 100644
--- a/Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs
+++ b/Content.Server/NPC/Systems/NpcFactionSelectorSystem.cs
@@ -4,6 +4,8 @@
 using Content.Shared.Verbs;
 using Robust.Shared.Prototypes;
 using System.Linq;
+using Content.Shared.NPC.Components;
+
 
 namespace Content.Server.NPC.Systems;
 public sealed partial class NpcFactionSelectorSystem : EntitySystem
diff --git a/Content.Server/NPC/Components/NpcFactionSelectorComponent.cs b/Content.Shared/NPC/NpcFactionSelectorComponent.cs
similarity index 79%
rename from Content.Server/NPC/Components/NpcFactionSelectorComponent.cs
rename to Content.Shared/NPC/NpcFactionSelectorComponent.cs
index cf42f186226..4e59d2a26fc 100644
--- a/Content.Server/NPC/Components/NpcFactionSelectorComponent.cs
+++ b/Content.Shared/NPC/NpcFactionSelectorComponent.cs
@@ -1,4 +1,4 @@
-namespace Content.Server.NPC.Components;
+namespace Content.Shared.NPC.Components;
 
 [RegisterComponent]
 public sealed partial class NpcFactionSelectorComponent : Component