]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Make pricing system aware of SpawnItemsOnUseComponent (#13626)
authorVisne <39844191+Visne@users.noreply.github.com>
Tue, 28 Feb 2023 15:55:25 +0000 (16:55 +0100)
committerGitHub <noreply@github.com>
Tue, 28 Feb 2023 15:55:25 +0000 (07:55 -0800)
* Make appraisal tool aware of SpawnItemsOnUseComponent

* Move to SpawnItemsOnUseSystem

Content.Server/Cargo/Systems/PricingSystem.cs
Content.Server/Storage/Components/SpawnItemsOnUseComponent.cs
Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs
Content.Shared/Storage/EntitySpawnEntry.cs
Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/tin.yml

index 39bd51628cda8c29219b8ae57d05ab430db5c0b4..56f8e2ffd10b6e4ee37fe4c912cb99b20a8dfe88 100644 (file)
@@ -3,7 +3,6 @@ using Content.Server.Administration;
 using Content.Server.Body.Systems;
 using Content.Server.Cargo.Components;
 using Content.Server.Chemistry.Components.SolutionManager;
-using Content.Server.Stack;
 using Content.Shared.Administration;
 using Content.Shared.Body.Components;
 using Content.Shared.Chemistry.Reagent;
@@ -88,6 +87,9 @@ public sealed class PricingSystem : EntitySystem
 
     private void CalculateMobPrice(EntityUid uid, MobPriceComponent component, ref PriceCalculationEvent args)
     {
+        if (args.Handled)
+            return;
+
         if (!TryComp<BodyComponent>(uid, out var body) || !TryComp<MobStateComponent>(uid, out var state))
         {
             Logger.ErrorS("pricing", $"Tried to get the mob price of {ToPrettyString(uid)}, which has no {nameof(BodyComponent)} and no {nameof(MobStateComponent)}.");
@@ -106,6 +108,9 @@ public sealed class PricingSystem : EntitySystem
 
     private void CalculateStackPrice(EntityUid uid, StackPriceComponent component, ref PriceCalculationEvent args)
     {
+        if (args.Handled)
+            return;
+
         if (!TryComp<StackComponent>(uid, out var stack))
         {
             Logger.ErrorS("pricing", $"Tried to get the stack price of {ToPrettyString(uid)}, which has no {nameof(StackComponent)}.");
@@ -117,6 +122,9 @@ public sealed class PricingSystem : EntitySystem
 
     private void CalculateSolutionPrice(EntityUid uid, SolutionContainerManagerComponent component, ref PriceCalculationEvent args)
     {
+        if (args.Handled)
+            return;
+
         var price = 0f;
 
         foreach (var solution in component.Solutions.Values)
@@ -133,6 +141,9 @@ public sealed class PricingSystem : EntitySystem
 
     private void CalculateStaticPrice(EntityUid uid, StaticPriceComponent component, ref PriceCalculationEvent args)
     {
+        if (args.Handled)
+            return;
+
         args.Price += component.Price;
     }
 
@@ -187,6 +198,9 @@ public sealed class PricingSystem : EntitySystem
         var ev = new PriceCalculationEvent();
         RaiseLocalEvent(uid, ref ev);
 
+        if (ev.Handled)
+            return ev.Price;
+
         //TODO: Add an OpaqueToAppraisal component or similar for blocking the recursive descent into containers, or preventing material pricing.
 
         if (TryComp<MaterialComponent>(uid, out var material) && !HasComp<StackPriceComponent>(uid))
@@ -249,5 +263,10 @@ public struct PriceCalculationEvent
     /// </summary>
     public double Price = 0;
 
+    /// <summary>
+    /// Whether this event was already handled.
+    /// </summary>
+    public bool Handled = false;
+
     public PriceCalculationEvent() { }
 }
index 363561606c66491b60d4db7b473500ff937a6710..95ef6b33c3f896feac46e5d99fa92f61eeab76dd 100644 (file)
@@ -12,7 +12,6 @@ namespace Content.Server.Storage.Components
         /// <summary>
         ///     The list of entities to spawn, with amounts and orGroups.
         /// </summary>
-        /// <returns></returns>
         [DataField("items", required: true)]
         public List<EntitySpawnEntry> Items = new();
 
@@ -25,7 +24,6 @@ namespace Content.Server.Storage.Components
         /// <summary>
         ///     How many uses before the item should delete itself.
         /// </summary>
-        /// <returns></returns>
         [DataField("uses")]
         public int Uses = 1;
     }
index 35936fea5c199599a7b39f946e85ffef413f2587..b24348507dd579420498b8d9513f15be58af7671 100644 (file)
@@ -1,12 +1,15 @@
 using Content.Server.Administration.Logs;
+using Content.Server.Cargo.Systems;
 using Content.Server.Storage.Components;
 using Content.Shared.Database;
 using Content.Shared.Hands.EntitySystems;
 using Content.Shared.Interaction.Events;
 using Content.Shared.Storage;
 using Robust.Shared.Audio;
+using Robust.Shared.Map;
 using Robust.Shared.Player;
 using Robust.Shared.Random;
+using static Content.Shared.Storage.EntitySpawnCollection;
 
 namespace Content.Server.Storage.EntitySystems
 {
@@ -14,13 +17,47 @@ namespace Content.Server.Storage.EntitySystems
     {
         [Dependency] private readonly IRobustRandom _random = default!;
         [Dependency] private readonly IAdminLogManager _adminLogger = default!;
-        [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
+        [Dependency] private readonly SharedHandsSystem _hands = default!;
+        [Dependency] private readonly PricingSystem _pricing = default!;
 
         public override void Initialize()
         {
             base.Initialize();
 
             SubscribeLocalEvent<SpawnItemsOnUseComponent, UseInHandEvent>(OnUseInHand);
+            SubscribeLocalEvent<SpawnItemsOnUseComponent, PriceCalculationEvent>(CalculatePrice, before: new[] { typeof(PricingSystem) });
+        }
+
+        private void CalculatePrice(EntityUid uid, SpawnItemsOnUseComponent component, ref PriceCalculationEvent args)
+        {
+            var ungrouped = CollectOrGroups(component.Items, out var orGroups);
+
+            foreach (var entry in ungrouped)
+            {
+                var protUid = Spawn(entry.PrototypeId, MapCoordinates.Nullspace);
+
+                // Calculate the average price of the possible spawned items
+                args.Price += _pricing.GetPrice(protUid) * entry.SpawnProbability * entry.GetAmount(getAverage: true);
+
+                EntityManager.DeleteEntity(protUid);
+            }
+
+            foreach (var group in orGroups)
+            {
+                foreach (var entry in group.Entries)
+                {
+                    var protUid = Spawn(entry.PrototypeId, MapCoordinates.Nullspace);
+
+                    // Calculate the average price of the possible spawned items
+                    args.Price += _pricing.GetPrice(protUid) *
+                                  (entry.SpawnProbability / group.CumulativeProbability) *
+                                  entry.GetAmount(getAverage: true);
+
+                    EntityManager.DeleteEntity(protUid);
+                }
+            }
+
+            args.Handled = true;
         }
 
         private void OnUseInHand(EntityUid uid, SpawnItemsOnUseComponent component, UseInHandEvent args)
@@ -29,7 +66,7 @@ namespace Content.Server.Storage.EntitySystems
                 return;
 
             var coords = Transform(args.User).Coordinates;
-            var spawnEntities = EntitySpawnCollection.GetSpawns(component.Items, _random);
+            var spawnEntities = GetSpawns(component.Items, _random);
             EntityUid? entityToPlaceInHands = null;
 
             foreach (var proto in spawnEntities)
@@ -50,7 +87,7 @@ namespace Content.Server.Storage.EntitySystems
 
             if (entityToPlaceInHands != null)
             {
-                _handsSystem.PickupOrDrop(args.User, entityToPlaceInHands.Value);
+                _hands.PickupOrDrop(args.User, entityToPlaceInHands.Value);
             }
         }
     }
index d1c92c59b80e58392572ee1080e1a17670f15736..da57ca850a2c1306e6335ac74597a5bccde0eef4 100644 (file)
@@ -1,6 +1,6 @@
-using Robust.Shared.Prototypes;
+using System.Linq;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
-using Robust.Shared.Serialization.Manager;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
 namespace Content.Shared.Storage;
@@ -62,7 +62,7 @@ public struct EntitySpawnEntry
 
 public static class EntitySpawnCollection
 {
-    private sealed class OrGroup
+    public sealed class OrGroup
     {
         public List<EntitySpawnEntry> Entries { get; set; } = new();
         public float CumulativeProbability { get; set; } = 0f;
@@ -84,34 +84,16 @@ public static class EntitySpawnCollection
         IoCManager.Resolve(ref random);
 
         var spawned = new List<string?>();
-        var orGroupedSpawns = new Dictionary<string, OrGroup>();
+        var ungrouped = CollectOrGroups(entries, out var orGroupedSpawns);
 
-        // collect groups together, create singular items that pass probability
-        foreach (var entry in entries)
+        foreach (var entry in ungrouped)
         {
-            // Handle "Or" groups
-            if (!string.IsNullOrEmpty(entry.GroupId))
-            {
-                if (!orGroupedSpawns.TryGetValue(entry.GroupId, out OrGroup? orGroup))
-                {
-                    orGroup = new();
-                    orGroupedSpawns.Add(entry.GroupId, orGroup);
-                }
-
-                orGroup.Entries.Add(entry);
-                orGroup.CumulativeProbability += entry.SpawnProbability;
-                continue;
-            }
-
-            // else
             // Check random spawn
             // ReSharper disable once CompareOfFloatsByEqualityOperator
-            if (entry.SpawnProbability != 1f && !random.Prob(entry.SpawnProbability)) continue;
-
-            var amount = entry.Amount;
+            if (entry.SpawnProbability != 1f && !random.Prob(entry.SpawnProbability))
+                continue;
 
-            if (entry.MaxAmount > amount)
-                amount = random.Next(amount, entry.MaxAmount);
+            var amount = (int) entry.GetAmount(random);
 
             for (var i = 0; i < amount; i++)
             {
@@ -119,25 +101,25 @@ public static class EntitySpawnCollection
             }
         }
 
-        // handle orgroup spawns
-        foreach (var spawnValue in orGroupedSpawns.Values)
+        // Handle OrGroup spawns
+        foreach (var spawnValue in orGroupedSpawns)
         {
             // For each group use the added cumulative probability to roll a double in that range
-            double diceRoll = random.NextDouble() * spawnValue.CumulativeProbability;
+            var diceRoll = random.NextDouble() * spawnValue.CumulativeProbability;
+
             // Add the entry's spawn probability to this value, if equals or lower, spawn item, otherwise continue to next item.
             var cumulative = 0.0;
+
             foreach (var entry in spawnValue.Entries)
             {
                 cumulative += entry.SpawnProbability;
-                if (diceRoll > cumulative) continue;
-                // Dice roll succeeded, add item and break loop
-
-                var amount = entry.Amount;
+                if (diceRoll > cumulative)
+                    continue;
 
-                if (entry.MaxAmount > amount)
-                    amount = random.Next(amount, entry.MaxAmount);
+                // Dice roll succeeded, add item and break loop
+                var amount = (int) entry.GetAmount(random);
 
-                for (var index = 0; index < amount; index++)
+                for (var i = 0; i < amount; i++)
                 {
                     spawned.Add(entry.PrototypeId);
                 }
@@ -148,4 +130,58 @@ public static class EntitySpawnCollection
 
         return spawned;
     }
+
+    /// <summary>
+    /// Collects all entries that belong together in an OrGroup, and then returns the leftover ungrouped entries.
+    /// </summary>
+    /// <param name="entries">A list of entries that will be collected into OrGroups.</param>
+    /// <param name="orGroups">A list of entries collected into OrGroups.</param>
+    /// <returns>A list of entries that are not in an OrGroup.</returns>
+    public static List<EntitySpawnEntry> CollectOrGroups(IEnumerable<EntitySpawnEntry> entries, out List<OrGroup> orGroups)
+    {
+        var ungrouped = new List<EntitySpawnEntry>();
+        var orGroupsDict = new Dictionary<string, OrGroup>();
+
+        foreach (var entry in entries)
+        {
+            // If the entry is in a group, collect it into an OrGroup. Otherwise just add it to a list of ungrouped
+            // entries.
+            if (!string.IsNullOrEmpty(entry.GroupId))
+            {
+                // Create a new OrGroup if necessary
+                if (!orGroupsDict.TryGetValue(entry.GroupId, out var orGroup))
+                {
+                    orGroup = new OrGroup();
+                    orGroupsDict.Add(entry.GroupId, orGroup);
+                }
+
+                orGroup.Entries.Add(entry);
+                orGroup.CumulativeProbability += entry.SpawnProbability;
+            }
+            else
+            {
+                ungrouped.Add(entry);
+            }
+        }
+
+        // We don't really need the group IDs anymore, so just return the values as a list
+        orGroups = orGroupsDict.Values.ToList();
+
+        return ungrouped;
+    }
+
+    public static double GetAmount(this EntitySpawnEntry entry, IRobustRandom? random = null, bool getAverage = false)
+    {
+        // Max amount is less or equal than amount, so just return the amount
+        if (entry.MaxAmount <= entry.Amount)
+            return entry.Amount;
+
+        // If we want the average, just calculate the expected amount
+        if (getAverage)
+            return (entry.Amount + entry.MaxAmount) / 2.0;
+
+        // Otherwise get a random value in between
+        IoCManager.Resolve(ref random);
+        return random.Next(entry.Amount, entry.MaxAmount);
+    }
 }
index c1867876e45163327b256cb3cd3b0abf52e21d21..84bbb670be0443746e6794ebc2c4a274961e76fa 100644 (file)
       - id: FoodTinMREOpen
     sound:
       path: /Audio/Items/can_open3.ogg
-  - type: StaticPrice
-    price: 50
 
 
 - type: entity