using System.Linq; using Content.Shared.CombatMode; using Content.Shared.Mobs.Systems; using Content.Shared.NPC.Components; using Content.Shared.NPC.Systems; using Content.Shared.Popups; using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee.Events; using Robust.Shared.Player; using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Content.Server.ForceAttack; /// /// This handles forcing a player-controlled mob to attack nearby enemies. /// public sealed class ForceAttackSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly SharedMeleeWeaponSystem _melee = default!; [Dependency] private readonly NpcFactionSystem _faction = default!; [Dependency] private readonly SharedCombatModeSystem _mode = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly MobStateSystem _mob = default!; /// public override void Initialize() { SubscribeLocalEvent(OnMeleeAttack); } private void OnMeleeAttack(Entity ent, ref MeleeAttackEvent args) { ent.Comp.NextAttack = _timing.CurTime + ent.Comp.PassiveTime; } /// public override void Update(float frameTime) { base.Update(frameTime); var curTime = _timing.CurTime; // Query includes ActorComponent to only get mobs currently controlled by players var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var forceComp, out var factionComp, out var modeComp, out _)) { // Check if we have a weapon if (!_melee.TryGetWeapon(uid, out var weaponUid, out var weapon) || weapon.NextAttack > curTime) continue; // Find a target in range that isn't critical or dead if (!_faction.GetNearbyHostiles((uid, factionComp), weapon.Range) .Where((potTarget) => !_mob.IsIncapacitated(potTarget)) .TryFirstOrNull(out var target)) { forceComp.InRange = false; continue; } if (!forceComp.InRange) // Just entered range { forceComp.InRange = true; forceComp.NextAttack = curTime + forceComp.PassiveTime; continue; } if (forceComp.NextAttack > curTime) continue; // Force mob to enter combat mode (necessary for AttemptAttack to succeed). _mode.SetInCombatMode(uid, true, modeComp); var popupMessage = Loc.GetString(forceComp.Message); if (popupMessage.Length != 0) _popup.PopupEntity(popupMessage, uid, uid); _melee.AttemptLightAttack(uid, weaponUid, weapon, target.Value, false); } } }