Port Item Offering from Euphoria (#5597)
* Item offering port (#96) * Offer item system port * More fixes * Make the formatting a bit more readable * Add a missing keybind * Add missing alert click handler and make some of the code more readable * Duplicate offer-item-system.ftl Signed-off-by: Mnemotechnican <69920617+Mnemotechnician@users.noreply.github.com> * Namespace fix and prediction fixes * Port various carrying and pseudo-item fixes from floofstation * Refactor the offer system to hopefully bring more clarity * Final fixes * Final final final fixes maybe * Sike, just do it the proper way --------- Signed-off-by: Mnemotechnican <69920617+Mnemotechnician@users.noreply.github.com> * fixes * empty commit (weird test fail?) * Moved some stuff to shared. Reorganized a bit. I could be here all day... --------- Signed-off-by: Mnemotechnican <69920617+Mnemotechnician@users.noreply.github.com> Co-authored-by: Mnemotechnican <69920617+Mnemotechnician@users.noreply.github.com> Co-authored-by: Vanessa <vanessalouwagie@gmail.com>
This commit is contained in:
parent
72f6bff008
commit
b67c029061
|
|
@ -34,6 +34,9 @@ namespace Content.Client.Input
|
||||||
common.AddFunction(ContentKeyFunctions.RotateStoredItem);
|
common.AddFunction(ContentKeyFunctions.RotateStoredItem);
|
||||||
common.AddFunction(ContentKeyFunctions.SaveItemLocation);
|
common.AddFunction(ContentKeyFunctions.SaveItemLocation);
|
||||||
common.AddFunction(ContentKeyFunctions.Point);
|
common.AddFunction(ContentKeyFunctions.Point);
|
||||||
|
// Floofstation section
|
||||||
|
common.AddFunction(ContentKeyFunctions.OfferItem);
|
||||||
|
// Floofstation section end
|
||||||
common.AddFunction(ContentKeyFunctions.ZoomOut);
|
common.AddFunction(ContentKeyFunctions.ZoomOut);
|
||||||
common.AddFunction(ContentKeyFunctions.ZoomIn);
|
common.AddFunction(ContentKeyFunctions.ZoomIn);
|
||||||
common.AddFunction(ContentKeyFunctions.ResetZoom);
|
common.AddFunction(ContentKeyFunctions.ResetZoom);
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||||
AddButton(ContentKeyFunctions.MovePulledObject);
|
AddButton(ContentKeyFunctions.MovePulledObject);
|
||||||
AddButton(ContentKeyFunctions.ReleasePulledObject);
|
AddButton(ContentKeyFunctions.ReleasePulledObject);
|
||||||
AddButton(ContentKeyFunctions.Point);
|
AddButton(ContentKeyFunctions.Point);
|
||||||
|
AddButton(ContentKeyFunctions.OfferItem); // Floofstation
|
||||||
AddButton(ContentKeyFunctions.RotateObjectClockwise);
|
AddButton(ContentKeyFunctions.RotateObjectClockwise);
|
||||||
AddButton(ContentKeyFunctions.RotateObjectCounterclockwise);
|
AddButton(ContentKeyFunctions.RotateObjectCounterclockwise);
|
||||||
AddButton(ContentKeyFunctions.FlipObject);
|
AddButton(ContentKeyFunctions.FlipObject);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.Input;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Client._Floof.OfferItem;
|
||||||
|
|
||||||
|
public sealed class OfferItemIndicatorsOverlay : Overlay
|
||||||
|
{
|
||||||
|
private readonly IInputManager _inputManager;
|
||||||
|
private readonly IEntityManager _entMan;
|
||||||
|
private readonly IEyeManager _eye;
|
||||||
|
private readonly OfferItemSystem _offer;
|
||||||
|
|
||||||
|
private readonly Texture _sight;
|
||||||
|
|
||||||
|
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||||
|
|
||||||
|
private readonly Color _mainColor = Color.White.WithAlpha(0.3f);
|
||||||
|
private readonly Color _strokeColor = Color.Black.WithAlpha(0.5f);
|
||||||
|
private readonly float _scale = 0.6f; // 1 is a little big
|
||||||
|
|
||||||
|
public OfferItemIndicatorsOverlay(IInputManager input, IEntityManager entMan,
|
||||||
|
IEyeManager eye, OfferItemSystem offerSys)
|
||||||
|
{
|
||||||
|
_inputManager = input;
|
||||||
|
_entMan = entMan;
|
||||||
|
_eye = eye;
|
||||||
|
_offer = offerSys;
|
||||||
|
|
||||||
|
var spriteSys = _entMan.EntitySysManager.GetEntitySystem<SpriteSystem>();
|
||||||
|
_sight = spriteSys.Frame0(new SpriteSpecifier.Rsi(new("/Textures/_Floof/Interface/Misc/give_item.rsi"), "give_item"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
if (!_offer.IsInOfferMode())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return base.BeforeDraw(in args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
var mouseScreenPosition = _inputManager.MouseScreenPosition;
|
||||||
|
var mousePosMap = _eye.PixelToMap(mouseScreenPosition);
|
||||||
|
if (mousePosMap.MapId != args.MapId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
var mousePos = mouseScreenPosition.Position;
|
||||||
|
var uiScale = (args.ViewportControl as Control)?.UIScale ?? 1f;
|
||||||
|
var limitedScale = uiScale > 1.25f ? 1.25f : uiScale;
|
||||||
|
|
||||||
|
DrawSight(_sight, args.ScreenHandle, mousePos, limitedScale * _scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawSight(Texture sight, DrawingHandleScreen screen, Vector2 centerPos, float scale)
|
||||||
|
{
|
||||||
|
var sightSize = sight.Size * scale;
|
||||||
|
var expandedSize = sightSize + new Vector2(7f, 7f);
|
||||||
|
|
||||||
|
screen.DrawTextureRect(sight,
|
||||||
|
UIBox2.FromDimensions(centerPos - sightSize * 0.5f, sightSize), _strokeColor);
|
||||||
|
screen.DrawTextureRect(sight,
|
||||||
|
UIBox2.FromDimensions(centerPos - expandedSize * 0.5f, expandedSize), _mainColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
using Content.Shared._Floof.OfferItem;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.Input;
|
||||||
|
using Robust.Client.Player;
|
||||||
|
|
||||||
|
namespace Content.Client._Floof.OfferItem;
|
||||||
|
|
||||||
|
public sealed class OfferItemSystem : SharedOfferItemSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||||
|
[Dependency] private readonly IEyeManager _eye = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
_overlayManager.AddOverlay(new OfferItemIndicatorsOverlay(
|
||||||
|
_inputManager,
|
||||||
|
EntityManager,
|
||||||
|
_eye,
|
||||||
|
this));
|
||||||
|
}
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
_overlayManager.RemoveOverlay<OfferItemIndicatorsOverlay>();
|
||||||
|
base.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsInOfferMode()
|
||||||
|
{
|
||||||
|
var entity = _playerManager.LocalEntity;
|
||||||
|
|
||||||
|
if (entity == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return IsInOfferMode(entity.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -55,9 +55,13 @@ public sealed class PseudoItemSystem : SharedPseudoItemSystem
|
||||||
|
|
||||||
protected override void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component, GettingPickedUpAttemptEvent args)
|
protected override void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component, GettingPickedUpAttemptEvent args)
|
||||||
{
|
{
|
||||||
|
// Floof - changed this a bit to actually start a do-after
|
||||||
// Try to pick the entity up instead first
|
// Try to pick the entity up instead first
|
||||||
if (args.User != args.Item && _carrying.TryCarry(args.User, uid))
|
if (args.User != args.Item
|
||||||
|
&& TryComp<CarriableComponent>(uid, out var carriable)
|
||||||
|
&& _carrying.CanCarry(args.User, (uid, carriable)))
|
||||||
{
|
{
|
||||||
|
_carrying.StartCarryDoAfter(args.User, (uid, carriable));
|
||||||
args.Cancel();
|
args.Cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
using Content.Shared._Floof.OfferItem;
|
||||||
|
|
||||||
|
namespace Content.Server._Floof.OfferItem;
|
||||||
|
|
||||||
|
public sealed partial class OfferItemSystem : SharedOfferItemSystem;
|
||||||
|
|
@ -68,6 +68,7 @@ namespace Content.Shared.Input
|
||||||
public static readonly BoundKeyFunction TakeScreenshotNoUI = "TakeScreenshotNoUI";
|
public static readonly BoundKeyFunction TakeScreenshotNoUI = "TakeScreenshotNoUI";
|
||||||
public static readonly BoundKeyFunction ToggleFullscreen = "ToggleFullscreen";
|
public static readonly BoundKeyFunction ToggleFullscreen = "ToggleFullscreen";
|
||||||
public static readonly BoundKeyFunction Point = "Point";
|
public static readonly BoundKeyFunction Point = "Point";
|
||||||
|
public static readonly BoundKeyFunction OfferItem = "OfferItem"; // Floofstation
|
||||||
public static readonly BoundKeyFunction ZoomOut = "ZoomOut";
|
public static readonly BoundKeyFunction ZoomOut = "ZoomOut";
|
||||||
public static readonly BoundKeyFunction ZoomIn = "ZoomIn";
|
public static readonly BoundKeyFunction ZoomIn = "ZoomIn";
|
||||||
public static readonly BoundKeyFunction ResetZoom = "ResetZoom";
|
public static readonly BoundKeyFunction ResetZoom = "ResetZoom";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Content.Shared._ST.Interaction; // Stellar - interaction particles
|
using Content.Shared._ST.Interaction; // Stellar - interaction particles
|
||||||
|
using Content.Shared._Floof.OfferItem; // Floof
|
||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.Alert;
|
using Content.Shared.Alert;
|
||||||
|
|
@ -117,10 +118,11 @@ public sealed class PullingSystem : EntitySystem
|
||||||
if (TryComp(args.PullerUid, out PullerComponent? pullerComp) && !pullerComp.NeedsHands)
|
if (TryComp(args.PullerUid, out PullerComponent? pullerComp) && !pullerComp.NeedsHands)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!_virtual.TrySpawnVirtualItemInHand(args.PulledUid, uid))
|
if (!_virtual.TrySpawnVirtualItemInHand(args.PulledUid, uid, out var virt)) // Floofstation - store item
|
||||||
{
|
{
|
||||||
DebugTools.Assert("Unable to find available hand when starting pulling??");
|
DebugTools.Assert("Unable to find available hand when starting pulling??");
|
||||||
}
|
}
|
||||||
|
EnsureComp<OfferableVirtualItemComponent>(virt.Value); // Floofstation - add a special component to allow offering it
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStoppedMessage args)
|
private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStoppedMessage args)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
using Content.Shared.Actions;
|
using Content.Shared.Actions;
|
||||||
using Content.Shared.Bed.Sleep;
|
|
||||||
using Content.Shared.DoAfter;
|
using Content.Shared.DoAfter;
|
||||||
using Content.Shared.Hands;
|
using Content.Shared.Hands;
|
||||||
using Content.Shared.IdentityManagement;
|
using Content.Shared.Hands.EntitySystems;
|
||||||
using Content.Shared.Interaction.Events;
|
using Content.Shared.Interaction.Events;
|
||||||
using Content.Shared.Item;
|
using Content.Shared.Item;
|
||||||
using Content.Shared.Item.PseudoItem;
|
using Content.Shared.Item.PseudoItem;
|
||||||
|
|
@ -24,7 +23,8 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem
|
||||||
[Dependency] private readonly TagSystem _tag = default!;
|
[Dependency] private readonly TagSystem _tag = default!;
|
||||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
[Dependency] private readonly SharedContainerSystem _container = default!; // Floofstation
|
||||||
|
[Dependency] private readonly SharedHandsSystem _hands = default!; // Floofstation
|
||||||
|
|
||||||
private readonly ProtoId<TagPrototype> PreventTag = "PreventLabel";
|
private readonly ProtoId<TagPrototype> PreventTag = "PreventLabel";
|
||||||
private readonly EntProtoId SleepActionId = "ActionSleep"; // The action used for sleeping inside bags. Currently uses the default sleep action (same as beds)
|
private readonly EntProtoId SleepActionId = "ActionSleep"; // The action used for sleeping inside bags. Currently uses the default sleep action (same as beds)
|
||||||
|
|
@ -116,17 +116,35 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem
|
||||||
protected virtual void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component,
|
protected virtual void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component,
|
||||||
GettingPickedUpAttemptEvent args)
|
GettingPickedUpAttemptEvent args)
|
||||||
{
|
{
|
||||||
if (args.User == args.Item)
|
args.Cancel(); // Floof - this is a terrible idea. This triggers every time ANY system checks if a pseudo-item can be picked up.
|
||||||
return;
|
// WHY DID YOU DO THAT, NYANOTRASEN???
|
||||||
|
|
||||||
_transform.AttachToGridOrMap(uid);
|
// if (args.User == args.Item)
|
||||||
args.Cancel();
|
// return;
|
||||||
|
//
|
||||||
|
// _transform.AttachToGridOrMap(uid);
|
||||||
|
// args.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDropAttempt(EntityUid uid, PseudoItemComponent component, DropAttemptEvent args)
|
private void OnDropAttempt(EntityUid uid, PseudoItemComponent component, DropAttemptEvent args)
|
||||||
{
|
{
|
||||||
if (component.Active)
|
if (!component.Active)
|
||||||
args.Cancel();
|
return;
|
||||||
|
|
||||||
|
// Floof - we try to get the containing container and try to drop it into it
|
||||||
|
// If possible, we do it, since a bagged cat probably can put things back into the bag just like they can pick them up.
|
||||||
|
string? failReason = null;
|
||||||
|
if (_hands.GetActiveItem(uid) is { Valid: true, } droppedItem
|
||||||
|
&& _container.TryGetContainingContainer(Transform(uid).ParentUid, uid, out var pseudoItemContainer)
|
||||||
|
&& TryComp<StorageComponent>(pseudoItemContainer.Owner, out var targetStorage)
|
||||||
|
&& _storage.CanInsert(pseudoItemContainer.Owner, droppedItem, out failReason, targetStorage, ignoreStacks: true)
|
||||||
|
)
|
||||||
|
_storage.Insert(pseudoItemContainer.Owner, droppedItem, out _, uid, targetStorage, stackAutomatically: false);
|
||||||
|
|
||||||
|
if (failReason != null)
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString(failReason), uid, uid);
|
||||||
|
|
||||||
|
args.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnInsertAttempt(EntityUid uid, PseudoItemComponent component,
|
private void OnInsertAttempt(EntityUid uid, PseudoItemComponent component,
|
||||||
|
|
@ -141,8 +159,9 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem
|
||||||
// Prevents moving within the bag :)
|
// Prevents moving within the bag :)
|
||||||
private void OnInteractAttempt(EntityUid uid, PseudoItemComponent component, InteractionAttemptEvent args)
|
private void OnInteractAttempt(EntityUid uid, PseudoItemComponent component, InteractionAttemptEvent args)
|
||||||
{
|
{
|
||||||
if (args.Uid == args.Target && component.Active)
|
// Floof - why the fuck.
|
||||||
args.Cancelled = true;
|
// if (args.Uid == args.Target && component.Active)
|
||||||
|
// args.Cancelled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDoAfter(EntityUid uid, PseudoItemComponent component, DoAfterEvent args)
|
private void OnDoAfter(EntityUid uid, PseudoItemComponent component, DoAfterEvent args)
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ using Robust.Shared.Network;
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Physics.Components;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Shared._DV.Polymorph;
|
using Content.Shared._DV.Polymorph;
|
||||||
|
using Content.Shared._Floof.OfferItem;
|
||||||
using Content.Shared.Hands.EntitySystems;
|
using Content.Shared.Hands.EntitySystems;
|
||||||
|
|
||||||
namespace Content.Shared._DV.Carrying;
|
namespace Content.Shared._DV.Carrying;
|
||||||
|
|
@ -181,14 +182,15 @@ public sealed class CarryingSystem : EntitySystem
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnInteractionAttempt(Entity<BeingCarriedComponent> ent, ref InteractionAttemptEvent args)
|
private void OnInteractionAttempt(Entity<BeingCarriedComponent> ent, ref InteractionAttemptEvent args)
|
||||||
{
|
{
|
||||||
if (args.Target is not {} target)
|
// Floofstation - no - this prevents the person from escaping and more.
|
||||||
return;
|
// if (args.Target is not {} target)
|
||||||
|
// return;
|
||||||
var targetParent = Transform(target).ParentUid;
|
//
|
||||||
|
// var targetParent = Transform(target).ParentUid;
|
||||||
var carrier = ent.Comp.Carrier;
|
//
|
||||||
if (target != carrier && targetParent != carrier && targetParent != ent.Owner)
|
// var carrier = ent.Comp.Carrier;
|
||||||
args.Cancelled = true;
|
// if (target != carrier && targetParent != carrier && targetParent != ent.Owner)
|
||||||
|
// args.Cancelled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMoveAttempt(Entity<BeingCarriedComponent> ent, ref UpdateCanMoveEvent args)
|
private void OnMoveAttempt(Entity<BeingCarriedComponent> ent, ref UpdateCanMoveEvent args)
|
||||||
|
|
@ -203,8 +205,9 @@ public sealed class CarryingSystem : EntitySystem
|
||||||
|
|
||||||
private void OnInteractedWith(Entity<BeingCarriedComponent> ent, ref GettingInteractedWithAttemptEvent args)
|
private void OnInteractedWith(Entity<BeingCarriedComponent> ent, ref GettingInteractedWithAttemptEvent args)
|
||||||
{
|
{
|
||||||
if (args.Uid != ent.Comp.Carrier)
|
// Floofstation - why?
|
||||||
args.Cancelled = true;
|
// if (args.Uid != ent.Comp.Carrier)
|
||||||
|
// args.Cancelled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPullAttempt(Entity<BeingCarriedComponent> ent, ref PullAttemptEvent args)
|
private void OnPullAttempt(Entity<BeingCarriedComponent> ent, ref PullAttemptEvent args)
|
||||||
|
|
@ -242,7 +245,8 @@ public sealed class CarryingSystem : EntitySystem
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartCarryDoAfter(EntityUid carrier, Entity<CarriableComponent> carried)
|
// Floofstation - made public
|
||||||
|
public void StartCarryDoAfter(EntityUid carrier, Entity<CarriableComponent> carried)
|
||||||
{
|
{
|
||||||
TimeSpan length = GetPickupDuration(carrier, carried);
|
TimeSpan length = GetPickupDuration(carrier, carried);
|
||||||
|
|
||||||
|
|
@ -299,7 +303,8 @@ public sealed class CarryingSystem : EntitySystem
|
||||||
|
|
||||||
for (var x = 0; x < Comp<CarriableComponent>(carried).FreeHandsRequired; x++)
|
for (var x = 0; x < Comp<CarriableComponent>(carried).FreeHandsRequired; x++)
|
||||||
{
|
{
|
||||||
_virtualItem.TrySpawnVirtualItemInHand(carried, carrier);
|
if (_virtualItem.TrySpawnVirtualItemInHand(carried, carrier, out var virtualItem))
|
||||||
|
EnsureComp<OfferableVirtualItemComponent>(virtualItem.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
using Content.Shared.Alert;
|
||||||
|
|
||||||
|
namespace Content.Shared._Floof.OfferItem;
|
||||||
|
|
||||||
|
public sealed partial class AcceptOfferAlertEvent : BaseAlertEvent;
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
using Content.Shared.Alert;
|
||||||
|
using Content.Shared.Inventory.VirtualItem;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared._Floof.OfferItem;
|
||||||
|
|
||||||
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||||
|
[Access(typeof(SharedOfferItemSystem))]
|
||||||
|
public sealed partial class OfferItemComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Apparently this indicates whether the entity is currently choosing an entity to offer (right after pressing F).
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||||
|
public bool IsInOfferMode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If this is true, then someone is currently offering an item to this entity, and <see cref="ReceivingFrom"/>
|
||||||
|
/// stores the ID of that entity.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public bool IsInReceiveMode;
|
||||||
|
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public string? Hand;
|
||||||
|
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public EntityUid? Item;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Floofstation note. So, this is EE shitcode, so prepare for an emotional rollercoaster.
|
||||||
|
/// This field can mean TWO things. It's either the target entity this entity is offering an item to,
|
||||||
|
/// or an entity that is offering an item to this entity.
|
||||||
|
/// Whether it's one or the other is distinguished by <see cref="IsInReceiveMode"/>.<br/><br/>
|
||||||
|
///
|
||||||
|
/// In rare cases it can be both. According to my research, if entity A offers an item to entity B, and entity B offers to entity A,
|
||||||
|
/// then both entities will end up in receive mode, and they will have each other as targets. There's a check preventing offer loops
|
||||||
|
/// of length more than 2.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public EntityUid? ReceivingFrom;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public float MaxOfferDistance = 2f;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<AlertPrototype> OfferAlert = "Offer";
|
||||||
|
|
||||||
|
public EntityUid GetRealEntity(EntityManager entityManager) =>
|
||||||
|
entityManager.GetComponentOrNull<VirtualItemComponent>(Item)?.BlockingEntity ?? Item ?? EntityUid.Invalid;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
|
namespace Content.Shared._Floof.OfferItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A marker component that, when applied to a virtual item, allows it to be offered using item offering.
|
||||||
|
/// Implementors have to listen on ItemTransferredEvent.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
[NetworkedComponent]
|
||||||
|
public sealed partial class OfferableVirtualItemComponent : Component;
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
using Content.Shared.ActionBlocker;
|
||||||
|
using Content.Shared.Alert;
|
||||||
|
using Content.Shared.Hands.Components;
|
||||||
|
using Content.Shared.Hands.EntitySystems;
|
||||||
|
using Content.Shared.Input;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Robust.Shared.Input.Binding;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
|
||||||
|
namespace Content.Shared._Floof.OfferItem;
|
||||||
|
|
||||||
|
public abstract partial class SharedOfferItemSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||||
|
|
||||||
|
|
||||||
|
private void InitializeInteractions()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
CommandBinds.Builder
|
||||||
|
.Bind(ContentKeyFunctions.OfferItem, InputCmdHandler.FromDelegate(SetInOfferMode, handle: false, outsidePrediction: false))
|
||||||
|
.Register<SharedOfferItemSystem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
|
||||||
|
CommandBinds.Unregister<SharedOfferItemSystem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This sets IsInOfferMode to true, allowing the player to select whom to offer an item to with interaction.
|
||||||
|
/// </summary>
|
||||||
|
private void SetInOfferMode(ICommonSession? offerer)
|
||||||
|
{
|
||||||
|
if (offerer is not { } playerSession)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (playerSession.AttachedEntity is not { Valid: true } uid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!Exists(uid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_actionBlocker.CanInteract(uid, null))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<OfferItemComponent>(uid, out var offerItem))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<HandsComponent>(uid, out var hands))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_hands.GetActiveHand((uid, hands)) is not { } activeHandName)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_hands.TryGetHeldItem((uid, hands), activeHandName, out var heldItem))
|
||||||
|
return;
|
||||||
|
|
||||||
|
offerItem.Item = heldItem;
|
||||||
|
if (!offerItem.IsInOfferMode)
|
||||||
|
{
|
||||||
|
if (offerItem.Item == null)
|
||||||
|
{
|
||||||
|
_popup.PopupEntity(Loc.GetString("offer-item-empty-hand"), uid, uid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offerItem.Hand == null || offerItem.ReceivingFrom == null)
|
||||||
|
{
|
||||||
|
offerItem.IsInOfferMode = true;
|
||||||
|
offerItem.Hand = activeHandName;
|
||||||
|
|
||||||
|
Dirty(uid, offerItem);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're already offering an item to someone, cancel that offer
|
||||||
|
if (offerItem.ReceivingFrom != null)
|
||||||
|
{
|
||||||
|
UnReceive(offerItem.ReceivingFrom.Value, offererComp: offerItem);
|
||||||
|
offerItem.IsInOfferMode = false;
|
||||||
|
Dirty(uid, offerItem);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnOffer(uid, offerItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,414 @@
|
||||||
|
using Content.Shared._DV.Carrying;
|
||||||
|
using Content.Shared.Alert;
|
||||||
|
using Content.Shared.Hands.Components;
|
||||||
|
using Content.Shared.Hands.EntitySystems;
|
||||||
|
using Content.Shared.IdentityManagement;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Movement.Pulling.Components;
|
||||||
|
using Content.Shared.Movement.Pulling.Systems;
|
||||||
|
using Content.Shared.Nutrition.EntitySystems;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
// Dear contributor.
|
||||||
|
// This system is fucking unmaintainable.
|
||||||
|
// If you ever happen to touch this again, please do your best to document your changes and try to resolve mysteries surrounding this code.
|
||||||
|
// I did what I could to document the parts I managed to understand, but there is still more truth to be unveiled.
|
||||||
|
//
|
||||||
|
// HOURS_WASTED_HERE_FLOOFSTATION = 10
|
||||||
|
// HOURS_WASTED_HERE_DELTAV = 1
|
||||||
|
|
||||||
|
namespace Content.Shared._Floof.OfferItem;
|
||||||
|
|
||||||
|
public abstract partial class SharedOfferItemSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
[Dependency] private readonly INetManager _net = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
|
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||||
|
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
|
||||||
|
[Dependency] private readonly CarryingSystem _carrying = default!;
|
||||||
|
[Dependency] private readonly PullingSystem _pulling = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<OfferItemComponent, AcceptOfferAlertEvent>(OnAcceptOffer);
|
||||||
|
SubscribeLocalEvent<OfferItemComponent, InteractUsingEvent>(OnInteractWithReceiver, before: [typeof(IngestionSystem)]);
|
||||||
|
SubscribeLocalEvent<OfferableVirtualItemComponent, BeforeRangedInteractEvent>(OnRangedInteractWithReceiver);
|
||||||
|
SubscribeLocalEvent<OfferItemComponent, MoveEvent>(OnMove);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<BeingCarriedComponent, ItemTransferredEvent>(OnCarryTransfer);
|
||||||
|
SubscribeLocalEvent<PullableComponent, ItemTransferredEvent>(OnPulledTransfer);
|
||||||
|
|
||||||
|
InitializeInteractions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
var query = EntityQueryEnumerator<OfferItemComponent, HandsComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var offerItem, out var hands))
|
||||||
|
{
|
||||||
|
// If the mob no longer holds an item in the original offering hand, clear offering mode
|
||||||
|
if (offerItem.Hand != null && !_hands.TryGetHeldItem((uid, hands), offerItem.Hand, out _))
|
||||||
|
{
|
||||||
|
if (offerItem.ReceivingFrom != null)
|
||||||
|
{
|
||||||
|
UnReceive(offerItem.ReceivingFrom.Value, offererComp: offerItem);
|
||||||
|
offerItem.IsInOfferMode = false;
|
||||||
|
Dirty(uid, offerItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
UnOffer(uid, offerItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!offerItem.IsInReceiveMode)
|
||||||
|
{
|
||||||
|
_alertsSystem.ClearAlert(uid, offerItem.OfferAlert);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_alertsSystem.ShowAlert(uid, offerItem.OfferAlert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
private void OnAcceptOffer(Entity<OfferItemComponent> ent, ref AcceptOfferAlertEvent args)
|
||||||
|
{
|
||||||
|
Receive((ent, ent.Comp));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInteractWithReceiver(Entity<OfferItemComponent> receiver, ref InteractUsingEvent args)
|
||||||
|
{
|
||||||
|
if (!_timing.IsFirstTimePredicted || _timing.ApplyingState || args.Handled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<OfferItemComponent>(args.User, out var offererComponent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Handled = CreateOffer(receiver, (args.User, offererComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRangedInteractWithReceiver(Entity<OfferableVirtualItemComponent> virtItem, ref BeforeRangedInteractEvent args)
|
||||||
|
{
|
||||||
|
// If the entity being offered is a virtual item, InteractUsing will not be raised
|
||||||
|
// because virtual items exclude themselves from being marked as used
|
||||||
|
// If this is the case, InteractHand will be raised instead, which we can use anyway because OfferItem.Item stores the offered item
|
||||||
|
//
|
||||||
|
// We also can't check Handled here because VirtualItemSystem handles it, ffs
|
||||||
|
// This won't lead you to accidentally offering someone your gun
|
||||||
|
//
|
||||||
|
// This is shitcode, this time my shitcode. My changes to the offering system allow you to transfer carrying and pulling,
|
||||||
|
// but in order to handle these, we need to be able to intercept interactions with virtual items.
|
||||||
|
//
|
||||||
|
// Ideally this code should be rewritten to:
|
||||||
|
// a) Have each different virtual item have a distinct component (e.g. CarryingVirtualItem) which would allow to distinguish them from the rest
|
||||||
|
// b) Not rely on the InteractionSystem.
|
||||||
|
// However, I'm not in the mood to do either. And I'm too deep into the rabbit hole of getting this shit to work.
|
||||||
|
if (!_timing.IsFirstTimePredicted || _timing.ApplyingState)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var receiver = args.Target;
|
||||||
|
if (!TryComp<OfferItemComponent>(receiver, out var receiverComponent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var offerer = args.User;
|
||||||
|
if (!TryComp<OfferItemComponent>(offerer, out var offererComponent) || offererComponent.Item == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Since this is ranged, we must also check distance, because the interaction system wont check it for us in this case
|
||||||
|
if (!Transform(offerer).Coordinates.TryDistance(EntityManager, _transform, Transform(receiver.Value).Coordinates, out var dst)
|
||||||
|
|| dst > offererComponent.MaxOfferDistance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Handled = CreateOffer((receiver.Value, receiverComponent), (offerer, offererComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMove(EntityUid uid, OfferItemComponent component, MoveEvent args)
|
||||||
|
{
|
||||||
|
if (_net.IsClient) // Client often mispredicts movement, we cant trust it here
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (component.ReceivingFrom == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_transform.InRange(args.NewPosition, Transform(component.ReceivingFrom.Value).Coordinates, component.MaxOfferDistance))
|
||||||
|
return;
|
||||||
|
|
||||||
|
UnOffer(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCarryTransfer(Entity<BeingCarriedComponent> ent, ref ItemTransferredEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled
|
||||||
|
|| args.PassedItem == args.RealItem // Means the entity is transferred NOT via carrying
|
||||||
|
|| args.RealItem is not { Valid: true } carried
|
||||||
|
|| ent.Comp.Carrier is not { Valid: true } oldCarrier)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_carrying.DropCarried(oldCarrier, ent);
|
||||||
|
args.Handled = _carrying.TryCarry(args.Target, carried);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPulledTransfer(Entity<PullableComponent> ent, ref ItemTransferredEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled
|
||||||
|
|| args.PassedItem == args.RealItem // Means the entity is transferred NOT via pulling
|
||||||
|
|| args.RealItem is not { Valid: true } pulled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_pulling.TryStopPull(pulled, ent);
|
||||||
|
args.Handled = _pulling.TryStartPull(args.Target, ent, null, ent.Comp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Offering / Recieving
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to create an offer. Expects offerer.Item to already be set to the offered item, offererComponent.InReceiveMode == true.
|
||||||
|
/// Will fail if offerer == receiver or if receiver already has a set TargetOrOfferer, and that person is not the current offerer
|
||||||
|
/// </summary>
|
||||||
|
private bool CreateOffer(Entity<OfferItemComponent> receiver, Entity<OfferItemComponent> offerer)
|
||||||
|
{
|
||||||
|
var offererComponent = offerer.Comp;
|
||||||
|
var receiverComponent = receiver.Comp;
|
||||||
|
if (offerer == receiver || receiverComponent.IsInReceiveMode || !offererComponent.IsInOfferMode)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (offererComponent.IsInReceiveMode && offererComponent.ReceivingFrom != receiver)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
receiverComponent.IsInReceiveMode = true;
|
||||||
|
receiverComponent.ReceivingFrom = offerer;
|
||||||
|
|
||||||
|
Dirty(receiver, receiverComponent);
|
||||||
|
|
||||||
|
offererComponent.ReceivingFrom = receiver; // TODO this is ee shitcode, may not be necessary?
|
||||||
|
offererComponent.IsInOfferMode = false;
|
||||||
|
|
||||||
|
Dirty(offerer, offererComponent);
|
||||||
|
|
||||||
|
if (offererComponent.Item == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Sender popup (client-side only)
|
||||||
|
_popup.PopupClient(
|
||||||
|
Loc.GetString("offer-item-try-give",
|
||||||
|
("item", Identity.Entity(offererComponent.GetRealEntity(EntityManager), EntityManager)),
|
||||||
|
("target", Identity.Entity(receiver, EntityManager))),
|
||||||
|
offerer,
|
||||||
|
offerer);
|
||||||
|
// Receiver popup (server side only, not predicted because recipient != local player)
|
||||||
|
_popup.PopupEntity(
|
||||||
|
Loc.GetString("offer-item-try-give-target",
|
||||||
|
("user", Identity.Entity(receiverComponent.ReceivingFrom.Value, EntityManager)),
|
||||||
|
("item", Identity.Entity(offererComponent.GetRealEntity(EntityManager), EntityManager))),
|
||||||
|
offerer,
|
||||||
|
receiver,
|
||||||
|
Popups.PopupType.Medium);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the <see cref="OfferItemComponent"/> of the user and the target
|
||||||
|
/// </summary>
|
||||||
|
protected void UnOffer(EntityUid thisEntity, OfferItemComponent offererComp)
|
||||||
|
{
|
||||||
|
if (!TryComp<HandsComponent>(thisEntity, out var hands) || _hands.GetActiveHand((thisEntity, hands)) is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (offererComp.ReceivingFrom is { } otherEntity && TryComp<OfferItemComponent>(otherEntity, out var otherOfferer))
|
||||||
|
{
|
||||||
|
// So this tries to figure out which of these entities do what...
|
||||||
|
// if A.OfferItemComponent.Item != null, then A is currently offering an item to A.OfferItemComponent.TargetOrOfferer
|
||||||
|
// If it is null, then it is ONLY being offered an item TO.
|
||||||
|
if (offererComp.Item != null && _net.IsServer)
|
||||||
|
{
|
||||||
|
_popup.PopupEntity(
|
||||||
|
Loc.GetString("offer-item-no-give",
|
||||||
|
("item", Identity.Entity(offererComp.GetRealEntity(EntityManager), EntityManager)), // Floof - resolve virtual items
|
||||||
|
("target", Identity.Entity(otherEntity, EntityManager))),
|
||||||
|
thisEntity,
|
||||||
|
thisEntity);
|
||||||
|
_popup.PopupEntity(
|
||||||
|
Loc.GetString("offer-item-no-give-target",
|
||||||
|
("user", Identity.Entity(thisEntity, EntityManager)),
|
||||||
|
("item", Identity.Entity(offererComp.GetRealEntity(EntityManager), EntityManager))),
|
||||||
|
thisEntity,
|
||||||
|
otherEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (otherOfferer.Item != null && _net.IsServer)
|
||||||
|
{
|
||||||
|
_popup.PopupEntity(
|
||||||
|
Loc.GetString("offer-item-no-give",
|
||||||
|
("item", Identity.Entity(otherOfferer.GetRealEntity(EntityManager), EntityManager)), // Floof - resolve virtual items
|
||||||
|
("target", Identity.Entity(thisEntity, EntityManager))),
|
||||||
|
otherEntity,
|
||||||
|
otherEntity);
|
||||||
|
_popup.PopupEntity(
|
||||||
|
Loc.GetString("offer-item-no-give-target",
|
||||||
|
("user", Identity.Entity(otherEntity, EntityManager)),
|
||||||
|
("item", Identity.Entity(otherOfferer.GetRealEntity(EntityManager), EntityManager))),
|
||||||
|
otherEntity,
|
||||||
|
thisEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
otherOfferer.IsInOfferMode = false;
|
||||||
|
otherOfferer.IsInReceiveMode = false;
|
||||||
|
otherOfferer.Hand = null;
|
||||||
|
otherOfferer.ReceivingFrom = null;
|
||||||
|
otherOfferer.Item = null;
|
||||||
|
|
||||||
|
Dirty(otherEntity, otherOfferer);
|
||||||
|
}
|
||||||
|
|
||||||
|
offererComp.IsInOfferMode = false;
|
||||||
|
offererComp.IsInReceiveMode = false;
|
||||||
|
offererComp.Hand = null;
|
||||||
|
offererComp.ReceivingFrom = null;
|
||||||
|
offererComp.Item = null;
|
||||||
|
|
||||||
|
Dirty(thisEntity, offererComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancels the transfer of the item
|
||||||
|
/// </summary>
|
||||||
|
protected void UnReceive(EntityUid receiver, OfferItemComponent? receiverComp = null, OfferItemComponent? offererComp = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(receiver, ref receiverComp)
|
||||||
|
|| receiverComp.ReceivingFrom is not {} offerer
|
||||||
|
|| !Resolve(offerer, ref offererComp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Idk why this check is here
|
||||||
|
if (!TryComp<HandsComponent>(receiver, out var hands) || _hands.GetActiveHand((receiver, hands)) == null || receiverComp.ReceivingFrom == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If offererComp.Item != null, then they are actively offering to TargetOrOfferer
|
||||||
|
// Normally this method is called right after a transfer is done, but this part can be called from SetInOfferMode when the player presses F again to cancel an ongoing offer
|
||||||
|
if (offererComp.Item != null)
|
||||||
|
{
|
||||||
|
_popup.PopupClient(
|
||||||
|
Loc.GetString("offer-item-no-give",
|
||||||
|
("item", Identity.Entity(offererComp.GetRealEntity(EntityManager), EntityManager)), // Floof - resolve virtual items
|
||||||
|
("target", Identity.Entity(receiver, EntityManager))),
|
||||||
|
offerer,
|
||||||
|
offerer);
|
||||||
|
_popup.PopupEntity(
|
||||||
|
Loc.GetString("offer-item-no-give-target",
|
||||||
|
("user", Identity.Entity(receiverComp.ReceivingFrom.Value, EntityManager)), // Floof - resolve virtual items
|
||||||
|
("item", Identity.Entity(offererComp.GetRealEntity(EntityManager), EntityManager))),
|
||||||
|
offerer,
|
||||||
|
receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!offererComp.IsInReceiveMode)
|
||||||
|
{
|
||||||
|
offererComp.ReceivingFrom = null;
|
||||||
|
receiverComp.ReceivingFrom = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
offererComp.Item = null;
|
||||||
|
offererComp.Hand = null;
|
||||||
|
receiverComp.IsInReceiveMode = false;
|
||||||
|
|
||||||
|
Dirty(receiver, receiverComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Accepting the offer and receive item
|
||||||
|
/// </summary>
|
||||||
|
public void Receive(Entity<OfferItemComponent?> receiver)
|
||||||
|
{
|
||||||
|
if (!_timing.IsFirstTimePredicted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!Resolve(receiver, ref receiver.Comp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<OfferItemComponent>(receiver.Comp.ReceivingFrom, out var offererComponent) ||
|
||||||
|
offererComponent.Hand == null ||
|
||||||
|
receiver.Comp.ReceivingFrom is not {} sender ||
|
||||||
|
!TryComp<HandsComponent>(receiver, out var hands))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (offererComponent.Item != null)
|
||||||
|
{
|
||||||
|
// Floof - check if there's something else handling it first
|
||||||
|
var realItem = offererComponent.GetRealEntity(EntityManager);
|
||||||
|
if (!TryHandleExtendedTransfer(sender, receiver, offererComponent.Item.Value, realItem)
|
||||||
|
&& !_hands.TryPickup(receiver, offererComponent.Item.Value, handsComp: hands))
|
||||||
|
{
|
||||||
|
_popup.PopupEntity(Loc.GetString("offer-item-full-hand"), receiver, receiver);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_popup.PopupEntity(
|
||||||
|
Loc.GetString("offer-item-give",
|
||||||
|
("item", Identity.Entity(realItem, EntityManager)), // FLoof - resolve virtual items
|
||||||
|
("target", Identity.Entity(receiver, EntityManager))),
|
||||||
|
sender,
|
||||||
|
sender);
|
||||||
|
_popup.PopupEntity(
|
||||||
|
Loc.GetString("offer-item-give-other",
|
||||||
|
("user", Identity.Entity(receiver.Comp.ReceivingFrom.Value, EntityManager)),
|
||||||
|
("item", Identity.Entity(realItem, EntityManager)), // FLoof - resolve virtual items
|
||||||
|
("target", Identity.Entity(receiver, EntityManager))),
|
||||||
|
sender,
|
||||||
|
Filter.PvsExcept(sender, entityManager: EntityManager),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
offererComponent.Item = null;
|
||||||
|
UnReceive(receiver, receiver.Comp, offererComponent);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if <see cref="OfferItemComponent.IsInOfferMode"/> = true
|
||||||
|
/// </summary>
|
||||||
|
protected bool IsInOfferMode(Entity<OfferItemComponent?> ent)
|
||||||
|
{
|
||||||
|
return Resolve(ent, ref ent.Comp, false) && ent.Comp.IsInOfferMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryHandleExtendedTransfer(EntityUid user, EntityUid target, EntityUid offeredItem, EntityUid realItem)
|
||||||
|
{
|
||||||
|
var ev = new ItemTransferredEvent
|
||||||
|
{
|
||||||
|
User = user,
|
||||||
|
Target = target,
|
||||||
|
PassedItem = offeredItem,
|
||||||
|
RealItem = realItem,
|
||||||
|
};
|
||||||
|
RaiseLocalEvent(realItem, ref ev);
|
||||||
|
return ev.Handled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised on the entity that was transferred via item offering.
|
||||||
|
/// </summary>
|
||||||
|
[ByRefEvent]
|
||||||
|
public sealed class ItemTransferredEvent : HandledEntityEventArgs
|
||||||
|
{
|
||||||
|
public EntityUid User;
|
||||||
|
public EntityUid Target;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The actual item being passed around. Can be a virtual item.
|
||||||
|
/// </summary>
|
||||||
|
public EntityUid PassedItem;
|
||||||
|
/// <summary>
|
||||||
|
/// If <see cref="PassedItem"/> is a virtual item, this field contains the real item that was transferred.
|
||||||
|
/// </summary>
|
||||||
|
public EntityUid? RealItem;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
offer-item-empty-hand = You don't have anything in your hand to give!
|
||||||
|
|
||||||
|
offer-item-full-hand = Your hand isn't free to receive the item.
|
||||||
|
|
||||||
|
offer-item-try-give = You offer {THE($item)} to {$target}.
|
||||||
|
offer-item-try-give-target = {CAPITALIZE(THE($user))} offers you {THE($item)}.
|
||||||
|
|
||||||
|
offer-item-give = You handed {THE($item)} to {$target}.
|
||||||
|
offer-item-give-other = {CAPITALIZE(THE($user))} handed {THE($item)} to {$target}.
|
||||||
|
offer-item-give-target = {CAPITALIZE(THE($user))} handed you {THE($item)}.
|
||||||
|
|
||||||
|
offer-item-no-give = You stop offering {THE($item)} to {$target}.
|
||||||
|
offer-item-no-give-target = {CAPITALIZE(THE($user))} is no longer offering {THE($item)} to you.
|
||||||
|
|
||||||
|
|
||||||
|
alerts-offer-name = Accept Offer
|
||||||
|
alerts-offer-desc = Click this alert to accept the item offered to you.
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
- alertType: Rooted
|
- alertType: Rooted
|
||||||
- alertType: Pacified
|
- alertType: Pacified
|
||||||
- alertType: Stealthy
|
- alertType: Stealthy
|
||||||
|
- alertType: Offer # Floofstation - port from EE
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: AlertSpriteView
|
id: AlertSpriteView
|
||||||
|
|
|
||||||
|
|
@ -97,3 +97,4 @@
|
||||||
type: HumanoidMarkingModifierBoundUserInterface
|
type: HumanoidMarkingModifierBoundUserInterface
|
||||||
enum.StrippingUiKey.Key:
|
enum.StrippingUiKey.Key:
|
||||||
type: StrippableBoundUserInterface
|
type: StrippableBoundUserInterface
|
||||||
|
- type: OfferItem # Floofstation
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
- type: alert
|
||||||
|
id: Offer
|
||||||
|
clickEvent: !type:AcceptOfferAlertEvent { }
|
||||||
|
icons:
|
||||||
|
- sprite: /Textures/_Floof/Interface/Alerts/offer_item.rsi
|
||||||
|
state: offer_item
|
||||||
|
name: alerts-offer-name
|
||||||
|
description: alerts-offer-desc
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Original icon taken from https://github.com/ss220-space/Paradise/blob/master220/icons/mob/screen_alert.dmi, modified by Mocho",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "offer_item"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 782 B |
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "https://github.com/ss220-space/Paradise/blob/master220/icons/misc/mouse_icons/give_item.dmi",
|
||||||
|
"size": {
|
||||||
|
"x": 64,
|
||||||
|
"y": 64
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "give_item"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -544,6 +544,11 @@ binds:
|
||||||
type: State
|
type: State
|
||||||
key: MouseMiddle
|
key: MouseMiddle
|
||||||
mod1: Shift
|
mod1: Shift
|
||||||
|
# Floofstation section
|
||||||
|
- function: OfferItem
|
||||||
|
type: State
|
||||||
|
key: F
|
||||||
|
# Floofstation section end
|
||||||
- function: ArcadeUp
|
- function: ArcadeUp
|
||||||
type: State
|
type: State
|
||||||
key: Up
|
key: Up
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue