public sealed class StickyVisualizerSystem : VisualizerSystem<StickyVisualizerComponent>
{
+ private EntityQuery<SpriteComponent> _spriteQuery;
+
public override void Initialize()
{
base.Initialize();
+
+ _spriteQuery = GetEntityQuery<SpriteComponent>();
+
SubscribeLocalEvent<StickyVisualizerComponent, ComponentInit>(OnInit);
}
- private void OnInit(EntityUid uid, StickyVisualizerComponent component, ComponentInit args)
+ private void OnInit(Entity<StickyVisualizerComponent> ent, ref ComponentInit args)
{
- if (!TryComp(uid, out SpriteComponent? sprite))
+ if (!_spriteQuery.TryComp(ent, out var sprite))
return;
- component.DefaultDrawDepth = sprite.DrawDepth;
+ ent.Comp.OriginalDrawDepth = sprite.DrawDepth;
}
- protected override void OnAppearanceChange(EntityUid uid, StickyVisualizerComponent component, ref AppearanceChangeEvent args)
+ protected override void OnAppearanceChange(EntityUid uid, StickyVisualizerComponent comp, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
if (!AppearanceSystem.TryGetData<bool>(uid, StickyVisuals.IsStuck, out var isStuck, args.Component))
return;
- var drawDepth = isStuck ? component.StuckDrawDepth : component.DefaultDrawDepth;
+ var drawDepth = isStuck ? comp.StuckDrawDepth : comp.OriginalDrawDepth;
args.Sprite.DrawDepth = drawDepth;
-
}
}
using Content.Server.Explosion.Components;
-using Content.Server.Sticky.Events;
using Content.Shared.Examine;
using Content.Shared.Explosion.Components;
using Content.Shared.Interaction.Events;
using Content.Shared.Popups;
+using Content.Shared.Sticky;
using Content.Shared.Verbs;
namespace Content.Server.Explosion.EntitySystems;
SubscribeLocalEvent<RandomTimerTriggerComponent, MapInitEvent>(OnRandomTimerTriggerMapInit);
}
- private void OnStuck(EntityUid uid, OnUseTimerTriggerComponent component, EntityStuckEvent args)
+ private void OnStuck(EntityUid uid, OnUseTimerTriggerComponent component, ref EntityStuckEvent args)
{
if (!component.StartOnStick)
return;
using Content.Server.Objectives.Components;
using Content.Server.Popups;
using Content.Server.Roles;
-using Content.Server.Sticky.Events;
using Content.Shared.Interaction;
using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
+using Content.Shared.Sticky;
using Robust.Shared.GameObjects;
namespace Content.Server.Ninja.Systems;
/// <summary>
/// Require that the planter is a ninja and the charge is near the target warp point.
/// </summary>
- private void OnAttemptStick(EntityUid uid, SpiderChargeComponent comp, AttemptEntityStickEvent args)
+ private void OnAttemptStick(EntityUid uid, SpiderChargeComponent comp, ref AttemptEntityStickEvent args)
{
if (args.Cancelled)
return;
/// <summary>
/// Allows greentext to occur after exploding.
/// </summary>
- private void OnStuck(EntityUid uid, SpiderChargeComponent comp, EntityStuckEvent args)
+ private void OnStuck(EntityUid uid, SpiderChargeComponent comp, ref EntityStuckEvent args)
{
comp.Planter = args.User;
}
+++ /dev/null
-using Content.Shared.Whitelist;
-
-namespace Content.Server.Sticky.Components;
-
-/// <summary>
-/// Items that can be stick to other structures or entities.
-/// For example paper stickers or C4 charges.
-/// </summary>
-[RegisterComponent]
-public sealed partial class StickyComponent : Component
-{
- /// <summary>
- /// What target entities are valid to be surface for sticky entity.
- /// </summary>
- [DataField("whitelist")]
- [ViewVariables(VVAccess.ReadWrite)]
- public EntityWhitelist? Whitelist;
-
- /// <summary>
- /// What target entities can't be used as surface for sticky entity.
- /// </summary>
- [DataField("blacklist")]
- [ViewVariables(VVAccess.ReadWrite)]
- public EntityWhitelist? Blacklist;
-
- /// <summary>
- /// How much time does it take to stick entity to target.
- /// If zero will stick entity immediately.
- /// </summary>
- [DataField("stickDelay")]
- [ViewVariables(VVAccess.ReadWrite)]
- public TimeSpan StickDelay = TimeSpan.Zero;
-
- /// <summary>
- /// Whether users can unstick item when it was stuck to surface.
- /// </summary>
- [DataField("canUnstick")]
- [ViewVariables(VVAccess.ReadWrite)]
- public bool CanUnstick = true;
-
- /// <summary>
- /// How much time does it take to unstick entity.
- /// If zero will unstick entity immediately.
- /// </summary>
- [DataField("unstickDelay")]
- [ViewVariables(VVAccess.ReadWrite)]
- public TimeSpan UnstickDelay = TimeSpan.Zero;
-
- /// <summary>
- /// Popup message shown when player started sticking entity to another entity.
- /// </summary>
- [DataField("stickPopupStart")]
- [ViewVariables(VVAccess.ReadWrite)]
- public string? StickPopupStart;
-
- /// <summary>
- /// Popup message shown when player successfully stuck entity.
- /// </summary>
- [DataField("stickPopupSuccess")]
- [ViewVariables(VVAccess.ReadWrite)]
- public string? StickPopupSuccess;
-
- /// <summary>
- /// Popup message shown when player started unsticking entity from another entity.
- /// </summary>
- [DataField("unstickPopupStart")]
- [ViewVariables(VVAccess.ReadWrite)]
- public string? UnstickPopupStart;
-
- /// <summary>
- /// Popup message shown when player successfully unstuck entity.
- /// </summary>
- [DataField("unstickPopupSuccess")]
- [ViewVariables(VVAccess.ReadWrite)]
- public string? UnstickPopupSuccess;
-
- /// <summary>
- /// Entity that is used as surface for sticky entity.
- /// Null if entity doesn't stuck to anything.
- /// </summary>
- [ViewVariables(VVAccess.ReadOnly)]
- public EntityUid? StuckTo;
-
- /// <summary>
- /// For the DoAfter event to tell if it should stick or unstick
- /// </summary>
- public bool Stick;
-}
+++ /dev/null
-namespace Content.Server.Sticky.Events;
-
-/// <summary>
-/// Risen on sticky entity to see if it can stick to another entity.
-/// </summary>
-[ByRefEvent]
-public record struct AttemptEntityStickEvent(EntityUid Target, EntityUid User)
-{
- public readonly EntityUid Target = Target;
- public readonly EntityUid User = User;
- public bool Cancelled = false;
-}
-
-/// <summary>
-/// Risen on sticky entity to see if it can unstick from another entity.
-/// </summary>
-[ByRefEvent]
-public record struct AttemptEntityUnstickEvent(EntityUid Target, EntityUid User)
-{
- public readonly EntityUid Target = Target;
- public readonly EntityUid User = User;
- public bool Cancelled = false;
-}
-
-
-/// <summary>
-/// Risen on sticky entity when it was stuck to other entity.
-/// </summary>
-public sealed class EntityStuckEvent : EntityEventArgs
-{
- /// <summary>
- /// Entity that was used as a surface for sticky object.
- /// </summary>
- public readonly EntityUid Target;
-
- /// <summary>
- /// Entity that stuck sticky object on target.
- /// </summary>
- public readonly EntityUid User;
-
- public EntityStuckEvent(EntityUid target, EntityUid user)
- {
- Target = target;
- User = user;
- }
-}
-
-/// <summary>
-/// Risen on sticky entity when it was unstuck from other entity.
-/// </summary>
-public sealed class EntityUnstuckEvent : EntityEventArgs
-{
- /// <summary>
- /// Entity that was used as a surface for sticky object.
- /// </summary>
- public readonly EntityUid Target;
-
- /// <summary>
- /// Entity that unstuck sticky object on target.
- /// </summary>
- public readonly EntityUid User;
-
- public EntityUnstuckEvent(EntityUid target, EntityUid user)
- {
- Target = target;
- User = user;
- }
-}
+++ /dev/null
-using Content.Server.Popups;
-using Content.Server.Sticky.Components;
-using Content.Server.Sticky.Events;
-using Content.Shared.DoAfter;
-using Content.Shared.Hands.EntitySystems;
-using Content.Shared.Interaction;
-using Content.Shared.Sticky;
-using Content.Shared.Sticky.Components;
-using Content.Shared.Verbs;
-using Content.Shared.Whitelist;
-using Robust.Shared.Containers;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Sticky.Systems;
-
-public sealed class StickySystem : EntitySystem
-{
- [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
- [Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
- [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
- [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
-
- private const string StickerSlotId = "stickers_container";
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<StickyComponent, StickyDoAfterEvent>(OnStickFinished);
- SubscribeLocalEvent<StickyComponent, AfterInteractEvent>(OnAfterInteract);
- SubscribeLocalEvent<StickyComponent, GetVerbsEvent<Verb>>(AddUnstickVerb);
- }
-
- private void OnAfterInteract(EntityUid uid, StickyComponent component, AfterInteractEvent args)
- {
- if (args.Handled || !args.CanReach || args.Target == null)
- return;
-
- // try stick object to a clicked target entity
- args.Handled = StartSticking(uid, args.User, args.Target.Value, component);
- }
-
- private void AddUnstickVerb(EntityUid uid, StickyComponent component, GetVerbsEvent<Verb> args)
- {
- if (component.StuckTo == null || !component.CanUnstick || !args.CanInteract || args.Hands == null)
- return;
-
- // we can't use args.CanAccess, because it stuck in another container
- // we also need to ignore entity that it stuck to
- var inRange = _interactionSystem.InRangeUnobstructed(uid, args.User,
- predicate: entity => component.StuckTo == entity);
- if (!inRange)
- return;
-
- args.Verbs.Add(new Verb
- {
- DoContactInteraction = true,
- Text = Loc.GetString("comp-sticky-unstick-verb-text"),
- Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/eject.svg.192dpi.png")),
- Act = () => StartUnsticking(uid, args.User, component)
- });
- }
-
- private bool StartSticking(EntityUid uid, EntityUid user, EntityUid target, StickyComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return false;
-
- // check whitelist and blacklist
- if (_whitelistSystem.IsWhitelistFail(component.Whitelist, target) ||
- _whitelistSystem.IsBlacklistPass(component.Blacklist, target))
- return false;
-
- var attemptEv = new AttemptEntityStickEvent(target, user);
- RaiseLocalEvent(uid, ref attemptEv);
- if (attemptEv.Cancelled)
- return false;
-
- // check if delay is not zero to start do after
- var delay = (float) component.StickDelay.TotalSeconds;
- if (delay > 0)
- {
- // show message to user
- if (component.StickPopupStart != null)
- {
- var msg = Loc.GetString(component.StickPopupStart);
- _popupSystem.PopupEntity(msg, user, user);
- }
-
- component.Stick = true;
-
- // start sticking object to target
- _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, delay, new StickyDoAfterEvent(), uid, target: target, used: uid)
- {
- BreakOnMove = true,
- NeedHand = true,
- });
- }
- else
- {
- // if delay is zero - stick entity immediately
- StickToEntity(uid, target, user, component);
- }
-
- return true;
- }
-
- private void OnStickFinished(EntityUid uid, StickyComponent component, DoAfterEvent args)
- {
- if (args.Handled || args.Cancelled || args.Args.Target == null)
- return;
-
- if (component.Stick)
- StickToEntity(uid, args.Args.Target.Value, args.Args.User, component);
- else
- UnstickFromEntity(uid, args.Args.User, component);
-
- args.Handled = true;
- }
-
- private void StartUnsticking(EntityUid uid, EntityUid user, StickyComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- if (component.StuckTo is not { } stuckTo)
- return;
-
- var attemptEv = new AttemptEntityUnstickEvent(stuckTo, user);
- RaiseLocalEvent(uid, ref attemptEv);
- if (attemptEv.Cancelled)
- return;
-
- var delay = (float) component.UnstickDelay.TotalSeconds;
- if (delay > 0)
- {
- // show message to user
- if (component.UnstickPopupStart != null)
- {
- var msg = Loc.GetString(component.UnstickPopupStart);
- _popupSystem.PopupEntity(msg, user, user);
- }
-
- component.Stick = false;
-
- // start unsticking object
- _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, delay, new StickyDoAfterEvent(), uid, target: uid)
- {
- BreakOnMove = true,
- NeedHand = true,
- });
- }
- else
- {
- // if delay is zero - unstick entity immediately
- UnstickFromEntity(uid, user, component);
- }
- }
-
- public void StickToEntity(EntityUid uid, EntityUid target, EntityUid user, StickyComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- var attemptEv = new AttemptEntityStickEvent(target, user);
- RaiseLocalEvent(uid, ref attemptEv);
- if (attemptEv.Cancelled)
- return;
-
- // add container to entity and insert sticker into it
- var container = _containerSystem.EnsureContainer<Container>(target, StickerSlotId);
- container.ShowContents = true;
- if (!_containerSystem.Insert(uid, container))
- return;
-
- // show message to user
- if (component.StickPopupSuccess != null)
- {
- var msg = Loc.GetString(component.StickPopupSuccess);
- _popupSystem.PopupEntity(msg, user, user);
- }
-
- // send information to appearance that entity is stuck
- if (TryComp(uid, out AppearanceComponent? appearance))
- {
- _appearance.SetData(uid, StickyVisuals.IsStuck, true, appearance);
- }
-
- component.StuckTo = target;
- RaiseLocalEvent(uid, new EntityStuckEvent(target, user), true);
- }
-
- public void UnstickFromEntity(EntityUid uid, EntityUid user, StickyComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- if (component.StuckTo is not { } stuckTo)
- return;
-
- var attemptEv = new AttemptEntityUnstickEvent(stuckTo, user);
- RaiseLocalEvent(uid, ref attemptEv);
- if (attemptEv.Cancelled)
- return;
-
- // try to remove sticky item from target container
- if (!_containerSystem.TryGetContainer(stuckTo, StickerSlotId, out var container) || !_containerSystem.Remove(uid, container))
- return;
- // delete container if it's now empty
- if (container.ContainedEntities.Count == 0)
- _containerSystem.ShutdownContainer(container);
-
- // try place dropped entity into user hands
- _handsSystem.PickupOrDrop(user, uid);
-
- // send information to appearance that entity isn't stuck
- if (TryComp(uid, out AppearanceComponent? appearance))
- {
- _appearance.SetData(uid, StickyVisuals.IsStuck, false, appearance);
- }
-
- // show message to user
- if (component.UnstickPopupSuccess != null)
- {
- var msg = Loc.GetString(component.UnstickPopupSuccess);
- _popupSystem.PopupEntity(msg, user, user);
- }
-
- component.StuckTo = null;
- RaiseLocalEvent(uid, new EntityUnstuckEvent(stuckTo, user), true);
- }
-}
--- /dev/null
+using Content.Shared.Sticky.Systems;
+using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Sticky.Components;
+
+/// <summary>
+/// Items that can be stuck to other structures or entities.
+/// For example, paper stickers or C4 charges.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(StickySystem))]
+[AutoGenerateComponentState]
+public sealed partial class StickyComponent : Component
+{
+ /// <summary>
+ /// What target entities are valid to be surface for sticky entity.
+ /// </summary>
+ [DataField]
+ public EntityWhitelist? Whitelist;
+
+ /// <summary>
+ /// What target entities can't be used as surface for sticky entity.
+ /// </summary>
+ [DataField]
+ public EntityWhitelist? Blacklist;
+
+ /// <summary>
+ /// How much time it takes to stick the entity to a target.
+ /// If zero, it will immediately be stuck.
+ /// </summary>
+ [DataField]
+ public TimeSpan StickDelay = TimeSpan.Zero;
+
+ /// <summary>
+ /// Whether users can unstick the entity after it has been stuck.
+ /// </summary>
+ [DataField]
+ public bool CanUnstick = true;
+
+ /// <summary>
+ /// How much time it takes to unstick the entity.
+ /// If zero, it will immediately be unstuck.
+ /// </summary>
+ [DataField]
+ public TimeSpan UnstickDelay = TimeSpan.Zero;
+
+ /// <summary>
+ /// Popup message shown when player starts sticking the entity to another entity.
+ /// </summary>
+ [DataField]
+ public LocId? StickPopupStart;
+
+ /// <summary>
+ /// Popup message shown when a player successfully sticks the entity.
+ /// </summary>
+ [DataField]
+ public LocId? StickPopupSuccess;
+
+ /// <summary>
+ /// Popup message shown when a player starts unsticking the entity from another entity.
+ /// </summary>
+ [DataField]
+ public LocId? UnstickPopupStart;
+
+ /// <summary>
+ /// Popup message shown when a player successfully unsticks the entity.
+ /// </summary>
+ [DataField]
+ public LocId? UnstickPopupSuccess;
+
+ /// <summary>
+ /// Entity that is used as a surface for the sticky entity.
+ /// Null if entity isn't stuck to anything.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public EntityUid? StuckTo;
+
+ /// <summary>
+ /// Text to use for the unstick verb.
+ /// </summary>
+ [DataField]
+ public LocId VerbText = "comp-sticky-unstick-verb-text";
+
+ /// <summary>
+ /// Icon to use for the unstick verb.
+ /// </summary>
+ [DataField]
+ public SpriteSpecifier VerbIcon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/eject.svg.192dpi.png"));
+}
-using Robust.Shared.Serialization;
+using Robust.Shared.Serialization;
namespace Content.Shared.Sticky.Components;
-using DrawDepth;
+using DrawDepth;
+
+/// <summary>
+/// Sets the sprite's draw depth depending on whether it's stuck.
+/// </summary>
[RegisterComponent]
public sealed partial class StickyVisualizerComponent : Component
{
/// <summary>
- /// What sprite draw depth set when entity stuck.
+ /// What sprite draw depth gets set to when stuck to something.
/// </summary>
- [DataField("stuckDrawDepth")]
- [ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public int StuckDrawDepth = (int) DrawDepth.Overdoors;
/// <summary>
- /// What sprite draw depth set when entity unstuck.
+ /// The sprite's original draw depth before being stuck.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- public int DefaultDrawDepth;
+ [DataField]
+ public int OriginalDrawDepth;
}
[Serializable, NetSerializable]
--- /dev/null
+namespace Content.Shared.Sticky;
+
+/// <summary>
+/// Risen on sticky entity to see if it can stick to another entity.
+/// </summary>
+[ByRefEvent]
+public record struct AttemptEntityStickEvent(EntityUid Target, EntityUid User, bool Cancelled = false);
+
+/// <summary>
+/// Risen on sticky entity to see if it can unstick from another entity.
+/// </summary>
+[ByRefEvent]
+public record struct AttemptEntityUnstickEvent(EntityUid Target, EntityUid User, bool Cancelled = false);
+
+
+/// <summary>
+/// Risen on sticky entity when it was stuck to other entity.
+/// </summary>
+[ByRefEvent]
+public record struct EntityStuckEvent(EntityUid Target, EntityUid User);
+
+/// <summary>
+/// Risen on sticky entity when it was unstuck from other entity.
+/// </summary>
+[ByRefEvent]
+public record struct EntityUnstuckEvent(EntityUid Target, EntityUid User);
--- /dev/null
+using Content.Shared.DoAfter;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Popups;
+using Content.Shared.Sticky.Components;
+using Content.Shared.Verbs;
+using Content.Shared.Whitelist;
+using Robust.Shared.Containers;
+
+namespace Content.Shared.Sticky.Systems;
+
+public sealed class StickySystem : EntitySystem
+{
+ [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedHandsSystem _hands = default!;
+ [Dependency] private readonly SharedInteractionSystem _interaction = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+ private const string StickerSlotId = "stickers_container";
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<StickyComponent, AfterInteractEvent>(OnAfterInteract);
+ SubscribeLocalEvent<StickyComponent, StickyDoAfterEvent>(OnStickyDoAfter);
+ SubscribeLocalEvent<StickyComponent, GetVerbsEvent<Verb>>(OnGetVerbs);
+ }
+
+ private void OnAfterInteract(Entity<StickyComponent> ent, ref AfterInteractEvent args)
+ {
+ if (args.Handled || !args.CanReach || args.Target is not {} target)
+ return;
+
+ // try stick object to a clicked target entity
+ args.Handled = StartSticking(ent, target, args.User);
+ }
+
+ private void OnGetVerbs(Entity<StickyComponent> ent, ref GetVerbsEvent<Verb> args)
+ {
+ var (uid, comp) = ent;
+ if (comp.StuckTo == null || !comp.CanUnstick || !args.CanInteract || args.Hands == null)
+ return;
+
+ // we can't use args.CanAccess, because it stuck in another container
+ // we also need to ignore entity that it stuck to
+ var user = args.User;
+ var inRange = _interaction.InRangeUnobstructed(uid, user,
+ predicate: entity => comp.StuckTo == entity);
+ if (!inRange)
+ return;
+
+ args.Verbs.Add(new Verb
+ {
+ DoContactInteraction = true,
+ Text = Loc.GetString(comp.VerbText),
+ Icon = comp.VerbIcon,
+ Act = () => StartUnsticking(ent, user)
+ });
+ }
+
+ private bool StartSticking(Entity<StickyComponent> ent, EntityUid target, EntityUid user)
+ {
+ var (uid, comp) = ent;
+
+ // check whitelist and blacklist
+ if (_whitelist.IsWhitelistFail(comp.Whitelist, target) ||
+ _whitelist.IsBlacklistPass(comp.Blacklist, target))
+ return false;
+
+ var attemptEv = new AttemptEntityStickEvent(target, user);
+ RaiseLocalEvent(uid, ref attemptEv);
+ if (attemptEv.Cancelled)
+ return false;
+
+ // skip doafter and popup if it's instant
+ if (comp.StickDelay <= TimeSpan.Zero)
+ {
+ StickToEntity(ent, target, user);
+ return true;
+ }
+
+ // show message to user
+ if (comp.StickPopupStart != null)
+ {
+ var msg = Loc.GetString(comp.StickPopupStart);
+ _popup.PopupClient(msg, user, user);
+ }
+
+ // start sticking object to target
+ _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, comp.StickDelay, new StickyDoAfterEvent(), uid, target: target, used: uid)
+ {
+ BreakOnMove = true,
+ NeedHand = true,
+ });
+
+ return true;
+ }
+
+ private void OnStickyDoAfter(Entity<StickyComponent> ent, ref StickyDoAfterEvent args)
+ {
+ // target is the sticky item when unsticking and the surface when sticking, it will never be null
+ if (args.Handled || args.Cancelled || args.Args.Target is not {} target)
+ return;
+
+ var user = args.User;
+ if (ent.Comp.StuckTo == null)
+ StickToEntity(ent, target, user);
+ else
+ UnstickFromEntity(ent, user);
+
+ args.Handled = true;
+ }
+
+ private void StartUnsticking(Entity<StickyComponent> ent, EntityUid user)
+ {
+ var (uid, comp) = ent;
+ if (comp.StuckTo is not {} stuckTo)
+ return;
+
+ var attemptEv = new AttemptEntityUnstickEvent(stuckTo, user);
+ RaiseLocalEvent(uid, ref attemptEv);
+ if (attemptEv.Cancelled)
+ return;
+
+ // skip doafter and popup if it's instant
+ if (comp.UnstickDelay <= TimeSpan.Zero)
+ {
+ UnstickFromEntity(ent, user);
+ return;
+ }
+
+ // show message to user
+ if (comp.UnstickPopupStart != null)
+ {
+ var msg = Loc.GetString(comp.UnstickPopupStart);
+ _popup.PopupClient(msg, user, user);
+ }
+
+ // start unsticking object
+ _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, comp.UnstickDelay, new StickyDoAfterEvent(), uid, target: uid)
+ {
+ BreakOnMove = true,
+ NeedHand = true,
+ });
+ }
+
+ public void StickToEntity(Entity<StickyComponent> ent, EntityUid target, EntityUid user)
+ {
+ var (uid, comp) = ent;
+ var attemptEv = new AttemptEntityStickEvent(target, user);
+ RaiseLocalEvent(uid, ref attemptEv);
+ if (attemptEv.Cancelled)
+ return;
+
+ // add container to entity and insert sticker into it
+ var container = _container.EnsureContainer<Container>(target, StickerSlotId);
+ container.ShowContents = true;
+ if (!_container.Insert(uid, container))
+ return;
+
+ // show message to user
+ if (comp.StickPopupSuccess != null)
+ {
+ var msg = Loc.GetString(comp.StickPopupSuccess);
+ _popup.PopupClient(msg, user, user);
+ }
+
+ // send information to appearance that entity is stuck
+ _appearance.SetData(uid, StickyVisuals.IsStuck, true);
+
+ comp.StuckTo = target;
+ Dirty(uid, comp);
+
+ var ev = new EntityStuckEvent(target, user);
+ RaiseLocalEvent(uid, ref ev);
+ }
+
+ public void UnstickFromEntity(Entity<StickyComponent> ent, EntityUid user)
+ {
+ var (uid, comp) = ent;
+ if (comp.StuckTo is not {} stuckTo)
+ return;
+
+ var attemptEv = new AttemptEntityUnstickEvent(stuckTo, user);
+ RaiseLocalEvent(uid, ref attemptEv);
+ if (attemptEv.Cancelled)
+ return;
+
+ // try to remove sticky item from target container
+ if (!_container.TryGetContainer(stuckTo, StickerSlotId, out var container) || !_container.Remove(uid, container))
+ return;
+
+ // delete container if it's now empty
+ if (container.ContainedEntities.Count == 0)
+ _container.ShutdownContainer(container);
+
+ // try place dropped entity into user hands
+ _hands.PickupOrDrop(user, uid);
+
+ // send information to appearance that entity isn't stuck
+ _appearance.SetData(uid, StickyVisuals.IsStuck, false);
+
+ // show message to user
+ if (comp.UnstickPopupSuccess != null)
+ {
+ var msg = Loc.GetString(comp.UnstickPopupSuccess);
+ _popup.PopupClient(msg, user, user);
+ }
+
+ comp.StuckTo = null;
+ Dirty(uid, comp);
+
+ var ev = new EntityUnstuckEvent(stuckTo, user);
+ RaiseLocalEvent(uid, ref ev);
+ }
+}