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;
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)}.");
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)}.");
private void CalculateSolutionPrice(EntityUid uid, SolutionContainerManagerComponent component, ref PriceCalculationEvent args)
{
+ if (args.Handled)
+ return;
+
var price = 0f;
foreach (var solution in component.Solutions.Values)
private void CalculateStaticPrice(EntityUid uid, StaticPriceComponent component, ref PriceCalculationEvent args)
{
+ if (args.Handled)
+ return;
+
args.Price += component.Price;
}
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))
/// </summary>
public double Price = 0;
+ /// <summary>
+ /// Whether this event was already handled.
+ /// </summary>
+ public bool Handled = false;
+
public PriceCalculationEvent() { }
}
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
{
{
[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)
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)
if (entityToPlaceInHands != null)
{
- _handsSystem.PickupOrDrop(args.User, entityToPlaceInHands.Value);
+ _hands.PickupOrDrop(args.User, entityToPlaceInHands.Value);
}
}
}
-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;
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;
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++)
{
}
}
- // 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);
}
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);
+ }
}