full sticky prediction (#30230)
* move all sticky stuff to shared and cleanup/grammar fix * update imports and ref --------- Co-authored-by: deltanedas <@deltanedas:kde.org>
This commit is contained in:
@@ -5,21 +5,26 @@ namespace Content.Client.Sticky.Visualizers;
|
||||
|
||||
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;
|
||||
@@ -27,8 +32,7 @@ public sealed class StickyVisualizerSystem : VisualizerSystem<StickyVisualizerCo
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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;
|
||||
@@ -21,7 +21,7 @@ public sealed partial class TriggerSystem
|
||||
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;
|
||||
|
||||
@@ -4,10 +4,10 @@ using Content.Server.Mind;
|
||||
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;
|
||||
@@ -34,7 +34,7 @@ public sealed class SpiderChargeSystem : SharedSpiderChargeSystem
|
||||
/// <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;
|
||||
@@ -67,7 +67,7 @@ public sealed class SpiderChargeSystem : SharedSpiderChargeSystem
|
||||
/// <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;
|
||||
}
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
90
Content.Shared/Sticky/Components/StickyComponent.cs
Normal file
90
Content.Shared/Sticky/Components/StickyComponent.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
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"));
|
||||
}
|
||||
@@ -1,23 +1,26 @@
|
||||
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]
|
||||
|
||||
26
Content.Shared/Sticky/EntityStuckEvent.cs
Normal file
26
Content.Shared/Sticky/EntityStuckEvent.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
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);
|
||||
220
Content.Shared/Sticky/Systems/StickySystem.cs
Normal file
220
Content.Shared/Sticky/Systems/StickySystem.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user