--- /dev/null
+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;
+}
--- /dev/null
+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";
+}
--- /dev/null
+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,
+}
--- /dev/null
+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;
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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
+}.
cost: 15000
category: cargoproduct-category-name-cargo
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
contents:
- 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
id: CrateCargoGambling
name: the grand lottery $$$
whitelist:
components:
- SecretStash
+ - WrappedParcel
- type: entity
parent: [ PersonalAI, BaseSyndicateContraband]
--- /dev/null
+- 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
- type: Tag
id: Pancake
+- type: Tag
+ id: ParcelWrapBlacklist
+
- type: Tag
id: Payload # for grenade/bomb crafting
--- /dev/null
+{
+ "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
+ }
+ ]
+}
--- /dev/null
+{
+ "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
+ }
+ ]
+}
--- /dev/null
+{
+ "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
+ }
+ ]
+}