diff --git a/Content.Shared/Construction/Components/BlockAnchorOnComponent.cs b/Content.Shared/Construction/Components/BlockAnchorOnComponent.cs
new file mode 100644
index 0000000000..0ade0dba62
--- /dev/null
+++ b/Content.Shared/Construction/Components/BlockAnchorOnComponent.cs
@@ -0,0 +1,24 @@
+using Content.Shared.Construction.EntitySystems;
+using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Construction.Components;
+
+///
+/// Will not allow anchoring if there is an anchored item in the same tile that fails the .
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(BlockAnchorOnSystem))]
+public sealed partial class BlockAnchorOnComponent : Component
+{
+ ///
+ /// If not null, entities that match this whitelist are allowed.
+ ///
+ [DataField]
+ public EntityWhitelist? Whitelist;
+
+ ///
+ /// If not null, entities that match this blacklist are not allowed.
+ ///
+ [DataField]
+ public EntityWhitelist? Blacklist;
+}
diff --git a/Content.Shared/Construction/EntitySystems/BlockAnchorOnSystem.cs b/Content.Shared/Construction/EntitySystems/BlockAnchorOnSystem.cs
new file mode 100644
index 0000000000..46550fb0fc
--- /dev/null
+++ b/Content.Shared/Construction/EntitySystems/BlockAnchorOnSystem.cs
@@ -0,0 +1,82 @@
+using Content.Shared.Construction.Components;
+using Content.Shared.Popups;
+using Content.Shared.Whitelist;
+using Robust.Shared.Map.Components;
+
+namespace Content.Shared.Construction.EntitySystems;
+
+///
+/// Prevents anchoring an item in the same tile as an item matching the .
+///
+///
+public sealed class BlockAnchorOnSystem : EntitySystem
+{
+ [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
+ [Dependency] private readonly SharedMapSystem _map = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly SharedTransformSystem _xform = default!;
+
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnAnchorStateChanged);
+ SubscribeLocalEvent(OnAnchorAttempt);
+ }
+
+ ///
+ /// Handles the .
+ ///
+ private void OnAnchorStateChanged(Entity ent, ref AnchorStateChangedEvent args)
+ {
+ if (!args.Anchored)
+ return;
+
+ if (!HasOverlap((ent, ent.Comp, Transform(ent))))
+ return;
+
+ _popup.PopupPredicted(Loc.GetString("anchored-already-present"), ent, null);
+ _xform.Unanchor(ent, Transform(ent));
+ }
+
+ ///
+ /// Handles the .
+ ///
+ private void OnAnchorAttempt(Entity ent, ref AnchorAttemptEvent args)
+ {
+ if (args.Cancelled)
+ return;
+
+ if (!HasOverlap((ent, ent.Comp, Transform(ent))))
+ return;
+
+ _popup.PopupPredicted(Loc.GetString("anchored-already-present"), ent, args.User);
+ args.Cancel();
+ }
+
+ ///
+ /// Check if there is any anchored overlap with non whitelisted or blacklisted entities.
+ ///
+ /// True if there is, false if there isn't
+ private bool HasOverlap(Entity ent)
+ {
+ if (ent.Comp2.GridUid is not { } grid || !TryComp(grid, out var gridComp))
+ return false;
+
+ var indices = _map.TileIndicesFor(grid, gridComp, ent.Comp2.Coordinates);
+ var enumerator = _map.GetAnchoredEntitiesEnumerator(grid, gridComp, indices);
+
+ while (enumerator.MoveNext(out var otherEnt))
+ {
+ // Don't match yourself.
+ if (otherEnt == ent)
+ continue;
+
+ if (!_whitelist.CheckBoth(otherEnt, ent.Comp1.Blacklist, ent.Comp1.Whitelist))
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/Content.Shared/Storage/Components/AnchoredStorageFilterComponent.cs b/Content.Shared/Storage/Components/AnchoredStorageFilterComponent.cs
new file mode 100644
index 0000000000..c3cde90cab
--- /dev/null
+++ b/Content.Shared/Storage/Components/AnchoredStorageFilterComponent.cs
@@ -0,0 +1,28 @@
+using Content.Shared.Storage.EntitySystems;
+using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Storage.Components;
+
+///
+/// Entities with this component will eject all items that match the whitelist / blacklist when anchored.
+/// It also doesn't allow any items to be inserted that fit the whitelist / blacklist while anchored.
+///
+///
+/// If you have a smuggler stash that has a player inside of it, you want to eject the player before its anchored so they don't get stuck
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(AnchoredStorageFilterSystem))]
+public sealed partial class AnchoredStorageFilterComponent : Component
+{
+ ///
+ /// If not null, entities that do not match this whitelist will be ejected.
+ ///
+ [DataField]
+ public EntityWhitelist? Whitelist;
+
+ ///
+ /// If not null, entities that match this blacklist will be ejected..
+ ///
+ [DataField]
+ public EntityWhitelist? Blacklist;
+}
diff --git a/Content.Shared/Storage/EntitySystems/AnchoredStorageFilterSystem.cs b/Content.Shared/Storage/EntitySystems/AnchoredStorageFilterSystem.cs
new file mode 100644
index 0000000000..ea98fc2852
--- /dev/null
+++ b/Content.Shared/Storage/EntitySystems/AnchoredStorageFilterSystem.cs
@@ -0,0 +1,54 @@
+using Content.Shared.Storage.Components;
+using Content.Shared.Whitelist;
+using Robust.Shared.Containers;
+
+namespace Content.Shared.Storage.EntitySystems;
+
+///
+/// Ejects items that do not match a from a storage when it is anchored.
+///
+///
+public sealed class AnchoredStorageFilterSystem : EntitySystem
+{
+ [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
+
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnAnchorStateChanged);
+ SubscribeLocalEvent(OnInsertAttempt);
+ }
+
+ ///
+ /// Handles the .
+ ///
+ private void OnAnchorStateChanged(Entity ent, ref AnchorStateChangedEvent args)
+ {
+ if (!args.Anchored)
+ return;
+
+ if (!TryComp(ent, out var storage))
+ return;
+
+ foreach (var item in storage.StoredItems.Keys)
+ {
+ if (!_whitelist.CheckBoth(item, ent.Comp.Blacklist, ent.Comp.Whitelist))
+ _container.RemoveEntity(ent, item);
+ }
+ }
+
+ ///
+ /// Handles the .
+ ///
+ private void OnInsertAttempt(Entity ent, ref ContainerIsInsertingAttemptEvent args)
+ {
+ if (args.Cancelled)
+ return;
+
+ if (Transform(ent).Anchored && !_whitelist.CheckBoth(args.EntityUid, ent.Comp.Blacklist, ent.Comp.Whitelist))
+ args.Cancel();
+ }
+}
diff --git a/Resources/Locale/en-US/construction/components/block-anchor-component.ftl b/Resources/Locale/en-US/construction/components/block-anchor-component.ftl
new file mode 100644
index 0000000000..cc6fcc95ea
--- /dev/null
+++ b/Resources/Locale/en-US/construction/components/block-anchor-component.ftl
@@ -0,0 +1 @@
+anchored-already-present = There's already something anchored here!
diff --git a/Resources/Locale/en-US/store/uplink-catalog.ftl b/Resources/Locale/en-US/store/uplink-catalog.ftl
index 4d618d45dc..7d93dab869 100644
--- a/Resources/Locale/en-US/store/uplink-catalog.ftl
+++ b/Resources/Locale/en-US/store/uplink-catalog.ftl
@@ -463,3 +463,6 @@ uplink-business-card-desc = A business card that you can give to someone to demo
uplink-fake-mindshield-name = Fake Mindshield
uplink-fake-mindshield-desc = A togglable implant capable of mimicking the same transmissions a real mindshield puts out when on, tricking capable Heads-up displays into thinking you have a mindshield (Nanotrasen brand implanter not provided.)
+
+uplink-smuggler-satchel-name = Smuggler's Satchel
+uplink-smuggler-satchel-desc = A handy, suspicious looking satchel. Just flat enough to fit underneath floor tiles.
diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml
index 7b000e30ad..4b5688a46f 100644
--- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml
+++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml
@@ -98,4 +98,5 @@
ClothingUniformJumpskirtTacticool: 1
ToyFigurinePassenger: 1
ToyFigurineGreytider: 1
+ ClothingBackpackSatchelSmugglerUnanchored: 1
# DO NOT ADD MORE, USE UNIFORM DYING
diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml
index 086c29304c..f4f1d754c4 100644
--- a/Resources/Prototypes/Catalog/uplink_catalog.yml
+++ b/Resources/Prototypes/Catalog/uplink_catalog.yml
@@ -2148,3 +2148,16 @@
whitelist:
- Chef
- Mime
+
+- type: listing
+ id: UplinkSmugglerSatchel
+ name: uplink-smuggler-satchel-name
+ description: uplink-smuggler-satchel-desc
+ productEntity: ClothingBackpackSatchelSmugglerUnanchored
+ discountCategory: usualDiscounts
+ discountDownTo:
+ Telecrystal: 1
+ cost:
+ Telecrystal: 2
+ categories:
+ - UplinkDeception
diff --git a/Resources/Prototypes/Entities/Clothing/Back/smuggler.yml b/Resources/Prototypes/Entities/Clothing/Back/smuggler.yml
new file mode 100644
index 0000000000..c9d7f61890
--- /dev/null
+++ b/Resources/Prototypes/Entities/Clothing/Back/smuggler.yml
@@ -0,0 +1,120 @@
+- type: entity
+ abstract: true
+ id: BaseSubfloorAnchorStorage
+ components:
+ - type: Appearance
+ - type: SubFloorHide
+ - type: Anchorable
+ - type: CollideOnAnchor
+ - type: Transform
+ anchored: false
+ - type: AnchoredStorageFilter
+ blacklist:
+ components:
+ - HumanoidAppearance # for forks with felines
+ - type: BlockAnchorOn
+ blacklist:
+ components:
+ - AnchoredStorageFilter
+ - type: Visibility
+ layer: 1
+
+- type: entity
+ abstract: true
+ parent: BaseSubfloorAnchorStorage
+ id: BaseSubfloorAnchorStorageAnchored
+ placement:
+ mode: SnapgridCenter
+ components:
+ - type: Transform
+ anchored: true
+ - type: Physics
+ canCollide: false
+ bodyType: Static
+
+- type: entity
+ parent: [ BaseSubfloorAnchorStorageAnchored, ClothingBackpackSatchel, BaseMinorContraband ]
+ id: ClothingBackpackSatchelSmuggler
+ name: smuggler's satchel
+ suffix: Empty
+ description: A dingy, suspicious looking satchel.
+ components:
+ - type: Sprite
+ sprite: Clothing/Back/Satchels/smuggler.rsi
+ state: icon
+
+- type: entity
+ parent: [ BaseSubfloorAnchorStorage, ClothingBackpackSatchel, BaseMinorContraband ]
+ id: ClothingBackpackSatchelSmugglerUnanchored
+ name: smuggler's satchel
+ suffix: Empty, Unanchored
+ description: A dingy, suspicious looking satchel.
+ components:
+ - type: Sprite
+ sprite: Clothing/Back/Satchels/smuggler.rsi
+ state: icon
+
+- type: entity
+ parent: [ BaseSubfloorAnchorStorageAnchored, BriefcaseSyndie, BaseMinorContraband ]
+ id: BriefcaseSmugglerCash
+ name: smuggler's briefcase
+ suffix: Smuggler, Do Not Map
+ components:
+ - type: EntityTableContainerFill
+ containers:
+ storagebase: !type:AllSelector
+ children:
+ - id: SpaceCash5000
+ amount: !type:RangeNumberSelector
+ range: 1, 11
+
+- type: entity
+ parent: [ BaseSubfloorAnchorStorageAnchored, ClothingBackpackDuffelClown, BaseMinorContraband ]
+ id: ClothingBackpackDuffelClownSmuggler
+ name: smuggler's clown duffel bag
+ suffix: Smuggler, Do Not Map
+ components:
+ - type: EntityTableContainerFill
+ containers:
+ storagebase: !type:AllSelector
+ children:
+ - id: SpeedLoaderCap
+ amount: !type:RangeNumberSelector
+ range: 4, 8
+ - !type:GroupSelector
+ children:
+ - id: RevolverCapGun
+ amount: !type:RangeNumberSelector
+ range: 4, 8
+ weight: 95
+ - id: RevolverCapGunFake
+ amount: !type:RangeNumberSelector
+ range: 1, 8
+ weight: 5
+
+- type: entity
+ parent: ClothingBackpackSatchelSmuggler
+ id: ClothingBackpackSatchelSmugglerFilled
+ suffix: Smuggler, Do Not Map
+ components:
+ - type: Sprite
+ sprite: Clothing/Back/Satchels/smuggler.rsi
+ state: icon
+ - type: EntityTableContainerFill
+ containers:
+ storagebase: !type:NestedSelector
+ tableId: FillSmugglerBackpack
+
+- type: entity
+ parent: MarkerBase
+ id: RandomSatchelSpawner
+ name: random smuggler's satchel spawner
+ suffix: Do Not Map
+ components:
+ - type: Sprite
+ layers:
+ - sprite: Clothing/Back/Satchels/smuggler.rsi
+ state: icon
+ - type: EntityTableSpawner
+ table: !type:NestedSelector
+ tableId: RandomSatchelTable
diff --git a/Resources/Prototypes/Entities/Clothing/Back/smuggler_tables.yml b/Resources/Prototypes/Entities/Clothing/Back/smuggler_tables.yml
new file mode 100644
index 0000000000..b89f451cbb
--- /dev/null
+++ b/Resources/Prototypes/Entities/Clothing/Back/smuggler_tables.yml
@@ -0,0 +1,673 @@
+#Table
+- type: entityTable
+ id: RandomSatchelTable
+ table: !type:GroupSelector
+ children:
+ - id: ClothingBackpackSatchelSmugglerFilled
+ weight: 85
+ - id: BriefcaseSmugglerCash
+ weight: 10
+ - id: ClothingBackpackDuffelClownSmuggler
+ weight: 5
+
+#Table
+- type: entityTable
+ id: FillSmugglerBackpack
+ table: !type:GroupSelector
+ children:
+ - !type:NestedSelector
+ tableId: RandomSatchelTable1
+ - !type:NestedSelector
+ tableId: RandomSatchelTable2
+ - !type:NestedSelector
+ tableId: RandomSatchelTable3
+ - !type:NestedSelector
+ tableId: RandomSatchelTable4
+ - !type:NestedSelector
+ tableId: RandomSatchelTable5
+ - !type:NestedSelector
+ tableId: RandomSatchelTable6
+
+- type: entityTable
+ id: RandomSatchelTable1
+ table: !type:AllSelector
+ children:
+ - !type:NestedSelector
+ tableId: RandomSatchelGenericTable
+ - !type:NestedSelector
+ tableId: RandomSatchelFunnyTable
+ - !type:NestedSelector
+ tableId: RandomSatchelClothingTable
+ - !type:NestedSelector
+ tableId: RandomSatchelCannabisTable
+ - !type:NestedSelector
+ tableId: RandomSatchelGizmosTable
+ - !type:NestedSelector
+ tableId: RandomSatchelChemsTable
+
+- type: entityTable
+ id: RandomSatchelFunnyTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 20
+ - id: WhoopieCushion
+ - id: RubberChicken
+ - id: PlasticBanana
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: PillSpaceDrugs
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+ - id: StrangePill
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+
+- type: entityTable
+ id: RandomSatchelCannabisTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 4
+ - id: Joint
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: Blunt
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: SmokingPipeFilledCannabis
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: GroundCannabis
+ amount: !type:RangeNumberSelector
+ range: 1, 15
+
+- type: entityTable
+ id: RandomSatchelGizmosTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 40
+ - id: TimerTrigger
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+ - id: SignalTrigger
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+ - id: VoiceTrigger
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+ - id: ProximitySensor
+
+- type: entityTable
+ id: RandomSatchelChemsTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 20
+ - id: ChemistryBottleUnstableMutagen
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: ChemistryBottleLeft4Zed
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: ChemistryBottleEZNutrient
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: ChemistryBottleRobustHarvest
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: ChemistryBottleEpinephrine
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: ChemistryBottleEphedrine
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: ChemistryBottleOmnizine
+ - id: ChemistryBottleCognizine
+ - id: ChemistryBottleToxin
+ - id: ChemistryBottleNocturine
+ - id: VestineChemistryVial
+
+- type: entityTable
+ id: RandomSatchelTable2
+ table: !type:AllSelector
+ children:
+ - !type:NestedSelector
+ tableId: RandomSatchelTobaccoTable
+ - !type:NestedSelector
+ tableId: RandomSatchelPartyTable
+ - !type:NestedSelector
+ tableId: RandomSatchelClothingTable
+ - !type:NestedSelector
+ tableId: RandomSatchelPayloadTable
+ - !type:NestedSelector
+ tableId: RandomSatchelCircuitboardsTable
+
+- type: entityTable
+ id: RandomSatchelTobaccoTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 8
+ - id: CigPackSyndicate
+ weight: 0.5
+ - id: CigCartonGreen
+ - id: CigCartonRed
+ - id: CigCartonGreen
+ - id: CigCartonBlack
+ - id: CigarCase
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+ - id: CigarGoldCase
+ weight: 0.25
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+
+- type: entityTable
+ id: RandomSatchelPartyTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 2
+ - id: GlowstickBase
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: GlowstickRed
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: GlowstickPurple
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: GlowstickYellow
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: GlowstickBlue
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+
+- type: entityTable
+ id: RandomSatchelClothingTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 3
+ - id: ClothingEyesGlassesOutlawGlasses
+ - id: ClothingEyesEyepatch
+ - id: ClothingHandsGlovesNitrile
+ - id: ClothingHeadHatOutlawHat
+ - id: ClothingMaskItalianMoustache
+ - id: ClothingHandsGlovesCombat
+ - id: ClothingNeckScarfStripedSyndieRed
+
+- type: entityTable
+ id: RandomSatchelPayloadTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 45
+ - id: FlashPayload
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+ - id: ChemicalPayload
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+ - id: ExplosivePayload
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+
+- type: entityTable
+ id: RandomSatchelCircuitboardsTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 15
+ - id: ChemDispenserMachineCircuitboard
+ - id: SyndicateMicrowaveMachineCircuitboard
+ - id: HydroponicsTrayMachineCircuitboard
+ - id: DawInstrumentMachineCircuitboard
+ - id: PortableGeneratorPacmanMachineCircuitboard
+ - id: PortableGeneratorSuperPacmanMachineCircuitboard
+ - id: HellfireFreezerMachineCircuitBoard
+ - id: HellfireHeaterMachineCircuitBoard
+ - id: ReagentGrinderMachineCircuitboard
+ - id: ReagentGrinderIndustrialMachineCircuitboard
+ - id: BoozeDispenserMachineCircuitboard
+ - id: MiniGravityGeneratorCircuitboard
+ - id: AmmoTechFabCircuitboard
+ - id: CryoPodMachineCircuitboard
+ - id: PowerCageRechargerCircuitboard
+ - id: ShuttleConsoleCircuitboard
+ - id: TurboItemRechargerCircuitboard
+
+- type: entityTable
+ id: RandomSatchelTable3
+ table: !type:AllSelector
+ children:
+ - !type:NestedSelector
+ tableId: RandomSatchelPresentsOrToysTable
+ - !type:NestedSelector
+ tableId: RandomSatchelCashTable
+ - !type:NestedSelector
+ tableId: RandomSatchelWeaponTable
+ - !type:NestedSelector
+ tableId: RandomSatchelBurgerTable
+ - !type:NestedSelector
+ tableId: RandomSatchelGenericTable
+ - !type:NestedSelector
+ tableId: RandomSatchelKeysTable
+
+- type: entityTable
+ id: RandomSatchelPresentsOrToysTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 5
+ - id: PresentRandom
+ amount: !type:RangeNumberSelector
+ range: 1, 3
+ - id: ToyFigurineHamlet
+ - id: ToyFigurineSpaceDragon
+ - id: ToyFigurineQueen
+ - id: ToyFigurineRatKing
+ - id: PresentRandomUnsafe
+ weight: 0.001
+
+- type: entityTable
+ id: RandomSatchelCashTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 2
+ amount: !type:RangeNumberSelector
+ range: 1, 4
+ - id: SpaceCash1000
+ weight: 2
+ amount: !type:RangeNumberSelector
+ range: 1, 4
+ - id: SpaceCash2500
+ amount: !type:RangeNumberSelector
+ range: 1, 4
+ - id: SpaceCash5000
+ weight: 0.25
+ amount: !type:RangeNumberSelector
+ range: 1, 4
+ - id: SpaceCash10000
+ weight: 0.005
+ amount: !type:RangeNumberSelector
+ range: 1, 4
+ - id: SpaceCash1000000
+ prob: 0.0001
+ - id: SpaceCash
+ weight: 0.01
+ amount: !type:RangeNumberSelector
+ range: 1, 10
+
+- type: entityTable
+ id: RandomSatchelWeaponTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 100
+ - id: Katana
+ - id: ThrowingStar
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+
+- type: entityTable
+ id: RandomSatchelBurgerTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 10
+ - id: FoodBurgerAppendix
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: FoodBurgerEmpowered
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: FoodBurgerClown
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: FoodBurgerGhost
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+
+- type: entityTable
+ id: RandomSatchelGenericTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 15
+ - id: RemoteSignaller
+ - id: PersonalAI
+ - id: WeaponFlareGun
+ - id: ModularReceiver
+ - id: RifleStock
+ - id: DrinkSpaceGlue
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+ - id: DrinkSpaceLube
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+ - id: CrazyGlue
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+
+- type: entityTable
+ id: RandomSatchelKeysTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 50
+ - id: EncryptionKeyCommon
+ - id: EncryptionKeyCargo
+ - id: EncryptionKeyService
+ - id: EncryptionKeyRobo
+ - id: EncryptionKeyScience
+ - id: EncryptionKeyMedical
+ - id: EncryptionKeyEngineering
+ - id: EncryptionKeySecurity
+ weight: 0.5
+ - id: EncryptionKeyCommand
+ weight: 0.25
+ - id: EncryptionKeyStationMaster
+ weight: 0.01
+
+- type: entityTable
+ id: RandomSatchelTable4
+ table: !type:AllSelector
+ children:
+ - !type:NestedSelector
+ tableId: RandomSatchelMaterialsTable
+ - !type:NestedSelector
+ tableId: RandomSatchelImplantersTable
+ - !type:NestedSelector
+ tableId: RandomSatchelCellsTable
+ - !type:NestedSelector
+ tableId: RandomSatchelSyndicateTable
+ - !type:NestedSelector
+ tableId: RandomSatchelToolsTable
+
+- type: entityTable
+ id: RandomSatchelMaterialsTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 6
+ - !type:GroupSelector
+ children:
+ - id: MaterialDiamond1
+ - !type:GroupSelector
+ children:
+ - id: MaterialBananium1
+ weight: 2
+ - id: MaterialBananium
+ - !type:GroupSelector
+ children:
+ - id: IngotGold1
+ weight: 2
+ - id: IngotGold
+ - !type:GroupSelector
+ children:
+ - id: IngotSilver1
+ weight: 2
+ - id: IngotSilver
+ - !type:GroupSelector
+ children:
+ - id: SheetPlasma1
+ weight: 2
+ - id: SheetPlasma10
+ - id: SheetPlasma
+ weight: 0.50
+ - !type:GroupSelector
+ children:
+ - id: SheetUranium1
+ weight: 2
+ - id: SheetUranium
+
+- type: entityTable
+ id: RandomSatchelImplantersTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 3
+ - id: LightImplanter
+ - id: BikeHornImplanter
+ - id: SadTromboneImplanter
+
+- type: entityTable
+ id: RandomSatchelCellsTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 15
+ - id: PowerCellHyper
+ - id: PowerCellMicroreactor
+ - id: PowerCellHigh
+
+- type: entityTable
+ id: RandomSatchelSyndicateTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 50
+ - id: Telecrystal1
+ amount: !type:RangeNumberSelector
+ range: 1, 3
+ - id: GatfruitSeeds
+ - id: ToySword
+ - id: NukeDiskFake
+ - id: RadioJammer
+ - id: SoapSyndie
+ - id: SingularityToy
+
+- type: entityTable
+ id: RandomSatchelToolsTable
+ table: !type:GroupSelector
+ children:
+ - id: Crowbar
+ - id: Multitool
+ - id: ClothingHandsGlovesColorYellow
+ - id: Screwdriver
+ - id: ClothingHeadHatWeldingMaskFlame
+ - id: WelderExperimental
+ weight: 0.50
+
+- type: entityTable
+ id: RandomSatchelTable5
+ table: !type:AllSelector
+ children:
+ - !type:NestedSelector
+ tableId: RandomSatchelAlcoholTable
+ - !type:NestedSelector
+ tableId: RandomSatchelInstrumentTable
+ - !type:NestedSelector
+ tableId: RandomSatchelMedsTable
+ - !type:NestedSelector
+ tableId: RandomSatchelMysteriesTable
+
+- type: entityTable
+ id: RandomSatchelAlcoholTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 5
+ - id: DrinkCognacBottleFull
+ amount: !type:RangeNumberSelector
+ range: 1, 4
+ - id: DrinkGildlagerBottleFull
+ amount: !type:RangeNumberSelector
+ range: 1, 4
+ - id: DrinkPatronBottleFull
+ amount: !type:RangeNumberSelector
+ range: 1, 4
+ - id: DrinkRumBottleFull
+ amount: !type:RangeNumberSelector
+ range: 1, 4
+
+- type: entityTable
+ id: RandomSatchelInstrumentTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 15
+ - id: SeashellInstrument
+ - id: MusicalLungInstrument
+ - id: HelicopterInstrument
+ - id: GunpetInstrument
+ - id: RockGuitarInstrument
+ - id: BassGuitarInstrument
+
+- type: entityTable
+ id: RandomSatchelMedsTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 10
+ - id: SyringeAmbuzol
+ - id: SyringeHyronalin
+ - id: SyringeDermaline
+ - id: SyringeBicaridine
+ - id: SyringeTranexamicAcid
+ - id: SyringeInaprovaline
+ - id: SyringeEphedrine
+ - id: Gauze
+ - id: Bloodpack
+ - id: RegenerativeMesh
+ - id: MedicatedSuture
+ - id: EmergencyMedipen
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: AntiPoisonMedipen
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: BruteAutoInjector
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: BurnAutoInjector
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: SpaceMedipen
+ amount: !type:RangeNumberSelector
+ range: 1, 5
+ - id: Stimpack
+ - id: CombatMedipen
+
+- type: entityTable
+ id: RandomSatchelMysteriesTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 15
+ - id: EggSpider
+ weight: 5
+ - id: ArtifactFragment1
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+ weight: 10
+ - id: AnomalyCorePyroclastic
+ amount: !type:RangeNumberSelector
+ range: 1, 3
+ - id: AnomalyCoreGravity
+ amount: !type:RangeNumberSelector
+ range: 1, 3
+ - id: AnomalyCoreIce
+ amount: !type:RangeNumberSelector
+ range: 1, 3
+ - id: AnomalyCoreFlesh
+ amount: !type:RangeNumberSelector
+ range: 1, 3
+ - id: AnomalyCoreLiquid
+ amount: !type:RangeNumberSelector
+ range: 1, 3
+ - id: AnomalyCoreBluespace
+ amount: !type:RangeNumberSelector
+ range: 1, 3
+ - id: AnomalyCoreElectricity
+ amount: !type:RangeNumberSelector
+ range: 1, 3
+ - id: AnomalyCoreFlora
+ amount: !type:RangeNumberSelector
+ range: 1, 3
+ - id: AnomalyCoreShadow
+ amount: !type:RangeNumberSelector
+ range: 1, 3
+
+- type: entityTable
+ id: RandomSatchelTable6
+ table: !type:AllSelector
+ children:
+ - !type:NestedSelector
+ tableId: RandomSatchelGearTable
+ - !type:NestedSelector
+ tableId: RandomSatchelGadgetsTable
+ - !type:NestedSelector
+ tableId: CubeTable
+
+- type: entityTable
+ id: RandomSatchelGearTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 8
+ - id: JetpackMiniFilled
+ - id: HandheldGPSBasic
+ - id: WelderIndustrialAdvanced
+ - id: HandheldStationMap
+ - id: PinpointerStation
+
+- type: entityTable
+ id: RandomSatchelGadgetsTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 30
+ - id: HolofanProjector
+ - id: HoloprojectorField
+ - id: HoloprojectorSecurity
+ - id: DeviceQuantumSpinInverter
+ amount: !type:ConstantNumberSelector
+ value: 2
+ - id: SpectralLocator
+ - id: ArabianLamp
+ weight: 0.50
+ - id: ChameleonProjector
+ weight: 0.25
+
+- type: entityTable
+ id: CubeTable
+ table: !type:GroupSelector
+ children:
+ - id: SpaceCash100
+ weight: 8
+ - id: MonkeyCube
+ amount: !type:RangeNumberSelector
+ range: 1, 10
+ - id: KoboldCube
+ amount: !type:RangeNumberSelector
+ range: 1, 10
+ - id: CowCube
+ amount: !type:RangeNumberSelector
+ range: 1, 10
+ - id: GoatCube
+ amount: !type:RangeNumberSelector
+ range: 1, 10
+ - id: MothroachCube
+ amount: !type:RangeNumberSelector
+ range: 1, 10
+ - id: MouseCube
+ amount: !type:RangeNumberSelector
+ range: 1, 10
+ - id: CockroachCube
+ amount: !type:RangeNumberSelector
+ range: 1, 10
+ - id: SpaceCarpCube
+ amount: !type:RangeNumberSelector
+ range: 1, 10
+ - id: SpaceTickCube
+ amount: !type:RangeNumberSelector
+ range: 1, 10
+ - id: AbominationCube
+ amount: !type:RangeNumberSelector
+ range: 1, 10
diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml
index f942cafe6c..41655a4871 100644
--- a/Resources/Prototypes/GameRules/events.yml
+++ b/Resources/Prototypes/GameRules/events.yml
@@ -640,6 +640,19 @@
blacklist:
- External # don't space everything
+- type: entity
+ parent: BaseGameRule
+ id: SmugglerStashVariationPass
+ components:
+ - type: StationEvent
+ earliestStart: 0
+ duration: 1
+ minimumPlayers: 1
+ maxOccurrences: 2
+ weight: 10
+ - type: RandomSpawnRule
+ prototype: RandomSatchelSpawner
+
- type: entity
parent: BaseGameRule
id: DerelictCyborgSpawn
diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml
index a6c552e2e4..87f8981d8d 100644
--- a/Resources/Prototypes/GameRules/roundstart.yml
+++ b/Resources/Prototypes/GameRules/roundstart.yml
@@ -443,3 +443,5 @@
- id: BloodbathPuddleMessVariationPass
prob: 0.01
orGroup: puddleMess
+ - id: SmugglerStashVariationPass
+ prob: 0.90
diff --git a/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/equipped-BACKPACK.png b/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/equipped-BACKPACK.png
new file mode 100644
index 0000000000..c24f815bc1
Binary files /dev/null and b/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/equipped-BACKPACK.png differ
diff --git a/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/icon.png b/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/icon.png
new file mode 100644
index 0000000000..a32ce14950
Binary files /dev/null and b/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/icon.png differ
diff --git a/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/inhand-left.png b/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/inhand-left.png
new file mode 100644
index 0000000000..2f5f5f35cd
Binary files /dev/null and b/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/inhand-left.png differ
diff --git a/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/inhand-right.png b/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/inhand-right.png
new file mode 100644
index 0000000000..7952889f7d
Binary files /dev/null and b/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/inhand-right.png differ
diff --git a/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/meta.json b/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/meta.json
new file mode 100644
index 0000000000..d3b44ffaa2
--- /dev/null
+++ b/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/meta.json
@@ -0,0 +1,26 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/a8056c6ba7f5367934ef829116e57d743226e1f0",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ },
+ {
+ "name": "equipped-BACKPACK",
+ "directions": 4
+ },
+ {
+ "name": "inhand-left",
+ "directions": 4
+ },
+ {
+ "name": "inhand-right",
+ "directions": 4
+ }
+ ]
+}