Make pricing system aware of SpawnItemsOnUseComponent (#13626)

* Make appraisal tool aware of SpawnItemsOnUseComponent

* Move to SpawnItemsOnUseSystem
This commit is contained in:
Visne
2023-02-28 16:55:25 +01:00
committed by GitHub
parent 5e6a446c02
commit 7f8860187e
5 changed files with 132 additions and 44 deletions

View File

@@ -3,7 +3,6 @@ using Content.Server.Administration;
using Content.Server.Body.Systems; using Content.Server.Body.Systems;
using Content.Server.Cargo.Components; using Content.Server.Cargo.Components;
using Content.Server.Chemistry.Components.SolutionManager; using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Stack;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
@@ -88,6 +87,9 @@ public sealed class PricingSystem : EntitySystem
private void CalculateMobPrice(EntityUid uid, MobPriceComponent component, ref PriceCalculationEvent args) 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)) 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)}."); 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) private void CalculateStackPrice(EntityUid uid, StackPriceComponent component, ref PriceCalculationEvent args)
{ {
if (args.Handled)
return;
if (!TryComp<StackComponent>(uid, out var stack)) if (!TryComp<StackComponent>(uid, out var stack))
{ {
Logger.ErrorS("pricing", $"Tried to get the stack price of {ToPrettyString(uid)}, which has no {nameof(StackComponent)}."); 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) private void CalculateSolutionPrice(EntityUid uid, SolutionContainerManagerComponent component, ref PriceCalculationEvent args)
{ {
if (args.Handled)
return;
var price = 0f; var price = 0f;
foreach (var solution in component.Solutions.Values) 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) private void CalculateStaticPrice(EntityUid uid, StaticPriceComponent component, ref PriceCalculationEvent args)
{ {
if (args.Handled)
return;
args.Price += component.Price; args.Price += component.Price;
} }
@@ -187,6 +198,9 @@ public sealed class PricingSystem : EntitySystem
var ev = new PriceCalculationEvent(); var ev = new PriceCalculationEvent();
RaiseLocalEvent(uid, ref ev); 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. //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)) if (TryComp<MaterialComponent>(uid, out var material) && !HasComp<StackPriceComponent>(uid))
@@ -249,5 +263,10 @@ public struct PriceCalculationEvent
/// </summary> /// </summary>
public double Price = 0; public double Price = 0;
/// <summary>
/// Whether this event was already handled.
/// </summary>
public bool Handled = false;
public PriceCalculationEvent() { } public PriceCalculationEvent() { }
} }

View File

@@ -12,7 +12,6 @@ namespace Content.Server.Storage.Components
/// <summary> /// <summary>
/// The list of entities to spawn, with amounts and orGroups. /// The list of entities to spawn, with amounts and orGroups.
/// </summary> /// </summary>
/// <returns></returns>
[DataField("items", required: true)] [DataField("items", required: true)]
public List<EntitySpawnEntry> Items = new(); public List<EntitySpawnEntry> Items = new();
@@ -25,7 +24,6 @@ namespace Content.Server.Storage.Components
/// <summary> /// <summary>
/// How many uses before the item should delete itself. /// How many uses before the item should delete itself.
/// </summary> /// </summary>
/// <returns></returns>
[DataField("uses")] [DataField("uses")]
public int Uses = 1; public int Uses = 1;
} }

View File

