From 0f29e4e83f20127806c9066f60f53b7af9deaf06 Mon Sep 17 00:00:00 2001 From: Centronias Date: Thu, 16 Jan 2025 13:17:16 -0800 Subject: [PATCH 01/10] Parcel Wrap --- .../Components/ParcelWrapComponent.cs | 49 ++++ .../Components/WrappedParcelComponent.cs | 31 +++ .../EntitySystems/ParcelWrappingSystem.cs | 236 ++++++++++++++++++ .../Components/WrappedParcelVisuals.cs | 13 + Resources/Locale/en-US/pacel-wrap.ftl | 4 + .../Entities/Objects/Misc/ParcelWrap.yml | 66 +++++ Resources/Prototypes/tags.yml | 5 +- .../Misc/ParcelWrap/parcel_wrap.rsi/brown.png | Bin 0 -> 358 bytes .../ParcelWrap/parcel_wrap.rsi/empty-roll.png | Bin 0 -> 307 bytes .../green-with-red-stripes.png | Bin 0 -> 587 bytes .../Misc/ParcelWrap/parcel_wrap.rsi/meta.json | 31 +++ .../parcel_wrap.rsi/stripe-mask.png | Bin 0 -> 353 bytes .../parcel_wrap.rsi/striped-base-mask.png | Bin 0 -> 465 bytes .../parcel_wrap_trash.rsi/brown.png | Bin 0 -> 662 bytes .../parcel_wrap_trash.rsi/meta.json | 15 ++ .../amorphous-giftwrap-base.png | Bin 0 -> 360 bytes .../amorphous-giftwrap-ribbon.png | Bin 0 -> 336 bytes .../amorphous-red-with-green.png | Bin 0 -> 397 bytes .../wrapped_parcel.rsi/amorphous.png | Bin 0 -> 402 bytes .../wrapped_parcel.rsi/barcode-amorphous.png | Bin 0 -> 271 bytes .../wrapped_parcel.rsi/barcode-crate.png | Bin 0 -> 268 bytes .../wrapped_parcel.rsi/barcode-locker.png | Bin 0 -> 272 bytes .../barcode-parcel-large.png | Bin 0 -> 268 bytes .../barcode-parcel-medium.png | Bin 0 -> 258 bytes .../barcode-parcel-small.png | Bin 0 -> 256 bytes .../barcode-parcel-tiny.png | Bin 0 -> 256 bytes .../wrapped_parcel.rsi/barcode-tall-crate.png | Bin 0 -> 267 bytes .../crate-giftwrap-base.png | Bin 0 -> 332 bytes .../crate-giftwrap-ribbon.png | Bin 0 -> 322 bytes .../crate-red-with-green.png | Bin 0 -> 369 bytes .../ParcelWrap/wrapped_parcel.rsi/crate.png | Bin 0 -> 317 bytes .../humanoid-green-with-red.png | Bin 0 -> 444 bytes .../locker-giftwrap-base.png | Bin 0 -> 338 bytes .../locker-giftwrap-ribbon.png | Bin 0 -> 311 bytes .../locker-red-with-green.png | Bin 0 -> 358 bytes .../ParcelWrap/wrapped_parcel.rsi/locker.png | Bin 0 -> 314 bytes .../ParcelWrap/wrapped_parcel.rsi/meta.json | 175 +++++++++++++ .../parcel-large-giftwrap-base.png | Bin 0 -> 334 bytes .../parcel-large-giftwrap-ribbon.png | Bin 0 -> 312 bytes .../parcel-large-red-with-green.png | Bin 0 -> 359 bytes .../wrapped_parcel.rsi/parcel-large.png | Bin 0 -> 313 bytes .../parcel-medium-giftwrap-base.png | Bin 0 -> 309 bytes .../parcel-medium-giftwrap-ribbon.png | Bin 0 -> 313 bytes .../parcel-medium-red-with-green.png | Bin 0 -> 339 bytes .../wrapped_parcel.rsi/parcel-medium.png | Bin 0 -> 296 bytes .../parcel-small-giftwrap-base.png | Bin 0 -> 292 bytes .../parcel-small-giftwrap-ribbon.png | Bin 0 -> 310 bytes .../parcel-small-red-with-green.png | Bin 0 -> 322 bytes .../wrapped_parcel.rsi/parcel-small.png | Bin 0 -> 290 bytes .../parcel-tiny-giftwrap-base.png | Bin 0 -> 282 bytes .../parcel-tiny-giftwrap-ribbon.png | Bin 0 -> 294 bytes .../parcel-tiny-red-with-green.png | Bin 0 -> 306 bytes .../wrapped_parcel.rsi/parcel-tiny.png | Bin 0 -> 283 bytes .../tall-crate-giftwrap-base.png | Bin 0 -> 350 bytes .../tall-crate-giftwrap-ribbon.png | Bin 0 -> 313 bytes .../tall-crate-red-with-green.png | Bin 0 -> 384 bytes .../wrapped_parcel.rsi/tall-crate.png | Bin 0 -> 332 bytes 57 files changed, 624 insertions(+), 1 deletion(-) create mode 100644 Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs create mode 100644 Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs create mode 100644 Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs create mode 100644 Content.Shared/ParcelWrap/Components/WrappedParcelVisuals.cs create mode 100644 Resources/Locale/en-US/pacel-wrap.ftl create mode 100644 Resources/Prototypes/Entities/Objects/Misc/ParcelWrap.yml create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/brown.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/empty-roll.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/green-with-red-stripes.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/meta.json create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/stripe-mask.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/striped-base-mask.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi/brown.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi/meta.json create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/amorphous-giftwrap-base.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/amorphous-giftwrap-ribbon.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/amorphous-red-with-green.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/amorphous.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-amorphous.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-crate.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-locker.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-parcel-large.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-parcel-medium.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-parcel-small.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-parcel-tiny.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-tall-crate.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate-giftwrap-base.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate-giftwrap-ribbon.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate-red-with-green.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/humanoid-green-with-red.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/locker-giftwrap-base.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/locker-giftwrap-ribbon.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/locker-red-with-green.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/locker.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/meta.json create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-large-giftwrap-base.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-large-giftwrap-ribbon.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-large-red-with-green.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-large.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-medium-giftwrap-base.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-medium-giftwrap-ribbon.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-medium-red-with-green.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-medium.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-small-giftwrap-base.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-small-giftwrap-ribbon.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-small-red-with-green.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-small.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-tiny-giftwrap-base.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-tiny-giftwrap-ribbon.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-tiny-red-with-green.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-tiny.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/tall-crate-giftwrap-base.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/tall-crate-giftwrap-ribbon.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/tall-crate-red-with-green.png create mode 100644 Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/tall-crate.png diff --git a/Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs b/Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs new file mode 100644 index 000000000000..c190ce9176c5 --- /dev/null +++ b/Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs @@ -0,0 +1,49 @@ +using Content.Shared.Item; +using Content.Shared.Whitelist; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Server.ParcelWrap.Components; + +/// +/// This component gives its owning entity the ability to wrap items into parcels. +/// +/// +[RegisterComponent] +[Access] // Readonly, except for VV editing +public sealed partial class ParcelWrapComponent : Component +{ + /// + /// If true, parcels created by this will have the same size as the item they + /// contain. If false, parcels created by this will always have the size specified by . + /// + [DataField, ViewVariables] + public bool WrappedItemsMaintainSize = true; + + /// + /// The size of parcels created by this component's entity. This is used if + /// is false, or if the item being wrapped somehow doesn't have a size. + /// + [DataField, ViewVariables] + public ProtoId FallbackItemSize = "Ginormous"; + + /// + /// 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. + /// + [DataField, ViewVariables] + public bool WrappedItemsMaintainShape = false; + + /// + /// Sound played when this is used to wrap something. + /// + [DataField, ViewVariables] + public SoundSpecifier? WrapSound; + + /// + /// Defines the set of things which cannot be wrapped. + /// + [DataField, ViewVariables(VVAccess.ReadOnly)] + public EntityWhitelist? Blacklist; +} diff --git a/Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs b/Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs new file mode 100644 index 000000000000..2c0dce84de6e --- /dev/null +++ b/Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs @@ -0,0 +1,31 @@ +using Content.Server.ParcelWrap.EntitySystems; +using Robust.Shared.Audio; +using Robust.Shared.Containers; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Server.ParcelWrap.Components; + +/// +/// This component marks its owner as being a parcel created by wrapping another item up. It can be unwrapped, +/// destroying this entity and releasing . +/// +/// +[RegisterComponent, Access(typeof(ParcelWrappingSystem))] +public sealed partial class WrappedParcelComponent : Component +{ + /// + /// The contents of this parcel. + /// + [ViewVariables(VVAccess.ReadOnly)] + public ContainerSlot Contents = default!; + + [DataField, ViewVariables] + public ProtoId? UnwrapTrash; + + /// + /// Sound played when unwrapping this parcel. + /// + [DataField, ViewVariables] + public SoundSpecifier? UnwrapSound; +} diff --git a/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs b/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs new file mode 100644 index 000000000000..a669341e9188 --- /dev/null +++ b/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs @@ -0,0 +1,236 @@ +using Content.Server.ParcelWrap.Components; +using Content.Shared.Destructible; +using Content.Shared.IdentityManagement; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Item; +using Content.Shared.Materials; +using Content.Shared.ParcelWrap.Components; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Content.Shared.Whitelist; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Utility; + +namespace Content.Server.ParcelWrap.EntitySystems; + +/// +/// This system handles things related to package wrap, both wrapping items to create parcels, and unwrapping existing +/// parcels. +/// +/// +/// +public sealed class ParcelWrappingSystem : EntitySystem +{ + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SharedItemSystem _item = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + private const string WrappedParcelContainerId = "wrapped_parcel"; + + /// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAfterInteract); + SubscribeLocalEvent>(OnGetVerbsForParcelWrap); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent>(OnGetVerbsForWrappedParcel); + SubscribeLocalEvent(OnDestroyed); + SubscribeLocalEvent(OnDestroyed); + } + + private void OnAfterInteract(Entity entity, ref AfterInteractEvent args) + { + if (args.Handled || + args.Target is not { } target || + !args.CanReach || + !IsWrappable(entity, target)) + return; + + WrapInternal(args.User, entity, target); + + args.Handled = true; + } + + private void OnGetVerbsForParcelWrap(Entity entity, + ref GetVerbsEvent 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 = () => WrapInternal(user, entity, target), + }); + } + + private void OnComponentInit(Entity entity, ref ComponentInit args) + { + entity.Comp.Contents = _container.EnsureContainer(entity, WrappedParcelContainerId); + } + + private void OnUseInHand(Entity entity, ref UseInHandEvent args) + { + if (args.Handled) + { + return; + } + + UnwrapInternal(entity); + args.Handled = true; + } + + private void OnGetVerbsForWrappedParcel(Entity entity, + ref GetVerbsEvent args) + { + if (!args.CanAccess) + return; + + args.Verbs.Add(new InteractionVerb + { + Text = Loc.GetString("parcel-wrap-verb-unwrap"), + Act = () => UnwrapInternal(entity), + }); + } + + private void OnDestroyed(Entity parcel, ref T args) + { + // Unwrap the package and if something was in it, show a popup describing "wow something came out!" + if (UnwrapInternal(parcel) is { } contents) + { + var parcelId = Identity.Name(contents, EntityManager); + _popup.PopupPredicted(Loc.GetString("parcel-wrap-popup-parcel-destroyed", ("contents", contents)), + contents, + null, + PopupType.MediumCaution); + } + } + + #region accessors + + /// + /// Returns whether or not can be used to wrap . + /// + public bool IsWrappable(Entity wrapper, EntityUid target) => + // Wrapping cannot wrap itself + wrapper.Owner != target && + // Only wrap items + HasComp(target) && + _whitelist.IsBlacklistFail(wrapper.Comp.Blacklist, target); + + #endregion + + #region internalImplementation + + /// + /// Spawns a WrappedParcel containing . + /// + /// The entity using to wrap . + /// The wrapping being used. Determines appearance of the spawned parcel. + /// The entity being wrapped. + /// The newly created parcel. Returns null only in exceptional failure cases. + private Entity? WrapInternal(EntityUid user, ParcelWrapComponent wrapper, EntityUid target) + { + var spawned = Spawn("WrappedParcel", 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. + ItemComponent? targetItemComp = null; + var size = wrapper.FallbackItemSize; + if (wrapper.WrappedItemsMaintainSize && Resolve(target, ref targetItemComp, logMissing: false)) + { + size = targetItemComp.Size; + } + + var item = Comp(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.WrappedItemsMaintainShape && Resolve(target, ref targetItemComp, logMissing: false) && + targetItemComp.Shape is { } 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(spawned); + if (!_container.Insert(target, parcel.Contents)) + { + DebugTools.Assert( + $"Failed to insert target entity into newly spawned parcel. target={PrettyPrint.PrintUserFacing(target)}"); + QueueDel(spawned); + return null; + } + + // Play a wrapping sound. + _audio.PlayPvs(wrapper.WrapSound, spawned); + + return (spawned, parcel); + } + + /// + /// Despawns , leaving the contained entity where the parcel was. + /// + /// The entity being unwrapped. + /// + /// 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. + /// + private EntityUid? UnwrapInternal(Entity parcel) + { + var parcelCoords = Comp(parcel).Coordinates; + + var containedEntity = parcel.Comp.Contents.ContainedEntity; + if (containedEntity is { } parcelContents) + { + _container.Remove(parcelContents, + parcel.Comp.Contents, + true, + true, + parcelCoords); + + // 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); + } + } + + // Make some trash and play an unwrapping sound. + var trash = Spawn(parcel.Comp.UnwrapTrash, parcelCoords); + _transform.DropNextTo((trash, null), (parcel, null)); + _audio.PlayPvs(parcel.Comp.UnwrapSound, parcelCoords); + + EntityManager.DeleteEntity(parcel); + + return containedEntity; + } + + #endregion +} diff --git a/Content.Shared/ParcelWrap/Components/WrappedParcelVisuals.cs b/Content.Shared/ParcelWrap/Components/WrappedParcelVisuals.cs new file mode 100644 index 000000000000..1c90ca031d25 --- /dev/null +++ b/Content.Shared/ParcelWrap/Components/WrappedParcelVisuals.cs @@ -0,0 +1,13 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.ParcelWrap.Components; + +/// +/// This enum is used to change the sprite used by WrappedParcels based on the parcel's size. +/// +[Serializable, NetSerializable] +public enum WrappedParcelVisuals : byte +{ + Size, + Layer, +} diff --git a/Resources/Locale/en-US/pacel-wrap.ftl b/Resources/Locale/en-US/pacel-wrap.ftl new file mode 100644 index 000000000000..62fefa4039a0 --- /dev/null +++ b/Resources/Locale/en-US/pacel-wrap.ftl @@ -0,0 +1,4 @@ +parcel-wrap-verb-wrap = Wrap +parcel-wrap-verb-unwrap = Unwrap + +parcel-wrap-popup-parcel-destroyed = The wrapping containing { THE($contents) } is destroyed! diff --git a/Resources/Prototypes/Entities/Objects/Misc/ParcelWrap.yml b/Resources/Prototypes/Entities/Objects/Misc/ParcelWrap.yml new file mode 100644 index 000000000000..055bed5a1c3b --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Misc/ParcelWrap.yml @@ -0,0 +1,66 @@ +- type: entity + name: parcel wrap + parent: BaseItem + id: ParcelWrap + description: Paper used contain items for transport. + components: + - type: Sprite + sprite: Objects/Misc/ParcelWrap/parcel_wrap.rsi + state: brown + - type: ParcelWrap + wrapSound: /Audio/Items/Handcuffs/rope_start.ogg + 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: entity + categories: [ HideSpawnMenu ] + name: wrapped parcel + parent: BaseItem + id: WrappedParcel + description: Something wrapped up in paper. I wonder what's inside... + components: + - 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 + unwrapSound: /Audio/Effects/poster_broken.ogg + unwrapTrash: ParcelWrapTrash + - type: Tag + tags: + - Recyclable # Parcel is recyclable, and when it's destroyed, it'll drop its contents. + +- type: entity + id: ParcelWrapTrash + categories: [ HideSpawnMenu ] + parent: BaseItem + 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 + - Recyclable + - type: SpaceGarbage diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index ea2dffbe6a30..a2cc8c10e2aa 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -927,11 +927,14 @@ - type: Tag id: Packet +- type: Tag + id: Pancake + - type: Tag id: Paper - type: Tag - id: Pancake + 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 index 0000000000000000000000000000000000000000..6112a7c0522e4d34c284c19c551cc643d03ac39f GIT binary patch literal 358 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^T<3Z5>GArY;~ zHU$qD4m&U{W87MMg>f;TMCpMQ%xulf5gEA$r&gx^Pt0M|Gh>_CA?B7+>6Ue!^@Uj1 zr@{p%_W%3Bd{j`Rm0?q=0e7hCj|Vk!hLt?F+v8)>COAHt{D#5F&Sm?TKTA&+m~DET z(2yV!koEAjzkR?H(a>wt5z&t z@&D5+#V#ok_5(WYKSiQtCM7ExIq(bi2+Y{~T!|S7oLA1*WbT(J)&29~t+>sLLaTfc xe5;~8wlGUb2wZf`_3kWVuszTaejFMG43jk%%uzgisu>t?44$rjF6*2UngF67igN$} literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..aba2d26ab9f713f1728d8c5227aad6ce97b3aa4c GIT binary patch literal 307 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Z#`WcLn2z= zPTk1YWFX?I@8; z%ih0k6<^x3B~0e{g_#q>b_p6VxgFs6(|n*J<3O6|-AG><>5E0#A74*Px%1xZ9fR9J=Wma%KpU>Jv=lp92sQ%GnWTI$eD7M$8K>kS>RkdsUzneRUc_y=SY zLdS!q(6K`ok#BG~mlIA9a!a{_2EHr>MGvaPA@`-ZYqYdSJ%_wgNJxIq`@C;RG$91b ztd*sv*8nv@4NwF84*-q34tTS-mq{tF!trQnGEE#q~18 z!T_zXow-qpVHij$Y5FFXnRiQ+UVHzI0S;_CQ(-+vr)lMnc>d%efs~iwz$n}z@P^oH z>);JJ_RX6E9N2cI<_F-G!jx7>0CL^lki-2A9(t9Hj;#f`=*^yQW zpl0YsX-*DBmzJqt(K=Z9%3yy3UdD8HEC4Rve&f7kVFbV`C>#D5M1_I!0s%aGd=GKP zjMl?xn9};bWaq<|38Qce*Bj+i`iP((7oEmQIt_}8Zq*2zR)dZZ*T078)9(NT{kWi2TDNVJk!==Q=a=k!3fOaW zh{kk;&^lEiRRAa6kv2bucdu62^kmU#St*c@Aj-Aw`pImE-d~cZrB(fuw!(I%Hat%9 zrQ7ur?6q~o*{o8v&9|*e00q&s8ii8G-;DVgE?R#C0C0qp0Sk9|Yk+@IOaC0#05w1j Z@EcPx$8%ab#R9J=WmN9OGFc3v&VoFL{BrF$m4>z0{30Yj@7cMvf4JF8_yowf^-BpMv z;_1LM{_lTV2%e^i?yRp{)eArYC;$cEUjS^|7661>000E7^=Z!~002VHsZ#1%fSg#$ zsfh3}3}mgP+%dcaNDZdMlv339eX@U^XCg}9hb#bqP}g;m`Z?C+wuZVC}RV*Ux zy+5RJnn2Su$qjR{F3Un9!T>_swgiW7bKA-Y=pV}QS?0#E=7!0iCvFmtU*F#M&p00000NkvXXu0mjfXNi-) literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/striped-base-mask.png b/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/striped-base-mask.png new file mode 100644 index 0000000000000000000000000000000000000000..6ac2dceee4a629e7cdf9942afb96eead233f1093 GIT binary patch literal 465 zcmV;?0WSWDP)Px$i%CR5R9J=Wl_9U{Fc^lP-ki)dOC?pa3l=pA0u4WPmLSS6z&1=Ha0^Jxcf?7X zb|a9S%ehC~eTtOA^Y+=>OdSpfesli(Dt!tt1(*U%0sgB1ZQF`-PKN91-S2|7Z6jp6 z-Ky}pC<+1C?RKnItGNF04qysunue;X0Jz`pabMr}09>!v?Du~az&R&P(`0L9Swn@c77GOr3Px%P)S5VR9J=Wmd}dXP!z_0aWs?h&nQTW63kjk8M-M7E`0*=S=`MV^f_iZ^adwETT{_#7_b^`YOU#Fya%;3Nugyneh|pby`1x%?|eB49C5@Ej|?qrbFo;& z3esuSgTuHlU%i$^CX@Mp1UjvH&~DZMn2d&$ESu|Ye~-jJC2&}6qgs~xl4S!3fqYu$ z^6Dm(Kr)QLK|&1(>|&0JnU#IV^#G`tStg?)0PSXt>ux{zx?F`82#|QxC?atXN&wKdaw@5VMzzeFx9>2|4d%<0T!oBIA)^Zkb-vT8 zhbD+@LAzPguDkugFIO1n21fEh zxRPZv84YnVa}Tz-j6P$Mq;=bTQj>@$d>NU+kh$Ez=u}141TlmBiD#&*aiN)5S{88Oa3Q+mRFz7CV$~{=oh2 z%qN%DF}Jfok=m`=UvKE%j%Yr!w5a5f1h#WP+MufT*PFdJ03N;xy}_8?V62kILgeRo wXOUpnA3*$b6LmvA2{NOBOV>U07>BPWN{*!q5uE@07*qoM6N<$f`%G7SO5S3 literal 0 HcmV?d00001 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 index 000000000000..a971f053fc2f --- /dev/null +++ b/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi/meta.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Original made with love by Shaeone, taken from SS14: 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/amorphous-giftwrap-base.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/amorphous-giftwrap-base.png new file mode 100644 index 0000000000000000000000000000000000000000..16bc51294a67414197a1ce8953fabcd349008c40 GIT binary patch literal 360 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yb(o-U3d7QIg=2J$r+a5$HD-fHNjq-oK zEbUph^?v5-Lro799pWZj^I|DU+9q$b`Ofvb+4r|SKhDQsobbgV$GL%d=7E>;!sj{W z*sbr_!}RdBrpO(+mkdgtowu)kXkeJ4acjZ0%$9SHtWT_7cdEa^FJzwqmzIyUqNv?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yb_o-U3d7QJsLIPx7(;9&WAMDPFqOvfaX-tOyirw-0I zvx6zEe($M^#XBZfuKb*%AYuFZkGllJ$quV!3Kh#NLfmZJqc)xI*;j1)_QA5&N69z( zwmbQ%^++ygIqJJ*=Z=mWEP@-~?z?$aq$+RvclkB!n<9Di^_2c^1lr8t>FVdQ&MBb@ E0I=Pl2><{9 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/amorphous-red-with-green.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/amorphous-red-with-green.png new file mode 100644 index 0000000000000000000000000000000000000000..c2f79920aa02244daf2a0f8d1ed446d765fd6413 GIT binary patch literal 397 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yciJzX3_EP9_#^cFg-z~fxZc>37R|FPQA9!@^r=dPUg zLh*m!1UOq?Pzf9u@3B`c=${8Fs@ zzMyI|_XL04MK8;%6wkG<7p@T6@kh*|_rTNV8#Gi6{z|k;o9>zQrXlT{!iIY+$_Y#= z*Iaq;7fDxc{cgGG;Kp@pKJGgzJ3(-E@v5ym6_~E3&)*?jR(E38w*U1}@^i98#d{pn R9f8hf@O1TaS?83{1OV8byz&45 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/amorphous.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/amorphous.png new file mode 100644 index 0000000000000000000000000000000000000000..401b7508059f48633a1b09b1ff73a7d70fa11e13 GIT binary patch literal 402 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ya+JY5_^EP9^?dGom_@>C^U{e8b8BUkPG{klnthI$9Y zqUJw;AHcNZ^9m-$-5*RV9_m~yR*Ns;``Vkc~_9dJcVbA??b^KT{}$=ey{zkxg)`>@yY5*8tV>iySYTmWaBE?KrP#kR>#+IrWT*f znK^|&AXJyt>a|tZq@oB;>)X3(rZ0BTdAq?cLYA+_>A1Y&{m6&kl2c3zAJ>b?mnblQ Un;7)F73g*bPgg&ebxsLQ0D)t=C;$Ke literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-amorphous.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-amorphous.png new file mode 100644 index 0000000000000000000000000000000000000000..7cbf9812e699f08c481c37dc7f4e9b13e31e0f14 GIT binary patch literal 271 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ycxo-U3d7QM*{60D08IGFwP{LBoV99h^fn}gM6j?EpL m3K52>42I_BqIay1FfeRb%2+O;kvScxk-^i|&t;ucLK6TEE_SH^ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-crate.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-crate.png new file mode 100644 index 0000000000000000000000000000000000000000..6a84d0b8509c76f353caaf2b2780589d5e3efebb GIT binary patch literal 268 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ybmo-U3d7QM*{60D08L>SFM8^>bP0l+XkK&xCg` literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-locker.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-locker.png new file mode 100644 index 0000000000000000000000000000000000000000..845fe7a4702565d05fff9e9ba49e30f05288d82c GIT binary patch literal 272 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yb`o-U3d7QM*{60D08L@tOG6cui2R8;3p o&kinTSi#bv?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ybmo-U3d7QM*{608{w-IIB_EDJRrsI6#dpUu9XN#P00 j#5-3CKWj*^nlLgX6*2byYh7#u)WzWG>gTe~DWM4f@8Ng= literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-parcel-medium.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-parcel-medium.png new file mode 100644 index 0000000000000000000000000000000000000000..b280bead556ed656b9e1675e8176bb6964126f8c GIT binary patch literal 258 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yaro-U3d7QM*{60FUF1%;ann-tXZSZ(f9i1suEI#@C= Yw2Lv!_jz%<5U7^H)78&qol`;+03W_{5&!@I literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-parcel-small.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-parcel-small.png new file mode 100644 index 0000000000000000000000000000000000000000..2bc717b71cdbc939f722cbad0631122c7e6167d0 GIT binary patch literal 256 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yb;o-U3d7QM*{60CbP0l+XkKkAZb1 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-parcel-tiny.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-parcel-tiny.png new file mode 100644 index 0000000000000000000000000000000000000000..f506f7019837a431bac58875f453ddada8bb8dde GIT binary patch literal 256 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yb;o-U3d7QM*{60D0E#2-9*_Auq~k<|^oPOOU)0+<=} XoEa8mi@mf5s$}qV^>bP0l+XkKiZyg! literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-tall-crate.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/barcode-tall-crate.png new file mode 100644 index 0000000000000000000000000000000000000000..e1c6e5eb2ea7f5822a74b5b17ca8eed94ba72de0 GIT binary patch literal 267 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ycRo-U3d7QM*{60D08jxg)_nQ1&x31>8SWVN|t^Fx$D iQN81d+Fj-?Obi|C7@wv*%TWMoV(@hJb6Mw<&;$TJE_P7> literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate-giftwrap-base.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate-giftwrap-base.png new file mode 100644 index 0000000000000000000000000000000000000000..6859d7dd6dc800a47a09399c752d8b99e3363519 GIT binary patch literal 332 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yblo-U3d7QJsL2J#(H;BdBNOS$uZj`NyJXH)*YJ*_6C ztpB)DKC0n}A0BvRPboFyt=akR{08;*& AUjP6A literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate-giftwrap-ribbon.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate-giftwrap-ribbon.png new file mode 100644 index 0000000000000000000000000000000000000000..26e49f4fc2e08f0046686ac4d3295e68e43706c1 GIT binary patch literal 322 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yaqo-U3d7QJsL2J#(H;BemSar^H7Db6XUd%Eg>Z{lQm znCRebIg7)EFTiKjBp!qPua+6>&S1KFpkrTg>w~tAs8wgba;zx*oZohrSFdz=;q#ia o^$fAA(|L03Uo$W!F!$_YoFOK($n0VMPoP~4p00i_>zopr04|7?S^xk5 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate-red-with-green.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate-red-with-green.png new file mode 100644 index 0000000000000000000000000000000000000000..72c3485930e220996db31c71b54fb82f73eb3a73 GIT binary patch literal 369 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ydfo-U3d7QJsL2l5?J;BeW?AUpTn-}6xmLJI76RWqnx zH29Fzx@~Q4YoAinRlBc-td6r3__}QrmAGEWrN%N^*f6BGu@>eqr7aNb=s3pGAy8|$ z$U3#b_KR3%>D_d;)0}#dGSjVZ?A>!Vr{?XVa}QT{%Y;gXoO}3O<-F2QkF9)Prcb|W lKEdI&>o3EQ-{<`c8B><4ckK__=K^#ZgQu&X%Q~loCIC_1sIve7 literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..6da1221634c33cdc352736938bfb593e4314a539 GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yc|o-U3d7QJsLH}WwEayVShUHAWgQuynRl{F39XTC5r zVh~^Y<4@(C#cmHXX8)VTrL?|#>FYyJW}M)%S=Tc+f~7|COKU~@51x~{ldP6Zt*LQY k@L=N$E@y^MmOn*!@3XMFOEdEI0j**1boFyt=akR{0R6+1*Z=?k literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/humanoid-green-with-red.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/humanoid-green-with-red.png new file mode 100644 index 0000000000000000000000000000000000000000..4854b0d56c571b339a67d9be1a4d6d8dd511a52a GIT binary patch literal 444 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ycuJY5_^EPB@ld5g6eaC~O^bLj7TtA%UN*(_bx##(DS zangapKZbYy%3k+iFOZoyQ&y2w5L)?0(FWu*Vho z-nvWAZ|PLr|M>yWsUtqS)sIP7@rU)VFbui1BO$DNQ{Og+qm?lq89#T6dbg(>?*sah N!PC{xWt~$(6980x(ANL} literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/locker-giftwrap-base.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/locker-giftwrap-base.png new file mode 100644 index 0000000000000000000000000000000000000000..6ebfc4974de5d7e77a1c27ce19ebe1b446c3490c GIT binary patch literal 338 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yamo-U3d7QJUDHF6zL;BfiNS^nUE{R@-AWSx~aC9IYD zYXWAue15d;T*c{T2IF0e-8CHe0@xIEj1<H;tbGk22WQ%mvv4F FO#qKboO}QP literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/locker-giftwrap-ribbon.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/locker-giftwrap-ribbon.png new file mode 100644 index 0000000000000000000000000000000000000000..228d1135cc53ee8673caa4f06fc1669a6e346036 GIT binary patch literal 311 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ycYo-U3d7QM*{4opc&9756p;_O~bOB+r2SRGWfwD{5z zSRE__3kw4mXvLHbP0l+XkKErf$F literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/locker-red-with-green.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/locker-red-with-green.png new file mode 100644 index 0000000000000000000000000000000000000000..9576244dd4167da4e9e6906a71e3ffc5a65b79c8 GIT binary patch literal 358 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yb3o-U3d7QK5XadI^n@UZ-K@caKiyqtSctB7Kkb0*Ww zdv~`I=g5_K akTJPWa`nxZKQe&sVeoYIb6Mw<&;$UJ5~RNX literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..4e474ac0f05c6a85d17914f84b85a33a05d40d18 GIT binary patch literal 314 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yayo-U3d7QM*{3UUTYjB7smcreWFa$MLT$(ay*;84>C zRc>xG)}8`U2enGB1T6<0BTv?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ybFo-U3d7QIg=IC3=@@UVOrHr(*Pe#ObCB?(Ekl^2%g zvRPeNa$tW_TA@R9jo=+Y?tokt4+rfZ4*9JgU6gZXGF)|1c$;Cz9m#Plm}6awiHgcJ zr#nj&c5&&u1Q)eA?a2LfaK`HXsJg92ho^61W~$}CX!!YxJkVYSPgg&ebxsLQ03I%u A+5i9m literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-large-giftwrap-ribbon.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-large-giftwrap-ribbon.png new file mode 100644 index 0000000000000000000000000000000000000000..00238723fab838e450e68d4f599063ecef9aeada GIT binary patch literal 312 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ybto-U3d7QJ^TIPx7(;9&WCWYhounc{2IkIpbtTPv*G zK9^zPl*b0y_a%Z>R-Ri#&9IHZXX)`njxgN@xNA7Lt%q literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-large-red-with-green.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-large-red-with-green.png new file mode 100644 index 0000000000000000000000000000000000000000..9a05e689eb4e22875934ae52d1f4b447858e0b0c GIT binary patch literal 359 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ycko-U3d7QJ^TIP$d`aJcMc;5qvH{?)`k_k9he%6}!v z2*pL6+El~0`TzQZcNxt$bA5M{VE!X?DlWlz;*@hO6W=J`v=^?}<#>}b+tKkRhv1yM zmCmj%$`vw9ul+Qfou;lm;K#WtO|ksL>Bxo^t{v>^FQz&s>dWy|KAsugy(?zx3dN&3 bduH)|@)SJd_RFjk=pY79S3j3^P6v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ydDo-U3d7QIg|cyl!v2ryr8bKd*EcWv%PO}9-2fk)PC zsxX*8<)3JeU_$3imTMA>CkmJ~8T0~oF)g=P#S(jE8RLxYcTO=oFjlbM;n=>!`m*h* feN|^v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yc&o-U3d7QJsLIC3!v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ydDo-U3d7QJsL9OONqz{9+`v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yc6o-U3d7QJsL9OONqz{4EbU}5|=c=OQaXd(Zi{T}KyGr8Xeay?3S=KT${oWax8&t;uc GLK6V2OPZbl literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..27d6dda3ba7d394fa2160fabedd178cae7e4554b GIT binary patch literal 296 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yb)o-U3d7QM*{609NYJsb_rjKVw@{1}aS9t0dX#8j%p z&CSQ!BOvM^HJxb#mq2SUqq4NMv~sSfKoPTIyZ{e7%Yk(X7R(Gwlo{)uX>RBRn#kbk L>gTe~DWM4fE#Z0< literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-small-giftwrap-base.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-small-giftwrap-base.png new file mode 100644 index 0000000000000000000000000000000000000000..0930ff554c5660ff2f80e000b25312b55b361df5 GIT binary patch literal 292 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ybao-U3d7QM*{60FU_9ZrUN3VM9X1*;m8Y{V2U&)`}R z_8?3_jZNj~kpis=lbC)xd2#h?XYz9Fa^%ucHC@mrvxR{n?HTiO8v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ya?o-U3d7QJsL81fx3;9&mD`S}0;EoDpccAT8HaH9av zwg*Qf!qr2*Zi`!T=aUDcs71Q|Oa+lHbA|aQ_N@=OvtRN6dxIcPBah>(i9h*I^Ln?< dJ&^yEVR{vtj%)kfX+ZNCJYD@<);T3K0RS#|k8=P3 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-small-red-with-green.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-small-red-with-green.png new file mode 100644 index 0000000000000000000000000000000000000000..ca514f69b727ff4ae73c3f416cfc55211324f8b4 GIT binary patch literal 322 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yaqo-U3d7QJsTY!p0Tz`^XG&oC+Rf2?v>$;F*M6IU$w zDF0&5wfW{kh5gSNBn3GmHkfC%F<0qKnZO)bFVdQ&MBb@0ORA2R{#J2 literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..729608b6ad83681c8d916740ca6847298b3c71b2 GIT binary patch literal 290 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yavo-U3d7QM*{60FUF28S5@SbYQx7I4i_WLVB5CbmI7 zBBDSmAtf<6nR(Iv?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ya%o-U3d7QM*{60FK%9bHZaehPf*1riLCMA8rBwyc;T x!Q4DUB9ApFC@7#QMuGJsYo4&i_9PZ&1|CCZ*~4#Rwgb&z@O1TaS?83{1OWb~cv?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yb4o-U3d7QJu#47nH-c$hzT8vp;lQtY5-^Xdn@Z!Z+| zeyNy4jlHFO93sp&S{gK%H!gEN`jX>c7Dw9;W$8So`OLEOJ}`?Me|>Ko&^!iD LS3j3^P6v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yaio-U3d7QJu#E%^>Oa4>&oPB8fYKYNZ^=$y66Z#xzz zuq3TL?BBV3Pjj_IR@ey@WBV4h1K*rBt@O}8qmZ^IAhe}RRAa#x=`XEzAEmdc^UdS# X`oNMX-0@8eXf}hVtDnm{r-UW|eb9_k literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..2aecef3c5f86775a38dcac40c06b15f71108f436 GIT binary patch literal 283 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ycNo-U3d7QM*{60FLi22PB5tUd`2olI#42i7*QausMN yq$DOY3ss01Y}#!6DEPpkL+AR=cLg>`Gcd@%VAy_<v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ybBo-U3d7QJUD#c~}`;AyVzTzBEOe97zeT^_R1Ih7Y% z_?HGYgtx4l%B~{H&^bBOlsSb#xA7GVm%p~cx*seSPLD2`H0*q@_w5X?!@NfAHw_9> z3X%L46C2!^6I>Xk^)$5cRWQ^GJ%90N(-i68oUZ|v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ydDo-U3d7QI*d9k~uD2r&P3IrG2%mPCMltf;1IdxMi( zneWTsqGiHgmxx@oVs>PFaa#M8gHxD^^ZB_4XQiHWtZjRcAE&iIdEsRivj-v9T3>d^ f@2i}-;UKg2Q6cF{)0w?MD;PXo{an^LB{Ts5Gcb>T literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/tall-crate-red-with-green.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/tall-crate-red-with-green.png new file mode 100644 index 0000000000000000000000000000000000000000..054441ff3ba482c7931343e97ef7a3018e9a29cf GIT binary patch literal 384 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|ydVJY5_^EPD4|isoxE;9$Lwx4>!U-}j|@TN;iTemQ)` zSD0H)e(knzUVT&Voou_#+18p-;?h6O`hbAq>_03P>Ml%Q8QAo;8_r|M>zm&5X#a!9 zfdZ~fY4@#Tmz|#}EK|dtF|VPU<(74`DpSO3rey^N*5L_fS;LeYQW%UFHI&S3elMtD zIezK6ch%ngU2AJslx_MYcJ;c>|MrfiHMg{XG8yn`JQjEB5&*iC!PC{xWt~$(69AOx BuQC7t literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..e8ca7ea8f238dd42697b995d7af1565eb3292303 GIT binary patch literal 332 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|k^_7~T!HkG z44a*0E=PNkr^K0UEOh+;|Nq^)ciRHB3=9m89zCj~qx1avbBCFFHDY3t4U%aL49n!? zIU_}{Td4c&F`GMg?v*Q7_U_#~Y0{)4M~>v?=KlNlFD)%CDJf~@%$diJAOG~}6H8uM zKhPS*k|4ie2B7J9f!i0qB|yblo-U3d7QIg|dGj?W@UUD6livHkcjf&$jhQnx6qohr zvvj!xA8M9}IQ55rO)5`ENU@0Ffz}I`8slW Date: Thu, 16 Jan 2025 18:04:19 -0800 Subject: [PATCH 02/10] fix TG sprite licenses update attribution on modified `unwrapped` sprite to better conform to CC's guidance --- .../Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/meta.json | 2 +- .../Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi/meta.json | 2 +- .../Objects/Misc/ParcelWrap/wrapped_parcel.rsi/meta.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/meta.json b/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/meta.json index 0f92016771b2..e598e0f001b4 100644 --- a/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/meta.json +++ b/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/meta.json @@ -1,6 +1,6 @@ { "version": 1, - "license": "AGPL-3.0", + "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, 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 index a971f053fc2f..6fcb6a4e91ce 100644 --- a/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi/meta.json +++ b/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Original made with love by Shaeone, taken from SS14: https://github.com/space-wizards/space-station-14/blob/43eb542a60772dc49e38993a54404a5799dfe344/Resources/Textures/Objects/Decoration/present.rsi/unwrapped.png", + "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 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/meta.json b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/meta.json index f29444976bde..e81086e29fc9 100644 --- a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/meta.json +++ b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/meta.json @@ -1,6 +1,6 @@ { "version": 1, - "license": "AGPL-3.0", + "license": "CC-BY-SA-3.0", "copyright": "Taken from tgstation https://github.com/tgstation/tgstation/blob/bd704770f7146d820e1e93b04ae1dcf3723b299a/icons/obj/storage/wrapping.dmi", "size": { "x": 32, From f2f3c1c567f267bfba51e5826707ef5bc3c2f92b Mon Sep 17 00:00:00 2001 From: Centronias Date: Thu, 16 Jan 2025 18:15:39 -0800 Subject: [PATCH 03/10] ContainerContainer test failure fix --- .../EntitySystems/ParcelWrappingSystem.cs | 2 +- .../Entities/Objects/Misc/ParcelWrap.yml | 91 ++++++++++--------- 2 files changed, 48 insertions(+), 45 deletions(-) diff --git a/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs b/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs index a669341e9188..8acae3fcd266 100644 --- a/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs +++ b/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs @@ -31,7 +31,7 @@ public sealed class ParcelWrappingSystem : EntitySystem [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; - private const string WrappedParcelContainerId = "wrapped_parcel"; + private const string WrappedParcelContainerId = "contents"; /// public override void Initialize() diff --git a/Resources/Prototypes/Entities/Objects/Misc/ParcelWrap.yml b/Resources/Prototypes/Entities/Objects/Misc/ParcelWrap.yml index 055bed5a1c3b..7b8a7b46f5cc 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/ParcelWrap.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/ParcelWrap.yml @@ -4,18 +4,18 @@ id: ParcelWrap description: Paper used contain items for transport. components: - - type: Sprite - sprite: Objects/Misc/ParcelWrap/parcel_wrap.rsi - state: brown - - type: ParcelWrap - wrapSound: /Audio/Items/Handcuffs/rope_start.ogg - 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: Sprite + sprite: Objects/Misc/ParcelWrap/parcel_wrap.rsi + state: brown + - type: ParcelWrap + wrapSound: /Audio/Items/Handcuffs/rope_start.ogg + 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: entity categories: [ HideSpawnMenu ] @@ -24,28 +24,31 @@ id: WrappedParcel description: Something wrapped up in paper. I wonder what's inside... components: - - 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 - unwrapSound: /Audio/Effects/poster_broken.ogg - unwrapTrash: ParcelWrapTrash - - type: Tag - tags: - - Recyclable # Parcel is recyclable, and when it's destroyed, it'll drop its contents. + - 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 + unwrapSound: /Audio/Effects/poster_broken.ogg + unwrapTrash: ParcelWrapTrash + - type: Tag + tags: + - Recyclable # Parcel is recyclable, and when it's destroyed, it'll drop its contents. - type: entity id: ParcelWrapTrash @@ -54,13 +57,13 @@ 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 - - Recyclable - - type: SpaceGarbage + - type: Sprite + sprite: Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi + layers: + - state: brown + - type: Tag + tags: + - Trash + - ParcelWrapBlacklist + - Recyclable + - type: SpaceGarbage From 35192de4f9b98c2bbba2060b03ec0669386f98d1 Mon Sep 17 00:00:00 2001 From: Centronias Date: Sat, 18 Jan 2025 12:25:35 -0800 Subject: [PATCH 04/10] Just easy changes for now. --- .../Components/ParcelWrapComponent.cs | 16 ++- .../Components/WrappedParcelComponent.cs | 11 +- .../ParcelWrappingSystem.ParcelWrap.cs | 40 +++++++ .../ParcelWrappingSystem.WrappedParcel.cs | 52 ++++++++ .../EntitySystems/ParcelWrappingSystem.cs | 111 +++--------------- .../Entities/Objects/Misc/ParcelWrap.yml | 69 ----------- .../Entities/Objects/Misc/parcel_wrap.yml | 73 ++++++++++++ 7 files changed, 202 insertions(+), 170 deletions(-) create mode 100644 Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs create mode 100644 Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs delete mode 100644 Resources/Prototypes/Entities/Objects/Misc/ParcelWrap.yml create mode 100644 Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml diff --git a/Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs b/Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs index c190ce9176c5..24af98b175d1 100644 --- a/Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs +++ b/Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs @@ -1,8 +1,8 @@ using Content.Shared.Item; using Content.Shared.Whitelist; using Robust.Shared.Audio; -using Robust.Shared.GameStates; using Robust.Shared.Prototypes; +using Robust.Shared.Containers; namespace Content.Server.ParcelWrap.Components; @@ -14,6 +14,12 @@ namespace Content.Server.ParcelWrap.Components; [Access] // Readonly, except for VV editing public sealed partial class ParcelWrapComponent : Component { + /// + /// The of the parcel created by using this component. + /// + [DataField(required: true), ViewVariables] + public ProtoId ParcelPrototype = default!; + /// /// If true, parcels created by this will have the same size as the item they /// contain. If false, parcels created by this will always have the size specified by . @@ -42,7 +48,13 @@ public sealed partial class ParcelWrapComponent : Component public SoundSpecifier? WrapSound; /// - /// Defines the set of things which cannot be wrapped. + /// Defines the set of things which can be wrapped (unless it fails the ). + /// + [DataField, ViewVariables(VVAccess.ReadOnly)] + public EntityWhitelist? Whitelist; + + /// + /// Defines the set of things which cannot be wrapped (even if it passes the ). /// [DataField, ViewVariables(VVAccess.ReadOnly)] public EntityWhitelist? Blacklist; diff --git a/Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs b/Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs index 2c0dce84de6e..737a1277d753 100644 --- a/Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs +++ b/Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs @@ -1,7 +1,6 @@ using Content.Server.ParcelWrap.EntitySystems; using Robust.Shared.Audio; using Robust.Shared.Containers; -using Robust.Shared.GameStates; using Robust.Shared.Prototypes; namespace Content.Server.ParcelWrap.Components; @@ -12,7 +11,7 @@ namespace Content.Server.ParcelWrap.Components; /// /// [RegisterComponent, Access(typeof(ParcelWrappingSystem))] -public sealed partial class WrappedParcelComponent : Component +public sealed class WrappedParcelComponent : Component { /// /// The contents of this parcel. @@ -20,6 +19,9 @@ public sealed partial class WrappedParcelComponent : Component [ViewVariables(VVAccess.ReadOnly)] public ContainerSlot Contents = default!; + /// + /// Specifies the entity to spawn when this parcel is unwrapped. + /// [DataField, ViewVariables] public ProtoId? UnwrapTrash; @@ -28,4 +30,9 @@ public sealed partial class WrappedParcelComponent : Component /// [DataField, ViewVariables] public SoundSpecifier? UnwrapSound; + + /// + /// The ID of . + /// + public const string ContainerId = "contents"; } diff --git a/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs b/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs new file mode 100644 index 000000000000..8f430e8d39f5 --- /dev/null +++ b/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs @@ -0,0 +1,40 @@ +using Content.Server.ParcelWrap.Components; +using Content.Shared.Interaction; +using Content.Shared.Verbs; + +namespace Content.Server.ParcelWrap.EntitySystems; + +// This part handles Parcel Wrap. +public sealed partial class ParcelWrappingSystem +{ + private void OnAfterInteract(Entity entity, ref AfterInteractEvent args) + { + if (args.Handled || + args.Target is not { } target || + !args.CanReach || + !IsWrappable(entity, target)) + return; + + WrapInternal(args.User, entity, target); + + args.Handled = true; + } + + private void OnGetVerbsForParcelWrap(Entity entity, + ref GetVerbsEvent 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 = () => WrapInternal(user, entity, target), + }); + } +} diff --git a/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs b/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs new file mode 100644 index 000000000000..3e0f4627da84 --- /dev/null +++ b/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs @@ -0,0 +1,52 @@ +using Content.Server.ParcelWrap.Components; +using Content.Shared.Interaction.Events; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Shared.Containers; + +namespace Content.Server.ParcelWrap.EntitySystems; + +// This part handles Wrapped Parcels +public sealed partial class ParcelWrappingSystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; + + private void OnComponentInit(Entity entity, ref ComponentInit args) + { + entity.Comp.Contents = _container.EnsureContainer(entity, WrappedParcelComponent.ContainerId); + } + + private void OnUseInHand(Entity entity, ref UseInHandEvent args) + { + if (args.Handled) + return; + + UnwrapInternal(entity); + args.Handled = true; + } + + private void OnGetVerbsForWrappedParcel(Entity entity, + ref GetVerbsEvent args) + { + if (!args.CanAccess) + return; + + args.Verbs.Add(new InteractionVerb + { + Text = Loc.GetString("parcel-wrap-verb-unwrap"), + Act = () => UnwrapInternal(entity), + }); + } + + private void OnDestroyed(Entity parcel, ref T args) + { + // Unwrap the package and if something was in it, show a popup describing "wow something came out!" + if (UnwrapInternal(parcel) is { } contents) + { + _popup.PopupPredicted(Loc.GetString("parcel-wrap-popup-parcel-destroyed", ("contents", contents)), + contents, + null, + PopupType.MediumCaution); + } + } +} diff --git a/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs b/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs index 8acae3fcd266..25a0cf821998 100644 --- a/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs +++ b/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs @@ -1,12 +1,10 @@ using Content.Server.ParcelWrap.Components; using Content.Shared.Destructible; -using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Item; using Content.Shared.Materials; using Content.Shared.ParcelWrap.Components; -using Content.Shared.Popups; using Content.Shared.Verbs; using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; @@ -21,17 +19,14 @@ namespace Content.Server.ParcelWrap.EntitySystems; /// /// /// -public sealed class ParcelWrappingSystem : EntitySystem +public sealed partial class ParcelWrappingSystem : EntitySystem { - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly SharedItemSystem _item = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SharedItemSystem _item = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; - - private const string WrappedParcelContainerId = "contents"; /// public override void Initialize() @@ -48,94 +43,18 @@ public override void Initialize() SubscribeLocalEvent(OnDestroyed); } - private void OnAfterInteract(Entity entity, ref AfterInteractEvent args) - { - if (args.Handled || - args.Target is not { } target || - !args.CanReach || - !IsWrappable(entity, target)) - return; - - WrapInternal(args.User, entity, target); - - args.Handled = true; - } - - private void OnGetVerbsForParcelWrap(Entity entity, - ref GetVerbsEvent 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 = () => WrapInternal(user, entity, target), - }); - } - - private void OnComponentInit(Entity entity, ref ComponentInit args) - { - entity.Comp.Contents = _container.EnsureContainer(entity, WrappedParcelContainerId); - } - - private void OnUseInHand(Entity entity, ref UseInHandEvent args) - { - if (args.Handled) - { - return; - } - - UnwrapInternal(entity); - args.Handled = true; - } - - private void OnGetVerbsForWrappedParcel(Entity entity, - ref GetVerbsEvent args) - { - if (!args.CanAccess) - return; - - args.Verbs.Add(new InteractionVerb - { - Text = Loc.GetString("parcel-wrap-verb-unwrap"), - Act = () => UnwrapInternal(entity), - }); - } - - private void OnDestroyed(Entity parcel, ref T args) - { - // Unwrap the package and if something was in it, show a popup describing "wow something came out!" - if (UnwrapInternal(parcel) is { } contents) - { - var parcelId = Identity.Name(contents, EntityManager); - _popup.PopupPredicted(Loc.GetString("parcel-wrap-popup-parcel-destroyed", ("contents", contents)), - contents, - null, - PopupType.MediumCaution); - } - } - - #region accessors /// /// Returns whether or not can be used to wrap . /// - public bool IsWrappable(Entity wrapper, EntityUid target) => - // Wrapping cannot wrap itself - wrapper.Owner != target && - // Only wrap items - HasComp(target) && - _whitelist.IsBlacklistFail(wrapper.Comp.Blacklist, target); - - #endregion - - #region internalImplementation + public bool IsWrappable(Entity wrapper, EntityUid target) + { + return + // Wrapping cannot wrap itself + wrapper.Owner != target && + _whitelist.IsWhitelistPass(wrapper.Comp.Whitelist, target) && + _whitelist.IsBlacklistFail(wrapper.Comp.Blacklist, target); + } /// /// Spawns a WrappedParcel containing . @@ -146,13 +65,13 @@ public bool IsWrappable(Entity wrapper, EntityUid target) = /// The newly created parcel. Returns null only in exceptional failure cases. private Entity? WrapInternal(EntityUid user, ParcelWrapComponent wrapper, EntityUid target) { - var spawned = Spawn("WrappedParcel", Transform(target).Coordinates); + var spawned = Spawn(wrapper.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. ItemComponent? targetItemComp = null; var size = wrapper.FallbackItemSize; - if (wrapper.WrappedItemsMaintainSize && Resolve(target, ref targetItemComp, logMissing: false)) + if (wrapper.WrappedItemsMaintainSize && TryComp(target, out targetItemComp)) { size = targetItemComp.Size; } @@ -202,7 +121,7 @@ public bool IsWrappable(Entity wrapper, EntityUid target) = /// private EntityUid? UnwrapInternal(Entity parcel) { - var parcelCoords = Comp(parcel).Coordinates; + var parcelCoords = Transform(parcel).Coordinates; var containedEntity = parcel.Comp.Contents.ContainedEntity; if (containedEntity is { } parcelContents) @@ -231,6 +150,4 @@ public bool IsWrappable(Entity wrapper, EntityUid target) = return containedEntity; } - - #endregion } diff --git a/Resources/Prototypes/Entities/Objects/Misc/ParcelWrap.yml b/Resources/Prototypes/Entities/Objects/Misc/ParcelWrap.yml deleted file mode 100644 index 7b8a7b46f5cc..000000000000 --- a/Resources/Prototypes/Entities/Objects/Misc/ParcelWrap.yml +++ /dev/null @@ -1,69 +0,0 @@ -- type: entity - name: parcel wrap - parent: BaseItem - id: ParcelWrap - description: Paper used contain items for transport. - components: - - type: Sprite - sprite: Objects/Misc/ParcelWrap/parcel_wrap.rsi - state: brown - - type: ParcelWrap - wrapSound: /Audio/Items/Handcuffs/rope_start.ogg - 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: entity - categories: [ HideSpawnMenu ] - name: wrapped parcel - parent: BaseItem - id: WrappedParcel - 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 - unwrapSound: /Audio/Effects/poster_broken.ogg - unwrapTrash: ParcelWrapTrash - - type: Tag - tags: - - Recyclable # Parcel is recyclable, and when it's destroyed, it'll drop its contents. - -- type: entity - id: ParcelWrapTrash - categories: [ HideSpawnMenu ] - parent: BaseItem - 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 - - Recyclable - - type: SpaceGarbage diff --git a/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml b/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml new file mode 100644 index 000000000000..81fa0a512737 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml @@ -0,0 +1,73 @@ +- type: entity + name: parcel wrap + parent: BaseItem + id: ParcelWrap + description: Paper used contain items for transport. + components: + - type: Sprite + sprite: Objects/Misc/ParcelWrap/parcel_wrap.rsi + state: brown + - type: ParcelWrap + ParcelPrototype: WrappedParcel + wrapSound: /Audio/Items/Handcuffs/rope_start.ogg + 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: entity + categories: [ HideSpawnMenu ] + name: wrapped parcel + parent: BaseItem + id: WrappedParcel + 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 + unwrapSound: /Audio/Effects/poster_broken.ogg + unwrapTrash: ParcelWrapTrash + - type: Tag + tags: + - Recyclable # Parcel entity is recyclable, and when it's destroyed, it'll drop its contents. + +- type: entity + id: ParcelWrapTrash + categories: [ HideSpawnMenu ] + parent: BaseItem + 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 From 32ee26243b33e6e6d23d72022314ccf548cb3dcb Mon Sep 17 00:00:00 2001 From: Centronias Date: Sat, 18 Jan 2025 12:34:02 -0800 Subject: [PATCH 05/10] Imagine building your code before pushing it for review --- Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs | 2 +- Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs | 2 +- Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs b/Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs index 24af98b175d1..a6bbee28bab6 100644 --- a/Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs +++ b/Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs @@ -18,7 +18,7 @@ public sealed partial class ParcelWrapComponent : Component /// The of the parcel created by using this component. /// [DataField(required: true), ViewVariables] - public ProtoId ParcelPrototype = default!; + public EntProtoId ParcelPrototype = default!; /// /// If true, parcels created by this will have the same size as the item they diff --git a/Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs b/Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs index 737a1277d753..47ec115e4ddd 100644 --- a/Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs +++ b/Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs @@ -11,7 +11,7 @@ namespace Content.Server.ParcelWrap.Components; /// /// [RegisterComponent, Access(typeof(ParcelWrappingSystem))] -public sealed class WrappedParcelComponent : Component +public sealed partial class WrappedParcelComponent : Component { /// /// The contents of this parcel. diff --git a/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml b/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml index 81fa0a512737..f88d6101433e 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml @@ -8,7 +8,7 @@ sprite: Objects/Misc/ParcelWrap/parcel_wrap.rsi state: brown - type: ParcelWrap - ParcelPrototype: WrappedParcel + parcelPrototype: WrappedParcel wrapSound: /Audio/Items/Handcuffs/rope_start.ogg whitelist: components: From 71c1589c7ac1db9ee00da57914ca42983e8bdca0 Mon Sep 17 00:00:00 2001 From: Centronias Date: Mon, 20 Jan 2025 22:01:51 -0800 Subject: [PATCH 06/10] The rest of the PR comments --- .../ParcelWrap/ParcelWrappingSystem.cs | 6 ++ .../ParcelWrappingSystem.ParcelWrap.cs | 40 ---------- .../ParcelWrappingSystem.WrappedParcel.cs | 52 ------------- .../ParcelWrap/ParcelWrappingSystem.cs | 62 ++++++++++++++++ .../Components/ParcelWrapComponent.cs | 31 +++++--- .../Components/WrappedParcelComponent.cs | 16 ++-- .../ParcelWrappingSystem.ParcelWrap.cs | 73 +++++++++++++++++++ .../ParcelWrappingSystem.WrappedParcel.cs | 56 ++++++++++++++ .../SharedParcelWrappingSystem.cs | 40 ++++++---- Resources/Locale/en-US/pacel-wrap.ftl | 6 ++ .../Prototypes/Entities/Objects/Fun/pai.yml | 1 + .../Entities/Objects/Misc/parcel_wrap.yml | 3 + 12 files changed, 266 insertions(+), 120 deletions(-) create mode 100644 Content.Client/ParcelWrap/ParcelWrappingSystem.cs delete mode 100644 Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs delete mode 100644 Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs create mode 100644 Content.Server/ParcelWrap/ParcelWrappingSystem.cs rename {Content.Server => Content.Shared}/ParcelWrap/Components/ParcelWrapComponent.cs (73%) rename {Content.Server => Content.Shared}/ParcelWrap/Components/WrappedParcelComponent.cs (73%) create mode 100644 Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs create mode 100644 Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs rename Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs => Content.Shared/ParcelWrap/EntitySystems/SharedParcelWrappingSystem.cs (82%) diff --git a/Content.Client/ParcelWrap/ParcelWrappingSystem.cs b/Content.Client/ParcelWrap/ParcelWrappingSystem.cs new file mode 100644 index 000000000000..f9874cb3f47f --- /dev/null +++ b/Content.Client/ParcelWrap/ParcelWrappingSystem.cs @@ -0,0 +1,6 @@ +using Content.Shared.ParcelWrap.EntitySystems; + +namespace Content.Client.ParcelWrap; + +/// +public sealed class ParcelWrappingSystem : SharedParcelWrappingSystem; diff --git a/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs b/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs deleted file mode 100644 index 8f430e8d39f5..000000000000 --- a/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Content.Server.ParcelWrap.Components; -using Content.Shared.Interaction; -using Content.Shared.Verbs; - -namespace Content.Server.ParcelWrap.EntitySystems; - -// This part handles Parcel Wrap. -public sealed partial class ParcelWrappingSystem -{ - private void OnAfterInteract(Entity entity, ref AfterInteractEvent args) - { - if (args.Handled || - args.Target is not { } target || - !args.CanReach || - !IsWrappable(entity, target)) - return; - - WrapInternal(args.User, entity, target); - - args.Handled = true; - } - - private void OnGetVerbsForParcelWrap(Entity entity, - ref GetVerbsEvent 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 = () => WrapInternal(user, entity, target), - }); - } -} diff --git a/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs b/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs deleted file mode 100644 index 3e0f4627da84..000000000000 --- a/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Content.Server.ParcelWrap.Components; -using Content.Shared.Interaction.Events; -using Content.Shared.Popups; -using Content.Shared.Verbs; -using Robust.Shared.Containers; - -namespace Content.Server.ParcelWrap.EntitySystems; - -// This part handles Wrapped Parcels -public sealed partial class ParcelWrappingSystem -{ - [Dependency] private readonly SharedPopupSystem _popup = default!; - - private void OnComponentInit(Entity entity, ref ComponentInit args) - { - entity.Comp.Contents = _container.EnsureContainer(entity, WrappedParcelComponent.ContainerId); - } - - private void OnUseInHand(Entity entity, ref UseInHandEvent args) - { - if (args.Handled) - return; - - UnwrapInternal(entity); - args.Handled = true; - } - - private void OnGetVerbsForWrappedParcel(Entity entity, - ref GetVerbsEvent args) - { - if (!args.CanAccess) - return; - - args.Verbs.Add(new InteractionVerb - { - Text = Loc.GetString("parcel-wrap-verb-unwrap"), - Act = () => UnwrapInternal(entity), - }); - } - - private void OnDestroyed(Entity parcel, ref T args) - { - // Unwrap the package and if something was in it, show a popup describing "wow something came out!" - if (UnwrapInternal(parcel) is { } contents) - { - _popup.PopupPredicted(Loc.GetString("parcel-wrap-popup-parcel-destroyed", ("contents", contents)), - contents, - null, - PopupType.MediumCaution); - } - } -} diff --git a/Content.Server/ParcelWrap/ParcelWrappingSystem.cs b/Content.Server/ParcelWrap/ParcelWrappingSystem.cs new file mode 100644 index 000000000000..4493f6719d64 --- /dev/null +++ b/Content.Server/ParcelWrap/ParcelWrappingSystem.cs @@ -0,0 +1,62 @@ +using Content.Shared.Destructible; +using Content.Shared.Materials; +using Content.Shared.ParcelWrap.Components; +using Content.Shared.ParcelWrap.EntitySystems; +using Content.Shared.Popups; + +namespace Content.Server.ParcelWrap; + +/// +public sealed class ParcelWrappingSystem : SharedParcelWrappingSystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; + + /// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnWrapItemDoAfter); + + SubscribeLocalEvent(OnUnwrapParcelDoAfter); + SubscribeLocalEvent(OnDestroyed); + SubscribeLocalEvent(OnDestroyed); + } + + private void OnWrapItemDoAfter(Entity _, ref ParcelWrapItemDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + if (args is { Target: { } target, Used: { } used } && + TryComp(used, out var wrapper)) + { + WrapInternal(args.User, (used, wrapper), target); + args.Handled = true; + } + } + + private void OnUnwrapParcelDoAfter(Entity _, ref UnwrapWrappedParcelDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + if (args.Target is { } target && TryComp(target, out var parcel)) + { + UnwrapInternal((target, parcel)); + args.Handled = true; + } + } + + private void OnDestroyed(Entity parcel, ref T args) + { + // Unwrap the package and if something was in it, show a popup describing "wow something came out!" + if (UnwrapInternal(parcel) is { } contents) + { + _popup.PopupPredicted(Loc.GetString("parcel-wrap-popup-parcel-destroyed", ("contents", contents)), + contents, + null, + PopupType.MediumCaution); + } + } +} diff --git a/Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs b/Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs similarity index 73% rename from Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs rename to Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs index a6bbee28bab6..46337185233f 100644 --- a/Content.Server/ParcelWrap/Components/ParcelWrapComponent.cs +++ b/Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs @@ -1,50 +1,63 @@ using Content.Shared.Item; +using Content.Shared.ParcelWrap.EntitySystems; using Content.Shared.Whitelist; using Robust.Shared.Audio; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -using Robust.Shared.Containers; -namespace Content.Server.ParcelWrap.Components; +namespace Content.Shared.ParcelWrap.Components; /// /// This component gives its owning entity the ability to wrap items into parcels. /// /// -[RegisterComponent] -[Access] // Readonly, except for VV editing +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access] // Default readonly, except for VV editing public sealed partial class ParcelWrapComponent : Component { /// /// The of the parcel created by using this component. /// - [DataField(required: true), ViewVariables] + [DataField(required: true)] public EntProtoId ParcelPrototype = default!; + /// + /// How many times this wrap can be used to create parcels. + /// + [DataField(required: true), AutoNetworkedField, Access(typeof(SharedParcelWrappingSystem))] + public int Uses = 30; + /// /// If true, parcels created by this will have the same size as the item they /// contain. If false, parcels created by this will always have the size specified by . /// - [DataField, ViewVariables] + [DataField] public bool WrappedItemsMaintainSize = true; /// /// The size of parcels created by this component's entity. This is used if /// is false, or if the item being wrapped somehow doesn't have a size. /// - [DataField, ViewVariables] + [DataField] public ProtoId FallbackItemSize = "Ginormous"; /// /// 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. /// - [DataField, ViewVariables] + [DataField] public bool WrappedItemsMaintainShape = false; + /// + /// How long it takes to use this to wrap something. + /// + [DataField(required: true)] + public float WrapDelay = 1.0f; + /// /// Sound played when this is used to wrap something. /// - [DataField, ViewVariables] + [DataField] public SoundSpecifier? WrapSound; /// diff --git a/Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs b/Content.Shared/ParcelWrap/Components/WrappedParcelComponent.cs similarity index 73% rename from Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs rename to Content.Shared/ParcelWrap/Components/WrappedParcelComponent.cs index 47ec115e4ddd..649915c75d16 100644 --- a/Content.Server/ParcelWrap/Components/WrappedParcelComponent.cs +++ b/Content.Shared/ParcelWrap/Components/WrappedParcelComponent.cs @@ -1,16 +1,16 @@ -using Content.Server.ParcelWrap.EntitySystems; +using Content.Shared.ParcelWrap.EntitySystems; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.Prototypes; -namespace Content.Server.ParcelWrap.Components; +namespace Content.Shared.ParcelWrap.Components; /// /// This component marks its owner as being a parcel created by wrapping another item up. It can be unwrapped, /// destroying this entity and releasing . /// /// -[RegisterComponent, Access(typeof(ParcelWrappingSystem))] +[RegisterComponent, Access(typeof(SharedParcelWrappingSystem))] public sealed partial class WrappedParcelComponent : Component { /// @@ -22,13 +22,19 @@ public sealed partial class WrappedParcelComponent : Component /// /// Specifies the entity to spawn when this parcel is unwrapped. /// - [DataField, ViewVariables] + [DataField] public ProtoId? UnwrapTrash; + /// + /// How long it takes to unwrap this parcel. + /// + [DataField(required: true)] + public float UnwrapDelay = 1.0f; + /// /// Sound played when unwrapping this parcel. /// - [DataField, ViewVariables] + [DataField] public SoundSpecifier? UnwrapSound; /// diff --git a/Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs b/Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs new file mode 100644 index 000000000000..68ee1afebcbe --- /dev/null +++ b/Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs @@ -0,0 +1,73 @@ +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Shared.Interaction; +using Content.Shared.ParcelWrap.Components; +using Content.Shared.Verbs; +using Robust.Shared.Serialization; + +namespace Content.Shared.ParcelWrap.EntitySystems; + +// This part handles Parcel Wrap. +public abstract partial class SharedParcelWrappingSystem +{ + private void OnExamined(Entity entity, ref ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + args.PushMarkup( + Loc.GetString("parcel-wrap-examine-detail-uses", + ("uses", entity.Comp.Uses), + ("markupUsesColor", "lightgray") + ) + ); + } + + private void OnAfterInteract(Entity entity, ref AfterInteractEvent args) + { + if (args.Handled || + args.Target is not { } target || + !args.CanReach || + !IsWrappable(entity, target)) + return; + + TryStartWrapDoAfter(args.User, entity, target); + + args.Handled = true; + } + + private void OnGetVerbsForParcelWrap(Entity entity, + ref GetVerbsEvent 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 bool TryStartWrapDoAfter(EntityUid user, Entity wrapper, EntityUid target) => + _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, + }); +} + +[Serializable, NetSerializable] +public sealed partial class ParcelWrapItemDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs b/Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs new file mode 100644 index 000000000000..8b18601ee01e --- /dev/null +++ b/Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs @@ -0,0 +1,56 @@ +using Content.Shared.DoAfter; +using Content.Shared.Interaction.Events; +using Content.Shared.ParcelWrap.Components; +using Content.Shared.Verbs; +using Robust.Shared.Containers; +using Robust.Shared.Serialization; + +namespace Content.Shared.ParcelWrap.EntitySystems; + +// This part handles Wrapped Parcels +public abstract partial class SharedParcelWrappingSystem +{ + private void OnComponentInit(Entity entity, ref ComponentInit args) + { + entity.Comp.Contents = _container.EnsureContainer(entity, WrappedParcelComponent.ContainerId); + } + + private void OnUseInHand(Entity entity, ref UseInHandEvent args) + { + if (args.Handled) + return; + + TryStartUnwrapDoAfter(args.User, entity); + args.Handled = true; + } + + private void OnGetVerbsForWrappedParcel(Entity entity, + ref GetVerbsEvent 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 bool TryStartUnwrapDoAfter(EntityUid user, Entity parcel) => + _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, + user, + parcel.Comp.UnwrapDelay, + new UnwrapWrappedParcelDoAfterEvent(), + parcel, + parcel) + { + NeedHand = true, + }); +} + +[Serializable, NetSerializable] +public sealed partial class UnwrapWrappedParcelDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs b/Content.Shared/ParcelWrap/EntitySystems/SharedParcelWrappingSystem.cs similarity index 82% rename from Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs rename to Content.Shared/ParcelWrap/EntitySystems/SharedParcelWrappingSystem.cs index 25a0cf821998..dee4cbeb3d73 100644 --- a/Content.Server/ParcelWrap/EntitySystems/ParcelWrappingSystem.cs +++ b/Content.Shared/ParcelWrap/EntitySystems/SharedParcelWrappingSystem.cs @@ -1,9 +1,8 @@ -using Content.Server.ParcelWrap.Components; -using Content.Shared.Destructible; +using Content.Shared.DoAfter; +using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Item; -using Content.Shared.Materials; using Content.Shared.ParcelWrap.Components; using Content.Shared.Verbs; using Content.Shared.Whitelist; @@ -11,7 +10,7 @@ using Robust.Shared.Containers; using Robust.Shared.Utility; -namespace Content.Server.ParcelWrap.EntitySystems; +namespace Content.Shared.ParcelWrap.EntitySystems; /// /// This system handles things related to package wrap, both wrapping items to create parcels, and unwrapping existing @@ -19,11 +18,12 @@ namespace Content.Server.ParcelWrap.EntitySystems; /// /// /// -public sealed partial class ParcelWrappingSystem : EntitySystem +public abstract partial class SharedParcelWrappingSystem : EntitySystem { [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedItemSystem _item = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; @@ -33,14 +33,13 @@ public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnAfterInteract); SubscribeLocalEvent>(OnGetVerbsForParcelWrap); SubscribeLocalEvent(OnComponentInit); SubscribeLocalEvent(OnUseInHand); SubscribeLocalEvent>(OnGetVerbsForWrappedParcel); - SubscribeLocalEvent(OnDestroyed); - SubscribeLocalEvent(OnDestroyed); } @@ -63,15 +62,17 @@ public bool IsWrappable(Entity wrapper, EntityUid target) /// The wrapping being used. Determines appearance of the spawned parcel. /// The entity being wrapped. /// The newly created parcel. Returns null only in exceptional failure cases. - private Entity? WrapInternal(EntityUid user, ParcelWrapComponent wrapper, EntityUid target) + protected Entity? WrapInternal(EntityUid user, + Entity wrapper, + EntityUid target) { - var spawned = Spawn(wrapper.ParcelPrototype, Transform(target).Coordinates); + 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. ItemComponent? targetItemComp = null; - var size = wrapper.FallbackItemSize; - if (wrapper.WrappedItemsMaintainSize && TryComp(target, out targetItemComp)) + var size = wrapper.Comp.FallbackItemSize; + if (wrapper.Comp.WrappedItemsMaintainSize && TryComp(target, out targetItemComp)) { size = targetItemComp.Size; } @@ -82,7 +83,7 @@ public bool IsWrappable(Entity wrapper, EntityUid target) // If this wrap maintains the shape when wrapping and the item has a shape override, copy the shape override to // the parcel. - if (wrapper.WrappedItemsMaintainShape && Resolve(target, ref targetItemComp, logMissing: false) && + if (wrapper.Comp.WrappedItemsMaintainShape && Resolve(target, ref targetItemComp, logMissing: false) && targetItemComp.Shape is { } shape) { _item.SetShape(spawned, shape, item); @@ -105,8 +106,19 @@ public bool IsWrappable(Entity wrapper, EntityUid target) return null; } + // Consume a `use` on the wrapper. + wrapper.Comp.Uses -= 1; + if (wrapper.Comp.Uses <= 0) + { + QueueDel(wrapper); + } + else + { + Dirty(wrapper); + } + // Play a wrapping sound. - _audio.PlayPvs(wrapper.WrapSound, spawned); + _audio.PlayPvs(wrapper.Comp.WrapSound, spawned); return (spawned, parcel); } @@ -119,7 +131,7 @@ public bool IsWrappable(Entity wrapper, EntityUid target) /// 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. /// - private EntityUid? UnwrapInternal(Entity parcel) + protected EntityUid? UnwrapInternal(Entity parcel) { var parcelCoords = Transform(parcel).Coordinates; diff --git a/Resources/Locale/en-US/pacel-wrap.ftl b/Resources/Locale/en-US/pacel-wrap.ftl index 62fefa4039a0..2351892fa544 100644 --- a/Resources/Locale/en-US/pacel-wrap.ftl +++ b/Resources/Locale/en-US/pacel-wrap.ftl @@ -2,3 +2,9 @@ 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 +}. diff --git a/Resources/Prototypes/Entities/Objects/Fun/pai.yml b/Resources/Prototypes/Entities/Objects/Fun/pai.yml index 19aec6e0e28a..a6cf9b0436f1 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/pai.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/pai.yml @@ -75,6 +75,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 index f88d6101433e..c89736bce1b2 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml @@ -9,6 +9,8 @@ state: brown - type: ParcelWrap parcelPrototype: WrappedParcel + uses: 30 + wrapDelay: 1.0 wrapSound: /Audio/Items/Handcuffs/rope_start.ogg whitelist: components: @@ -48,6 +50,7 @@ - state: parcel-medium map: [ "enum.WrappedParcelVisuals.Layer" ] - type: WrappedParcel + unwrapDelay: 0.5 unwrapSound: /Audio/Effects/poster_broken.ogg unwrapTrash: ParcelWrapTrash - type: Tag From abe377d90405a782ebcbdc723dd527a821ce62ee Mon Sep 17 00:00:00 2001 From: Centronias Date: Tue, 21 Jan 2025 12:56:30 -0800 Subject: [PATCH 07/10] PR comments --- .../ParcelWrap/ParcelWrappingSystem.cs | 17 +- .../ParcelWrap/ParcelWrappingSystem.cs | 62 ------- .../Systems/ParcelWrappingSystem.cs | 71 ++++++++ .../Components/ParcelWrapComponent.cs | 10 +- .../Components/WrappedParcelComponent.cs | 9 +- .../ParcelWrappingSystem.ParcelWrap.cs | 73 -------- .../ParcelWrappingSystem.WrappedParcel.cs | 56 ------ .../SharedParcelWrappingSystem.cs | 165 ------------------ .../ParcelWrap/Systems/ParcelWrapEvents.cs | 10 ++ .../ParcelWrappingSystem.ParcelWrap.cs | 124 +++++++++++++ .../ParcelWrappingSystem.WrappedParcel.cs | 137 +++++++++++++++ .../Systems/SharedParcelWrappingSystem.cs | 49 ++++++ .../Entities/Objects/Misc/parcel_wrap.yml | 8 +- 13 files changed, 420 insertions(+), 371 deletions(-) delete mode 100644 Content.Server/ParcelWrap/ParcelWrappingSystem.cs create mode 100644 Content.Server/ParcelWrap/Systems/ParcelWrappingSystem.cs delete mode 100644 Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs delete mode 100644 Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs delete mode 100644 Content.Shared/ParcelWrap/EntitySystems/SharedParcelWrappingSystem.cs create mode 100644 Content.Shared/ParcelWrap/Systems/ParcelWrapEvents.cs create mode 100644 Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs create mode 100644 Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs create mode 100644 Content.Shared/ParcelWrap/Systems/SharedParcelWrappingSystem.cs diff --git a/Content.Client/ParcelWrap/ParcelWrappingSystem.cs b/Content.Client/ParcelWrap/ParcelWrappingSystem.cs index f9874cb3f47f..071625bc6325 100644 --- a/Content.Client/ParcelWrap/ParcelWrappingSystem.cs +++ b/Content.Client/ParcelWrap/ParcelWrappingSystem.cs @@ -1,6 +1,19 @@ -using Content.Shared.ParcelWrap.EntitySystems; +using Content.Shared.ParcelWrap.Components; +using Content.Shared.ParcelWrap.Systems; namespace Content.Client.ParcelWrap; /// -public sealed class ParcelWrappingSystem : SharedParcelWrappingSystem; +public sealed class ParcelWrappingSystem : SharedParcelWrappingSystem +{ + // Do not spawn anything on the client. + protected override Entity? SpawnParcelAndInsertTarget(EntityUid user, + Entity wrapper, + EntityUid target) + { + return null; + } + + // Do not spawn anything on the client. + protected override void SpawnUnwrapTrash(Entity parcel) { } +} diff --git a/Content.Server/ParcelWrap/ParcelWrappingSystem.cs b/Content.Server/ParcelWrap/ParcelWrappingSystem.cs deleted file mode 100644 index 4493f6719d64..000000000000 --- a/Content.Server/ParcelWrap/ParcelWrappingSystem.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Content.Shared.Destructible; -using Content.Shared.Materials; -using Content.Shared.ParcelWrap.Components; -using Content.Shared.ParcelWrap.EntitySystems; -using Content.Shared.Popups; - -namespace Content.Server.ParcelWrap; - -/// -public sealed class ParcelWrappingSystem : SharedParcelWrappingSystem -{ - [Dependency] private readonly SharedPopupSystem _popup = default!; - - /// - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnWrapItemDoAfter); - - SubscribeLocalEvent(OnUnwrapParcelDoAfter); - SubscribeLocalEvent(OnDestroyed); - SubscribeLocalEvent(OnDestroyed); - } - - private void OnWrapItemDoAfter(Entity _, ref ParcelWrapItemDoAfterEvent args) - { - if (args.Handled || args.Cancelled) - return; - - if (args is { Target: { } target, Used: { } used } && - TryComp(used, out var wrapper)) - { - WrapInternal(args.User, (used, wrapper), target); - args.Handled = true; - } - } - - private void OnUnwrapParcelDoAfter(Entity _, ref UnwrapWrappedParcelDoAfterEvent args) - { - if (args.Handled || args.Cancelled) - return; - - if (args.Target is { } target && TryComp(target, out var parcel)) - { - UnwrapInternal((target, parcel)); - args.Handled = true; - } - } - - private void OnDestroyed(Entity parcel, ref T args) - { - // Unwrap the package and if something was in it, show a popup describing "wow something came out!" - if (UnwrapInternal(parcel) is { } contents) - { - _popup.PopupPredicted(Loc.GetString("parcel-wrap-popup-parcel-destroyed", ("contents", contents)), - contents, - null, - PopupType.MediumCaution); - } - } -} diff --git a/Content.Server/ParcelWrap/Systems/ParcelWrappingSystem.cs b/Content.Server/ParcelWrap/Systems/ParcelWrappingSystem.cs new file mode 100644 index 000000000000..fe6d84aed391 --- /dev/null +++ b/Content.Server/ParcelWrap/Systems/ParcelWrappingSystem.cs @@ -0,0 +1,71 @@ +using Content.Shared.Item; +using Content.Shared.ParcelWrap.Components; +using Content.Shared.ParcelWrap.Systems; +using Robust.Shared.Utility; + +namespace Content.Server.ParcelWrap.Systems; + +/// +public sealed class ParcelWrappingSystem : SharedParcelWrappingSystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedItemSystem _item = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + protected override Entity? SpawnParcelAndInsertTarget(EntityUid user, + Entity wrapper, + EntityUid target) + { + 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(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(spawned); + if (!Container.Insert(target, parcel.Contents)) + { + DebugTools.Assert( + $"Failed to insert target entity into newly spawned parcel. target={PrettyPrint.PrintUserFacing(target)}"); + QueueDel(spawned); + return null; + } + + return (spawned, parcel); + } + + protected override void SpawnUnwrapTrash(Entity parcel) + { + if (parcel.Comp1.UnwrapTrash is { } trashProto) + { + var trash = Spawn(trashProto, parcel.Comp2.Coordinates); + _transform.DropNextTo((trash, null), (parcel, parcel.Comp2)); + } + } +} diff --git a/Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs b/Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs index 46337185233f..3294d0d92f63 100644 --- a/Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs +++ b/Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs @@ -1,5 +1,5 @@ using Content.Shared.Item; -using Content.Shared.ParcelWrap.EntitySystems; +using Content.Shared.ParcelWrap.Systems; using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.GameStates; @@ -19,12 +19,12 @@ public sealed partial class ParcelWrapComponent : Component /// The of the parcel created by using this component. /// [DataField(required: true)] - public EntProtoId ParcelPrototype = default!; + public EntProtoId ParcelPrototype; /// /// How many times this wrap can be used to create parcels. /// - [DataField(required: true), AutoNetworkedField, Access(typeof(SharedParcelWrappingSystem))] + [DataField, AutoNetworkedField, Access(typeof(SharedParcelWrappingSystem))] public int Uses = 30; /// @@ -46,13 +46,13 @@ public sealed partial class ParcelWrapComponent : Component /// this will have the default shape for their size. /// [DataField] - public bool WrappedItemsMaintainShape = false; + public bool WrappedItemsMaintainShape; /// /// How long it takes to use this to wrap something. /// [DataField(required: true)] - public float WrapDelay = 1.0f; + public TimeSpan WrapDelay = TimeSpan.FromSeconds(1); /// /// Sound played when this is used to wrap something. diff --git a/Content.Shared/ParcelWrap/Components/WrappedParcelComponent.cs b/Content.Shared/ParcelWrap/Components/WrappedParcelComponent.cs index 649915c75d16..47fe643c895e 100644 --- a/Content.Shared/ParcelWrap/Components/WrappedParcelComponent.cs +++ b/Content.Shared/ParcelWrap/Components/WrappedParcelComponent.cs @@ -1,4 +1,4 @@ -using Content.Shared.ParcelWrap.EntitySystems; +using Content.Shared.ParcelWrap.Systems; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.Prototypes; @@ -23,13 +23,13 @@ public sealed partial class WrappedParcelComponent : Component /// Specifies the entity to spawn when this parcel is unwrapped. /// [DataField] - public ProtoId? UnwrapTrash; + public EntProtoId? UnwrapTrash; /// /// How long it takes to unwrap this parcel. /// [DataField(required: true)] - public float UnwrapDelay = 1.0f; + public TimeSpan UnwrapDelay = TimeSpan.FromSeconds(1); /// /// Sound played when unwrapping this parcel. @@ -40,5 +40,6 @@ public sealed partial class WrappedParcelComponent : Component /// /// The ID of . /// - public const string ContainerId = "contents"; + [DataField, ViewVariables(VVAccess.ReadOnly)] + public string ContainerId = "contents"; } diff --git a/Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs b/Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs deleted file mode 100644 index 68ee1afebcbe..000000000000 --- a/Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.ParcelWrap.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Content.Shared.DoAfter; -using Content.Shared.Examine; -using Content.Shared.Interaction; -using Content.Shared.ParcelWrap.Components; -using Content.Shared.Verbs; -using Robust.Shared.Serialization; - -namespace Content.Shared.ParcelWrap.EntitySystems; - -// This part handles Parcel Wrap. -public abstract partial class SharedParcelWrappingSystem -{ - private void OnExamined(Entity entity, ref ExaminedEvent args) - { - if (!args.IsInDetailsRange) - return; - - args.PushMarkup( - Loc.GetString("parcel-wrap-examine-detail-uses", - ("uses", entity.Comp.Uses), - ("markupUsesColor", "lightgray") - ) - ); - } - - private void OnAfterInteract(Entity entity, ref AfterInteractEvent args) - { - if (args.Handled || - args.Target is not { } target || - !args.CanReach || - !IsWrappable(entity, target)) - return; - - TryStartWrapDoAfter(args.User, entity, target); - - args.Handled = true; - } - - private void OnGetVerbsForParcelWrap(Entity entity, - ref GetVerbsEvent 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 bool TryStartWrapDoAfter(EntityUid user, Entity wrapper, EntityUid target) => - _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, - }); -} - -[Serializable, NetSerializable] -public sealed partial class ParcelWrapItemDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs b/Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs deleted file mode 100644 index 8b18601ee01e..000000000000 --- a/Content.Shared/ParcelWrap/EntitySystems/ParcelWrappingSystem.WrappedParcel.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Content.Shared.DoAfter; -using Content.Shared.Interaction.Events; -using Content.Shared.ParcelWrap.Components; -using Content.Shared.Verbs; -using Robust.Shared.Containers; -using Robust.Shared.Serialization; - -namespace Content.Shared.ParcelWrap.EntitySystems; - -// This part handles Wrapped Parcels -public abstract partial class SharedParcelWrappingSystem -{ - private void OnComponentInit(Entity entity, ref ComponentInit args) - { - entity.Comp.Contents = _container.EnsureContainer(entity, WrappedParcelComponent.ContainerId); - } - - private void OnUseInHand(Entity entity, ref UseInHandEvent args) - { - if (args.Handled) - return; - - TryStartUnwrapDoAfter(args.User, entity); - args.Handled = true; - } - - private void OnGetVerbsForWrappedParcel(Entity entity, - ref GetVerbsEvent 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 bool TryStartUnwrapDoAfter(EntityUid user, Entity parcel) => - _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, - user, - parcel.Comp.UnwrapDelay, - new UnwrapWrappedParcelDoAfterEvent(), - parcel, - parcel) - { - NeedHand = true, - }); -} - -[Serializable, NetSerializable] -public sealed partial class UnwrapWrappedParcelDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/ParcelWrap/EntitySystems/SharedParcelWrappingSystem.cs b/Content.Shared/ParcelWrap/EntitySystems/SharedParcelWrappingSystem.cs deleted file mode 100644 index dee4cbeb3d73..000000000000 --- a/Content.Shared/ParcelWrap/EntitySystems/SharedParcelWrappingSystem.cs +++ /dev/null @@ -1,165 +0,0 @@ -using Content.Shared.DoAfter; -using Content.Shared.Examine; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; -using Content.Shared.Item; -using Content.Shared.ParcelWrap.Components; -using Content.Shared.Verbs; -using Content.Shared.Whitelist; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Containers; -using Robust.Shared.Utility; - -namespace Content.Shared.ParcelWrap.EntitySystems; - -/// -/// This system handles things related to package wrap, both wrapping items to create parcels, and unwrapping existing -/// parcels. -/// -/// -/// -public abstract partial class SharedParcelWrappingSystem : EntitySystem -{ - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; - [Dependency] private readonly SharedItemSystem _item = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; - - /// - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnAfterInteract); - SubscribeLocalEvent>(OnGetVerbsForParcelWrap); - - SubscribeLocalEvent(OnComponentInit); - SubscribeLocalEvent(OnUseInHand); - SubscribeLocalEvent>(OnGetVerbsForWrappedParcel); - } - - - /// - /// Returns whether or not can be used to wrap . - /// - public bool IsWrappable(Entity wrapper, EntityUid target) - { - return - // Wrapping cannot wrap itself - wrapper.Owner != target && - _whitelist.IsWhitelistPass(wrapper.Comp.Whitelist, target) && - _whitelist.IsBlacklistFail(wrapper.Comp.Blacklist, target); - } - - /// - /// Spawns a WrappedParcel containing . - /// - /// The entity using to wrap . - /// The wrapping being used. Determines appearance of the spawned parcel. - /// The entity being wrapped. - /// The newly created parcel. Returns null only in exceptional failure cases. - protected Entity? WrapInternal(EntityUid user, - Entity wrapper, - EntityUid target) - { - 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. - ItemComponent? targetItemComp = null; - var size = wrapper.Comp.FallbackItemSize; - if (wrapper.Comp.WrappedItemsMaintainSize && TryComp(target, out targetItemComp)) - { - size = targetItemComp.Size; - } - - var item = Comp(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 && Resolve(target, ref targetItemComp, logMissing: false) && - targetItemComp.Shape is { } 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(spawned); - if (!_container.Insert(target, parcel.Contents)) - { - DebugTools.Assert( - $"Failed to insert target entity into newly spawned parcel. target={PrettyPrint.PrintUserFacing(target)}"); - QueueDel(spawned); - return null; - } - - // Consume a `use` on the wrapper. - wrapper.Comp.Uses -= 1; - if (wrapper.Comp.Uses <= 0) - { - QueueDel(wrapper); - } - else - { - Dirty(wrapper); - } - - // Play a wrapping sound. - _audio.PlayPvs(wrapper.Comp.WrapSound, spawned); - - return (spawned, parcel); - } - - /// - /// Despawns , leaving the contained entity where the parcel was. - /// - /// The entity being unwrapped. - /// - /// 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. - /// - protected EntityUid? UnwrapInternal(Entity parcel) - { - var parcelCoords = Transform(parcel).Coordinates; - - var containedEntity = parcel.Comp.Contents.ContainedEntity; - if (containedEntity is { } parcelContents) - { - _container.Remove(parcelContents, - parcel.Comp.Contents, - true, - true, - parcelCoords); - - // 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); - } - } - - // Make some trash and play an unwrapping sound. - var trash = Spawn(parcel.Comp.UnwrapTrash, parcelCoords); - _transform.DropNextTo((trash, null), (parcel, null)); - _audio.PlayPvs(parcel.Comp.UnwrapSound, parcelCoords); - - EntityManager.DeleteEntity(parcel); - - return containedEntity; - } -} diff --git a/Content.Shared/ParcelWrap/Systems/ParcelWrapEvents.cs b/Content.Shared/ParcelWrap/Systems/ParcelWrapEvents.cs new file mode 100644 index 000000000000..b64ecc3c00df --- /dev/null +++ b/Content.Shared/ParcelWrap/Systems/ParcelWrapEvents.cs @@ -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 index 000000000000..327f10131dd9 --- /dev/null +++ b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs @@ -0,0 +1,124 @@ +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Shared.Interaction; +using Content.Shared.ParcelWrap.Components; +using Content.Shared.Verbs; + +namespace Content.Shared.ParcelWrap.Systems; + +// This part handles Parcel Wrap. +public abstract partial class SharedParcelWrappingSystem +{ + private void InitializeParcelWrap() + { + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnAfterInteract); + SubscribeLocalEvent>(OnGetVerbsForParcelWrap); + SubscribeLocalEvent(OnWrapItemDoAfter); + } + + + private void OnExamined(Entity entity, ref ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + args.PushMarkup( + Loc.GetString("parcel-wrap-examine-detail-uses", + ("uses", entity.Comp.Uses), + ("markupUsesColor", "lightgray") + ) + ); + } + + private void OnAfterInteract(Entity 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 entity, ref GetVerbsEvent 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 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 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, + }); + } + + /// + /// Spawns a WrappedParcel containing . + /// + /// The entity using to wrap . + /// The wrapping being used. Determines appearance of the spawned parcel. + /// The entity being wrapped. + private void WrapInternal(EntityUid user, + Entity wrapper, + EntityUid target) + { + var parcel = SpawnParcelAndInsertTarget(user, wrapper, target); + + // Consume a `use` on the wrapper. + wrapper.Comp.Uses -= 1; + if (wrapper.Comp.Uses <= 0) + { + QueueDel(wrapper); + } + else + { + Dirty(wrapper); + } + + // Play a wrapping sound. + _audio.PlayPredicted(wrapper.Comp.WrapSound, parcel?.Owner ?? user, user); + } + + /// + /// Split off from so that entity spawning is only performed on the server. + /// + protected abstract Entity? SpawnParcelAndInsertTarget(EntityUid user, + Entity wrapper, + EntityUid target); +} diff --git a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs new file mode 100644 index 000000000000..d7c8a1fe877b --- /dev/null +++ b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs @@ -0,0 +1,137 @@ +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; +using Robust.Shared.Map; + +namespace Content.Shared.ParcelWrap.Systems; + +// This part handles Wrapped Parcels +public abstract partial class SharedParcelWrappingSystem +{ + private void InitializeWrappedParcel() + { + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent>(OnGetVerbsForWrappedParcel); + SubscribeLocalEvent(OnUnwrapParcelDoAfter); + SubscribeLocalEvent(OnDestroyed); + SubscribeLocalEvent(OnDestroyed); + } + + + private void OnComponentInit(Entity entity, ref ComponentInit args) + { + entity.Comp.Contents = Container.EnsureContainer(entity, entity.Comp.ContainerId); + } + + private void OnUseInHand(Entity entity, ref UseInHandEvent args) + { + if (args.Handled) + return; + + TryStartUnwrapDoAfter(args.User, entity); + args.Handled = true; + } + + private void OnGetVerbsForWrappedParcel(Entity entity, + ref GetVerbsEvent 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 entity, ref UnwrapWrappedParcelDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + if (args.Target is { } target && TryComp(target, out var parcel)) + { + UnwrapInternal(args.User, (target, parcel)); + args.Handled = true; + } + } + + private void OnDestroyed(Entity 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 parcel) => + _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, + user, + parcel.Comp.UnwrapDelay, + new UnwrapWrappedParcelDoAfterEvent(), + parcel, + parcel) + { + NeedHand = true, + }); + + /// + /// Despawns , leaving the contained entity where the parcel was. + /// + /// The entity doing the unwrapping. + /// The entity being unwrapped. + /// + /// 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. + /// + private EntityUid? UnwrapInternal(EntityUid? user, Entity parcel) + { + var parcelTransform = Transform(parcel); + + var containedEntity = parcel.Comp.Contents.ContainedEntity; + 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); + } + } + + // Make some trash and play an unwrapping sound. + SpawnUnwrapTrash((parcel, parcel.Comp, parcelTransform)); + _audio.PlayPredicted(parcel.Comp.UnwrapSound, parcel, user); + + Del(parcel); + + return containedEntity; + } + + /// + /// Split off from so that entity spawning is only performed on the server. + /// + protected abstract void SpawnUnwrapTrash(Entity parcel); +} diff --git a/Content.Shared/ParcelWrap/Systems/SharedParcelWrappingSystem.cs b/Content.Shared/ParcelWrap/Systems/SharedParcelWrappingSystem.cs new file mode 100644 index 000000000000..6843e668d8c4 --- /dev/null +++ b/Content.Shared/ParcelWrap/Systems/SharedParcelWrappingSystem.cs @@ -0,0 +1,49 @@ +using Content.Shared.DoAfter; +using Content.Shared.ParcelWrap.Components; +using Content.Shared.Popups; +using Content.Shared.Whitelist; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; + +namespace Content.Shared.ParcelWrap.Systems; + +/// +/// This system handles things related to package wrap, both wrapping items to create parcels, and unwrapping existing +/// parcels. +/// +/// +/// +public abstract partial class SharedParcelWrappingSystem : EntitySystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] protected readonly SharedContainerSystem Container = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + + /// + public override void Initialize() + { + base.Initialize(); + + InitializeParcelWrap(); + InitializeWrappedParcel(); + } + + /// + /// Returns whether or not can be used to wrap . + /// + /// The entity doing the wrapping. + /// The entity to be wrapped. + /// True if can be used to wrap , false otherwise. + public bool IsWrappable(Entity wrapper, EntityUid target) + { + return + // Wrapping cannot wrap itself + wrapper.Owner != target && + // Wrapper should never have non-positive uses, but may as well make sure. + wrapper.Comp.Uses > 0 && + _whitelist.IsWhitelistPass(wrapper.Comp.Whitelist, target) && + _whitelist.IsBlacklistFail(wrapper.Comp.Blacklist, target); + } +} diff --git a/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml b/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml index c89736bce1b2..be7f970f15fc 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml @@ -1,7 +1,7 @@ - type: entity - name: parcel wrap parent: BaseItem id: ParcelWrap + name: parcel wrap description: Paper used contain items for transport. components: - type: Sprite @@ -24,10 +24,10 @@ - FakeNukeDisk # So you can't tell if the nuke disk is real or fake depending on if it can be wrapped or not. - type: entity - categories: [ HideSpawnMenu ] - name: wrapped parcel parent: BaseItem id: WrappedParcel + categories: [ HideSpawnMenu ] + name: wrapped parcel description: Something wrapped up in paper. I wonder what's inside... components: - type: ContainerContainer @@ -58,9 +58,9 @@ - Recyclable # Parcel entity is recyclable, and when it's destroyed, it'll drop its contents. - type: entity + parent: BaseItem id: ParcelWrapTrash categories: [ HideSpawnMenu ] - parent: BaseItem name: parcel wrap description: The disappointing remnants of an unwrapped parcel. components: From 1a720b25a59d0b2348872db166fad6d899420333 Mon Sep 17 00:00:00 2001 From: Centronias Date: Wed, 22 Jan 2025 10:59:39 -0800 Subject: [PATCH 08/10] more comments + cargo orderability --- .../ParcelWrap/ParcelWrappingSystem.cs | 34 +++++++++--- .../Systems/ParcelWrappingSystem.cs | 53 +++++++++++++++---- .../ParcelWrappingSystem.ParcelWrap.cs | 28 +--------- .../ParcelWrappingSystem.WrappedParcel.cs | 37 +------------ .../Systems/SharedParcelWrappingSystem.cs | 2 +- .../Prototypes/Catalog/Cargo/cargo_cargo.yml | 10 ++++ .../Prototypes/Catalog/Fills/Crates/cargo.yml | 11 ++++ 7 files changed, 95 insertions(+), 80 deletions(-) diff --git a/Content.Client/ParcelWrap/ParcelWrappingSystem.cs b/Content.Client/ParcelWrap/ParcelWrappingSystem.cs index 071625bc6325..f3f3d1f041df 100644 --- a/Content.Client/ParcelWrap/ParcelWrappingSystem.cs +++ b/Content.Client/ParcelWrap/ParcelWrappingSystem.cs @@ -6,14 +6,34 @@ namespace Content.Client.ParcelWrap; /// public sealed class ParcelWrappingSystem : SharedParcelWrappingSystem { - // Do not spawn anything on the client. - protected override Entity? SpawnParcelAndInsertTarget(EntityUid user, - Entity wrapper, - EntityUid target) + protected override void WrapInternal(EntityUid user, Entity wrapper, EntityUid target) { - return null; + // Consume a `use` on the wrapper and play a wrapping sound. + wrapper.Comp.Uses -= 1; + Audio.PlayPredicted(wrapper.Comp.WrapSound, target, user); } - // Do not spawn anything on the client. - protected override void SpawnUnwrapTrash(Entity parcel) { } + protected override EntityUid? UnwrapInternal(EntityUid? user, Entity parcel) + { + var parcelTransform = Transform(parcel); + + var containedEntity = parcel.Comp.Contents.ContainedEntity; + 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); + } + } + + // Make some trash and play an unwrapping sound. + Audio.PlayPredicted(parcel.Comp.UnwrapSound, parcel, user); + + return containedEntity; + } } diff --git a/Content.Server/ParcelWrap/Systems/ParcelWrappingSystem.cs b/Content.Server/ParcelWrap/Systems/ParcelWrappingSystem.cs index fe6d84aed391..0d4b010bf127 100644 --- a/Content.Server/ParcelWrap/Systems/ParcelWrappingSystem.cs +++ b/Content.Server/ParcelWrap/Systems/ParcelWrappingSystem.cs @@ -12,9 +12,7 @@ public sealed class ParcelWrappingSystem : SharedParcelWrappingSystem [Dependency] private readonly SharedItemSystem _item = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; - protected override Entity? SpawnParcelAndInsertTarget(EntityUid user, - Entity wrapper, - EntityUid target) + protected override void WrapInternal(EntityUid user, Entity wrapper, EntityUid target) { var spawned = Spawn(wrapper.Comp.ParcelPrototype, Transform(target).Coordinates); @@ -54,18 +52,55 @@ public sealed class ParcelWrappingSystem : SharedParcelWrappingSystem DebugTools.Assert( $"Failed to insert target entity into newly spawned parcel. target={PrettyPrint.PrintUserFacing(target)}"); QueueDel(spawned); - return null; } - return (spawned, parcel); + // Consume a `use` on the wrapper. + wrapper.Comp.Uses -= 1; + if (wrapper.Comp.Uses <= 0) + { + QueueDel(wrapper); + } + else + { + Dirty(wrapper); + } + + // Play a wrapping sound. + Audio.PlayPredicted(wrapper.Comp.WrapSound, target, user); } - protected override void SpawnUnwrapTrash(Entity parcel) + protected override EntityUid? UnwrapInternal(EntityUid? user, Entity parcel) { - if (parcel.Comp1.UnwrapTrash is { } trashProto) + var parcelTransform = Transform(parcel); + + var containedEntity = parcel.Comp.Contents.ContainedEntity; + if (containedEntity is { } parcelContents) { - var trash = Spawn(trashProto, parcel.Comp2.Coordinates); - _transform.DropNextTo((trash, null), (parcel, parcel.Comp2)); + 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); + } + } + + // Make some trash and play an unwrapping sound. + Audio.PlayPredicted(parcel.Comp.UnwrapSound, parcel, user); + 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.ParcelWrap.cs b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs index 327f10131dd9..f65166f7acbc 100644 --- a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs +++ b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs @@ -94,31 +94,5 @@ private bool TryStartWrapDoAfter(EntityUid user, Entity wra /// The entity using to wrap . /// The wrapping being used. Determines appearance of the spawned parcel. /// The entity being wrapped. - private void WrapInternal(EntityUid user, - Entity wrapper, - EntityUid target) - { - var parcel = SpawnParcelAndInsertTarget(user, wrapper, target); - - // Consume a `use` on the wrapper. - wrapper.Comp.Uses -= 1; - if (wrapper.Comp.Uses <= 0) - { - QueueDel(wrapper); - } - else - { - Dirty(wrapper); - } - - // Play a wrapping sound. - _audio.PlayPredicted(wrapper.Comp.WrapSound, parcel?.Owner ?? user, user); - } - - /// - /// Split off from so that entity spawning is only performed on the server. - /// - protected abstract Entity? SpawnParcelAndInsertTarget(EntityUid user, - Entity wrapper, - EntityUid target); + protected abstract void WrapInternal(EntityUid user, Entity wrapper, EntityUid target); } diff --git a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs index d7c8a1fe877b..8b60719093f9 100644 --- a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs +++ b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs @@ -6,7 +6,6 @@ using Content.Shared.Popups; using Content.Shared.Verbs; using Robust.Shared.Containers; -using Robust.Shared.Map; namespace Content.Shared.ParcelWrap.Systems; @@ -99,39 +98,5 @@ private bool TryStartUnwrapDoAfter(EntityUid user, Entity - private EntityUid? UnwrapInternal(EntityUid? user, Entity parcel) - { - var parcelTransform = Transform(parcel); - - var containedEntity = parcel.Comp.Contents.ContainedEntity; - 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); - } - } - - // Make some trash and play an unwrapping sound. - SpawnUnwrapTrash((parcel, parcel.Comp, parcelTransform)); - _audio.PlayPredicted(parcel.Comp.UnwrapSound, parcel, user); - - Del(parcel); - - return containedEntity; - } - - /// - /// Split off from so that entity spawning is only performed on the server. - /// - protected abstract void SpawnUnwrapTrash(Entity parcel); + protected abstract EntityUid? UnwrapInternal(EntityUid? user, Entity parcel); } diff --git a/Content.Shared/ParcelWrap/Systems/SharedParcelWrappingSystem.cs b/Content.Shared/ParcelWrap/Systems/SharedParcelWrappingSystem.cs index 6843e668d8c4..cd7ac202ff97 100644 --- a/Content.Shared/ParcelWrap/Systems/SharedParcelWrappingSystem.cs +++ b/Content.Shared/ParcelWrap/Systems/SharedParcelWrappingSystem.cs @@ -15,7 +15,7 @@ namespace Content.Shared.ParcelWrap.Systems; /// public abstract partial class SharedParcelWrappingSystem : EntitySystem { - [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] protected readonly SharedAudioSystem Audio = default!; [Dependency] protected readonly SharedContainerSystem Container = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_cargo.yml b/Resources/Prototypes/Catalog/Cargo/cargo_cargo.yml index 409670636fbe..e415953a0d47 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_cargo.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_cargo.yml @@ -17,3 +17,13 @@ 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 diff --git a/Resources/Prototypes/Catalog/Fills/Crates/cargo.yml b/Resources/Prototypes/Catalog/Fills/Crates/cargo.yml index 469c24ab97f6..3d6b095b021b 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/cargo.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/cargo.yml @@ -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 $$$ From bdd29dfe442c67c91e5916d5f6bfc43d046a132b Mon Sep 17 00:00:00 2001 From: Centronias Date: Wed, 22 Jan 2025 11:48:37 -0800 Subject: [PATCH 09/10] whitespace: deduplicated. --- .../ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs | 2 -- .../ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs index f65166f7acbc..9104d4c1223c 100644 --- a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs +++ b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs @@ -17,7 +17,6 @@ private void InitializeParcelWrap() SubscribeLocalEvent(OnWrapItemDoAfter); } - private void OnExamined(Entity entity, ref ExaminedEvent args) { if (!args.IsInDetailsRange) @@ -71,7 +70,6 @@ private void OnWrapItemDoAfter(Entity wrapper, ref ParcelWr } } - private bool TryStartWrapDoAfter(EntityUid user, Entity wrapper, EntityUid target) { return _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, diff --git a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs index 8b60719093f9..a6596a7dca53 100644 --- a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs +++ b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs @@ -22,7 +22,6 @@ private void InitializeWrappedParcel() SubscribeLocalEvent(OnDestroyed); } - private void OnComponentInit(Entity entity, ref ComponentInit args) { entity.Comp.Contents = Container.EnsureContainer(entity, entity.Comp.ContainerId); @@ -77,7 +76,6 @@ private void OnDestroyed(Entity parcel, ref T args) } } - private bool TryStartUnwrapDoAfter(EntityUid user, Entity parcel) => _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, From 3ed9e5aa7143b56e3ce4bdc100c46b61970c91b2 Mon Sep 17 00:00:00 2001 From: Centronias Date: Wed, 22 Jan 2025 15:53:00 -0800 Subject: [PATCH 10/10] use limitedcharges replace mostly-duped client/server with if(onserver) --- .../ParcelWrap/ParcelWrappingSystem.cs | 39 ------- .../Systems/ParcelWrappingSystem.cs | 106 ------------------ .../Components/ParcelWrapComponent.cs | 10 +- .../ParcelWrappingSystem.ParcelWrap.cs | 73 +++++++++--- .../ParcelWrappingSystem.WrappedParcel.cs | 50 ++++++++- .../Systems/SharedParcelWrappingSystem.cs | 18 ++- .../Entities/Objects/Misc/parcel_wrap.yml | 4 +- 7 files changed, 119 insertions(+), 181 deletions(-) delete mode 100644 Content.Client/ParcelWrap/ParcelWrappingSystem.cs delete mode 100644 Content.Server/ParcelWrap/Systems/ParcelWrappingSystem.cs diff --git a/Content.Client/ParcelWrap/ParcelWrappingSystem.cs b/Content.Client/ParcelWrap/ParcelWrappingSystem.cs deleted file mode 100644 index f3f3d1f041df..000000000000 --- a/Content.Client/ParcelWrap/ParcelWrappingSystem.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Content.Shared.ParcelWrap.Components; -using Content.Shared.ParcelWrap.Systems; - -namespace Content.Client.ParcelWrap; - -/// -public sealed class ParcelWrappingSystem : SharedParcelWrappingSystem -{ - protected override void WrapInternal(EntityUid user, Entity wrapper, EntityUid target) - { - // Consume a `use` on the wrapper and play a wrapping sound. - wrapper.Comp.Uses -= 1; - Audio.PlayPredicted(wrapper.Comp.WrapSound, target, user); - } - - protected override EntityUid? UnwrapInternal(EntityUid? user, Entity parcel) - { - var parcelTransform = Transform(parcel); - - var containedEntity = parcel.Comp.Contents.ContainedEntity; - 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); - } - } - - // Make some trash and play an unwrapping sound. - Audio.PlayPredicted(parcel.Comp.UnwrapSound, parcel, user); - - return containedEntity; - } -} diff --git a/Content.Server/ParcelWrap/Systems/ParcelWrappingSystem.cs b/Content.Server/ParcelWrap/Systems/ParcelWrappingSystem.cs deleted file mode 100644 index 0d4b010bf127..000000000000 --- a/Content.Server/ParcelWrap/Systems/ParcelWrappingSystem.cs +++ /dev/null @@ -1,106 +0,0 @@ -using Content.Shared.Item; -using Content.Shared.ParcelWrap.Components; -using Content.Shared.ParcelWrap.Systems; -using Robust.Shared.Utility; - -namespace Content.Server.ParcelWrap.Systems; - -/// -public sealed class ParcelWrappingSystem : SharedParcelWrappingSystem -{ - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedItemSystem _item = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - - protected override void WrapInternal(EntityUid user, Entity wrapper, EntityUid target) - { - 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(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(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. - wrapper.Comp.Uses -= 1; - if (wrapper.Comp.Uses <= 0) - { - QueueDel(wrapper); - } - else - { - Dirty(wrapper); - } - - // Play a wrapping sound. - Audio.PlayPredicted(wrapper.Comp.WrapSound, target, user); - } - - protected override EntityUid? UnwrapInternal(EntityUid? user, Entity parcel) - { - var parcelTransform = Transform(parcel); - - var containedEntity = parcel.Comp.Contents.ContainedEntity; - 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); - } - } - - // Make some trash and play an unwrapping sound. - Audio.PlayPredicted(parcel.Comp.UnwrapSound, parcel, user); - 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/Components/ParcelWrapComponent.cs b/Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs index 3294d0d92f63..20e7596cec8e 100644 --- a/Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs +++ b/Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs @@ -11,8 +11,8 @@ namespace Content.Shared.ParcelWrap.Components; /// This component gives its owning entity the ability to wrap items into parcels. /// /// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -[Access] // Default readonly, except for VV editing +[RegisterComponent, NetworkedComponent] +[Access] // Readonly, except for VV editing public sealed partial class ParcelWrapComponent : Component { /// @@ -21,12 +21,6 @@ public sealed partial class ParcelWrapComponent : Component [DataField(required: true)] public EntProtoId ParcelPrototype; - /// - /// How many times this wrap can be used to create parcels. - /// - [DataField, AutoNetworkedField, Access(typeof(SharedParcelWrappingSystem))] - public int Uses = 30; - /// /// If true, parcels created by this will have the same size as the item they /// contain. If false, parcels created by this will always have the size specified by . diff --git a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs index 9104d4c1223c..8bfa4fcb86ba 100644 --- a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs +++ b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs @@ -1,35 +1,22 @@ using Content.Shared.DoAfter; -using Content.Shared.Examine; 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 abstract partial class SharedParcelWrappingSystem +public sealed partial class SharedParcelWrappingSystem { private void InitializeParcelWrap() { - SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnAfterInteract); SubscribeLocalEvent>(OnGetVerbsForParcelWrap); SubscribeLocalEvent(OnWrapItemDoAfter); } - private void OnExamined(Entity entity, ref ExaminedEvent args) - { - if (!args.IsInDetailsRange) - return; - - args.PushMarkup( - Loc.GetString("parcel-wrap-examine-detail-uses", - ("uses", entity.Comp.Uses), - ("markupUsesColor", "lightgray") - ) - ); - } - private void OnAfterInteract(Entity entity, ref AfterInteractEvent args) { if (args.Handled || @@ -92,5 +79,57 @@ private bool TryStartWrapDoAfter(EntityUid user, Entity wra /// The entity using to wrap . /// The wrapping being used. Determines appearance of the spawned parcel. /// The entity being wrapped. - protected abstract void WrapInternal(EntityUid user, Entity wrapper, EntityUid target); + private void WrapInternal(EntityUid user, Entity 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(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(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.UseCharge(wrapper); + if (_net.IsServer && _charges.IsEmpty(wrapper)) + 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 index a6596a7dca53..0db26776b041 100644 --- a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs +++ b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs @@ -10,7 +10,7 @@ namespace Content.Shared.ParcelWrap.Systems; // This part handles Wrapped Parcels -public abstract partial class SharedParcelWrappingSystem +public sealed partial class SharedParcelWrappingSystem { private void InitializeWrappedParcel() { @@ -24,7 +24,7 @@ private void InitializeWrappedParcel() private void OnComponentInit(Entity entity, ref ComponentInit args) { - entity.Comp.Contents = Container.EnsureContainer(entity, entity.Comp.ContainerId); + entity.Comp.Contents = _container.EnsureContainer(entity, entity.Comp.ContainerId); } private void OnUseInHand(Entity entity, ref UseInHandEvent args) @@ -76,8 +76,9 @@ private void OnDestroyed(Entity parcel, ref T args) } } - private bool TryStartUnwrapDoAfter(EntityUid user, Entity parcel) => - _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, + private bool TryStartUnwrapDoAfter(EntityUid user, Entity parcel) + { + return _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, parcel.Comp.UnwrapDelay, new UnwrapWrappedParcelDoAfterEvent(), @@ -86,6 +87,7 @@ private bool TryStartUnwrapDoAfter(EntityUid user, Entity /// Despawns , leaving the contained entity where the parcel was. @@ -96,5 +98,43 @@ private bool TryStartUnwrapDoAfter(EntityUid user, Entity - protected abstract EntityUid? UnwrapInternal(EntityUid? user, Entity parcel); + private EntityUid? UnwrapInternal(EntityUid? user, Entity 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/SharedParcelWrappingSystem.cs b/Content.Shared/ParcelWrap/Systems/SharedParcelWrappingSystem.cs index cd7ac202ff97..0cb447930973 100644 --- a/Content.Shared/ParcelWrap/Systems/SharedParcelWrappingSystem.cs +++ b/Content.Shared/ParcelWrap/Systems/SharedParcelWrappingSystem.cs @@ -1,9 +1,12 @@ +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; @@ -13,12 +16,17 @@ namespace Content.Shared.ParcelWrap.Systems; /// /// /// -public abstract partial class SharedParcelWrappingSystem : EntitySystem +public sealed partial class SharedParcelWrappingSystem : EntitySystem { - [Dependency] protected readonly SharedAudioSystem Audio = default!; - [Dependency] protected readonly SharedContainerSystem Container = default!; + [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!; /// @@ -41,8 +49,8 @@ public bool IsWrappable(Entity wrapper, EntityUid target) return // Wrapping cannot wrap itself wrapper.Owner != target && - // Wrapper should never have non-positive uses, but may as well make sure. - wrapper.Comp.Uses > 0 && + // Wrapper should never be empty, but may as well make sure. + !_charges.IsEmpty(wrapper) && _whitelist.IsWhitelistPass(wrapper.Comp.Whitelist, target) && _whitelist.IsBlacklistFail(wrapper.Comp.Blacklist, target); } diff --git a/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml b/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml index be7f970f15fc..5b26f2a8ffed 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml @@ -9,7 +9,6 @@ state: brown - type: ParcelWrap parcelPrototype: WrappedParcel - uses: 30 wrapDelay: 1.0 wrapSound: /Audio/Items/Handcuffs/rope_start.ogg whitelist: @@ -22,6 +21,9 @@ 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 + charges: 30 + maxCharges: 30 - type: entity parent: BaseItem