diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs
index 3263c077b7..71bd6a731d 100644
--- a/Content.Client/Entry/IgnoredComponents.cs
+++ b/Content.Client/Entry/IgnoredComponents.cs
@@ -142,6 +142,7 @@ namespace Content.Client.Entry
"PowerMonitoringConsole",
"RCD",
"RCDAmmo",
+ "SurplusBundle",
"CursedEntityStorage",
"DiseaseArtifact",
"Radio",
diff --git a/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleComponent.cs b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleComponent.cs
new file mode 100644
index 0000000000..5682863f2f
--- /dev/null
+++ b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleComponent.cs
@@ -0,0 +1,15 @@
+namespace Content.Server.Traitor.Uplink.SurplusBundle;
+
+///
+/// Fill crate with a random uplink items.
+///
+[RegisterComponent]
+public sealed class SurplusBundleComponent : Component
+{
+ ///
+ /// Total price of all content inside bundle.
+ ///
+ [ViewVariables(VVAccess.ReadOnly)]
+ [DataField("totalPrice")]
+ public int TotalPrice = 20;
+}
diff --git a/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleSystem.cs b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleSystem.cs
new file mode 100644
index 0000000000..479e8ce956
--- /dev/null
+++ b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleSystem.cs
@@ -0,0 +1,89 @@
+using System.Linq;
+using Content.Server.Storage.Components;
+using Content.Shared.PDA;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server.Traitor.Uplink.SurplusBundle;
+
+public sealed class SurplusBundleSystem : EntitySystem
+{
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ private UplinkStoreListingPrototype[] _uplinks = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnMapInit);
+
+ InitList();
+ }
+
+ private void InitList()
+ {
+ // sort data in price descending order
+ _uplinks = _prototypeManager.EnumeratePrototypes()
+ .Where(item => item.CanSurplus).ToArray();
+ Array.Sort(_uplinks, (a, b) => b.Price - a.Price);
+ }
+
+ private void OnMapInit(EntityUid uid, SurplusBundleComponent component, MapInitEvent args)
+ {
+ FillStorage(uid, component: component);
+ }
+
+ private void FillStorage(EntityUid uid, IStorageComponent? storage = null,
+ SurplusBundleComponent? component = null)
+ {
+ if (!Resolve(uid, ref storage, ref component))
+ return;
+
+ var cords = Transform(uid).Coordinates;
+
+ var content = GetRandomContent(component.TotalPrice);
+ foreach (var item in content)
+ {
+ var ent = EntityManager.SpawnEntity(item.ItemId, cords);
+ storage.Insert(ent);
+ }
+ }
+
+ // wow, is this leetcode reference?
+ private List GetRandomContent(int targetCost)
+ {
+ var ret = new List();
+ if (_uplinks.Length == 0)
+ return ret;
+
+ var totalCost = 0;
+ var index = 0;
+ while (totalCost < targetCost)
+ {
+ // All data is sorted in price descending order
+ // Find new item with the lowest acceptable price
+ // All expansive items will be before index, all acceptable after
+ var remainingBudget = targetCost - totalCost;
+ while (_uplinks[index].Price > remainingBudget)
+ {
+ index++;
+ if (index >= _uplinks.Length)
+ {
+ // Looks like no cheap items left
+ // It shouldn't be case for ss14 content
+ // Because there are 1 TC items
+ return ret;
+ }
+ }
+
+ // Select random listing and add into crate
+ var randomIndex = _random.Next(index, _uplinks.Length);
+ var randomItem = _uplinks[randomIndex];
+ ret.Add(randomItem);
+ totalCost += randomItem.Price;
+ }
+
+ return ret;
+ }
+}
diff --git a/Content.Shared/PDA/UplinkStoreListingPrototype.cs b/Content.Shared/PDA/UplinkStoreListingPrototype.cs
index c616b45fa9..9442bdda3e 100644
--- a/Content.Shared/PDA/UplinkStoreListingPrototype.cs
+++ b/Content.Shared/PDA/UplinkStoreListingPrototype.cs
@@ -30,5 +30,8 @@ namespace Content.Shared.PDA
[DataField("icon")]
public SpriteSpecifier? Icon { get; } = null;
+
+ [DataField("surplus")]
+ public bool CanSurplus = true;
}
}
diff --git a/Resources/Prototypes/Catalog/Fills/Crates/syndicate.yml b/Resources/Prototypes/Catalog/Fills/Crates/syndicate.yml
new file mode 100644
index 0000000000..9db32cefbb
--- /dev/null
+++ b/Resources/Prototypes/Catalog/Fills/Crates/syndicate.yml
@@ -0,0 +1,15 @@
+- type: entity
+ id: CrateSyndicateSurplusBundle
+ name: syndicate surplus crate
+ parent: CrateGenericSteel
+ components:
+ - type: SurplusBundle
+ totalPrice: 50
+
+- type: entity
+ id: CrateSyndicateSuperSurplusBundle
+ name: syndicate super surplus crate
+ parent: CrateGenericSteel
+ components:
+ - type: SurplusBundle
+ totalPrice: 125
diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml
index a7839348ce..890eb9d226 100644
--- a/Resources/Prototypes/Catalog/uplink_catalog.yml
+++ b/Resources/Prototypes/Catalog/uplink_catalog.yml
@@ -89,6 +89,7 @@
category: Explosives
itemId: MobGrenadePenguin
price: 6
+ surplus: false # got wrecked by penguins from surplus crate
- type: uplinkListing
id: UplinkC4
@@ -211,6 +212,21 @@
price: 50
icon: /Textures/Structures/Wallmounts/signs.rsi/bio.png
+- type: uplinkListing
+ id: UplinkSurplusBundle
+ category: Bundles
+ itemId: CrateSyndicateSurplusBundle
+ description: Contains 50 telecrystals worth of completely random Syndicate items. It can be useless junk or really good.
+ price: 20
+ surplus: false
+
+- type: uplinkListing
+ id: UplinkSuperSurplusBundle
+ category: Bundles
+ itemId: CrateSyndicateSuperSurplusBundle
+ description: Contains 125 telecrystals worth of completely random Syndicate items.
+ price: 40
+ surplus: false
#- type: uplinkListing
# id: UplinkCarbineBundle
@@ -384,3 +400,4 @@
listingName: syndicate segway
description: Be an enemy of the corporation, in style!
price: 5
+ surplus: false