Adds Parcel Wrap (#34471)
* Parcel Wrap * fix TG sprite licenses update attribution on modified `unwrapped` sprite to better conform to CC's guidance * ContainerContainer test failure fix * Just easy changes for now. * Imagine building your code before pushing it for review * The rest of the PR comments * PR comments * more comments + cargo orderability * whitespace: deduplicated. * use limitedcharges replace mostly-duped client/server with if(onserver) * cabinet perspective sprites * web edit detected fite me * @ps3moira 's new sprites for me :) * add a touch of attribution * EmoGarbage Review * Merge with master * Merge with master --------- Co-authored-by: EmoGarbage404 <retron404@gmail.com>
67
Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
using Content.Shared.Item;
|
||||||
|
using Content.Shared.Whitelist;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.ParcelWrap.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This component gives its owning entity the ability to wrap items into parcels.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="Components.WrappedParcelComponent"/>
|
||||||
|
[RegisterComponent, NetworkedComponent]
|
||||||
|
[Access] // Readonly, except for VV editing
|
||||||
|
public sealed partial class ParcelWrapComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="EntityPrototype"/> of the parcel created by using this component.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public EntProtoId ParcelPrototype;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, parcels created by this will have the same <see cref="ItemSizePrototype">size</see> as the item they
|
||||||
|
/// contain. If false, parcels created by this will always have the size specified by <see cref="FallbackItemSize"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool WrappedItemsMaintainSize = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="ItemSizePrototype">size</see> of parcels created by this component's entity. This is used if
|
||||||
|
/// <see cref="WrappedItemsMaintainSize"/> is false, or if the item being wrapped somehow doesn't have a size.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<ItemSizePrototype> FallbackItemSize = "Ginormous";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, parcels created by this will have the same shape as the item they contain. If false, parcels created by
|
||||||
|
/// this will have the default shape for their size.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool WrappedItemsMaintainShape;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long it takes to use this to wrap something.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public TimeSpan WrapDelay = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sound played when this is used to wrap something.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public SoundSpecifier? WrapSound;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the set of things which can be wrapped (unless it fails the <see cref="Blacklist"/>).
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public EntityWhitelist? Whitelist;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the set of things which cannot be wrapped (even if it passes the <see cref="Whitelist"/>).
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public EntityWhitelist? Blacklist;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using Content.Shared.ParcelWrap.Systems;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.ParcelWrap.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This component marks its owner as being a parcel created by wrapping another item up. It can be unwrapped,
|
||||||
|
/// destroying this entity and releasing <see cref="Contents"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="ParcelWrapComponent"/>
|
||||||
|
[RegisterComponent, NetworkedComponent, Access(typeof(ParcelWrappingSystem))]
|
||||||
|
public sealed partial class WrappedParcelComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The contents of this parcel.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public ContainerSlot Contents = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the entity to spawn when this parcel is unwrapped.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public EntProtoId? UnwrapTrash;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long it takes to unwrap this parcel.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public TimeSpan UnwrapDelay = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sound played when unwrapping this parcel.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public SoundSpecifier? UnwrapSound;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of <see cref="Contents"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public string ContainerId = "contents";
|
||||||
|
}
|
||||||
13
Content.Shared/ParcelWrap/Components/WrappedParcelVisuals.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.ParcelWrap.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This enum is used to change the sprite used by WrappedParcels based on the parcel's size.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum WrappedParcelVisuals : byte
|
||||||
|
{
|
||||||
|
Size,
|
||||||
|
Layer,
|
||||||
|
}
|
||||||
10
Content.Shared/ParcelWrap/Systems/ParcelWrapEvents.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using Content.Shared.DoAfter;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.ParcelWrap.Systems;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed partial class ParcelWrapItemDoAfterEvent : SimpleDoAfterEvent;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed partial class UnwrapWrappedParcelDoAfterEvent : SimpleDoAfterEvent;
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
using Content.Shared.DoAfter;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Item;
|
||||||
|
using Content.Shared.ParcelWrap.Components;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Shared.ParcelWrap.Systems;
|
||||||
|
|
||||||
|
// This part handles Parcel Wrap.
|
||||||
|
public sealed partial class ParcelWrappingSystem
|
||||||
|
{
|
||||||
|
private void InitializeParcelWrap()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<ParcelWrapComponent, AfterInteractEvent>(OnAfterInteract);
|
||||||
|
SubscribeLocalEvent<ParcelWrapComponent, GetVerbsEvent<UtilityVerb>>(OnGetVerbsForParcelWrap);
|
||||||
|
SubscribeLocalEvent<ParcelWrapComponent, ParcelWrapItemDoAfterEvent>(OnWrapItemDoAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAfterInteract(Entity<ParcelWrapComponent> entity, ref AfterInteractEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled ||
|
||||||
|
args.Target is not { } target ||
|
||||||
|
!args.CanReach ||
|
||||||
|
!IsWrappable(entity, target))
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Handled = TryStartWrapDoAfter(args.User, entity, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGetVerbsForParcelWrap(Entity<ParcelWrapComponent> entity, ref GetVerbsEvent<UtilityVerb> args)
|
||||||
|
{
|
||||||
|
if (!args.CanAccess || !IsWrappable(entity, args.Target))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// "Capture" the values from `args` because C# doesn't like doing the capturing for `ref` values.
|
||||||
|
var user = args.User;
|
||||||
|
var target = args.Target;
|
||||||
|
|
||||||
|
// "Wrap" verb for when just left-clicking doesn't work.
|
||||||
|
args.Verbs.Add(new UtilityVerb
|
||||||
|
{
|
||||||
|
Text = Loc.GetString("parcel-wrap-verb-wrap"),
|
||||||
|
Act = () => TryStartWrapDoAfter(user, entity, target),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWrapItemDoAfter(Entity<ParcelWrapComponent> wrapper, ref ParcelWrapItemDoAfterEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled || args.Cancelled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (args.Target is { } target)
|
||||||
|
{
|
||||||
|
WrapInternal(args.User, wrapper, target);
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryStartWrapDoAfter(EntityUid user, Entity<ParcelWrapComponent> wrapper, EntityUid target)
|
||||||
|
{
|
||||||
|
return _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager,
|
||||||
|
user,
|
||||||
|
wrapper.Comp.WrapDelay,
|
||||||
|
new ParcelWrapItemDoAfterEvent(),
|
||||||
|
wrapper, // Raise the event on the wrapper because that's what the event handler expects.
|
||||||
|
target,
|
||||||
|
wrapper)
|
||||||
|
{
|
||||||
|
NeedHand = true,
|
||||||
|
BreakOnMove = true,
|
||||||
|
BreakOnDamage = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spawns a WrappedParcel containing <paramref name="target"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">The entity using <paramref name="wrapper"/> to wrap <paramref name="target"/>.</param>
|
||||||
|
/// <param name="wrapper">The wrapping being used. Determines appearance of the spawned parcel.</param>
|
||||||
|
/// <param name="target">The entity being wrapped.</param>
|
||||||
|
private void WrapInternal(EntityUid user, Entity<ParcelWrapComponent> wrapper, EntityUid target)
|
||||||
|
{
|
||||||
|
if (_net.IsServer)
|
||||||
|
{
|
||||||
|
var spawned = Spawn(wrapper.Comp.ParcelPrototype, Transform(target).Coordinates);
|
||||||
|
|
||||||
|
// If this wrap maintains the size when wrapping, set the parcel's size to the target's size. Otherwise use the
|
||||||
|
// wrap's fallback size.
|
||||||
|
TryComp(target, out ItemComponent? targetItemComp);
|
||||||
|
var size = wrapper.Comp.FallbackItemSize;
|
||||||
|
if (wrapper.Comp.WrappedItemsMaintainSize && targetItemComp is not null)
|
||||||
|
{
|
||||||
|
size = targetItemComp.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParcelWrap's spawned entity should always have an `ItemComp`. As of writing, the only use has it hardcoded on
|
||||||
|
// its prototype.
|
||||||
|
var item = Comp<ItemComponent>(spawned);
|
||||||
|
_item.SetSize(spawned, size, item);
|
||||||
|
_appearance.SetData(spawned, WrappedParcelVisuals.Size, size.Id);
|
||||||
|
|
||||||
|
// If this wrap maintains the shape when wrapping and the item has a shape override, copy the shape override to
|
||||||
|
// the parcel.
|
||||||
|
if (wrapper.Comp.WrappedItemsMaintainShape && targetItemComp is { Shape: { } shape })
|
||||||
|
{
|
||||||
|
_item.SetShape(spawned, shape, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the target's in a container, try to put the parcel in its place in the container.
|
||||||
|
if (_container.TryGetContainingContainer((target, null, null), out var containerOfTarget))
|
||||||
|
{
|
||||||
|
_container.Remove(target, containerOfTarget);
|
||||||
|
_container.InsertOrDrop((spawned, null, null), containerOfTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert the target into the parcel.
|
||||||
|
var parcel = EnsureComp<WrappedParcelComponent>(spawned);
|
||||||
|
if (!_container.Insert(target, parcel.Contents))
|
||||||
|
{
|
||||||
|
DebugTools.Assert(
|
||||||
|
$"Failed to insert target entity into newly spawned parcel. target={PrettyPrint.PrintUserFacing(target)}");
|
||||||
|
QueueDel(spawned);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume a `use` on the wrapper, and delete the wrapper if it's empty.
|
||||||
|
_charges.TryUseCharges(wrapper.Owner, 1);
|
||||||
|
if (_net.IsServer && _charges.IsEmpty(wrapper.Owner))
|
||||||
|
QueueDel(wrapper);
|
||||||
|
|
||||||
|
// Play a wrapping sound.
|
||||||
|
_audio.PlayPredicted(wrapper.Comp.WrapSound, target, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
using Content.Shared.Destructible;
|
||||||
|
using Content.Shared.DoAfter;
|
||||||
|
using Content.Shared.Interaction.Events;
|
||||||
|
using Content.Shared.Materials;
|
||||||
|
using Content.Shared.ParcelWrap.Components;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
|
||||||
|
namespace Content.Shared.ParcelWrap.Systems;
|
||||||
|
|
||||||
|
// This part handles Wrapped Parcels
|
||||||
|
public sealed partial class ParcelWrappingSystem
|
||||||
|
{
|
||||||
|
private void InitializeWrappedParcel()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<WrappedParcelComponent, ComponentInit>(OnComponentInit);
|
||||||
|
SubscribeLocalEvent<WrappedParcelComponent, UseInHandEvent>(OnUseInHand);
|
||||||
|
SubscribeLocalEvent<WrappedParcelComponent, GetVerbsEvent<InteractionVerb>>(OnGetVerbsForWrappedParcel);
|
||||||
|
SubscribeLocalEvent<WrappedParcelComponent, UnwrapWrappedParcelDoAfterEvent>(OnUnwrapParcelDoAfter);
|
||||||
|
SubscribeLocalEvent<WrappedParcelComponent, DestructionEventArgs>(OnDestroyed);
|
||||||
|
SubscribeLocalEvent<WrappedParcelComponent, GotReclaimedEvent>(OnDestroyed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnComponentInit(Entity<WrappedParcelComponent> entity, ref ComponentInit args)
|
||||||
|
{
|
||||||
|
entity.Comp.Contents = _container.EnsureContainer<ContainerSlot>(entity, entity.Comp.ContainerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUseInHand(Entity<WrappedParcelComponent> entity, ref UseInHandEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Handled = TryStartUnwrapDoAfter(args.User, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGetVerbsForWrappedParcel(Entity<WrappedParcelComponent> entity,
|
||||||
|
ref GetVerbsEvent<InteractionVerb> args)
|
||||||
|
{
|
||||||
|
if (!args.CanAccess)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// "Capture" the values from `args` because C# doesn't like doing the capturing for `ref` values.
|
||||||
|
var user = args.User;
|
||||||
|
|
||||||
|
args.Verbs.Add(new InteractionVerb
|
||||||
|
{
|
||||||
|
Text = Loc.GetString("parcel-wrap-verb-unwrap"),
|
||||||
|
Act = () => TryStartUnwrapDoAfter(user, entity),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnwrapParcelDoAfter(Entity<WrappedParcelComponent> entity, ref UnwrapWrappedParcelDoAfterEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled || args.Cancelled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (args.Target is { } target && TryComp<WrappedParcelComponent>(target, out var parcel))
|
||||||
|
{
|
||||||
|
UnwrapInternal(args.User, (target, parcel));
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroyed<T>(Entity<WrappedParcelComponent> parcel, ref T args)
|
||||||
|
{
|
||||||
|
// Unwrap the package and if something was in it, show a popup describing "wow something came out!"
|
||||||
|
if (UnwrapInternal(user: null, parcel) is { } contents)
|
||||||
|
{
|
||||||
|
_popup.PopupPredicted(Loc.GetString("parcel-wrap-popup-parcel-destroyed", ("contents", contents)),
|
||||||
|
contents,
|
||||||
|
null,
|
||||||
|
PopupType.MediumCaution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryStartUnwrapDoAfter(EntityUid user, Entity<WrappedParcelComponent> parcel)
|
||||||
|
{
|
||||||
|
return _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager,
|
||||||
|
user,
|
||||||
|
parcel.Comp.UnwrapDelay,
|
||||||
|
new UnwrapWrappedParcelDoAfterEvent(),
|
||||||
|
parcel,
|
||||||
|
parcel)
|
||||||
|
{
|
||||||
|
NeedHand = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Despawns <paramref name="parcel"/>, leaving the contained entity where the parcel was.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">The entity doing the unwrapping.</param>
|
||||||
|
/// <param name="parcel">The entity being unwrapped.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The newly unwrapped, contained entity. Returns null only in the exceptional case that the parcel contained
|
||||||
|
/// nothing, which should be prevented by not creating such parcels.
|
||||||
|
/// </returns>
|
||||||
|
private EntityUid? UnwrapInternal(EntityUid? user, Entity<WrappedParcelComponent> parcel)
|
||||||
|
{
|
||||||
|
var containedEntity = parcel.Comp.Contents.ContainedEntity;
|
||||||
|
_audio.PlayPredicted(parcel.Comp.UnwrapSound, parcel, user);
|
||||||
|
|
||||||
|
// If we're on the client, just return the contained entity and don't try to despawn the parcel.
|
||||||
|
if (!_net.IsServer)
|
||||||
|
return containedEntity;
|
||||||
|
|
||||||
|
var parcelTransform = Transform(parcel);
|
||||||
|
|
||||||
|
if (containedEntity is { } parcelContents)
|
||||||
|
{
|
||||||
|
_container.Remove(parcelContents,
|
||||||
|
parcel.Comp.Contents,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
parcelTransform.Coordinates);
|
||||||
|
|
||||||
|
// If the parcel is in a container, try to put the unwrapped contents in that container.
|
||||||
|
if (_container.TryGetContainingContainer((parcel, null, null), out var outerContainer))
|
||||||
|
{
|
||||||
|
// Make space in the container for the parcel contents.
|
||||||
|
_container.Remove((parcel, null, null), outerContainer, force: true);
|
||||||
|
_container.InsertOrDrop((parcelContents, null, null), outerContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn unwrap trash.
|
||||||
|
if (parcel.Comp.UnwrapTrash is { } trashProto)
|
||||||
|
{
|
||||||
|
var trash = Spawn(trashProto, parcelTransform.Coordinates);
|
||||||
|
_transform.DropNextTo((trash, null), (parcel, parcelTransform));
|
||||||
|
}
|
||||||
|
|
||||||
|
QueueDel(parcel);
|
||||||
|
|
||||||
|
return containedEntity;
|
||||||
|
}
|
||||||
|
}
|
||||||
57
Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using Content.Shared.Charges.Systems;
|
||||||
|
using Content.Shared.DoAfter;
|
||||||
|
using Content.Shared.Item;
|
||||||
|
using Content.Shared.ParcelWrap.Components;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Whitelist;
|
||||||
|
using Robust.Shared.Audio.Systems;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Shared.ParcelWrap.Systems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This system handles things related to package wrap, both wrapping items to create parcels, and unwrapping existing
|
||||||
|
/// parcels.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="ParcelWrapComponent"/>
|
||||||
|
/// <seealso cref="WrappedParcelComponent"/>
|
||||||
|
public sealed partial class ParcelWrappingSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
|
[Dependency] private readonly SharedChargesSystem _charges = default!;
|
||||||
|
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||||
|
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||||
|
[Dependency] private readonly SharedItemSystem _item = default!;
|
||||||
|
[Dependency] private readonly INetManager _net = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
|
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
InitializeParcelWrap();
|
||||||
|
InitializeWrappedParcel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether or not <paramref name="wrapper"/> can be used to wrap <paramref name="target"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="wrapper">The entity doing the wrapping.</param>
|
||||||
|
/// <param name="target">The entity to be wrapped.</param>
|
||||||
|
/// <returns>True if <paramref name="wrapper"/> can be used to wrap <paramref name="target"/>, false otherwise.</returns>
|
||||||
|
public bool IsWrappable(Entity<ParcelWrapComponent> wrapper, EntityUid target)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
// Wrapping cannot wrap itself
|
||||||
|
wrapper.Owner != target &&
|
||||||
|
// Wrapper should never be empty, but may as well make sure.
|
||||||
|
!_charges.IsEmpty(wrapper.Owner) &&
|
||||||
|
_whitelist.IsWhitelistPass(wrapper.Comp.Whitelist, target) &&
|
||||||
|
_whitelist.IsBlacklistFail(wrapper.Comp.Blacklist, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Resources/Locale/en-US/pacel-wrap.ftl
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
parcel-wrap-verb-wrap = Wrap
|
||||||
|
parcel-wrap-verb-unwrap = Unwrap
|
||||||
|
|
||||||
|
parcel-wrap-popup-parcel-destroyed = The wrapping containing { THE($contents) } is destroyed!
|
||||||
|
|
||||||
|
# Shown when parcel wrap is examined in details range
|
||||||
|
parcel-wrap-examine-detail-uses = { $uses ->
|
||||||
|
[one] There is [color={$markupUsesColor}]{$uses}[/color] use left
|
||||||
|
*[other] There are [color={$markupUsesColor}]{$uses}[/color] uses left
|
||||||
|
}.
|
||||||
@@ -17,3 +17,13 @@
|
|||||||
cost: 15000
|
cost: 15000
|
||||||
category: cargoproduct-category-name-cargo
|
category: cargoproduct-category-name-cargo
|
||||||
group: market
|
group: market
|
||||||
|
|
||||||
|
- type: cargoProduct
|
||||||
|
id: CargoParcelWrap
|
||||||
|
icon:
|
||||||
|
sprite: Objects/Misc/ParcelWrap/parcel_wrap.rsi
|
||||||
|
state: brown
|
||||||
|
product: CrateCargoParcelWrap
|
||||||
|
cost: 750
|
||||||
|
category: cargoproduct-category-name-cargo
|
||||||
|
group: market
|
||||||
|
|||||||
@@ -8,6 +8,17 @@
|
|||||||
contents:
|
contents:
|
||||||
- id: ClothingOuterHardsuitLuxury
|
- id: ClothingOuterHardsuitLuxury
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: CrateCargoParcelWrap
|
||||||
|
parent: CrateGenericSteel
|
||||||
|
name: parcel wrap crate
|
||||||
|
description: All your parcel wrapping needs in one crate, containing three rolls of parcel wrap.
|
||||||
|
components:
|
||||||
|
- type: StorageFill
|
||||||
|
contents:
|
||||||
|
- id: ParcelWrap
|
||||||
|
amount: 3
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: CrateCargoGambling
|
id: CrateCargoGambling
|
||||||
name: the grand lottery $$$
|
name: the grand lottery $$$
|
||||||
|
|||||||
@@ -92,6 +92,7 @@
|
|||||||
whitelist:
|
whitelist:
|
||||||
components:
|
components:
|
||||||
- SecretStash
|
- SecretStash
|
||||||
|
- WrappedParcel
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: [ PersonalAI, BaseSyndicateContraband]
|
parent: [ PersonalAI, BaseSyndicateContraband]
|
||||||
|
|||||||
100
Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
- type: entity
|
||||||
|
parent: BaseItem
|
||||||
|
id: ParcelWrap
|
||||||
|
name: parcel wrap
|
||||||
|
description: Paper used contain items for transport.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Misc/ParcelWrap/parcel_wrap.rsi
|
||||||
|
state: brown
|
||||||
|
- type: ParcelWrap
|
||||||
|
parcelPrototype: WrappedParcel
|
||||||
|
wrapDelay: 1.0
|
||||||
|
wrapSound:
|
||||||
|
path: /Audio/Items/Handcuffs/rope_start.ogg
|
||||||
|
params:
|
||||||
|
volume: -5
|
||||||
|
variation: 0.05
|
||||||
|
whitelist:
|
||||||
|
components:
|
||||||
|
- Item
|
||||||
|
blacklist:
|
||||||
|
components:
|
||||||
|
- NukeDisk # Don't try to hide the disk.
|
||||||
|
- WrappedParcel # No wrapping wrapped things.
|
||||||
|
tags:
|
||||||
|
- ParcelWrapBlacklist
|
||||||
|
- FakeNukeDisk # So you can't tell if the nuke disk is real or fake depending on if it can be wrapped or not.
|
||||||
|
- type: LimitedCharges
|
||||||
|
maxCharges: 30
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: BaseItem
|
||||||
|
id: WrappedParcel
|
||||||
|
categories: [ HideSpawnMenu ]
|
||||||
|
name: wrapped parcel
|
||||||
|
description: Something wrapped up in paper. I wonder what's inside...
|
||||||
|
components:
|
||||||
|
- type: ContainerContainer
|
||||||
|
containers:
|
||||||
|
contents: !type:ContainerSlot
|
||||||
|
- type: Appearance
|
||||||
|
- type: GenericVisualizer
|
||||||
|
visuals:
|
||||||
|
enum.WrappedParcelVisuals.Size:
|
||||||
|
enum.WrappedParcelVisuals.Layer:
|
||||||
|
"Tiny": { state: "parcel-tiny" }
|
||||||
|
"Small": { state: "parcel-small" }
|
||||||
|
"Medium": { state: "parcel-medium" }
|
||||||
|
"Large": { state: "parcel-medium" }
|
||||||
|
"Huge": { state: "parcel-large" }
|
||||||
|
"Ginormous": { state: "parcel-large" }
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Misc/ParcelWrap/wrapped_parcel.rsi
|
||||||
|
layers:
|
||||||
|
- state: parcel-medium
|
||||||
|
map: [ "enum.WrappedParcelVisuals.Layer" ]
|
||||||
|
- type: WrappedParcel
|
||||||
|
unwrapDelay: 0.5
|
||||||
|
unwrapSound:
|
||||||
|
path: /Audio/Effects/poster_broken.ogg
|
||||||
|
params:
|
||||||
|
volume: -4
|
||||||
|
unwrapTrash: ParcelWrapTrash
|
||||||
|
- type: Damageable
|
||||||
|
damageContainer: Inorganic
|
||||||
|
- type: Destructible
|
||||||
|
thresholds:
|
||||||
|
- trigger:
|
||||||
|
!type:DamageTypeTrigger
|
||||||
|
damageType: Slash
|
||||||
|
damage: 5
|
||||||
|
behaviors:
|
||||||
|
- !type:PlaySoundBehavior
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/poster_broken.ogg
|
||||||
|
params:
|
||||||
|
volume: -4
|
||||||
|
- !type:DoActsBehavior
|
||||||
|
acts: [ "Destruction" ]
|
||||||
|
- type: Tag
|
||||||
|
tags:
|
||||||
|
- Recyclable # Parcel entity is recyclable, and when it's destroyed, it'll drop its contents.
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: BaseItem
|
||||||
|
id: ParcelWrapTrash
|
||||||
|
categories: [ HideSpawnMenu ]
|
||||||
|
name: parcel wrap
|
||||||
|
description: The disappointing remnants of an unwrapped parcel.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi
|
||||||
|
layers:
|
||||||
|
- state: brown
|
||||||
|
- type: Tag
|
||||||
|
tags:
|
||||||
|
- Trash
|
||||||
|
- ParcelWrapBlacklist # No exponential wrapper trash-splosions.
|
||||||
|
- Recyclable
|
||||||
|
- type: SpaceGarbage
|
||||||
@@ -957,6 +957,9 @@
|
|||||||
- type: Tag
|
- type: Tag
|
||||||
id: Pancake
|
id: Pancake
|
||||||
|
|
||||||
|
- type: Tag
|
||||||
|
id: ParcelWrapBlacklist
|
||||||
|
|
||||||
- type: Tag
|
- type: Tag
|
||||||
id: Payload # for grenade/bomb crafting
|
id: Payload # for grenade/bomb crafting
|
||||||
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 358 B |
|
After Width: | Height: | Size: 307 B |
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Taken from tgstation https://github.com/tgstation/tgstation/blob/bd704770f7146d820e1e93b04ae1dcf3723b299a/icons/obj/stack_objects.dmi",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "brown",
|
||||||
|
"directions": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "empty-roll",
|
||||||
|
"directions": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 482 B |
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Hue shifted from the original by Shaeone: https://github.com/space-wizards/space-station-14/blob/43eb542a60772dc49e38993a54404a5799dfe344/Resources/Textures/Objects/Decoration/present.rsi/unwrapped.png",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "brown",
|
||||||
|
"directions": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 320 B |
|
After Width: | Height: | Size: 325 B |
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-4.0",
|
||||||
|
"copyright": "created for ss14 by ps3moira (github) based on package sprites from vgstation at https://github.com/vgstation-coders/vgstation13/pull/36993",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "parcel-large",
|
||||||
|
"directions": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parcel-medium",
|
||||||
|
"directions": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parcel-small",
|
||||||
|
"directions": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parcel-tiny",
|
||||||
|
"directions": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locker",
|
||||||
|
"directions": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "crate",
|
||||||
|
"directions": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tall-crate",
|
||||||
|
"directions": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 321 B |
|
After Width: | Height: | Size: 319 B |
|
After Width: | Height: | Size: 313 B |
|
After Width: | Height: | Size: 314 B |
|
After Width: | Height: | Size: 322 B |