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(); + } } +