]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add a test for sliceable cargo bounty exploits (#28357)
authorTayrtahn <tayrtahn@gmail.com>
Mon, 3 Jun 2024 16:24:32 +0000 (12:24 -0400)
committerGitHub <noreply@github.com>
Mon, 3 Jun 2024 16:24:32 +0000 (12:24 -0400)
Content.IntegrationTests/Tests/CargoTest.cs
Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
Resources/Prototypes/Catalog/Bounties/bounties.yml

index 09f179cf4f5a506e2d96bfbfc9374ce653afcea0..8e1d536054f2b9babd101784846e2ebee7d904ae 100644 (file)
@@ -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();
     }
 
+    /// <summary>
+    /// 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.
+    /// </summary>
+    [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<IEntityManager>();
+        var mapManager = server.ResolveDependency<IMapManager>();
+        var protoManager = server.ResolveDependency<IPrototypeManager>();
+        var componentFactory = server.ResolveDependency<IComponentFactory>();
+        var whitelist = entManager.System<EntityWhitelistSystem>();
+        var cargo = entManager.System<CargoSystem>();
+        var sliceableSys = entManager.System<SliceableFoodSystem>();
+
+        var bounties = protoManager.EnumeratePrototypes<CargoBountyPrototype>().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<EntityPrototype>()
+                .Where(p => !p.Abstract)
+                .Where(p => !pair.IsTestPrototype(p))
+                .Where(p => p.TryGetComponent<SliceableFoodComponent>(out _, componentFactory))
+                .Select(p => p.ID)
+                .ToList();
+
+            foreach (var proto in sliceableEntityProtos)
+            {
+                var ent = entManager.SpawnEntity(proto, coord);
+                var sliceable = entManager.GetComponent<SliceableFoodComponent>(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 = @"
index 0fcfd160bb3923f8be041a2e37e70673947ed55e..554b349b9ba60e949918879312089b6f3faa1355 100644 (file)
@@ -300,6 +300,21 @@ public sealed partial class CargoSystem
         return IsBountyComplete(GetBountyEntities(container), entries, out bountyEntities);
     }
 
+    /// <summary>
+    /// Determines whether the <paramref name="entity"/> meets the criteria for the bounty <paramref name="entry"/>.
+    /// </summary>
+    /// <returns>true if <paramref name="entity"/> is a valid item for the bounty entry, otherwise false</returns>
+    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<EntityUid> entities, IEnumerable<CargoBountyItemEntry> entries, out HashSet<EntityUid> bountyEntities)
     {
         bountyEntities = new();
@@ -313,7 +328,7 @@ public sealed partial class CargoSystem
             var temp = new HashSet<EntityUid>();
             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;
index bedfe442872eeb9876ac05666eb9b7112d1fe615..08bb2a142250a83403f387f3e86f8ba00a725d3c 100644 (file)
@@ -42,6 +42,9 @@
     whitelist:
       tags:
       - Bread
+    blacklist:
+      tags:
+      - Slice
 
 - type: cargoBounty
   id: BountyCarrot
     whitelist:
       tags:
       - Meat
+    blacklist:
+      components:
+      - SliceableFood
 
 - type: cargoBounty
   id: BountyFruit