_prototypeManager.RegisterIgnore("seed"); // Seeds prototypes are server-only.
_prototypeManager.RegisterIgnore("objective");
_prototypeManager.RegisterIgnore("holiday");
- _prototypeManager.RegisterIgnore("aiFaction");
_prototypeManager.RegisterIgnore("htnCompound");
_prototypeManager.RegisterIgnore("htnPrimitive");
_prototypeManager.RegisterIgnore("gameMap");
_prototypeManager.RegisterIgnore("gameMapPool");
- _prototypeManager.RegisterIgnore("npcFaction");
_prototypeManager.RegisterIgnore("lobbyBackground");
_prototypeManager.RegisterIgnore("advertisementsPack");
_prototypeManager.RegisterIgnore("gamePreset");
+using Content.Shared.NPC.Prototypes;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
{
Params = AudioParams.Default.WithVolume(3f),
};
+
+ /// <summary>
+ /// NPC faction to re-add after being zombified.
+ /// Prevents zombie dragon from being attacked by its own carp.
+ /// </summary>
+ [DataField]
+ public ProtoId<NpcFactionPrototype> Faction = "Dragon";
}
}
using Content.Shared.Mind.Components;
using Content.Shared.Mobs;
using Content.Shared.Movement.Systems;
+using Content.Shared.NPC.Systems;
+using Content.Shared.Zombies;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.GameStates;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDef = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
+ [Dependency] private readonly NpcFactionSystem _faction = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly RoleSystem _role = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
SubscribeLocalEvent<DragonComponent, RefreshMovementSpeedModifiersEvent>(OnDragonMove);
SubscribeLocalEvent<DragonComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<DragonComponent, GenericAntagCreatedEvent>(OnCreated);
+ SubscribeLocalEvent<DragonComponent, EntityZombifiedEvent>(OnZombified);
}
public override void Update(float frameTime)
}, mind);
}
+ private void OnZombified(Entity<DragonComponent> ent, ref EntityZombifiedEvent args)
+ {
+ // prevent carp attacking zombie dragon
+ _faction.AddFaction(ent.Owner, ent.Comp.Faction);
+ }
+
private void Roar(EntityUid uid, DragonComponent comp)
{
if (comp.SoundRoar != null)
+++ /dev/null
-using Content.Server.Friends.Systems;
-
-namespace Content.Server.Friends.Components;
-
-/// <summary>
-/// Pet something to become friends with it (use in hand, press Z)
-/// Uses FactionExceptionComponent behind the scenes
-/// </summary>
-[RegisterComponent, Access(typeof(PettableFriendSystem))]
-public sealed partial class PettableFriendComponent : Component
-{
- /// <summary>
- /// Localized popup sent when petting for the first time
- /// </summary>
- [DataField("successString", required: true), ViewVariables(VVAccess.ReadWrite)]
- public string SuccessString = string.Empty;
-
- /// <summary>
- /// Localized popup sent when petting multiple times
- /// </summary>
- [DataField("failureString", required: true), ViewVariables(VVAccess.ReadWrite)]
- public string FailureString = string.Empty;
-}
+++ /dev/null
-using Content.Server.Friends.Components;
-using Content.Server.NPC.Components;
-using Content.Server.NPC.Systems;
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Interaction.Events;
-using Content.Shared.Popups;
-
-namespace Content.Server.Friends.Systems;
-
-public sealed class PettableFriendSystem : EntitySystem
-{
- [Dependency] private readonly NpcFactionSystem _factionException = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<PettableFriendComponent, UseInHandEvent>(OnUseInHand);
- SubscribeLocalEvent<PettableFriendComponent, GotRehydratedEvent>(OnRehydrated);
- }
-
- private void OnUseInHand(EntityUid uid, PettableFriendComponent comp, UseInHandEvent args)
- {
- var user = args.User;
- if (args.Handled || !TryComp<FactionExceptionComponent>(uid, out var factionException))
- return;
-
- if (_factionException.IsIgnored(uid, user, factionException))
- {
- _popup.PopupEntity(Loc.GetString(comp.FailureString, ("target", uid)), user, user);
- return;
- }
-
- // you have made a new friend :)
- _popup.PopupEntity(Loc.GetString(comp.SuccessString, ("target", uid)), user, user);
- _factionException.IgnoreEntity(uid, user, factionException);
- args.Handled = true;
- }
-
- private void OnRehydrated(EntityUid uid, PettableFriendComponent _, ref GotRehydratedEvent args)
- {
- // can only pet before hydrating, after that the fish cannot be negotiated with
- if (!TryComp<FactionExceptionComponent>(uid, out var comp))
- return;
-
- var targetComp = AddComp<FactionExceptionComponent>(args.Target);
- _factionException.IgnoreEntities(args.Target, comp.Ignored, targetComp);
- }
-}
using Content.Server.Maps;
-using Content.Server.NPC.Components;
using Content.Server.RoundEnd;
using Content.Server.StationEvents.Events;
using Content.Shared.Dataset;
+using Content.Shared.NPC.Prototypes;
using Content.Shared.Roles;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
-using Content.Server.NPC.Components;
using Content.Shared.Dataset;
+using Content.Shared.NPC.Prototypes;
using Content.Shared.Random;
using Content.Shared.Roles;
using Robust.Shared.Audio;
using Content.Server.Ghost.Roles.Events;
using Content.Server.Humanoid;
using Content.Server.Mind;
-using Content.Server.NPC.Components;
-using Content.Server.NPC.Systems;
using Content.Server.Nuke;
using Content.Server.NukeOps;
using Content.Server.Popups;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
+using Content.Shared.NPC.Components;
+using Content.Shared.NPC.Systems;
using Content.Shared.Nuke;
using Content.Shared.NukeOps;
using Content.Shared.Preferences;
var eligibleQuery = EntityQueryEnumerator<StationEventEligibleComponent, NpcFactionMemberComponent>();
while (eligibleQuery.MoveNext(out var eligibleUid, out var eligibleComp, out var member))
{
- if (!_npcFaction.IsFactionHostile(component.Faction, eligibleUid, member))
+ if (!_npcFaction.IsFactionHostile(component.Faction, (eligibleUid, member)))
continue;
eligible.Add((eligibleUid, eligibleComp, member));
using Content.Server.Cargo.Systems;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking.Rules.Components;
-using Content.Server.NPC.Components;
-using Content.Server.NPC.Systems;
using Content.Server.Preferences.Managers;
using Content.Server.Spawners.Components;
using Content.Server.Station.Components;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Mind;
+using Content.Shared.NPC.Prototypes;
+using Content.Shared.NPC.Systems;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Server.GameObjects;
using Content.Server.Flash;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Mind;
-using Content.Server.NPC.Components;
-using Content.Server.NPC.Systems;
using Content.Server.Popups;
using Content.Server.Revolutionary;
using Content.Server.Revolutionary.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
+using Content.Shared.NPC.Prototypes;
+using Content.Shared.NPC.Systems;
using Content.Shared.Revolutionary.Components;
using Content.Shared.Roles;
using Content.Shared.Stunnable;
using Content.Server.Antag;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Mind;
-using Content.Server.NPC.Systems;
using Content.Server.Objectives;
using Content.Server.PDA.Ringer;
using Content.Server.Roles;
using Content.Shared.Dataset;
using Content.Shared.Mind;
using Content.Shared.Mobs.Systems;
+using Content.Shared.NPC.Systems;
using Content.Shared.Objectives.Components;
using Content.Shared.PDA;
using Content.Shared.Roles;
+++ /dev/null
-using Content.Server.NPC.Systems;
-
-namespace Content.Server.NPC.Components;
-
-/// <summary>
-/// This is used for tracking entities stored in <see cref="FactionExceptionComponent"/>
-/// </summary>
-[RegisterComponent, Access(typeof(NpcFactionSystem))]
-public sealed partial class FactionExceptionTrackerComponent : Component
-{
- /// <summary>
- /// entities with <see cref="FactionExceptionComponent"/> that are tracking this entity.
- /// </summary>
- [DataField("entities")]
- public HashSet<EntityUid> Entities = new();
-}
+++ /dev/null
-using Content.Server.NPC.Systems;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
-
-namespace Content.Server.NPC.Components
-{
- [RegisterComponent]
- [Access(typeof(NpcFactionSystem))]
- public sealed partial class NpcFactionMemberComponent : Component
- {
- /// <summary>
- /// Factions this entity is a part of.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite),
- DataField("factions", customTypeSerializer:typeof(PrototypeIdHashSetSerializer<NpcFactionPrototype>))]
- public HashSet<string> Factions = new();
-
- /// <summary>
- /// Cached friendly factions.
- /// </summary>
- [ViewVariables]
- public readonly HashSet<string> FriendlyFactions = new();
-
- /// <summary>
- /// Cached hostile factions.
- /// </summary>
- [ViewVariables]
- public readonly HashSet<string> HostileFactions = new();
- }
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-
-namespace Content.Server.NPC.Components
-{
- /// <summary>
- /// Contains data about this faction's relations with other factions.
- /// </summary>
- [Prototype("npcFaction")]
- public sealed partial class NpcFactionPrototype : IPrototype
- {
- [ViewVariables]
- [IdDataField]
- public string ID { get; private set; } = default!;
-
- [ViewVariables(VVAccess.ReadWrite), DataField("friendly", customTypeSerializer:typeof(PrototypeIdListSerializer<NpcFactionPrototype>))]
- public List<string> Friendly = new();
-
- [ViewVariables(VVAccess.ReadWrite), DataField("hostile", customTypeSerializer:typeof(PrototypeIdListSerializer<NpcFactionPrototype>))]
- public List<string> Hostile = new();
- }
-}
+++ /dev/null
-namespace Content.Server.NPC;
-
-/// <summary>
-/// Cached data for the faction prototype. Can be modified at runtime.
-/// </summary>
-public sealed class FactionData
-{
- [ViewVariables]
- public HashSet<string> Friendly = new();
-
- [ViewVariables]
- public HashSet<string> Hostile = new();
-}
-using Content.Server.NPC.Components;
+using Content.Server.NPC.Components;
using Content.Shared.CombatMode;
using Content.Shared.Damage;
using Content.Shared.Mobs.Components;
+using Content.Shared.NPC.Components;
+using Content.Shared.NPC.Systems;
using Robust.Shared.Collections;
using Robust.Shared.Timing;
SubscribeLocalEvent<NPCRetaliationComponent, DisarmedEvent>(OnDisarmed);
}
- private void OnDamageChanged(EntityUid uid, NPCRetaliationComponent component, DamageChangedEvent args)
+ private void OnDamageChanged(Entity<NPCRetaliationComponent> ent, ref DamageChangedEvent args)
{
if (!args.DamageIncreased)
return;
- if (args.Origin is not { } origin)
+ if (args.Origin is not {} origin)
return;
- TryRetaliate(uid, origin, component);
+ TryRetaliate(ent, origin);
}
- private void OnDisarmed(EntityUid uid, NPCRetaliationComponent component, DisarmedEvent args)
+ private void OnDisarmed(Entity<NPCRetaliationComponent> ent, ref DisarmedEvent args)
{
- TryRetaliate(uid, args.Source, component);
+ TryRetaliate(ent, args.Source);
}
- public bool TryRetaliate(EntityUid uid, EntityUid target, NPCRetaliationComponent? component = null)
+ public bool TryRetaliate(Entity<NPCRetaliationComponent> ent, EntityUid target)
{
- if (!Resolve(uid, ref component))
- return false;
-
// don't retaliate against inanimate objects.
if (!HasComp<MobStateComponent>(target))
return false;
- if (_npcFaction.IsEntityFriendly(uid, target))
+ // don't retaliate against the same faction
+ if (_npcFaction.IsEntityFriendly(ent.Owner, target))
return false;
- _npcFaction.AggroEntity(uid, target);
- if (component.AttackMemoryLength is { } memoryLength)
- component.AttackMemories[target] = _timing.CurTime + memoryLength;
+ _npcFaction.AggroEntity(ent.Owner, target);
+ if (ent.Comp.AttackMemoryLength is {} memoryLength)
+ ent.Comp.AttackMemories[target] = _timing.CurTime + memoryLength;
return true;
}
var query = EntityQueryEnumerator<NPCRetaliationComponent, FactionExceptionComponent>();
while (query.MoveNext(out var uid, out var retaliationComponent, out var factionException))
{
+ // TODO: can probably reuse this allocation and clear it
foreach (var entity in new ValueList<EntityUid>(retaliationComponent.AttackMemories.Keys))
{
if (!TerminatingOrDeleted(entity) && _timing.CurTime < retaliationComponent.AttackMemories[entity])
continue;
- _npcFaction.DeAggroEntity(uid, entity, factionException);
+ _npcFaction.DeAggroEntity((uid, factionException), entity);
+ // TODO: should probably remove the AttackMemory, thats the whole point of the ValueList right??
}
}
}
(mask & otherBody.CollisionLayer) == 0x0 &&
(layer & otherBody.CollisionMask) == 0x0 ||
!_factionQuery.TryGetComponent(ent, out var otherFaction) ||
- !_npcFaction.IsEntityFriendly(uid, ent, ourFaction, otherFaction) ||
+ !_npcFaction.IsEntityFriendly((uid, ourFaction), (ent, otherFaction)) ||
// Use <= 0 so we ignore stationary friends in case.
Vector2.Dot(otherBody.LinearVelocity, ourVelocity) <= 0f)
{
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.NPC;
+using Content.Shared.NPC.Components;
+using Content.Shared.NPC.Systems;
using Content.Shared.NPC.Events;
using Content.Shared.Physics;
using Content.Shared.Weapons.Melee;
using Content.Shared.Hands.Components;
using Content.Shared.Inventory;
using Content.Shared.Mobs.Systems;
+using Content.Shared.NPC.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Tools.Systems;
using Content.Shared.Weapons.Melee;
+++ /dev/null
-using System.Linq;
-using Content.Server.NPC.Components;
-
-namespace Content.Server.NPC.Systems;
-
-/// <summary>
-/// Prevents an NPC from attacking some entities from an enemy faction.
-/// </summary>
-public sealed partial class NpcFactionSystem
-{
- private EntityQuery<FactionExceptionComponent> _exceptionQuery;
- private EntityQuery<FactionExceptionTrackerComponent> _trackerQuery;
-
- public void InitializeException()
- {
- _exceptionQuery = GetEntityQuery<FactionExceptionComponent>();
- _trackerQuery = GetEntityQuery<FactionExceptionTrackerComponent>();
-
- SubscribeLocalEvent<FactionExceptionComponent, ComponentShutdown>(OnShutdown);
- SubscribeLocalEvent<FactionExceptionTrackerComponent, ComponentShutdown>(OnTrackerShutdown);
- }
-
- private void OnShutdown(EntityUid uid, FactionExceptionComponent component, ComponentShutdown args)
- {
- foreach (var ent in component.Hostiles)
- {
- if (!_trackerQuery.TryGetComponent(ent, out var trackerComponent))
- continue;
- trackerComponent.Entities.Remove(uid);
- }
-
- foreach (var ent in component.Ignored)
- {
- if (!_trackerQuery.TryGetComponent(ent, out var trackerComponent))
- continue;
- trackerComponent.Entities.Remove(uid);
- }
- }
-
- private void OnTrackerShutdown(EntityUid uid, FactionExceptionTrackerComponent component, ComponentShutdown args)
- {
- foreach (var ent in component.Entities)
- {
- if (!_exceptionQuery.TryGetComponent(ent, out var exceptionComponent))
- continue;
- exceptionComponent.Ignored.Remove(uid);
- exceptionComponent.Hostiles.Remove(uid);
- }
- }
-
- /// <summary>
- /// Returns whether the entity from an enemy faction won't be attacked
- /// </summary>
- public bool IsIgnored(EntityUid uid, EntityUid target, FactionExceptionComponent? comp = null)
- {
- if (!Resolve(uid, ref comp, false))
- return false;
-
- return comp.Ignored.Contains(target);
- }
-
- /// <summary>
- /// Returns the specific hostile entities for a given entity.
- /// </summary>
- public IEnumerable<EntityUid> GetHostiles(EntityUid uid, FactionExceptionComponent? comp = null)
- {
- if (!Resolve(uid, ref comp, false))
- return Array.Empty<EntityUid>();
-
- return comp.Hostiles;
- }
-
- /// <summary>
- /// Prevents an entity from an enemy faction from being attacked
- /// </summary>
- public void IgnoreEntity(EntityUid uid, EntityUid target, FactionExceptionComponent? comp = null)
- {
- comp ??= EnsureComp<FactionExceptionComponent>(uid);
- comp.Ignored.Add(target);
- EnsureComp<FactionExceptionTrackerComponent>(target).Entities.Add(uid);
- }
-
- /// <summary>
- /// Prevents a list of entities from an enemy faction from being attacked
- /// </summary>
- public void IgnoreEntities(EntityUid uid, IEnumerable<EntityUid> ignored, FactionExceptionComponent? comp = null)
- {
- comp ??= EnsureComp<FactionExceptionComponent>(uid);
- foreach (var ignore in ignored)
- {
- IgnoreEntity(uid, ignore, comp);
- }
- }
-
- /// <summary>
- /// Makes an entity always be considered hostile.
- /// </summary>
- public void AggroEntity(EntityUid uid, EntityUid target, FactionExceptionComponent? comp = null)
- {
- comp ??= EnsureComp<FactionExceptionComponent>(uid);
- comp.Hostiles.Add(target);
- EnsureComp<FactionExceptionTrackerComponent>(target).Entities.Add(uid);
- }
-
- /// <summary>
- /// Makes an entity no longer be considered hostile, if it was.
- /// Doesn't apply to regular faction hostilities.
- /// </summary>
- public void DeAggroEntity(EntityUid uid, EntityUid target, FactionExceptionComponent? comp = null)
- {
- if (!Resolve(uid, ref comp, false))
- return;
- if (!comp.Hostiles.Remove(target) || !_trackerQuery.TryGetComponent(target, out var tracker))
- return;
- tracker.Entities.Remove(uid);
- }
-
- /// <summary>
- /// Makes a list of entities no longer be considered hostile, if it was.
- /// Doesn't apply to regular faction hostilities.
- /// </summary>
- public void AggroEntities(EntityUid uid, IEnumerable<EntityUid> entities, FactionExceptionComponent? comp = null)
- {
- comp ??= EnsureComp<FactionExceptionComponent>(uid);
- foreach (var ent in entities)
- {
- AggroEntity(uid, ent, comp);
- }
- }
-}
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Systems;
+using Content.Shared.NPC.Components;
+using Content.Shared.NPC.Systems;
using Content.Shared.Nutrition.AnimalHusbandry;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
_damageable.SetAllDamage(target, damageablecomp, 0);
_mobState.ChangeMobState(target, MobState.Alive);
- var factionComp = EnsureComp<NpcFactionMemberComponent>(target);
- foreach (var id in new List<string>(factionComp.Factions))
- {
- _faction.RemoveFaction(target, id);
- }
+ _faction.ClearFactions(target, dirty: false);
_faction.AddFaction(target, "Zombie");
//gives it the funny "Zombie ___" name.
--- /dev/null
+using Content.Shared.Friends.Systems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Friends.Components;
+
+/// <summary>
+/// Pet something to become friends with it (use in hand, press Z)
+/// Requires this entity to have FactionExceptionComponent to work.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(PettableFriendSystem))]
+public sealed partial class PettableFriendComponent : Component
+{
+ /// <summary>
+ /// Localized popup sent when petting for the first time
+ /// </summary>
+ [DataField(required: true)]
+ public LocId SuccessString = string.Empty;
+
+ /// <summary>
+ /// Localized popup sent when petting multiple times
+ /// </summary>
+ [DataField(required: true)]
+ public LocId FailureString = string.Empty;
+}
--- /dev/null
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Friends.Components;
+using Content.Shared.Interaction.Events;
+using Content.Shared.NPC.Components;
+using Content.Shared.NPC.Systems;
+using Content.Shared.Popups;
+using Content.Shared.Timing;
+
+namespace Content.Shared.Friends.Systems;
+
+public sealed class PettableFriendSystem : EntitySystem
+{
+ [Dependency] private readonly NpcFactionSystem _factionException = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly UseDelaySystem _useDelay = default!;
+
+ private EntityQuery<FactionExceptionComponent> _exceptionQuery;
+ private EntityQuery<UseDelayComponent> _useDelayQuery;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _exceptionQuery = GetEntityQuery<FactionExceptionComponent>();
+ _useDelayQuery = GetEntityQuery<UseDelayComponent>();
+
+ SubscribeLocalEvent<PettableFriendComponent, UseInHandEvent>(OnUseInHand);
+ SubscribeLocalEvent<PettableFriendComponent, GotRehydratedEvent>(OnRehydrated);
+ }
+
+ private void OnUseInHand(Entity<PettableFriendComponent> ent, ref UseInHandEvent args)
+ {
+ var (uid, comp) = ent;
+ var user = args.User;
+ if (args.Handled || !_exceptionQuery.TryGetComponent(uid, out var exceptionComp))
+ return;
+
+ if (_useDelayQuery.TryGetComponent(uid, out var useDelay) && !_useDelay.TryResetDelay((uid, useDelay), true))
+ return;
+
+ var exception = (uid, exceptionComp);
+ if (_factionException.IsIgnored(exception, user))
+ {
+ _popup.PopupClient(Loc.GetString(comp.FailureString, ("target", uid)), user, user);
+ return;
+ }
+
+ // you have made a new friend :)
+ _popup.PopupClient(Loc.GetString(comp.SuccessString, ("target", uid)), user, user);
+ _factionException.IgnoreEntity(exception, user);
+ args.Handled = true;
+ }
+
+ private void OnRehydrated(Entity<PettableFriendComponent> ent, ref GotRehydratedEvent args)
+ {
+ // can only pet before hydrating, after that the fish cannot be negotiated with
+ if (!TryComp<FactionExceptionComponent>(ent, out var comp))
+ return;
+
+ _factionException.IgnoreEntities(args.Target, comp.Ignored);
+ }
+}
-using Content.Server.NPC.Systems;
+using Content.Shared.NPC.Systems;
+using Robust.Shared.GameStates;
-namespace Content.Server.NPC.Components;
+namespace Content.Shared.NPC.Components;
/// <summary>
/// Prevents an NPC from attacking ignored entities from enemy factions.
/// Can be added to if pettable, see PettableFriendComponent.
/// </summary>
-[RegisterComponent, Access(typeof(NpcFactionSystem))]
+[RegisterComponent, NetworkedComponent, Access(typeof(NpcFactionSystem))]
public sealed partial class FactionExceptionComponent : Component
{
/// <summary>
/// Collection of entities that this NPC will refuse to attack
/// </summary>
- [DataField("ignored")]
+ [DataField]
public HashSet<EntityUid> Ignored = new();
/// <summary>
/// Collection of entities that this NPC will attack, regardless of faction.
/// </summary>
- [DataField("hostiles")]
+ [DataField]
public HashSet<EntityUid> Hostiles = new();
}
--- /dev/null
+using Content.Shared.NPC.Systems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.NPC.Components;
+
+/// <summary>
+/// This is used for tracking entities stored in <see cref="FactionExceptionComponent"/>.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(NpcFactionSystem))]
+public sealed partial class FactionExceptionTrackerComponent : Component
+{
+ /// <summary>
+ /// Entities with <see cref="FactionExceptionComponent"/> that are tracking this entity.
+ /// </summary>
+ [DataField]
+ public HashSet<EntityUid> Entities = new();
+}
--- /dev/null
+using Content.Shared.NPC.Prototypes;
+using Content.Shared.NPC.Systems;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.NPC.Components;
+
+[RegisterComponent, NetworkedComponent, Access(typeof(NpcFactionSystem))]
+public sealed partial class NpcFactionMemberComponent : Component
+{
+ /// <summary>
+ /// Factions this entity is a part of.
+ /// </summary>
+ [DataField]
+ public HashSet<ProtoId<NpcFactionPrototype>> Factions = new();
+
+ /// <summary>
+ /// Cached friendly factions.
+ /// </summary>
+ [ViewVariables]
+ public readonly HashSet<ProtoId<NpcFactionPrototype>> FriendlyFactions = new();
+
+ /// <summary>
+ /// Cached hostile factions.
+ /// </summary>
+ [ViewVariables]
+ public readonly HashSet<ProtoId<NpcFactionPrototype>> HostileFactions = new();
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.NPC.Prototypes;
+
+/// <summary>
+/// Contains data about this faction's relations with other factions.
+/// </summary>
+[Prototype("npcFaction")]
+public sealed partial class NpcFactionPrototype : IPrototype
+{
+ [ViewVariables]
+ [IdDataField]
+ public string ID { get; private set; } = default!;
+
+ [DataField]
+ public List<ProtoId<NpcFactionPrototype>> Friendly = new();
+
+ [DataField]
+ public List<ProtoId<NpcFactionPrototype>> Hostile = new();
+}
+
+/// <summary>
+/// Cached data for the faction prototype. Is modified at runtime, whereas the prototype is not.
+/// </summary>
+public record struct FactionData
+{
+ [ViewVariables]
+ public HashSet<ProtoId<NpcFactionPrototype>> Friendly;
+
+ [ViewVariables]
+ public HashSet<ProtoId<NpcFactionPrototype>> Hostile;
+}
--- /dev/null
+using Content.Shared.NPC.Components;
+using System.Linq;
+
+namespace Content.Shared.NPC.Systems;
+
+/// <summary>
+/// Prevents an NPC from attacking some entities from an enemy faction.
+/// Also makes it attack some entities even if they are in neutral factions (retaliation).
+/// </summary>
+public sealed partial class NpcFactionSystem
+{
+ private EntityQuery<FactionExceptionComponent> _exceptionQuery;
+ private EntityQuery<FactionExceptionTrackerComponent> _trackerQuery;
+
+ public void InitializeException()
+ {
+ _exceptionQuery = GetEntityQuery<FactionExceptionComponent>();
+ _trackerQuery = GetEntityQuery<FactionExceptionTrackerComponent>();
+
+ SubscribeLocalEvent<FactionExceptionComponent, ComponentShutdown>(OnShutdown);
+ SubscribeLocalEvent<FactionExceptionTrackerComponent, ComponentShutdown>(OnTrackerShutdown);
+ }
+
+ private void OnShutdown(Entity<FactionExceptionComponent> ent, ref ComponentShutdown args)
+ {
+ foreach (var uid in ent.Comp.Hostiles)
+ {
+ if (_trackerQuery.TryGetComponent(uid, out var tracker))
+ tracker.Entities.Remove(ent);
+ }
+
+ foreach (var uid in ent.Comp.Ignored)
+ {
+ if (_trackerQuery.TryGetComponent(uid, out var tracker))
+ tracker.Entities.Remove(ent);
+ }
+ }
+
+ private void OnTrackerShutdown(Entity<FactionExceptionTrackerComponent> ent, ref ComponentShutdown args)
+ {
+ foreach (var uid in ent.Comp.Entities)
+ {
+ if (!_exceptionQuery.TryGetComponent(uid, out var exception))
+ continue;
+
+ exception.Ignored.Remove(ent);
+ exception.Hostiles.Remove(ent);
+ }
+ }
+
+ /// <summary>
+ /// Returns whether the entity from an enemy faction won't be attacked
+ /// </summary>
+ public bool IsIgnored(Entity<FactionExceptionComponent?> ent, EntityUid target)
+ {
+ if (!Resolve(ent, ref ent.Comp, false))
+ return false;
+
+ return ent.Comp.Ignored.Contains(target);
+ }
+
+ /// <summary>
+ /// Returns the specific hostile entities for a given entity.
+ /// </summary>
+ public IEnumerable<EntityUid> GetHostiles(Entity<FactionExceptionComponent?> ent)
+ {
+ if (!Resolve(ent, ref ent.Comp, false))
+ return Array.Empty<EntityUid>();
+
+ // evil c#
+ return ent.Comp!.Hostiles;
+ }
+
+ /// <summary>
+ /// Prevents an entity from an enemy faction from being attacked
+ /// </summary>
+ public void IgnoreEntity(Entity<FactionExceptionComponent?> ent, Entity<FactionExceptionTrackerComponent?> target)
+ {
+ ent.Comp ??= EnsureComp<FactionExceptionComponent>(ent);
+ ent.Comp.Ignored.Add(target);
+ target.Comp ??= EnsureComp<FactionExceptionTrackerComponent>(target);
+ target.Comp.Entities.Add(ent);
+ }
+
+ /// <summary>
+ /// Prevents a list of entities from an enemy faction from being attacked
+ /// </summary>
+ public void IgnoreEntities(Entity<FactionExceptionComponent?> ent, IEnumerable<EntityUid> ignored)
+ {
+ ent.Comp ??= EnsureComp<FactionExceptionComponent>(ent);
+ foreach (var ignore in ignored)
+ {
+ IgnoreEntity(ent, ignore);
+ }
+ }
+
+ /// <summary>
+ /// Makes an entity always be considered hostile.
+ /// </summary>
+ public void AggroEntity(Entity<FactionExceptionComponent?> ent, Entity<FactionExceptionTrackerComponent?> target)
+ {
+ ent.Comp ??= EnsureComp<FactionExceptionComponent>(ent);
+ ent.Comp.Hostiles.Add(target);
+ target.Comp ??= EnsureComp<FactionExceptionTrackerComponent>(target);
+ target.Comp.Entities.Add(ent);
+ }
+
+ /// <summary>
+ /// Makes an entity no longer be considered hostile, if it was.
+ /// Doesn't apply to regular faction hostilities.
+ /// </summary>
+ public void DeAggroEntity(Entity<FactionExceptionComponent?> ent, EntityUid target)
+ {
+ if (!Resolve(ent, ref ent.Comp, false))
+ return;
+
+ if (!ent.Comp.Hostiles.Remove(target) || !_trackerQuery.TryGetComponent(target, out var tracker))
+ return;
+
+ tracker.Entities.Remove(ent);
+ }
+
+ /// <summary>
+ /// Makes a list of entities no longer be considered hostile, if it was.
+ /// Doesn't apply to regular faction hostilities.
+ /// </summary>
+ public void AggroEntities(Entity<FactionExceptionComponent?> ent, IEnumerable<EntityUid> entities)
+ {
+ ent.Comp ??= EnsureComp<FactionExceptionComponent>(ent);
+ foreach (var uid in entities)
+ {
+ AggroEntity(ent, uid);
+ }
+ }
+}
+using Content.Shared.NPC.Components;
+using Content.Shared.NPC.Prototypes;
+using Robust.Shared.Prototypes;
using System.Collections.Frozen;
using System.Linq;
-using Content.Server.NPC.Components;
-using JetBrains.Annotations;
-using Robust.Shared.Prototypes;
-namespace Content.Server.NPC.Systems;
+namespace Content.Shared.NPC.Systems;
/// <summary>
/// Outlines faction relationships with each other.
public sealed partial class NpcFactionSystem : EntitySystem
{
[Dependency] private readonly EntityLookupSystem _lookup = default!;
- [Dependency] private readonly IPrototypeManager _protoManager = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly SharedTransformSystem _xform = default!;
/// <summary>
/// To avoid prototype mutability we store an intermediary data class that gets used instead.
RefreshFactions();
}
- private void OnFactionStartup(EntityUid uid, NpcFactionMemberComponent memberComponent, ComponentStartup args)
+ private void OnFactionStartup(Entity<NpcFactionMemberComponent> ent, ref ComponentStartup args)
{
- RefreshFactions(memberComponent);
+ RefreshFactions(ent);
}
/// <summary>
/// Refreshes the cached factions for this component.
/// </summary>
- private void RefreshFactions(NpcFactionMemberComponent memberComponent)
+ private void RefreshFactions(Entity<NpcFactionMemberComponent> ent)
{
- memberComponent.FriendlyFactions.Clear();
- memberComponent.HostileFactions.Clear();
+ ent.Comp.FriendlyFactions.Clear();
+ ent.Comp.HostileFactions.Clear();
- foreach (var faction in memberComponent.Factions)
+ foreach (var faction in ent.Comp.Factions)
{
- // YAML Linter already yells about this
+ // YAML Linter already yells about this, don't need to log an error here
if (!_factions.TryGetValue(faction, out var factionData))
continue;
- memberComponent.FriendlyFactions.UnionWith(factionData.Friendly);
- memberComponent.HostileFactions.UnionWith(factionData.Hostile);
+ ent.Comp.FriendlyFactions.UnionWith(factionData.Friendly);
+ ent.Comp.HostileFactions.UnionWith(factionData.Hostile);
}
}
+ /// <summary>
+ /// Returns whether an entity is a member of a faction.
+ /// </summary>
+ public bool IsMember(Entity<NpcFactionMemberComponent?> ent, string faction)
+ {
+ if (!Resolve(ent, ref ent.Comp, false))
+ return false;
+
+ return ent.Comp.Factions.Contains(faction);
+ }
+
/// <summary>
/// Adds this entity to the particular faction.
/// </summary>
- public void AddFaction(EntityUid uid, string faction, bool dirty = true)
+ public void AddFaction(Entity<NpcFactionMemberComponent?> ent, string faction, bool dirty = true)
{
- if (!_protoManager.HasIndex<NpcFactionPrototype>(faction))
+ if (!_proto.HasIndex<NpcFactionPrototype>(faction))
{
Log.Error($"Unable to find faction {faction}");
return;
}
- var comp = EnsureComp<NpcFactionMemberComponent>(uid);
- if (!comp.Factions.Add(faction))
+ ent.Comp ??= EnsureComp<NpcFactionMemberComponent>(ent);
+ if (!ent.Comp.Factions.Add(faction))
return;
if (dirty)
- {
- RefreshFactions(comp);
- }
+ RefreshFactions((ent, ent.Comp));
}
/// <summary>
/// Removes this entity from the particular faction.
/// </summary>
- public void RemoveFaction(EntityUid uid, string faction, bool dirty = true)
+ public void RemoveFaction(Entity<NpcFactionMemberComponent?> ent, string faction, bool dirty = true)
{
- if (!_protoManager.HasIndex<NpcFactionPrototype>(faction))
+ if (!_proto.HasIndex<NpcFactionPrototype>(faction))
{
Log.Error($"Unable to find faction {faction}");
return;
}
- if (!TryComp<NpcFactionMemberComponent>(uid, out var component))
+ if (!Resolve(ent, ref ent.Comp, false))
return;
- if (!component.Factions.Remove(faction))
+ if (!ent.Comp.Factions.Remove(faction))
return;
if (dirty)
- {
- RefreshFactions(component);
- }
+ RefreshFactions((ent, ent.Comp));
}
/// <summary>
/// Remove this entity from all factions.
/// </summary>
- public void ClearFactions(EntityUid uid, bool dirty = true)
+ public void ClearFactions(Entity<NpcFactionMemberComponent?> ent, bool dirty = true)
{
- if (!TryComp<NpcFactionMemberComponent>(uid, out var component))
+ if (!Resolve(ent, ref ent.Comp, false))
return;
- component.Factions.Clear();
+ ent.Comp.Factions.Clear();
if (dirty)
- RefreshFactions(component);
+ RefreshFactions((ent, ent.Comp));
}
- public IEnumerable<EntityUid> GetNearbyHostiles(EntityUid entity, float range, NpcFactionMemberComponent? component = null)
+ public IEnumerable<EntityUid> GetNearbyHostiles(Entity<NpcFactionMemberComponent?, FactionExceptionComponent?> ent, float range)
{
- if (!Resolve(entity, ref component, false))
+ if (!Resolve(ent, ref ent.Comp1, false))
return Array.Empty<EntityUid>();
- var hostiles = GetNearbyFactions(entity, range, component.HostileFactions);
- if (TryComp<FactionExceptionComponent>(entity, out var factionException))
- {
- // ignore anything from enemy faction that we are explicitly friendly towards
- return hostiles
- .Union(GetHostiles(entity, factionException))
- .Where(target => !IsIgnored(entity, target, factionException));
- }
-
- return hostiles;
+ var hostiles = GetNearbyFactions(ent, range, ent.Comp1.HostileFactions)
+ // ignore mobs that have both hostile faction and the same faction,
+ // otherwise having multiple factions is strictly negative
+ .Where(target => !IsEntityFriendly((ent, ent.Comp1), target));
+ if (!Resolve(ent, ref ent.Comp2, false))
+ return hostiles;
+
+ // ignore anything from enemy faction that we are explicitly friendly towards
+ var faction = (ent.Owner, ent.Comp2);
+ return hostiles
+ .Union(GetHostiles(faction))
+ .Where(target => !IsIgnored(faction, target));
}
- [PublicAPI]
- public IEnumerable<EntityUid> GetNearbyFriendlies(EntityUid entity, float range, NpcFactionMemberComponent? component = null)
+ public IEnumerable<EntityUid> GetNearbyFriendlies(Entity<NpcFactionMemberComponent?> ent, float range)
{
- if (!Resolve(entity, ref component, false))
+ if (!Resolve(ent, ref ent.Comp, false))
return Array.Empty<EntityUid>();
- return GetNearbyFactions(entity, range, component.FriendlyFactions);
+ return GetNearbyFactions(ent, range, ent.Comp.FriendlyFactions);
}
- private IEnumerable<EntityUid> GetNearbyFactions(EntityUid entity, float range, HashSet<string> factions)
+ private IEnumerable<EntityUid> GetNearbyFactions(EntityUid entity, float range, HashSet<ProtoId<NpcFactionPrototype>> factions)
{
- var xformQuery = GetEntityQuery<TransformComponent>();
-
- if (!xformQuery.TryGetComponent(entity, out var entityXform))
- yield break;
-
- foreach (var ent in _lookup.GetEntitiesInRange<NpcFactionMemberComponent>(entityXform.MapPosition, range))
+ var xform = Transform(entity);
+ foreach (var ent in _lookup.GetEntitiesInRange<NpcFactionMemberComponent>(_xform.GetMapCoordinates((entity, xform)), range))
{
if (ent.Owner == entity)
continue;
}
}
- public bool IsEntityFriendly(EntityUid uidA, EntityUid uidB, NpcFactionMemberComponent? factionA = null, NpcFactionMemberComponent? factionB = null)
+ /// <remarks>
+ /// 1-way and purely faction based, ignores faction exception.
+ /// </remarks>
+ public bool IsEntityFriendly(Entity<NpcFactionMemberComponent?> ent, Entity<NpcFactionMemberComponent?> other)
{
- if (!Resolve(uidA, ref factionA, false) || !Resolve(uidB, ref factionB, false))
+ if (!Resolve(ent, ref ent.Comp, false) || !Resolve(other, ref other.Comp, false))
return false;
- return factionA.Factions.Overlaps(factionB.Factions) || factionA.FriendlyFactions.Overlaps(factionB.Factions);
+ return ent.Comp.Factions.Overlaps(other.Comp.Factions) || ent.Comp.FriendlyFactions.Overlaps(other.Comp.Factions);
}
public bool IsFactionFriendly(string target, string with)
return _factions[target].Friendly.Contains(with) && _factions[with].Friendly.Contains(target);
}
- public bool IsFactionFriendly(string target, EntityUid with, NpcFactionMemberComponent? factionWith = null)
+ public bool IsFactionFriendly(string target, Entity<NpcFactionMemberComponent?> with)
{
- if (!Resolve(with, ref factionWith, false))
+ if (!Resolve(with, ref with.Comp, false))
return false;
- return factionWith.Factions.All(x => IsFactionFriendly(target, x)) ||
- factionWith.FriendlyFactions.Contains(target);
+ return with.Comp.Factions.All(x => IsFactionFriendly(target, x)) ||
+ with.Comp.FriendlyFactions.Contains(target);
}
public bool IsFactionHostile(string target, string with)
return _factions[target].Hostile.Contains(with) && _factions[with].Hostile.Contains(target);
}
- public bool IsFactionHostile(string target, EntityUid with, NpcFactionMemberComponent? factionWith = null)
+ public bool IsFactionHostile(string target, Entity<NpcFactionMemberComponent?> with)
{
- if (!Resolve(with, ref factionWith, false))
+ if (!Resolve(with, ref with.Comp, false))
return false;
- return factionWith.Factions.All(x => IsFactionHostile(target, x)) ||
- factionWith.HostileFactions.Contains(target);
+ return with.Comp.Factions.All(x => IsFactionHostile(target, x)) ||
+ with.Comp.HostileFactions.Contains(target);
}
public bool IsFactionNeutral(string target, string with)
RefreshFactions();
}
- private void RefreshFactions()
- {
-
- _factions = _protoManager.EnumeratePrototypes<NpcFactionPrototype>().ToFrozenDictionary(
- faction => faction.ID,
- faction => new FactionData
- {
- Friendly = faction.Friendly.ToHashSet(),
- Hostile = faction.Hostile.ToHashSet()
-
- });
-
- foreach (var comp in EntityQuery<NpcFactionMemberComponent>(true))
- {
- comp.FriendlyFactions.Clear();
- comp.HostileFactions.Clear();
- RefreshFactions(comp);
- }
- }
-
/// <summary>
/// Makes the source faction hostile to the target faction, 1-way.
/// </summary>
sourceFaction.Hostile.Add(target);
RefreshFactions();
}
-}
+ private void RefreshFactions()
+ {
+ _factions = _proto.EnumeratePrototypes<NpcFactionPrototype>().ToFrozenDictionary(
+ faction => faction.ID,
+ faction => new FactionData
+ {
+ Friendly = faction.Friendly.ToHashSet(),
+ Hostile = faction.Hostile.ToHashSet()
+ });
+
+ var query = AllEntityQuery<NpcFactionMemberComponent>();
+ while (query.MoveNext(out var uid, out var comp))
+ {
+ comp.FriendlyFactions.Clear();
+ comp.HostileFactions.Clear();
+ RefreshFactions((uid, comp));
+ }
+ }
+}