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; namespace Content.Server.NPC.Systems; /// /// Handles NPC which become aggressive after being attacked. /// public sealed class NPCRetaliationSystem : EntitySystem { [Dependency] private readonly NpcFactionSystem _npcFaction = default!; [Dependency] private readonly IGameTiming _timing = default!; /// public override void Initialize() { SubscribeLocalEvent(OnDamageChanged); SubscribeLocalEvent(OnDisarmed); } private void OnDamageChanged(Entity ent, ref DamageChangedEvent args) { if (!args.DamageIncreased) return; if (args.Origin is not {} origin) return; TryRetaliate(ent, origin); } private void OnDisarmed(Entity ent, ref DisarmedEvent args) { TryRetaliate(ent, args.Source); } public bool TryRetaliate(Entity ent, EntityUid target) { // don't retaliate against inanimate objects. if (!HasComp(target)) return false; // don't retaliate against the same faction if (_npcFaction.IsEntityFriendly(ent.Owner, target)) return false; _npcFaction.AggroEntity(ent.Owner, target); if (ent.Comp.AttackMemoryLength is {} memoryLength) ent.Comp.AttackMemories[target] = _timing.CurTime + memoryLength; return true; } public override void Update(float frameTime) { base.Update(frameTime); var query = EntityQueryEnumerator(); 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(retaliationComponent.AttackMemories.Keys)) { if (!TerminatingOrDeleted(entity) && _timing.CurTime < retaliationComponent.AttackMemories[entity]) continue; _npcFaction.DeAggroEntity((uid, factionException), entity); // TODO: should probably remove the AttackMemory, thats the whole point of the ValueList right?? } } } }