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
{
2023-03-24 15:27:55 +11:00
[Dependency] private readonly IComponentFactory _factory = default ! ;
2022-06-03 10:56:11 -05:00
[Dependency] private readonly IConsoleHost _consoleHost = default ! ;
[Dependency] private readonly IMapManager _mapManager = default ! ;
2022-11-15 12:51:30 +01:00
[Dependency] private readonly IPrototypeManager _prototypeManager = default ! ;
2022-10-23 00:46:28 +02:00
[Dependency] private readonly BodySystem _bodySystem = default ! ;
2023-03-24 15:27:55 +11:00
[Dependency] private readonly MobStateSystem _mobStateSystem = default ! ;
2022-10-23 00:46:28 +02:00
2022-06-03 10:56:11 -05:00
/// <inheritdoc/>
public override void Initialize ( )
{
SubscribeLocalEvent < MobPriceComponent , PriceCalculationEvent > ( CalculateMobPrice ) ;
_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 ( ) ;
} ) ;
2023-08-06 22:43:34 -07:00
shell . WriteLine ( $"Grid {gid} appraised to {value} spesos." ) ;
2022-06-03 10:56:11 -05:00
shell . WriteLine ( $"The top most valuable items were:" ) ;
foreach ( var ( price , ent ) in mostValuable )
{
2023-08-06 22:43:34 -07:00
shell . WriteLine ( $"- {ToPrettyString(ent)} @ {price} spesos" ) ;
2022-06-03 10:56:11 -05:00
}
}
}
private void CalculateMobPrice ( EntityUid uid , MobPriceComponent component , ref PriceCalculationEvent args )
{
2023-03-24 15:27:55 +11:00
// TODO: Estimated pricing.
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
}
2023-03-24 15:27:55 +11:00
private double GetSolutionPrice ( SolutionContainerManagerComponent component )
2022-06-03 10:56:11 -05:00
{
2023-03-24 15:27:55 +11:00
var price = 0.0 ;
2022-11-15 12:51:30 +01:00
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 ;
}
}
2023-03-24 15:27:55 +11:00
return price ;
2022-11-15 12:51:30 +01:00
}
2023-04-29 00:53:41 -04:00
private double GetMaterialPrice ( PhysicalCompositionComponent component )
2022-06-03 10:56:11 -05:00
{
2023-03-24 15:27:55 +11:00
double price = 0 ;
2023-04-29 00:53:41 -04:00
foreach ( var ( id , quantity ) in component . MaterialComposition )
2023-03-24 15:27:55 +11:00
{
price + = _prototypeManager . Index < MaterialPrototype > ( id ) . Price * quantity ;
}
return price ;
2022-06-03 10:56:11 -05:00
}
2022-09-15 11:53:17 +10:00
/// <summary>
/// Get a rough price for an entityprototype. Does not consider contained entities.
/// </summary>
2023-03-24 15:27:55 +11:00
public double GetEstimatedPrice ( EntityPrototype prototype )
2022-09-15 11:53:17 +10:00
{
2023-03-24 15:27:55 +11:00
var ev = new EstimatedPriceCalculationEvent ( )
2022-09-15 11:53:17 +10:00
{
2023-03-24 15:27:55 +11:00
Prototype = prototype ,
} ;
2022-09-15 11:53:17 +10:00
2023-03-24 15:27:55 +11:00
RaiseLocalEvent ( ref ev ) ;
2022-09-15 11:53:17 +10:00
2023-03-24 15:27:55 +11:00
if ( ev . Handled )
return ev . Price ;
2022-09-15 11:53:17 +10:00
2023-03-24 15:27:55 +11:00
var price = ev . Price ;
price + = GetMaterialsPrice ( prototype ) ;
price + = GetSolutionsPrice ( prototype ) ;
2023-03-27 04:01:42 +11:00
// Can't use static price with stackprice
var oldPrice = price ;
2023-03-24 15:27:55 +11:00
price + = GetStackPrice ( prototype ) ;
2023-03-27 04:01:42 +11:00
if ( oldPrice . Equals ( price ) )
{
price + = GetStaticPrice ( prototype ) ;
}
2023-03-24 15:27:55 +11:00
// TODO: Proper container support.
2022-09-15 11:53:17 +10:00
2023-01-08 11:36:32 +13:00
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 ;
2023-03-24 15:27:55 +11:00
var price = 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.
2023-03-24 15:27:55 +11:00
// DO NOT FORGET TO UPDATE ESTIMATED PRICING
price + = GetMaterialsPrice ( uid ) ;
price + = GetSolutionsPrice ( uid ) ;
2023-03-27 04:01:42 +11:00
// Can't use static price with stackprice
var oldPrice = price ;
2023-03-24 15:27:55 +11:00
price + = GetStackPrice ( uid ) ;
2023-03-27 04:01:42 +11:00
if ( oldPrice . Equals ( price ) )
{
price + = GetStaticPrice ( uid ) ;
}
2023-03-24 15:27:55 +11:00
if ( TryComp < ContainerManagerComponent > ( uid , out var containers ) )
{
foreach ( var container in containers . Containers . Values )
{
foreach ( var ent in container . ContainedEntities )
{
price + = GetPrice ( ent ) ;
}
}
}
return price ;
}
private double GetMaterialsPrice ( EntityUid uid )
{
double price = 0 ;
2022-06-03 10:56:11 -05:00
2023-04-29 00:53:41 -04:00
if ( HasComp < MaterialComponent > ( uid ) & &
TryComp < PhysicalCompositionComponent > ( uid , out var composition ) )
2022-06-03 10:56:11 -05:00
{
2023-04-29 00:53:41 -04:00
var matPrice = GetMaterialPrice ( composition ) ;
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 ;
2023-03-24 15:27:55 +11:00
price + = matPrice ;
2022-06-03 10:56:11 -05:00
}
2023-03-24 15:27:55 +11:00
return price ;
}
private double GetMaterialsPrice ( EntityPrototype prototype )
{
double price = 0 ;
2023-04-29 00:53:41 -04:00
if ( prototype . Components . ContainsKey ( _factory . GetComponentName ( typeof ( MaterialComponent ) ) ) & &
prototype . Components . TryGetValue ( _factory . GetComponentName ( typeof ( PhysicalCompositionComponent ) ) , out var composition ) )
2022-06-03 10:56:11 -05:00
{
2023-04-29 00:53:41 -04:00
var compositionComp = ( PhysicalCompositionComponent ) composition . Component ;
var matPrice = GetMaterialPrice ( compositionComp ) ;
2023-03-24 15:27:55 +11:00
if ( prototype . Components . TryGetValue ( _factory . GetComponentName ( typeof ( StackComponent ) ) , out var stackProto ) )
2022-06-03 10:56:11 -05:00
{
2023-03-24 15:27:55 +11:00
matPrice * = ( ( StackComponent ) stackProto . Component ) . Count ;
2022-06-03 10:56:11 -05:00
}
2023-03-24 15:27:55 +11:00
price + = matPrice ;
}
return price ;
}
private double GetSolutionsPrice ( EntityUid uid )
{
var price = 0.0 ;
if ( TryComp < SolutionContainerManagerComponent > ( uid , out var solComp ) )
{
price + = GetSolutionPrice ( solComp ) ;
2022-06-03 10:56:11 -05:00
}
2023-03-24 15:27:55 +11:00
return price ;
}
private double GetSolutionsPrice ( EntityPrototype prototype )
{
var price = 0.0 ;
if ( prototype . Components . TryGetValue ( _factory . GetComponentName ( typeof ( SolutionContainerManagerComponent ) ) , out var solManager ) )
{
var solComp = ( SolutionContainerManagerComponent ) solManager . Component ;
price + = GetSolutionPrice ( solComp ) ;
}
return price ;
}
private double GetStackPrice ( EntityUid uid )
{
var price = 0.0 ;
if ( TryComp < StackPriceComponent > ( uid , out var stackPrice ) & &
2023-04-10 00:38:20 -04:00
TryComp < StackComponent > ( uid , out var stack ) & &
! HasComp < MaterialComponent > ( uid ) ) // don't double count material prices
2023-03-24 15:27:55 +11:00
{
price + = stack . Count * stackPrice . Price ;
}
return price ;
}
private double GetStackPrice ( EntityPrototype prototype )
{
var price = 0.0 ;
if ( prototype . Components . TryGetValue ( _factory . GetComponentName ( typeof ( StackPriceComponent ) ) , out var stackpriceProto ) & &
2023-04-10 00:38:20 -04:00
prototype . Components . TryGetValue ( _factory . GetComponentName ( typeof ( StackComponent ) ) , out var stackProto ) & &
! prototype . Components . ContainsKey ( _factory . GetComponentName ( typeof ( MaterialComponent ) ) ) )
2023-03-24 15:27:55 +11:00
{
var stackPrice = ( StackPriceComponent ) stackpriceProto . Component ;
var stack = ( StackComponent ) stackProto . Component ;
price + = stack . Count * stackPrice . Price ;
}
return price ;
}
private double GetStaticPrice ( EntityUid uid )
{
var price = 0.0 ;
if ( TryComp < StaticPriceComponent > ( uid , out var staticPrice ) )
{
price + = staticPrice . Price ;
}
return price ;
}
private double GetStaticPrice ( EntityPrototype prototype )
{
var price = 0.0 ;
if ( prototype . Components . TryGetValue ( _factory . GetComponentName ( typeof ( StaticPriceComponent ) ) , out var staticProto ) )
{
var staticPrice = ( StaticPriceComponent ) staticProto . Component ;
price + = staticPrice . Price ;
}
return price ;
2022-06-03 10:56:11 -05:00
}
/// <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]
2023-03-24 15:27:55 +11:00
public record struct PriceCalculationEvent ( )
2022-06-03 10:56:11 -05:00
{
/// <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 ;
2023-03-24 15:27:55 +11:00
}
/// <summary>
/// Raised broadcast for an entity prototype to determine its estimated price.
/// </summary>
[ByRefEvent]
public record struct EstimatedPriceCalculationEvent ( )
{
public EntityPrototype Prototype ;
2023-02-28 16:55:25 +01:00
2023-03-24 15:27:55 +11:00
/// <summary>
/// The total price of the entity.
/// </summary>
public double Price = 0 ;
/// <summary>
/// Whether this event was already handled.
/// </summary>
public bool Handled = false ;
2022-06-03 10:56:11 -05:00
}