diff --git a/Content.IntegrationTests/Tests/CargoTest.cs b/Content.IntegrationTests/Tests/CargoTest.cs
index 09f179cf4f..8e1d536054 100644
--- a/Content.IntegrationTests/Tests/CargoTest.cs
+++ b/Content.IntegrationTests/Tests/CargoTest.cs
@@ -3,8 +3,13 @@ using System.Linq;
using System.Numerics;
using Content.Server.Cargo.Components;
using Content.Server.Cargo.Systems;
+using Content.Server.Nutrition.Components;
+using Content.Server.Nutrition.EntitySystems;
using Content.Shared.Cargo.Prototypes;
+using Content.Shared.IdentityManagement;
using Content.Shared.Stacks;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
@@ -149,6 +154,80 @@ public sealed class CargoTest
await pair.CleanReturnAsync();
}
+ ///
+ /// Tests to see if any items that are valid for cargo bounties can be sliced into items that
+ /// are also valid for the same bounty entry.
+ ///
+ [Test]
+ public async Task NoSliceableBountyArbitrageTest()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var server = pair.Server;
+
+ var testMap = await pair.CreateTestMap();
+
+ var entManager = server.ResolveDependency();
+ var mapManager = server.ResolveDependency();
+ var protoManager = server.ResolveDependency();
+ var componentFactory = server.ResolveDependency();
+ var whitelist = entManager.System();
+ var cargo = entManager.System();
+ var sliceableSys = entManager.System();
+
+ var bounties = protoManager.EnumeratePrototypes().ToList();
+
+ await server.WaitAssertion(() =>
+ {
+ var mapId = testMap.MapId;
+ var grid = mapManager.CreateGridEntity(mapId);
+ var coord = new EntityCoordinates(grid.Owner, 0, 0);
+
+ var sliceableEntityProtos = protoManager.EnumeratePrototypes()
+ .Where(p => !p.Abstract)
+ .Where(p => !pair.IsTestPrototype(p))
+ .Where(p => p.TryGetComponent(out _, componentFactory))
+ .Select(p => p.ID)
+ .ToList();
+
+ foreach (var proto in sliceableEntityProtos)
+ {
+ var ent = entManager.SpawnEntity(proto, coord);
+ var sliceable = entManager.GetComponent(ent);
+
+ // Check each bounty
+ foreach (var bounty in bounties)
+ {
+ // Check each entry in the bounty
+ foreach (var entry in bounty.Entries)
+ {
+ // See if the entity counts as part of this bounty entry
+ if (!cargo.IsValidBountyEntry(ent, entry))
+ continue;
+
+ // Spawn a slice
+ var slice = entManager.SpawnEntity(sliceable.Slice, coord);
+
+ // See if the slice also counts for this bounty entry
+ if (!cargo.IsValidBountyEntry(slice, entry))
+ {
+ entManager.DeleteEntity(slice);
+ continue;
+ }
+
+ entManager.DeleteEntity(slice);
+
+ // If for some reason it can only make one slice, that's okay, I guess
+ Assert.That(sliceable.TotalCount, Is.EqualTo(1), $"{proto} counts as part of cargo bounty {bounty.ID} and slices into {sliceable.TotalCount} slices which count for the same bounty!");
+ }
+ }
+
+ entManager.DeleteEntity(ent);
+ }
+ mapManager.DeleteMap(mapId);
+ });
+
+ await pair.CleanReturnAsync();
+ }
[TestPrototypes]
private const string StackProto = @"
diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
index 0fcfd160bb..554b349b9b 100644
--- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
+++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
@@ -300,6 +300,21 @@ public sealed partial class CargoSystem
return IsBountyComplete(GetBountyEntities(container), entries, out bountyEntities);
}
+ ///
+ /// Determines whether the meets the criteria for the bounty .
+ ///
+ /// true if is a valid item for the bounty entry, otherwise false
+ public bool IsValidBountyEntry(EntityUid entity, CargoBountyItemEntry entry)
+ {
+ if (!_whitelistSys.IsValid(entry.Whitelist, entity))
+ return false;
+
+ if (entry.Blacklist != null && _whitelistSys.IsValid(entry.Blacklist, entity))
+ return false;
+
+ return true;
+ }
+
public bool IsBountyComplete(HashSet entities, IEnumerable entries, out HashSet bountyEntities)
{
bountyEntities = new();
@@ -313,7 +328,7 @@ public sealed partial class CargoSystem
var temp = new HashSet();
foreach (var entity in entities)
{
- if (!_whitelistSys.IsValid(entry.Whitelist, entity) || (entry.Blacklist != null && _whitelistSys.IsValid(entry.Blacklist, entity)))
+ if (!IsValidBountyEntry(entity, entry))
continue;
count += _stackQuery.CompOrNull(entity)?.Count ?? 1;
diff --git a/Resources/Prototypes/Catalog/Bounties/bounties.yml b/Resources/Prototypes/Catalog/Bounties/bounties.yml
index bedfe44287..08bb2a1422 100644
--- a/Resources/Prototypes/Catalog/Bounties/bounties.yml
+++ b/Resources/Prototypes/Catalog/Bounties/bounties.yml
@@ -42,6 +42,9 @@
whitelist:
tags:
- Bread
+ blacklist:
+ tags:
+ - Slice
- type: cargoBounty
id: BountyCarrot
@@ -533,6 +536,9 @@
whitelist:
tags:
- Meat
+ blacklist:
+ components:
+ - SliceableFood
- type: cargoBounty
id: BountyFruit