using System.Linq; using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Emag.Systems; using Content.Shared.Examine; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Whitelist; namespace Content.Shared.Pinpointer; public abstract class SharedPinpointerSystem : EntitySystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly EmagSystem _emag = default!; [Dependency] protected readonly EntityWhitelistSystem Whitelist = default!; // Goob edit [Dependency] private readonly SharedPopupSystem _popup = default!; // Goob edit public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnEmagged); SubscribeLocalEvent(OnAfterInteract); SubscribeLocalEvent(OnExamined); } /// /// Set the target if capable /// private void OnAfterInteract(EntityUid uid, PinpointerComponent component, AfterInteractEvent args) { if (!args.CanReach || args.Target is not { } target || args.Handled) return; if (!component.CanRetarget || component.IsActive) return; // Goob edit start: retargeting has a whitelist args.Handled = true; if (Whitelist.IsWhitelistFail(component.RetargetingWhitelist, target) || Whitelist.IsBlacklistPass(component.RetargetingBlacklist, target)) { return; } // TODO add doafter once the freeze is lifted // ignore can target multiple, because too hard to support component.Targets.Clear(); component.Targets.Add(target); _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):player} set target of {ToPrettyString(uid):pinpointer} to {ToPrettyString(target):target}"); if (component.UpdateTargetName) component.TargetName = Identity.Name(target, EntityManager); _popup.PopupPredicted(Loc.GetString("pinpointer-link-success"), uid, args.User); // Goob edit end } /// /// Set pinpointers target to track /// Goob edit: If CanTargetMultiple is true in Pinpointer component, then it will be ADDED, not set /// public virtual void SetTarget(EntityUid uid, EntityUid? target, PinpointerComponent? pinpointer = null) { if (!Resolve(uid, ref pinpointer)) return; if (target == null || pinpointer.Targets.Contains(target.Value)) { return; } if (!pinpointer.CanTargetMultiple) { pinpointer.Targets.Clear(); } if (TerminatingOrDeleted(target.Value)) { TrySetArrowAngle(uid, Angle.Zero, pinpointer); return; } pinpointer.Targets.Add(target.Value); if (pinpointer.UpdateTargetName) pinpointer.TargetName = Identity.Name(target.Value, EntityManager); // WD EDIT START - UpdateDirectionToTarget is triggered when updating, no need to run it again // if (pinpointer.IsActive) // UpdateDirectionToTarget(uid, pinpointer); // WD EDIT END } /// /// Goob edit: sets a list of targets for a pinpointer. /// public virtual void SetTargets(EntityUid uid, List targets, PinpointerComponent? pinpointer = null) { if (!Resolve(uid, ref pinpointer)) return; if (!pinpointer.CanTargetMultiple) { return; // No. } var targetsList = targets.Where(Exists).ToList(); pinpointer.Targets = targetsList; // WD EDIT START - UpdateDirectionToTarget is triggered when updating, no need to run it again // if (pinpointer.IsActive) // UpdateDirectionToTarget(uid, pinpointer); // WD EDIT END } /// /// Update direction from pinpointer to selected target (if it was set) /// protected virtual void UpdateDirectionToTarget(EntityUid uid, PinpointerComponent? pinpointer = null) { } private void OnExamined(EntityUid uid, PinpointerComponent component, ExaminedEvent args) { if (!component.CanExamine || !args.IsInDetailsRange || component.TargetName == null) // WD EDIT return; args.PushMarkup(Loc.GetString("examine-pinpointer-linked", ("target", component.TargetName))); } /// /// Manually set distance from pinpointer to target /// public void SetDistance(EntityUid uid, Distance distance, PinpointerComponent? pinpointer = null) { if (!Resolve(uid, ref pinpointer)) return; if (distance == pinpointer.DistanceToTarget) return; pinpointer.DistanceToTarget = distance; Dirty(uid, pinpointer); } /// /// Try to manually set pinpointer arrow direction. /// If difference between current angle and new angle is smaller than /// pinpointer precision, new value will be ignored and it will return false. /// public bool TrySetArrowAngle(EntityUid uid, Angle arrowAngle, PinpointerComponent? pinpointer = null) { if (!Resolve(uid, ref pinpointer)) return false; if (pinpointer.ArrowAngle.EqualsApprox(arrowAngle, pinpointer.Precision)) return false; pinpointer.ArrowAngle = arrowAngle; Dirty(uid, pinpointer); return true; } /// /// Activate/deactivate pinpointer screen. If it has target it will start tracking it. /// public void SetActive(EntityUid uid, bool isActive, PinpointerComponent? pinpointer = null) { if (!Resolve(uid, ref pinpointer)) return; if (isActive == pinpointer.IsActive) return; pinpointer.IsActive = isActive; Dirty(uid, pinpointer); } /// /// Toggle Pinpointer screen. If it has target it will start tracking it. /// /// True if pinpointer was activated, false otherwise public virtual bool TogglePinpointer(EntityUid uid, PinpointerComponent? pinpointer = null) { if (!Resolve(uid, ref pinpointer)) return false; var isActive = !pinpointer.IsActive; SetActive(uid, isActive, pinpointer); return isActive; } private void OnEmagged(EntityUid uid, PinpointerComponent component, ref GotEmaggedEvent args) { // WD EDIT START if (!component.CanEmag) return; // WD EDIT END if (!_emag.CompareFlag(args.Type, EmagType.Interaction)) return; if (_emag.CheckFlag(uid, EmagType.Interaction)) return; args.Handled = true; if (component.CanRetarget) { component.RetargetingWhitelist = null; // Can target anything return; } component.CanRetarget = true; } }