Single item cargo orders are delivered in Parcel Wrap instead of crates (#40834)

* - rework a bunch of cargo orders to not send a crate with just one thing
  - instead sends the item, wrapped
- add ability for cargo orders to wrap the item ordered in parcel wrap
- cargo order name and descriptions are now localization strings, but still 99.99% of the time just get that info from the entity they're spawning
- small refactor to how cargo orders move around in cargo code so that the actual order proto is kept around longer
- small rework to certain restock cargo orders to halve the cost and number of items received in one order

* oop

* Suffering

* Arbitrary containers for cargo products

* I ran the tests locally and they passed, github please rerun :)

* massage Slarti's work into this.
Mainly by taking the API wrapping ability out back with the rifle q-q

* testfail fake

* wow `_applyingState` is useful, I wonder where I should've been using that in other PRs before :^)

* pr comments

* tfw the hook doesn't update the submodule

* Update Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs

Co-authored-by: āda <ss.adasts@gmail.com>

* PR comments

* fixies

* Update Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs

* Update Content.Shared/ParcelWrap/Systems/ParcelWrappingSystem.WrappedParcel.cs

* fix wrapped parcel entity name + audio

* Actually read PR comments

* Apply suggestions from code review

Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>

---------

Co-authored-by: āda <ss.adasts@gmail.com>
Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
This commit is contained in:
Centronias
2026-06-29 12:44:06 +02:00
committed by Coryler
co-authored by āda ScarKy0
parent dfb76ff520
commit a667530e18
24 changed files with 419 additions and 408 deletions
@@ -121,66 +121,77 @@ namespace Content.IntegrationTests.Tests
await server.WaitAssertion(() =>
{
HashSet<string> restocks = new();
Dictionary<string, List<string>> restockStores = new();
// Collect all the prototypes with restock components.
// Collect all entity prototypes which are vending machine restocks.
var restockEntities = new HashSet<EntProtoId<VendingMachineRestockComponent>>();
foreach (var proto in prototypeManager.EnumeratePrototypes<EntityPrototype>())
{
if (proto.Abstract
|| pair.IsTestPrototype(proto)
|| !proto.HasComponent<VendingMachineRestockComponent>())
{
continue;
}
restocks.Add(proto.ID);
restockEntities.Add(proto.ID);
}
// Collect all the prototypes with EntityTableContainerFills referencing those entities.
// Collect all entity prototypes with `EntityTableContainerFill`s which contain those restock entities.
// Specifically, this is a mapping of entities-with-container-fill to their-contained-entities-which-are-restocks.
Dictionary<EntProtoId<EntityTableContainerFillComponent>,
List<EntProtoId<VendingMachineRestockComponent>>> entitiesWhichSpawnRestocks = new();
foreach (var proto in prototypeManager.EnumeratePrototypes<EntityPrototype>())
{
if (!proto.TryGetComponent<EntityTableContainerFillComponent>(out var storage, compFact))
if (!proto.TryGetComponent<EntityTableContainerFillComponent>(out var fill, compFact))
continue;
var containers = storage.Containers;
var containers = fill.Containers;
if (!containers.TryGetValue(SharedEntityStorageSystem.ContainerName, out var container)) // We only care about this container type.
// We only care about the special known container.
if (!containers.TryGetValue(SharedEntityStorageSystem.ContainerName, out var container))
continue;
List<string> restockStore = new();
foreach (var spawnEntry in entityTable.GetSpawns(container))
var entitiesInProtoContainingRestock = new List<EntProtoId<VendingMachineRestockComponent>>();
foreach (var (fillSpawnEntry, _) in entityTable.ListSpawns(container))
{
if (restocks.Contains(spawnEntry))
restockStore.Add(spawnEntry);
if (restockEntities.Contains(fillSpawnEntry.Id))
entitiesInProtoContainingRestock.Add(fillSpawnEntry.Id);
}
if (restockStore.Count > 0)
restockStores.Add(proto.ID, restockStore);
if (entitiesInProtoContainingRestock.Count > 0)
entitiesWhichSpawnRestocks.Add(proto.ID, entitiesInProtoContainingRestock);
}
// Iterate through every CargoProduct and make sure each
// prototype with a restock component is referenced in a
// purchaseable entity with an EntityTableContianerFill.
// Remove all restock entities from our set which are either directly purchasable as a CargoProduct, or
// which are spawned by EntityTableContainerFill on a CargoProduct.
foreach (var proto in prototypeManager.EnumeratePrototypes<CargoProductPrototype>())
{
if (restockStores.ContainsKey(proto.Product))
{
foreach (var entry in restockStores[proto.Product])
restocks.Remove(entry);
// If the cargo product's product is the restock itself, just remove it.
restockEntities.Remove(proto.Product.Id);
restockStores.Remove(proto.Product);
// Check if the product is an entity which spawns a restock.
if (entitiesWhichSpawnRestocks.TryGetValue(proto.Product.Id, out var restocksSpawnedByProduct))
{
foreach (var entry in restocksSpawnedByProduct)
{
restockEntities.Remove(entry);
}
entitiesWhichSpawnRestocks.Remove(proto.Product.Id);
}
}
// Any entities left in restockEntities are restocks which can't be bought from Cargo.
Assert.Multiple(() =>
{
Assert.That(restockStores, Has.Count.EqualTo(0),
$"Some entities containing entities with VendingMachineRestock components are unavailable for purchase: \n - {string.Join("\n - ", restockStores.Keys)}");
const string restockCompName = nameof(VendingMachineRestockComponent);
Assert.That(restocks, Has.Count.EqualTo(0),
$"Some entities with VendingMachineRestock components are unavailable for purchase: \n - {string.Join("\n - ", restocks)}");
Assert.That(entitiesWhichSpawnRestocks,
Has.Count.EqualTo(0),
$"Some entities containing entities with {restockCompName} are unavailable for purchase: \n - {string.Join("\n - ", entitiesWhichSpawnRestocks.Keys)}");
Assert.That(restockEntities,
Has.Count.EqualTo(0),
$"Some entities with {restockCompName} are unavailable for purchase: \n - {string.Join("\n - ", restockEntities)}");
});
});