Files
tbd-station-14/Content.Shared/Salvage/Fulton/SharedFultonSystem.cs
āda 8d8af1bab7 Stack System Cleanup (#38872)
* eye on the prize

* OnStackInteractUsing, TryMergeStacks, TryMergeToHands, TryMergeToContacts

* namespace

* Use, get count, getMaxCount

* component access

* add regions, mark TODO

* obsolete TryAdd, public TryMergeStacks

* GetMaxCount

* event handlers

* event handlers

* SetCount

* client server event handlers

* move to shared

* Revert "move to shared"

This reverts commit 45540a2d6b8e1e6d2a8f83a584267776c7edcd73.

* misc changes to shared

* split

* spawn and SpawnNextToOrDrop

* SpawnMultipleAtPosition, SpawnMultipleNextToOrDrop, CalculateSpawns, general server cleanup

* Rename Use to TryUse.

* Small misc changes

* Remove obsolete functions

* Remove some SetCount calls

* Partialize

* small misc change

* don't nuke the git dif with the namespace block

* Comments and reordering

* touchup to UpdateLingering

* Summary comment for StackStatusControl

* Last pass

* Actual last pass (for now)

* I know myself too well

* fixup

* goodbye lingering

* fixes

* review

* fix test

* second look

* fix test

* forgot

* remove early comp getting

---------

Co-authored-by: iaada <iaada@users.noreply.github.com>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2025-10-25 14:40:48 +00:00

216 lines
7.2 KiB
C#

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;
/// <summary>
/// Provides extraction devices that teleports the attached entity after <see cref="FultonDuration"/> elapses to the linked beacon.
/// </summary>
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<FultonedDoAfterEvent>(OnFultonDoAfter);
SubscribeLocalEvent<FultonedComponent, GetVerbsEvent<InteractionVerb>>(OnFultonedGetVerbs);
SubscribeLocalEvent<FultonedComponent, ExaminedEvent>(OnFultonedExamine);
SubscribeLocalEvent<FultonedComponent, EntGotInsertedIntoContainerMessage>(OnFultonContainerInserted);
SubscribeLocalEvent<FultonComponent, AfterInteractEvent>(OnFultonInteract);
SubscribeLocalEvent<FultonComponent, StackSplitEvent>(OnFultonSplit);
}
private void OnFultonContainerInserted(EntityUid uid, FultonedComponent component, EntGotInsertedIntoContainerMessage args)
{
RemCompDeferred<FultonedComponent>(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<InteractionVerb> 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<FultonedComponent>(uid);
}
private void OnFultonDoAfter(FultonedDoAfterEvent args)
{
if (args.Cancelled || args.Target == null || !TryComp<FultonComponent>(args.Used, out var fulton))
return;
if (!_stack.TryUse(args.Used.Value, 1))
{
return;
}
var fultoned = AddComp<FultonedComponent>(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<FultonBeaconComponent>(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<FultonedComponent>(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<FultonComponent>(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.
/// <summary>
/// Tells clients to play the fulton animation.
/// </summary>
[Serializable, NetSerializable]
protected sealed class FultonAnimationMessage : EntityEventArgs
{
public NetEntity Entity;
public NetCoordinates Coordinates;
}
}