]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Adds Parcel Wrap (#34471)
authorCentronias <me@centronias.com>
Sat, 26 Apr 2025 23:24:25 +0000 (16:24 -0700)
committerGitHub <noreply@github.com>
Sat, 26 Apr 2025 23:24:25 +0000 (19:24 -0400)
* 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>
26 files changed:
Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs [new file with mode: 0644]
Content.Shared/ParcelWrap/Components/WrappedParcelComponent.cs [new file with mode: 0644]
Content.Shared/ParcelWrap/Components/WrappedParcelVisuals.cs [new file with mode: 0644]
Content.Shared/ParcelWrap/Systems/ParcelWrapEvents.cs [new file with mode: 0644]
Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs [new file with mode: 0644]
Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs [new file with mode: 0644]
Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.cs [new file with mode: 0644]
Resources/Locale/en-US/pacel-wrap.ftl [new file with mode: 0644]
Resources/Prototypes/Catalog/Cargo/cargo_cargo.yml
Resources/Prototypes/Catalog/Fills/Crates/cargo.yml
Resources/Prototypes/Entities/Objects/Fun/pai.yml
Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml [new file with mode: 0644]
Resources/Prototypes/tags.yml
Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/brown.png [new file with mode: 0644]
Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/empty-roll.png [new file with mode: 0644]
Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi/brown.png [new file with mode: 0644]
Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate.png [new file with mode: 0644]
Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/locker.png [new file with mode: 0644]
Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-large.png [new file with mode: 0644]
Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-medium.png [new file with mode: 0644]
Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-small.png [new file with mode: 0644]
Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-tiny.png [new file with mode: 0644]
Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/tall-crate.png [new file with mode: 0644]

diff --git a/Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs b/Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs
new file mode 100644 (file)
index 0000000..b5afb49
--- /dev/null
@@ -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;
+}
diff --git a/Content.Shared/ParcelWrap/Components/WrappedParcelComponent.cs b/Content.Shared/ParcelWrap/Components/WrappedParcelComponent.cs
new file mode 100644 (file)
index 0000000..e0ceaea
--- /dev/null
@@ -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";
+}
diff --git a/Content.Shared/ParcelWrap/Components/WrappedParcelVisuals.cs b/Content.Shared/ParcelWrap/Components/WrappedParcelVisuals.cs
new file mode 100644 (file)
index 0000000..1c90ca0
--- /dev/null
@@ -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,
+}
diff --git a/Content.Shared/ParcelWrap/Systems/ParcelWrapEvents.cs b/Content.Shared/ParcelWrap/Systems/ParcelWrapEvents.cs
new file mode 100644 (file)
index 0000000..b64ecc3
--- /dev/null
@@ -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;
diff --git a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs
new file mode 100644 (file)
index 0000000..f924a7c
--- /dev/null
@@ -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);
+    }
+}
diff --git a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs
new file mode 100644 (file)
index 0000000..bb0c943
--- /dev/null
@@ -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;
+    }
+}
diff --git a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.cs b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.cs
new file mode 100644 (file)
index 0000000..b19f4b8
--- /dev/null
@@ -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);
+    }
+}
diff --git a/Resources/Locale/en-US/pacel-wrap.ftl b/Resources/Locale/en-US/pacel-wrap.ftl
new file mode 100644 (file)
index 0000000..2351892
--- /dev/null
@@ -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
+}.
index 409670636fbe951cd3ee26d922080af83af3851b..e415953a0d475203415a2646056d59c9b2866d52 100644 (file)
   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
index 469c24ab97f6cd6ff1b6f924e530339a75c0a675..3d6b095b021ba758355c13cb9e841115a11ccca1 100644 (file)
@@ -8,6 +8,17 @@
     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 $$$
index 2a8e0e6a7f46b19173c1a4661b2e1a566e82635f..1f5136935a0ac9a12a0a79eb2846554c48ea1a35 100644 (file)
@@ -92,6 +92,7 @@
     whitelist:
       components:
         - SecretStash
+        - WrappedParcel
 
 - type: entity
   parent: [ PersonalAI, BaseSyndicateContraband]
diff --git a/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml b/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml
new file mode 100644 (file)
index 0000000..58455ba
--- /dev/null
@@ -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
index 682b561f0fc5d18cca450706c1bef96d50e6d58a..3514589bc056492aa73f9fb790ee9a4e1f6e7f92 100644 (file)
 - type: Tag
   id: Pancake
 
+- type: Tag
+  id: ParcelWrapBlacklist
+
 - type: Tag
   id: Payload # for grenade/bomb crafting
 
diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/brown.png b/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/brown.png
new file mode 100644 (file)
index 0000000..6112a7c
Binary files /dev/null and b/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/brown.png differ
diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/empty-roll.png b/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/empty-roll.png
new file mode 100644 (file)
index 0000000..aba2d26
Binary files /dev/null and b/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/empty-roll.png differ
diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/meta.json b/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/meta.json
new file mode 100644 (file)
index 0000000..9f29dc1
--- /dev/null
@@ -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
+        }
+    ]
+}
diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi/brown.png b/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi/brown.png
new file mode 100644 (file)
index 0000000..799f2fa
Binary files /dev/null and b/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi/brown.png differ
diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi/meta.json b/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi/meta.json
new file mode 100644 (file)
index 0000000..6fcb6a4
--- /dev/null
@@ -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
+        }
+    ]
+}
diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate.png
new file mode 100644 (file)
index 0000000..7784d1f
Binary files /dev/null and b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate.png differ
diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/locker.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/locker.png
new file mode 100644 (file)
index 0000000..d3c2c32
Binary files /dev/null and b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/locker.png differ
diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/meta.json b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/meta.json
new file mode 100644 (file)
index 0000000..f07107a
--- /dev/null
@@ -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
+        }
+    ]
+}
diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-large.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-large.png
new file mode 100644 (file)
index 0000000..4c7bfbb
Binary files /dev/null and b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-large.png differ
diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-medium.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-medium.png
new file mode 100644 (file)
index 0000000..c94178d
Binary files /dev/null and b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-medium.png differ
diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-small.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-small.png
new file mode 100644 (file)
index 0000000..8f05e51
Binary files /dev/null and b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-small.png differ
diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-tiny.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-tiny.png
new file mode 100644 (file)
index 0000000..a822553
Binary files /dev/null and b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-tiny.png differ
diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/tall-crate.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/tall-crate.png
new file mode 100644 (file)
index 0000000..684d718
Binary files /dev/null and b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/tall-crate.png differ