@@ -1,12 +1,15 @@
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Server.Cargo.Systems;
using Content.Server.Storage.Components; using Content.Server.Storage.Components;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Hands.EntitySystems; using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Events; using Content.Shared.Interaction.Events;
using Content.Shared.Storage; using Content.Shared.Storage;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Random; using Robust.Shared.Random;
using static Content.Shared.Storage.EntitySpawnCollection;
namespace Content.Server.Storage.EntitySystems namespace Content.Server.Storage.EntitySystems
{ {
@@ -14,13 +17,47 @@ namespace Content.Server.Storage.EntitySystems
{ {
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = 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() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<SpawnItemsOnUseComponent, UseInHandEvent>(OnUseInHand); 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) private void OnUseInHand(EntityUid uid, SpawnItemsOnUseComponent component, UseInHandEvent args)
@@ -29,7 +66,7 @@ namespace Content.Server.Storage.EntitySystems
return; return;
var coords = Transform(args.User).Coordinates; var coords = Transform(args.User).Coordinates;
var spawnEntities = EntitySpawnCollection.GetSpawns(component.Items, _random); var spawnEntities = GetSpawns(component.Items, _random);
EntityUid? entityToPlaceInHands = null; EntityUid? entityToPlaceInHands = null;
foreach (var proto in spawnEntities) foreach (var proto in spawnEntities)
@@ -50,7 +87,7 @@ namespace Content.Server.Storage.EntitySystems
if (entityToPlaceInHands != null) if (entityToPlaceInHands != null)
{ {
_handsSystem.PickupOrDrop(args.User, entityToPlaceInHands.Value); _hands.PickupOrDrop(args.User, entityToPlaceInHands.Value);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
using Robust.Shared.Prototypes; using System.Linq;
using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Storage; namespace Content.Shared.Storage;
@@ -62,7 +62,7 @@ public struct EntitySpawnEntry
public static class EntitySpawnCollection public static class EntitySpawnCollection
{ {
private sealed class OrGroup public sealed class OrGroup
{ {
public List<EntitySpawnEntry> Entries { get; set; } = new(); public List<EntitySpawnEntry> Entries { get; set; } = new();
public float CumulativeProbability { get; set; } = 0f; public float CumulativeProbability { get; set; } = 0f;
@@ -84,34 +84,16 @@ public static class EntitySpawnCollection
IoCManager.Resolve(ref random); IoCManager.Resolve(ref random);
var spawned = new List<string?>(); 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 ungrouped)
foreach (var entry in entries)
{ {
// 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 // Check random spawn
// ReSharper disable once CompareOfFloatsByEqualityOperator // ReSharper disable once CompareOfFloatsByEqualityOperator
if (entry.SpawnProbability != 1f && !random.Prob(entry.SpawnProbability)) continue; if (entry.SpawnProbability != 1f && !random.Prob(entry.SpawnProbability))
continue;
var amount = entry.Amount; var amount = (int) entry.GetAmount(random);
if (entry.MaxAmount > amount)
amount = random.Next(amount, entry.MaxAmount);
for (var i = 0; i < amount; i++) for (var i = 0; i < amount; i++)
{ {
@@ -119,25 +101,25 @@ public static class EntitySpawnCollection
} }
} }
// handle orgroup spawns // Handle OrGroup spawns
foreach (var spawnValue in orGroupedSpawns.Values) foreach (var spawnValue in orGroupedSpawns)
{ {
// For each group use the added cumulative probability to roll a double in that range // 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. // Add the entry's spawn probability to this value, if equals or lower, spawn item, otherwise continue to next item.
var cumulative = 0.0; var cumulative = 0.0;
foreach (var entry in spawnValue.Entries) foreach (var entry in spawnValue.Entries)
{ {
cumulative += entry.SpawnProbability; cumulative += entry.SpawnProbability;
if (diceRoll > cumulative) continue; if (diceRoll > cumulative)
continue;
// Dice roll succeeded, add item and break loop // Dice roll succeeded, add item and break loop
var amount = (int) entry.GetAmount(random);
var amount = entry.Amount; for (var i = 0; i < amount; i++)
if (entry.MaxAmount > amount)
amount = random.Next(amount, entry.MaxAmount);
for (var index = 0; index < amount; index++)
{ {
spawned.Add(entry.PrototypeId); spawned.Add(entry.PrototypeId);
} }
@@ -148,4 +130,58 @@ public static class EntitySpawnCollection
return spawned; 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);
}
} }

View File

@@ -247,8 +247,6 @@
- id: FoodTinMREOpen - id: FoodTinMREOpen
sound: sound:
path: /Audio/Items/can_open3.ogg path: /Audio/Items/can_open3.ogg
- type: StaticPrice
price: 50
- type: entity - type: entity