using System.Numerics; using Content.Shared.DoAfter; using Content.Shared.Examine; using Content.Shared.Foldable; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Stacks; using Content.Shared.Verbs; using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Timing; namespace Content.Shared.Salvage.Fulton; /// /// Provides extraction devices that teleports the attached entity after elapses to the linked beacon. /// public abstract partial class SharedFultonSystem : EntitySystem { [Dependency] protected readonly IGameTiming Timing = default!; [Dependency] private readonly MetaDataSystem _metadata = default!; [Dependency] protected readonly SharedAudioSystem Audio = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly FoldableSystem _foldable = default!; [Dependency] protected readonly SharedContainerSystem Container = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedStackSystem _stack = default!; [Dependency] protected readonly SharedTransformSystem TransformSystem = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public static readonly EntProtoId EffectProto = "FultonEffect"; protected static readonly Vector2 EffectOffset = Vector2.Zero; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnFultonDoAfter); SubscribeLocalEvent>(OnFultonedGetVerbs); SubscribeLocalEvent(OnFultonedExamine); SubscribeLocalEvent(OnFultonContainerInserted); SubscribeLocalEvent(OnFultonInteract); SubscribeLocalEvent(OnFultonSplit); } private void OnFultonContainerInserted(EntityUid uid, FultonedComponent component, EntGotInsertedIntoContainerMessage args) { RemCompDeferred(uid); } private void OnFultonedExamine(EntityUid uid, FultonedComponent component, ExaminedEvent args) { var remaining = component.NextFulton + _metadata.GetPauseTime(uid) - Timing.CurTime; var message = Loc.GetString("fulton-examine", ("time", $"{remaining.TotalSeconds:0.00}")); args.PushText(message); } private void OnFultonedGetVerbs(EntityUid uid, FultonedComponent component, GetVerbsEvent args) { if (!args.CanAccess || !args.CanInteract) return; args.Verbs.Add(new InteractionVerb() { Text = Loc.GetString("fulton-remove"), Act = () => { Unfulton(uid); } }); } private void Unfulton(EntityUid uid, FultonedComponent? component = null) { if (!Resolve(uid, ref component, false) || !component.Removeable) return; RemCompDeferred(uid); } private void OnFultonDoAfter(FultonedDoAfterEvent args) { if (args.Cancelled || args.Target == null || !TryComp(args.Used, out var fulton)) return; if (!_stack.Use(args.Used.Value, 1)) { return; } var fultoned = AddComp(args.Target.Value); fultoned.Beacon = fulton.Beacon; fultoned.NextFulton = Timing.CurTime + fulton.FultonDuration; fultoned.FultonDuration = fulton.FultonDuration; fultoned.Removeable = fulton.Removeable; UpdateAppearance(args.Target.Value, fultoned); Dirty(args.Target.Value, fultoned); Audio.PlayPredicted(fulton.FultonSound, args.Target.Value, args.User); } private void OnFultonInteract(EntityUid uid, FultonComponent component, AfterInteractEvent args) { if (args.Target == null || args.Handled || !args.CanReach) return; if (TryComp(args.Target, out var beacon)) { if (!_foldable.IsFolded(args.Target.Value)) { component.Beacon = args.Target.Value; Audio.PlayPredicted(beacon.LinkSound, uid, args.User); _popup.PopupClient(Loc.GetString("fulton-linked"), uid, args.User); } else { component.Beacon = EntityUid.Invalid; _popup.PopupClient(Loc.GetString("fulton-folded"), uid, args.User); } return; } if (Deleted(component.Beacon)) { _popup.PopupClient(Loc.GetString("fulton-not-found"), uid, args.User); return; } if (!CanApplyFulton(args.Target.Value, component)) { _popup.PopupClient(Loc.GetString("fulton-invalid"), uid, uid); return; } if (HasComp(args.Target)) { _popup.PopupClient(Loc.GetString("fulton-fultoned"), uid, uid); return; } args.Handled = true; var ev = new FultonedDoAfterEvent(); _doAfter.TryStartDoAfter( new DoAfterArgs(EntityManager, args.User, component.ApplyFultonDuration, ev, args.Target, args.Target, args.Used) { MovementThreshold = 0.5f, BreakOnMove = true, Broadcast = true, NeedHand = true, }); } private void OnFultonSplit(EntityUid uid, FultonComponent component, ref StackSplitEvent args) { var newFulton = EnsureComp(args.NewId); newFulton.Beacon = component.Beacon; Dirty(args.NewId, newFulton); } protected virtual void UpdateAppearance(EntityUid uid, FultonedComponent fultoned) { return; } protected bool CanApplyFulton(EntityUid targetUid, FultonComponent component) { if (!CanFulton(targetUid)) return false; if (_whitelistSystem.IsWhitelistFailOrNull(component.Whitelist, targetUid)) return false; return true; } protected bool CanFulton(EntityUid uid) { var xform = Transform(uid); if (xform.Anchored) return false; // Shouldn't need recursive container checks I think. if (Container.IsEntityInContainer(uid)) return false; return true; } [Serializable, NetSerializable] private sealed partial class FultonedDoAfterEvent : SimpleDoAfterEvent { } // Animations aren't really good for networking hence this. /// /// Tells clients to play the fulton animation. /// [Serializable, NetSerializable] protected sealed class FultonAnimationMessage : EntityEventArgs { public NetEntity Entity; public NetCoordinates Coordinates; } }