diff --git a/Content.Server/NPC/Systems/FactionSystem.cs b/Content.Server/NPC/Systems/FactionSystem.cs
index a176ed149d..9445c7d6ad 100644
--- a/Content.Server/NPC/Systems/FactionSystem.cs
+++ b/Content.Server/NPC/Systems/FactionSystem.cs
@@ -1,221 +1,229 @@
-using System.Linq;
using Content.Server.NPC.Components;
using Robust.Shared.Prototypes;
+using System.Linq;
-namespace Content.Server.NPC.Systems
+namespace Content.Server.NPC.Systems;
+
+///
+/// Outlines faction relationships with each other.
+///
+public sealed class FactionSystem : EntitySystem
{
+ [Dependency] private readonly FactionExceptionSystem _factionException = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly IPrototypeManager _protoManager = default!;
+
+ private ISawmill _sawmill = default!;
+
///
- /// Outlines faction relationships with each other.
+ /// To avoid prototype mutability we store an intermediary data class that gets used instead.
///
- public sealed class FactionSystem : EntitySystem
+ private Dictionary _factions = new();
+
+ public override void Initialize()
{
- [Dependency] private readonly IPrototypeManager _protoManager = default!;
- [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ base.Initialize();
+ _sawmill = Logger.GetSawmill("faction");
+ SubscribeLocalEvent(OnFactionStartup);
+ _protoManager.PrototypesReloaded += OnProtoReload;
+ RefreshFactions();
+ }
- private ISawmill _sawmill = default!;
+ public override void Shutdown()
+ {
+ base.Shutdown();
+ _protoManager.PrototypesReloaded -= OnProtoReload;
+ }
- ///
- /// To avoid prototype mutability we store an intermediary data class that gets used instead.
- ///
- private Dictionary _factions = new();
+ private void OnProtoReload(PrototypesReloadedEventArgs obj)
+ {
+ if (!obj.ByType.ContainsKey(typeof(FactionPrototype)))
+ return;
- public override void Initialize()
+ RefreshFactions();
+ }
+
+ private void OnFactionStartup(EntityUid uid, FactionComponent component, ComponentStartup args)
+ {
+ RefreshFactions(component);
+ }
+
+ ///
+ /// Refreshes the cached factions for this component.
+ ///
+ private void RefreshFactions(FactionComponent component)
+ {
+ component.FriendlyFactions.Clear();
+ component.HostileFactions.Clear();
+
+ foreach (var faction in component.Factions)
{
- base.Initialize();
- _sawmill = Logger.GetSawmill("faction");
- SubscribeLocalEvent(OnFactionStartup);
- _protoManager.PrototypesReloaded += OnProtoReload;
- RefreshFactions();
+ // YAML Linter already yells about this
+ if (!_factions.TryGetValue(faction, out var factionData))
+ continue;
+
+ component.FriendlyFactions.UnionWith(factionData.Friendly);
+ component.HostileFactions.UnionWith(factionData.Hostile);
+ }
+ }
+
+ ///
+ /// Adds this entity to the particular faction.
+ ///
+ public void AddFaction(EntityUid uid, string faction, bool dirty = true)
+ {
+ if (!_protoManager.HasIndex(faction))
+ {
+ _sawmill.Error($"Unable to find faction {faction}");
+ return;
}
- public override void Shutdown()
+ var comp = EnsureComp(uid);
+ if (!comp.Factions.Add(faction))
+ return;
+
+ if (dirty)
{
- base.Shutdown();
- _protoManager.PrototypesReloaded -= OnProtoReload;
+ RefreshFactions(comp);
+ }
+ }
+
+ ///
+ /// Removes this entity from the particular faction.
+ ///
+ public void RemoveFaction(EntityUid uid, string faction, bool dirty = true)
+ {
+ if (!_protoManager.HasIndex(faction))
+ {
+ _sawmill.Error($"Unable to find faction {faction}");
+ return;
}
- private void OnProtoReload(PrototypesReloadedEventArgs obj)
- {
- if (!obj.ByType.ContainsKey(typeof(FactionPrototype)))
- return;
+ if (!TryComp(uid, out var component))
+ return;
- RefreshFactions();
- }
+ if (!component.Factions.Remove(faction))
+ return;
- private void OnFactionStartup(EntityUid uid, FactionComponent component, ComponentStartup args)
+ if (dirty)
{
RefreshFactions(component);
}
+ }
- ///
- /// Refreshes the cached factions for this component.
- ///
- private void RefreshFactions(FactionComponent component)
+ public IEnumerable GetNearbyHostiles(EntityUid entity, float range, FactionComponent? component = null)
+ {
+ if (!Resolve(entity, ref component, false))
+ return Array.Empty();
+
+ var hostiles = GetNearbyFactions(entity, range, component.HostileFactions);
+ if (TryComp(entity, out var factionException))
{
- component.FriendlyFactions.Clear();
- component.HostileFactions.Clear();
-
- foreach (var faction in component.Factions)
- {
- // YAML Linter already yells about this
- if (!_factions.TryGetValue(faction, out var factionData))
- continue;
-
- component.FriendlyFactions.UnionWith(factionData.Friendly);
- component.HostileFactions.UnionWith(factionData.Hostile);
- }
+ // ignore anything from enemy faction that we are explicitly friendly towards
+ return hostiles.Where(target => !_factionException.IsIgnored(factionException, target));
}
- ///
- /// Adds this entity to the particular faction.
- ///
- public void AddFaction(EntityUid uid, string faction, bool dirty = true)
+ return hostiles;
+ }
+
+ public IEnumerable GetNearbyFriendlies(EntityUid entity, float range, FactionComponent? component = null)
+ {
+ if (!Resolve(entity, ref component, false))
+ return Array.Empty();
+
+ return GetNearbyFactions(entity, range, component.FriendlyFactions);
+ }
+
+ private IEnumerable GetNearbyFactions(EntityUid entity, float range, HashSet factions)
+ {
+ var xformQuery = GetEntityQuery();
+
+ if (!xformQuery.TryGetComponent(entity, out var entityXform))
+ yield break;
+
+ foreach (var comp in _lookup.GetComponentsInRange(entityXform.MapPosition, range))
{
- if (!_protoManager.HasIndex(faction))
- {
- _sawmill.Error($"Unable to find faction {faction}");
- return;
- }
+ if (comp.Owner == entity)
+ continue;
- var comp = EnsureComp(uid);
- if (!comp.Factions.Add(faction))
- return;
+ if (!factions.Overlaps(comp.Factions))
+ continue;
- if (dirty)
- {
- RefreshFactions(comp);
- }
- }
-
- ///
- /// Removes this entity from the particular faction.
- ///
- public void RemoveFaction(EntityUid uid, string faction, bool dirty = true)
- {
- if (!_protoManager.HasIndex(faction))
- {
- _sawmill.Error($"Unable to find faction {faction}");
- return;
- }
-
- if (!TryComp(uid, out var component))
- return;
-
- if (!component.Factions.Remove(faction))
- return;
-
- if (dirty)
- {
- RefreshFactions(component);
- }
- }
-
- public IEnumerable GetNearbyHostiles(EntityUid entity, float range, FactionComponent? component = null)
- {
- if (!Resolve(entity, ref component, false))
- return Array.Empty();
-
- return GetNearbyFactions(entity, range, component.HostileFactions);
- }
-
- public IEnumerable GetNearbyFriendlies(EntityUid entity, float range, FactionComponent? component = null)
- {
- if (!Resolve(entity, ref component, false))
- return Array.Empty();
-
- return GetNearbyFactions(entity, range, component.FriendlyFactions);
- }
-
- private IEnumerable GetNearbyFactions(EntityUid entity, float range, HashSet factions)
- {
- var xformQuery = GetEntityQuery();
-
- if (!xformQuery.TryGetComponent(entity, out var entityXform))
- yield break;
-
- foreach (var comp in _lookup.GetComponentsInRange(entityXform.MapPosition, range))
- {
- if (comp.Owner == entity)
- continue;
-
- if (!factions.Overlaps(comp.Factions))
- continue;
-
- yield return comp.Owner;
- }
- }
-
- public bool IsFriendly(EntityUid uidA, EntityUid uidB, FactionComponent? factionA = null, FactionComponent? factionB = null)
- {
- if (!Resolve(uidA, ref factionA, false) || !Resolve(uidB, ref factionB, false))
- return false;
-
- return factionA.Factions.Overlaps(factionB.Factions) || factionA.FriendlyFactions.Overlaps(factionB.Factions);
- }
-
- ///
- /// Makes the source faction friendly to the target faction, 1-way.
- ///
- public void MakeFriendly(string source, string target)
- {
- if (!_factions.TryGetValue(source, out var sourceFaction))
- {
- _sawmill.Error($"Unable to find faction {source}");
- return;
- }
-
- if (!_factions.ContainsKey(target))
- {
- _sawmill.Error($"Unable to find faction {target}");
- return;
- }
-
- sourceFaction.Friendly.Add(target);
- sourceFaction.Hostile.Remove(target);
- RefreshFactions();
- }
-
- private void RefreshFactions()
- {
- _factions.Clear();
-
- foreach (var faction in _protoManager.EnumeratePrototypes())
- {
- _factions[faction.ID] = new FactionData()
- {
- Friendly = faction.Friendly.ToHashSet(),
- Hostile = faction.Hostile.ToHashSet(),
- };
- }
-
- foreach (var comp in EntityQuery(true))
- {
- comp.FriendlyFactions.Clear();
- comp.HostileFactions.Clear();
- RefreshFactions(comp);
- }
- }
-
- ///
- /// Makes the source faction hostile to the target faction, 1-way.
- ///
- public void MakeHostile(string source, string target)
- {
- if (!_factions.TryGetValue(source, out var sourceFaction))
- {
- _sawmill.Error($"Unable to find faction {source}");
- return;
- }
-
- if (!_factions.ContainsKey(target))
- {
- _sawmill.Error($"Unable to find faction {target}");
- return;
- }
-
- sourceFaction.Friendly.Remove(target);
- sourceFaction.Hostile.Add(target);
- RefreshFactions();
+ yield return comp.Owner;
}
}
+
+ public bool IsFriendly(EntityUid uidA, EntityUid uidB, FactionComponent? factionA = null, FactionComponent? factionB = null)
+ {
+ if (!Resolve(uidA, ref factionA, false) || !Resolve(uidB, ref factionB, false))
+ return false;
+
+ return factionA.Factions.Overlaps(factionB.Factions) || factionA.FriendlyFactions.Overlaps(factionB.Factions);
+ }
+
+ ///
+ /// Makes the source faction friendly to the target faction, 1-way.
+ ///
+ public void MakeFriendly(string source, string target)
+ {
+ if (!_factions.TryGetValue(source, out var sourceFaction))
+ {
+ _sawmill.Error($"Unable to find faction {source}");
+ return;
+ }
+
+ if (!_factions.ContainsKey(target))
+ {
+ _sawmill.Error($"Unable to find faction {target}");
+ return;
+ }
+
+ sourceFaction.Friendly.Add(target);
+ sourceFaction.Hostile.Remove(target);
+ RefreshFactions();
+ }
+
+ private void RefreshFactions()
+ {
+ _factions.Clear();
+
+ foreach (var faction in _protoManager.EnumeratePrototypes())
+ {
+ _factions[faction.ID] = new FactionData()
+ {
+ Friendly = faction.Friendly.ToHashSet(),
+ Hostile = faction.Hostile.ToHashSet(),
+ };
+ }
+
+ foreach (var comp in EntityQuery(true))
+ {
+ comp.FriendlyFactions.Clear();
+ comp.HostileFactions.Clear();
+ RefreshFactions(comp);
+ }
+ }
+
+ ///
+ /// Makes the source faction hostile to the target faction, 1-way.
+ ///
+ public void MakeHostile(string source, string target)
+ {
+ if (!_factions.TryGetValue(source, out var sourceFaction))
+ {
+ _sawmill.Error($"Unable to find faction {source}");
+ return;
+ }
+
+ if (!_factions.ContainsKey(target))
+ {
+ _sawmill.Error($"Unable to find faction {target}");
+ return;
+ }
+
+ sourceFaction.Friendly.Remove(target);
+ sourceFaction.Hostile.Add(target);
+ RefreshFactions();
+ }
}
+