From 90582f27eebc15aa8beecb1f5655826e0b052ade Mon Sep 17 00:00:00 2001 From: Centronias Date: Sat, 26 Apr 2025 16:24:25 -0700 Subject: [PATCH] Adds Parcel Wrap (#34471) * Parcel Wrap * fix TG sprite licenses update attribution on modified `unwrapped` sprite to better conform to CC's guidance * ContainerContainer test failure fix * Just easy changes for now. * Imagine building your code before pushing it for review * The rest of the PR comments * PR comments * more comments + cargo orderability * whitespace: deduplicated. * use limitedcharges replace mostly-duped client/server with if(onserver) * cabinet perspective sprites * web edit detected fite me * @ps3moira 's new sprites for me :) * add a touch of attribution * EmoGarbage Review * Merge with master * Merge with master --------- Co-authored-by: EmoGarbage404 --- .../Components/ParcelWrapComponent.cs | 67 +++++++++ .../Components/WrappedParcelComponent.cs | 46 ++++++ .../Components/WrappedParcelVisuals.cs | 13 ++ .../ParcelWrap/Systems/ParcelWrapEvents.cs | 10 ++ .../ParcelWrappingSystem.ParcelWrap.cs | 135 +++++++++++++++++ .../ParcelWrappingSystem.WrappedParcel.cs | 139 ++++++++++++++++++ .../Systems/ParcelWrappingSystem.cs | 57 +++++++ Resources/Locale/en-US/pacel-wrap.ftl | 10 ++ .../Prototypes/Catalog/Cargo/cargo_cargo.yml | 10 ++ .../Prototypes/Catalog/Fills/Crates/cargo.yml | 11 ++ .../Prototypes/Entities/Objects/Fun/pai.yml | 1 + .../Entities/Objects/Misc/parcel_wrap.yml | 100 +++++++++++++ Resources/Prototypes/tags.yml | 3 + .../Misc/ParcelWrap/parcel_wrap.rsi/brown.png | Bin 0 -> 358 bytes .../ParcelWrap/parcel_wrap.rsi/empty-roll.png | Bin 0 -> 307 bytes .../Misc/ParcelWrap/parcel_wrap.rsi/meta.json | 19 +++ .../parcel_wrap_trash.rsi/brown.png | Bin 0 -> 482 bytes .../parcel_wrap_trash.rsi/meta.json | 15 ++ .../ParcelWrap/wrapped_parcel.rsi/crate.png | Bin 0 -> 320 bytes .../ParcelWrap/wrapped_parcel.rsi/locker.png | Bin 0 -> 325 bytes .../ParcelWrap/wrapped_parcel.rsi/meta.json | 39 +++++ .../wrapped_parcel.rsi/parcel-large.png | Bin 0 -> 321 bytes .../wrapped_parcel.rsi/parcel-medium.png | Bin 0 -> 319 bytes .../wrapped_parcel.rsi/parcel-small.png | Bin 0 -> 313 bytes .../wrapped_parcel.rsi/parcel-tiny.png | Bin 0 -> 314 bytes .../wrapped_parcel.rsi/tall-crate.png | Bin 0 -> 322 bytes 26 files changed, 675 insertions(+) create mode 100644 Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs create mode 100644 Content.Shared/ParcelWrap/Components/WrappedParcelComponent.cs create mode 100644 Content.Shared/ParcelWrap/Components/WrappedParcelVisuals.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/ParcelWrappingSystem.cs create mode 100644 Resources/Locale/en-US/pacel-wrap.ftl create mode 100644 Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.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/meta.json 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/crate.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.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.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.png diff --git a/Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs b/Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs new file mode 100644 index 0000000000..b5afb49dca --- /dev/null +++ b/Content.Shared/ParcelWrap/Components/ParcelWrapComponent.cs @@ -0,0 +1,67 @@ +using Content.Shared.Item; +using Content.Shared.Whitelist; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.ParcelWrap.Components; + +/// +/// This component gives its owning entity the ability to wrap items into parcels. +/// +/// +[RegisterComponent, NetworkedComponent] +[Access] // Readonly, except for VV editing +public sealed partial class ParcelWrapComponent : Component +{ + /// + /// The of the parcel created by using this component. + /// + [DataField(required: true)] + public EntProtoId ParcelPrototype; + + /// + /// 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] + 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] + 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] + public bool WrappedItemsMaintainShape; + + /// + /// How long it takes to use this to wrap something. + /// + [DataField(required: true)] + public TimeSpan WrapDelay = TimeSpan.FromSeconds(1); + + /// + /// Sound played when this is used to wrap something. + /// + [DataField] + public SoundSpecifier? WrapSound; + + /// + /// Defines the set of things which can be wrapped (unless it fails the ). + /// + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// Defines the set of things which cannot be wrapped (even if it passes the ). + /// + [DataField] + public EntityWhitelist? Blacklist; +} diff --git a/Content.Shared/ParcelWrap/Components/WrappedParcelComponent.cs b/Content.Shared/ParcelWrap/Components/WrappedParcelComponent.cs new file mode 100644 index 0000000000..e0ceaeae49 --- /dev/null +++ b/Content.Shared/ParcelWrap/Components/WrappedParcelComponent.cs @@ -0,0 +1,46 @@ +using Content.Shared.ParcelWrap.Systems; +using Robust.Shared.Audio; +using Robust.Shared.Containers; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.ParcelWrap.Components; + +/// +/// 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, NetworkedComponent, Access(typeof(ParcelWrappingSystem))] +public sealed partial class WrappedParcelComponent : Component +{ + /// + /// The contents of this parcel. + /// + [ViewVariables(VVAccess.ReadOnly)] + public ContainerSlot Contents = default!; + + /// + /// Specifies the entity to spawn when this parcel is unwrapped. + /// + [DataField] + public EntProtoId? UnwrapTrash; + + /// + /// How long it takes to unwrap this parcel. + /// + [DataField(required: true)] + public TimeSpan UnwrapDelay = TimeSpan.FromSeconds(1); + + /// + /// Sound played when unwrapping this parcel. + /// + [DataField] + public SoundSpecifier? UnwrapSound; + + /// + /// The ID of . + /// + [DataField, ViewVariables(VVAccess.ReadOnly)] + public string ContainerId = "contents"; +} diff --git a/Content.Shared/ParcelWrap/Components/WrappedParcelVisuals.cs b/Content.Shared/ParcelWrap/Components/WrappedParcelVisuals.cs new file mode 100644 index 0000000000..1c90ca031d --- /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/Content.Shared/ParcelWrap/Systems/ParcelWrapEvents.cs b/Content.Shared/ParcelWrap/Systems/ParcelWrapEvents.cs new file mode 100644 index 0000000000..b64ecc3c00 --- /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 0000000000..f924a7c6c9 --- /dev/null +++ b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.ParcelWrap.cs @@ -0,0 +1,135 @@ +using Content.Shared.DoAfter; +using Content.Shared.Interaction; +using Content.Shared.Item; +using Content.Shared.ParcelWrap.Components; +using Content.Shared.Verbs; +using Robust.Shared.Utility; + +namespace Content.Shared.ParcelWrap.Systems; + +// This part handles Parcel Wrap. +public sealed partial class ParcelWrappingSystem +{ + private void InitializeParcelWrap() + { + SubscribeLocalEvent(OnAfterInteract); + SubscribeLocalEvent>(OnGetVerbsForParcelWrap); + SubscribeLocalEvent(OnWrapItemDoAfter); + } + + 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) + { + 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.TryUseCharges(wrapper.Owner, 1); + if (_net.IsServer && _charges.IsEmpty(wrapper.Owner)) + QueueDel(wrapper); + + // Play a wrapping sound. + _audio.PlayPredicted(wrapper.Comp.WrapSound, target, user); + } +} diff --git a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs new file mode 100644 index 0000000000..bb0c9437ff --- /dev/null +++ b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs @@ -0,0 +1,139 @@ +using Content.Shared.Destructible; +using Content.Shared.DoAfter; +using Content.Shared.Interaction.Events; +using Content.Shared.Materials; +using Content.Shared.ParcelWrap.Components; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Shared.Containers; + +namespace Content.Shared.ParcelWrap.Systems; + +// This part handles Wrapped Parcels +public sealed partial class ParcelWrappingSystem +{ + private void InitializeWrappedParcel() + { + SubscribeLocalEvent(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; + + args.Handled = TryStartUnwrapDoAfter(args.User, entity); + } + + 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) + { + return _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 containedEntity = parcel.Comp.Contents.ContainedEntity; + _audio.PlayPredicted(parcel.Comp.UnwrapSound, parcel, user); + + // If we're on the client, just return the contained entity and don't try to despawn the parcel. + if (!_net.IsServer) + return containedEntity; + + var parcelTransform = Transform(parcel); + + if (containedEntity is { } parcelContents) + { + _container.Remove(parcelContents, + parcel.Comp.Contents, + true, + true, + parcelTransform.Coordinates); + + // If the parcel is in a container, try to put the unwrapped contents in that container. + if (_container.TryGetContainingContainer((parcel, null, null), out var outerContainer)) + { + // Make space in the container for the parcel contents. + _container.Remove((parcel, null, null), outerContainer, force: true); + _container.InsertOrDrop((parcelContents, null, null), outerContainer); + } + } + + // Spawn unwrap trash. + if (parcel.Comp.UnwrapTrash is { } trashProto) + { + var trash = Spawn(trashProto, parcelTransform.Coordinates); + _transform.DropNextTo((trash, null), (parcel, parcelTransform)); + } + + QueueDel(parcel); + + return containedEntity; + } +} diff --git a/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.cs b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.cs new file mode 100644 index 0000000000..b19f4b845c --- /dev/null +++ b/Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.cs @@ -0,0 +1,57 @@ +using Content.Shared.Charges.Systems; +using Content.Shared.DoAfter; +using Content.Shared.Item; +using Content.Shared.ParcelWrap.Components; +using Content.Shared.Popups; +using Content.Shared.Whitelist; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Network; + +namespace Content.Shared.ParcelWrap.Systems; + +/// +/// This system handles things related to package wrap, both wrapping items to create parcels, and unwrapping existing +/// parcels. +/// +/// +/// +public sealed partial class ParcelWrappingSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedChargesSystem _charges = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedItemSystem _item = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + + /// + 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 be empty, but may as well make sure. + !_charges.IsEmpty(wrapper.Owner) && + _whitelist.IsWhitelistPass(wrapper.Comp.Whitelist, target) && + _whitelist.IsBlacklistFail(wrapper.Comp.Blacklist, target); + } +} diff --git a/Resources/Locale/en-US/pacel-wrap.ftl b/Resources/Locale/en-US/pacel-wrap.ftl new file mode 100644 index 0000000000..2351892fa5 --- /dev/null +++ b/Resources/Locale/en-US/pacel-wrap.ftl @@ -0,0 +1,10 @@ +parcel-wrap-verb-wrap = Wrap +parcel-wrap-verb-unwrap = Unwrap + +parcel-wrap-popup-parcel-destroyed = The wrapping containing { THE($contents) } is destroyed! + +# Shown when parcel wrap is examined in details range +parcel-wrap-examine-detail-uses = { $uses -> + [one] There is [color={$markupUsesColor}]{$uses}[/color] use left + *[other] There are [color={$markupUsesColor}]{$uses}[/color] uses left +}. diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_cargo.yml b/Resources/Prototypes/Catalog/Cargo/cargo_cargo.yml index 409670636f..e415953a0d 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 469c24ab97..3d6b095b02 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 $$$ diff --git a/Resources/Prototypes/Entities/Objects/Fun/pai.yml b/Resources/Prototypes/Entities/Objects/Fun/pai.yml index 2a8e0e6a7f..1f5136935a 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/pai.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/pai.yml @@ -92,6 +92,7 @@ whitelist: components: - SecretStash + - WrappedParcel - type: entity parent: [ PersonalAI, BaseSyndicateContraband] diff --git a/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml b/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml new file mode 100644 index 0000000000..58455ba211 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Misc/parcel_wrap.yml @@ -0,0 +1,100 @@ +- type: entity + parent: BaseItem + id: ParcelWrap + name: parcel wrap + description: Paper used contain items for transport. + components: + - type: Sprite + sprite: Objects/Misc/ParcelWrap/parcel_wrap.rsi + state: brown + - type: ParcelWrap + parcelPrototype: WrappedParcel + wrapDelay: 1.0 + wrapSound: + path: /Audio/Items/Handcuffs/rope_start.ogg + params: + volume: -5 + variation: 0.05 + whitelist: + components: + - Item + blacklist: + components: + - NukeDisk # Don't try to hide the disk. + - WrappedParcel # No wrapping wrapped things. + tags: + - ParcelWrapBlacklist + - FakeNukeDisk # So you can't tell if the nuke disk is real or fake depending on if it can be wrapped or not. + - type: LimitedCharges + maxCharges: 30 + +- type: entity + parent: BaseItem + id: WrappedParcel + categories: [ HideSpawnMenu ] + name: wrapped parcel + description: Something wrapped up in paper. I wonder what's inside... + components: + - type: ContainerContainer + containers: + contents: !type:ContainerSlot + - type: Appearance + - type: GenericVisualizer + visuals: + enum.WrappedParcelVisuals.Size: + enum.WrappedParcelVisuals.Layer: + "Tiny": { state: "parcel-tiny" } + "Small": { state: "parcel-small" } + "Medium": { state: "parcel-medium" } + "Large": { state: "parcel-medium" } + "Huge": { state: "parcel-large" } + "Ginormous": { state: "parcel-large" } + - type: Sprite + sprite: Objects/Misc/ParcelWrap/wrapped_parcel.rsi + layers: + - state: parcel-medium + map: [ "enum.WrappedParcelVisuals.Layer" ] + - type: WrappedParcel + unwrapDelay: 0.5 + unwrapSound: + path: /Audio/Effects/poster_broken.ogg + params: + volume: -4 + unwrapTrash: ParcelWrapTrash + - type: Damageable + damageContainer: Inorganic + - type: Destructible + thresholds: + - trigger: + !type:DamageTypeTrigger + damageType: Slash + damage: 5 + behaviors: + - !type:PlaySoundBehavior + sound: + path: /Audio/Effects/poster_broken.ogg + params: + volume: -4 + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: Tag + tags: + - Recyclable # Parcel entity is recyclable, and when it's destroyed, it'll drop its contents. + +- type: entity + parent: BaseItem + id: ParcelWrapTrash + categories: [ HideSpawnMenu ] + name: parcel wrap + description: The disappointing remnants of an unwrapped parcel. + components: + - type: Sprite + sprite: Objects/Misc/ParcelWrap/parcel_wrap_trash.rsi + layers: + - state: brown + - type: Tag + tags: + - Trash + - ParcelWrapBlacklist # No exponential wrapper trash-splosions. + - Recyclable + - type: SpaceGarbage diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 682b561f0f..3514589bc0 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -957,6 +957,9 @@ - type: Tag id: Pancake +- type: Tag + id: ParcelWrapBlacklist + - type: Tag id: Payload # for grenade/bomb crafting diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/brown.png b/Resources/Textures/Objects/Misc/ParcelWrap/parcel_wrap.rsi/brown.png new file mode 100644 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$oJmAMR9J=WmOpNTFcikWRFz=CT`)ts!2nV(z{1EmasiH$3-lZrvv7e*VW3+X z09C5#5W3JMj~bGKtwd@Eew*Vbf8YCK2Vh}g;g4C};*cZ`I(fa_9hA?#zlHcFasdEY zA>gkW0CcJ*v<-~24LuuKApn5HwU~8Dg4^9;mBc~kIu;zu==aN}1%SbC{+i)=|EkoA z$qLBj#i^`G9CVS-`>aV=HbQ$UC{C(P`ex1c}W+Kh&+dM>8$A80@&=Fo(Sok-5 Y1C8XvZZ}7l$N&HU07*qoM6N<$f(;hd0{{R3 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 0000000000..6fcb6a4e91 --- /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": "Hue shifted from the original by Shaeone: https://github.com/space-wizards/space-station-14/blob/43eb542a60772dc49e38993a54404a5799dfe344/Resources/Textures/Objects/Decoration/present.rsi/unwrapped.png", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "brown", + "directions": 1 + } + ] +} diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/crate.png new file mode 100644 index 0000000000000000000000000000000000000000..7784d1f2515c08fc7ae4958ebcaae536194ea3b0 GIT binary patch literal 320 zcmV-G0l)rPx#`bk7VR9J;$U>F6XU=)mkQ7{Td!2knBY=W*j68~vw>Fz5p;P$d$v%JJhhb9*H zY`cOXhT?!bd*=L?&~s(zP4}g$#lGcYhPFr2vU%W&lOE9?$%)sgsrch?GrySr8} zSg49o&GMdYR~TMLyE7D&moO-c^Wk;?0|NuY=JQXeW-&4lHxYr;KR3H1LuGb-+nRt`ioh>(!~KV%Rhhni6O^=u8oTM$tL;If>70j)aDan+zn5KP&91FPkO0ssID-Ce;% SwGRaV0000Px#|4BqaR9J;$U>NCukrc(QIuid$wP5#^7kKSpA;t0%FCC&S?%8&QD0M_R;Le^o z|0VQX8G6%w392ogxtW21fq`N3d3T1J&p%M^09PG}|95w-V7R+$1%rjE7(oLAoy-}g zPiSEf?Y2MY#jiB?TBPltoB#fU-Cr(Q27U zGMNTo)B&Ro7VQ!Pj5>g34*2lx*T6a8!?#}yzy1=}g&?X?N~~{S;Md>( z44=RKq@Dwa^$iSs{`QmM$n94IdqqS$fLPza0I`-cFc8@{%yHEqDF~+QzClu-b3g$A XYAjAFI%@S`00000NkvXXu0mjf?gNQa literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/meta.json b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/meta.json new file mode 100644 index 0000000000..f07107aaa8 --- /dev/null +++ b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/meta.json @@ -0,0 +1,39 @@ +{ + "version": 1, + "license": "CC-BY-SA-4.0", + "copyright": "created for ss14 by ps3moira (github) based on package sprites from vgstation at https://github.com/vgstation-coders/vgstation13/pull/36993", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "parcel-large", + "directions": 1 + }, + { + "name": "parcel-medium", + "directions": 1 + }, + { + "name": "parcel-small", + "directions": 1 + }, + { + "name": "parcel-tiny", + "directions": 1 + }, + { + "name": "locker", + "directions": 1 + }, + { + "name": "crate", + "directions": 1 + }, + { + "name": "tall-crate", + "directions": 1 + } + ] +} diff --git a/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-large.png b/Resources/Textures/Objects/Misc/ParcelWrap/wrapped_parcel.rsi/parcel-large.png new file mode 100644 index 0000000000000000000000000000000000000000..4c7bfbb06996393737fe4ca6914007f0c4ef04d3 GIT binary patch literal 321 zcmV-H0lxl;P)Px#`$F6XU=)mkQ82)Okr+j;IuidWv1Ip^7dY)VOa5ehr)hK79Md@ar$(N)&HVOpFh3fnR_BGkpH` zlQIVo;{#ma^S7T2M{d8u+ZG_`0AhTA3lL*D0|S8;QI4wy*+DR6_YI<2TcZE~rK?UB TWM=+&00000NkvXXu0mjfuMmNc 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..c94178d9cf641bd4c9330b177b386d2873e3e213 GIT binary patch literal 319 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}KRsO>Ln2y} z6C_v{Cy4Yk1sZU3g))$%yu=uO$+#be0VEVqs@BF5x42Js-u+{zjd;Y`E+y19y9eVUP zG@id-X9P5fE8o>+3UkLtRx^j5r#w6^A;&G(Nu{tJoO0K^W8EuvGX{qHAN>TnTcx6b PfxzJD>gTe~DWM4f)rW;! 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..8f05e5155c3a5c3c963b95b9d459a92ffbfecee6 GIT binary patch literal 313 zcmV-90mlA`P)Px#^GQTOR9J;$U>F6XU=)mkQ7{Td!AJ*;q-t>0k@!!n+TB-P5Hz2KRLe`ebO@^J z*>;6!Gl_P;qI;# z3>K3|R4ehr)hK79Md@ar$pWfk$cfLLGs`um^Z^S7VWcL1@z z`uy!D!;#ysh$@CjasaWuBE@nB1`;Zx99IpZf?&$-8zfh7BO3q!OPEd!Iq{M900000 LNkvXXu0mjf*#d=n 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..a82255387e73b80f47e740ba64fed25797681a64 GIT binary patch literal 314 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Up!qLLn2y} z6C_v{Cy4Yk1sZU<9n_F`*)TDLcb%2cf6kRRYoD@Se70#qR%PdOKEI#ya>G3g`%KvH z+%$f^zh$1bbN(*ffu^6Ok}yLX2fM6BpaJi%@? z)7WSJg+mIb4j(I+tn%8Eqw(zX`46VXTa^kmZ?cd-Jn{DW74I2JJsH`!Qw8^AO*#70 zW0M5ij1!kynQMN0%YGp9;o)ulw)lF^X8}A8B~l7ccE{KMH?H~jZE_hOkaPZiosrDW zzg`0rLn2y} z6C_v{Cy4YkP2`jK=(+2a$DiffT6eGC-?vxaaJLlcckAIUGpeHwjlldoc}^q z>v;CQ+$RzpV^eI*3GX2 z6n<2~umdKI;Vst0BOX4=l}o! literal 0 HcmV?d00001 -- 2.51.2