2022-06-03 10:56:11 -05:00
using System.Linq ;
using Content.Server.Administration ;
2022-10-23 00:46:28 +02:00
using Content.Server.Body.Systems ;
2022-06-03 10:56:11 -05:00
using Content.Server.Cargo.Components ;
2022-11-15 12:51:30 +01:00
using Content.Server.Chemistry.Components.SolutionManager ;
2022-06-03 10:56:11 -05:00
using Content.Shared.Administration ;
2022-10-23 00:46:28 +02:00
using Content.Shared.Body.Components ;
2022-11-15 12:51:30 +01:00
using Content.Shared.Chemistry.Reagent ;
2022-10-23 00:46:28 +02:00
using Content.Shared.Materials ;
2023-01-13 16:57:10 -08:00
using Content.Shared.Mobs.Components ;
using Content.Shared.Mobs.Systems ;
2022-12-24 23:28:21 -05:00
using Content.Shared.Stacks ;
2022-06-03 10:56:11 -05:00
using Robust.Shared.Console ;
using Robust.Shared.Containers ;
using Robust.Shared.Map ;
2022-09-15 11:53:17 +10:00
using Robust.Shared.Prototypes ;
2022-06-03 10:56:11 -05:00
using Robust.Shared.Utility ;
namespace Content.Server.Cargo.Systems ;
/// <summary>
/// This handles calculating the price of items, and implements two basic methods of pricing materials.
/// </summary>
public sealed class PricingSystem : EntitySystem
{
[Dependency] private readonly IConsoleHost _consoleHost = default ! ;
[Dependency] private readonly IMapManager _mapManager = default ! ;
2022-12-19 19:25:35 -08:00
[Dependency] private readonly MobStateSystem _mobStateSystem = default ! ;
2022-11-15 12:51:30 +01:00
[Dependency] private readonly IPrototypeManager _prototypeManager = default ! ;
2022-06-03 10:56:11 -05:00
2022-10-23 00:46:28 +02:00
[Dependency] private readonly BodySystem _bodySystem = default ! ;
2022-06-03 10:56:11 -05:00
/// <inheritdoc/>
public override void Initialize ( )
{
SubscribeLocalEvent < StaticPriceComponent , PriceCalculationEvent > ( CalculateStaticPrice ) ;
SubscribeLocalEvent < StackPriceComponent , PriceCalculationEvent > ( CalculateStackPrice ) ;
SubscribeLocalEvent < MobPriceComponent , PriceCalculationEvent > ( CalculateMobPrice ) ;
2022-11-15 12:51:30 +01:00
SubscribeLocalEvent < SolutionContainerManagerComponent , PriceCalculationEvent > ( CalculateSolutionPrice ) ;
2022-06-03 10:56:11 -05:00
_consoleHost . RegisterCommand ( "appraisegrid" ,
"Calculates the total value of the given grids." ,
"appraisegrid <grid Ids>" , AppraiseGridCommand ) ;
}
[AdminCommand(AdminFlags.Debug)]
private void AppraiseGridCommand ( IConsoleShell shell , string argstr , string [ ] args )
{
if ( args . Length = = 0 )
{
shell . WriteError ( "Not enough arguments." ) ;
return ;
}
foreach ( var gid in args )
{
2022-10-10 10:41:32 +13:00
if ( ! EntityUid . TryParse ( gid , out var gridId ) | | ! gridId . IsValid ( ) )
2022-06-03 10:56:11 -05:00
{
shell . WriteError ( $"Invalid grid ID \" { gid } \ "." ) ;
continue ;
}
if ( ! _mapManager . TryGetGrid ( gridId , out var mapGrid ) )
{
2022-10-10 10:41:32 +13:00
shell . WriteError ( $"Grid \" { gridId } \ " doesn't exist." ) ;
2022-06-03 10:56:11 -05:00
continue ;
}
List < ( double , EntityUid ) > mostValuable = new ( ) ;
2022-12-12 14:59:02 +11:00
var value = AppraiseGrid ( mapGrid . Owner , null , ( uid , price ) = >
2022-06-03 10:56:11 -05:00
{
mostValuable . Add ( ( price , uid ) ) ;
mostValuable . Sort ( ( i1 , i2 ) = > i2 . Item1 . CompareTo ( i1 . Item1 ) ) ;
if ( mostValuable . Count > 5 )
mostValuable . Pop ( ) ;
} ) ;
2022-06-24 07:43:44 -07:00
shell . WriteLine ( $"Grid {gid} appraised to {value} spacebucks." ) ;
2022-06-03 10:56:11 -05:00
shell . WriteLine ( $"The top most valuable items were:" ) ;
foreach ( var ( price , ent ) in mostValuable )
{
2022-06-24 07:43:44 -07:00
shell . WriteLine ( $"- {ToPrettyString(ent)} @ {price} spacebucks" ) ;
2022-06-03 10:56:11 -05:00
}
}
}
private void CalculateMobPrice ( EntityUid uid , MobPriceComponent component , ref PriceCalculationEvent args )
{
2023-02-28 16:55:25 +01:00
if ( args . Handled )
return ;
2022-06-03 10:56:11 -05:00
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)}." ) ;
return ;
}
2022-10-23 00:46:28 +02:00
var partList = _bodySystem . GetBodyAllSlots ( uid , body ) . ToList ( ) ;
var totalPartsPresent = partList . Sum ( x = > x . Child ! = null ? 1 : 0 ) ;
2022-06-03 10:56:11 -05:00
var totalParts = partList . Count ;
var partRatio = totalPartsPresent / ( double ) totalParts ;
var partPenalty = component . Price * ( 1 - partRatio ) * component . MissingBodyPartPenalty ;
2022-12-19 19:25:35 -08:00
args . Price + = ( component . Price - partPenalty ) * ( _mobStateSystem . IsAlive ( uid , state ) ? 1.0 : component . DeathPenalty ) ;
2022-06-03 10:56:11 -05:00
}
private void CalculateStackPrice ( EntityUid uid , StackPriceComponent component , ref PriceCalculationEvent args )
{
2023-02-28 16:55:25 +01:00
if ( args . Handled )
return ;
2022-06-03 10:56:11 -05:00
if ( ! TryComp < StackComponent > ( uid , out var stack ) )
{
Logger . ErrorS ( "pricing" , $"Tried to get the stack price of {ToPrettyString(uid)}, which has no {nameof(StackComponent)}." ) ;
return ;
}
args . Price + = stack . Count * component . Price ;
}
2022-11-15 12:51:30 +01:00
private void CalculateSolutionPrice ( EntityUid uid , SolutionContainerManagerComponent component , ref PriceCalculationEvent args )
{
2023-02-28 16:55:25 +01:00
if ( args . Handled )
return ;
2022-11-15 12:51:30 +01:00
var price = 0f ;
foreach ( var solution in component . Solutions . Values )
{
foreach ( var reagent in solution . Contents )
{
if ( ! _prototypeManager . TryIndex < ReagentPrototype > ( reagent . ReagentId , out var reagentProto ) )
continue ;
price + = ( float ) reagent . Quantity * reagentProto . PricePerUnit ;
}
}
args . Price + = price ;
}
2022-06-03 10:56:11 -05:00
private void CalculateStaticPrice ( EntityUid uid , StaticPriceComponent component , ref PriceCalculationEvent args )
{
2023-02-28 16:55:25 +01:00
if ( args . Handled )
return ;
2022-06-03 10:56:11 -05:00
args . Price + = component . Price ;
}
2022-09-15 11:53:17 +10:00
/// <summary>
/// Get a rough price for an entityprototype. Does not consider contained entities.
/// </summary>
public double GetEstimatedPrice ( EntityPrototype prototype , IComponentFactory ? factory = null )
{
IoCManager . Resolve ( ref factory ) ;
var price = 0.0 ;
if ( prototype . Components . TryGetValue ( factory . GetComponentName ( typeof ( StaticPriceComponent ) ) ,
out var staticPriceProto ) )
{
var staticComp = ( StaticPriceComponent ) staticPriceProto . Component ;
price + = staticComp . Price ;
}
if ( prototype . Components . TryGetValue ( factory . GetComponentName ( typeof ( StackPriceComponent ) ) , out var stackpriceProto ) & &
prototype . Components . TryGetValue ( factory . GetComponentName ( typeof ( StackComponent ) ) , out var stackProto ) )
{
var stackPrice = ( StackPriceComponent ) stackpriceProto . Component ;
var stack = ( StackComponent ) stackProto . Component ;
price + = stack . Count * stackPrice . Price ;
}
return price ;
}
2023-01-08 11:36:32 +13:00
public double GetMaterialPrice ( MaterialComponent component )
{
double price = 0 ;
foreach ( var ( id , quantity ) in component . Materials )
{
price + = _prototypeManager . Index < MaterialPrototype > ( id ) . Price * quantity ;
}
return price ;
}
2022-06-03 10:56:11 -05:00
/// <summary>
/// Appraises an entity, returning it's price.
/// </summary>
/// <param name="uid">The entity to appraise.</param>
/// <returns>The price of the entity.</returns>
/// <remarks>
/// This fires off an event to calculate the price.
/// Calculating the price of an entity that somehow contains itself will likely hang.
/// </remarks>
public double GetPrice ( EntityUid uid )
{
var ev = new PriceCalculationEvent ( ) ;
2022-09-15 11:53:17 +10:00
RaiseLocalEvent ( uid , ref ev ) ;
2022-06-03 10:56:11 -05:00
2023-02-28 16:55:25 +01:00
if ( ev . Handled )
return ev . Price ;
2022-06-03 10:56:11 -05:00
//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 ) )
{
2023-01-08 11:36:32 +13:00
var matPrice = GetMaterialPrice ( material ) ;
2022-06-03 10:56:11 -05:00
if ( TryComp < StackComponent > ( uid , out var stack ) )
2023-01-08 11:36:32 +13:00
matPrice * = stack . Count ;
ev . Price + = matPrice ;
2022-06-03 10:56:11 -05:00
}
if ( TryComp < ContainerManagerComponent > ( uid , out var containers ) )
{
foreach ( var container in containers . Containers )
{
foreach ( var ent in container . Value . ContainedEntities )
{
ev . Price + = GetPrice ( ent ) ;
}
}
}
return ev . Price ;
}
/// <summary>
/// Appraises a grid, this is mainly meant to be used by yarrs.
/// </summary>
/// <param name="grid">The grid to appraise.</param>
/// <param name="predicate">An optional predicate that controls whether or not the entity is counted toward the total.</param>
/// <param name="afterPredicate">An optional predicate to run after the price has been calculated. Useful for high scores or similar.</param>
/// <returns>The total value of the grid.</returns>
public double AppraiseGrid ( EntityUid grid , Func < EntityUid , bool > ? predicate = null , Action < EntityUid , double > ? afterPredicate = null )
{
var xform = Transform ( grid ) ;
var price = 0.0 ;
foreach ( var child in xform . ChildEntities )
{
if ( predicate is null | | predicate ( child ) )
{
var subPrice = GetPrice ( child ) ;
price + = subPrice ;
afterPredicate ? . Invoke ( child , subPrice ) ;
}
}
return price ;
}
}
/// <summary>
/// A directed by-ref event fired on an entity when something needs to know it's price. This value is not cached.
/// </summary>
[ByRefEvent]
public struct PriceCalculationEvent
{
/// <summary>
/// The total price of the entity.
/// </summary>
public double Price = 0 ;
2023-02-28 16:55:25 +01:00
/// <summary>
/// Whether this event was already handled.
/// </summary>
public bool Handled = false ;
2022-06-03 10:56:11 -05:00
public PriceCalculationEvent ( ) { }
}