using System.Globalization; using System.Linq; using Content.Server.Administration; using Content.Server.Cargo.Systems; using Content.Server.EUI; using Content.Server.Item; using Content.Server.Power.Components; using Content.Shared.Administration; using Content.Shared.Damage.Prototypes; using Content.Shared.Item; using Content.Shared.Research.Prototypes; using Content.Shared.UserInterface; using Content.Shared.Weapons.Melee; using Content.Shared.Wieldable.Components; using Robust.Shared.Console; using Robust.Shared.Prototypes; namespace Content.Server.UserInterface; [AdminCommand(AdminFlags.Debug)] public sealed class StatValuesCommand : IConsoleCommand { [Dependency] private readonly EuiManager _eui = default!; [Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IPrototypeManager _proto = default!; public string Command => "showvalues"; public string Description => Loc.GetString("stat-values-desc"); public string Help => $"{Command} "; public void Execute(IConsoleShell shell, string argStr, string[] args) { if (shell.Player is not { } pSession) { shell.WriteError(Loc.GetString("stat-values-server")); return; } if (args.Length != 1) { shell.WriteError(Loc.GetString("stat-values-args")); return; } StatValuesEuiMessage message; switch (args[0]) { case "cargosell": message = GetCargo(); break; case "lathesell": message = GetLatheMessage(); break; case "melee": message = GetMelee(); break; case "itemsize": message = GetItem(); break; case "drawrate": message = GetDrawRateMessage(); break; default: shell.WriteError(Loc.GetString("stat-values-invalid", ("arg", args[0]))); return; } var eui = new StatValuesEui(); _eui.OpenEui(eui, pSession); eui.SendMessage(message); } public CompletionResult GetCompletion(IConsoleShell shell, string[] args) { if (args.Length == 1) { return CompletionResult.FromOptions(new[] { "cargosell", "lathesell", "melee", "itemsize", "drawrate" }); } return CompletionResult.Empty; } private StatValuesEuiMessage GetCargo() { // Okay so there's no easy way to do this with how pricing works // So we'll just get the first value for each prototype ID which is probably good enough for the majority. var values = new List(); var priceSystem = _entManager.System(); var metaQuery = _entManager.GetEntityQuery(); var prices = new HashSet(256); var ents = _entManager.GetEntities().ToArray(); foreach (var entity in ents) { if (!metaQuery.TryGetComponent(entity, out var meta)) continue; var id = meta.EntityPrototype?.ID; // We'll add it even if we don't have it so we don't have to raise the event again because this is probably faster. if (id == null || !prices.Add(id)) continue; var price = priceSystem.GetPrice(entity); if (price == 0) continue; values.Add(new[] { id, $"{price:0}", }); } var state = new StatValuesEuiMessage() { Title = Loc.GetString("stat-cargo-values"), Headers = new List() { Loc.GetString("stat-cargo-id"), Loc.GetString("stat-cargo-price"), }, Values = values, }; return state; } private StatValuesEuiMessage GetItem() { var values = new List(); var itemSystem = _entManager.System(); var metaQuery = _entManager.GetEntityQuery(); var itemQuery = _entManager.GetEntityQuery(); var items = new HashSet(1024); var ents = _entManager.GetEntities().ToArray(); foreach (var entity in ents) { if (!metaQuery.TryGetComponent(entity, out var meta)) continue; var id = meta.EntityPrototype?.ID; // We'll add it even if we don't have it so we don't have to raise the event again because this is probably faster. if (id == null || !items.Add(id)) continue; if (!itemQuery.TryGetComponent(entity, out var itemComp)) continue; values.Add(new[] { id, $"{itemSystem.GetItemSizeLocale(itemComp.Size)}", }); } var state = new StatValuesEuiMessage { Title = Loc.GetString("stat-item-values"), Headers = new List { Loc.GetString("stat-item-id"), Loc.GetString("stat-item-price"), }, Values = values, }; return state; } private static readonly ProtoId StructuralDamageType = "Structural"; private StatValuesEuiMessage GetMelee() { var values = new List(); var meleeName = _entManager.ComponentFactory.GetComponentName(); var increaseDamageName = _entManager.ComponentFactory.GetComponentName(); foreach (var proto in _proto.EnumeratePrototypes()) { if (proto.Abstract || !proto.Components.TryGetValue(meleeName, out var meleeComp)) { continue; } var comp = (MeleeWeaponComponent) meleeComp.Component; // TODO: Esword damage var structuralDamage = comp.Damage.DamageDict.GetValueOrDefault(StructuralDamageType); var baseDamage = comp.Damage.GetTotal() - comp.Damage.DamageDict.GetValueOrDefault(StructuralDamageType); var wieldedStructuralDamage = "-"; var wieldedDamage = "-"; if (proto.Components.TryGetValue(increaseDamageName, out var increaseDamageComp)) { var comp2 = (IncreaseDamageOnWieldComponent) increaseDamageComp.Component; wieldedStructuralDamage = (structuralDamage + comp2.BonusDamage.DamageDict.GetValueOrDefault(StructuralDamageType)).ToString(); wieldedDamage = (baseDamage + comp2.BonusDamage.GetTotal() - comp2.BonusDamage.DamageDict.GetValueOrDefault(StructuralDamageType)).ToString(); } values.Add(new[] { proto.ID, baseDamage.ToString(), wieldedDamage, comp.AttackRate.ToString("0.00", CultureInfo.CurrentCulture), (comp.AttackRate * baseDamage).Float().ToString("0.00", CultureInfo.CurrentCulture), structuralDamage.ToString(), wieldedStructuralDamage, }); } var state = new StatValuesEuiMessage { Title = Loc.GetString("stat-melee-values"), Headers = new List { Loc.GetString("stat-melee-id"), Loc.GetString("stat-melee-base-damage"), Loc.GetString("stat-melee-wield-damage"), Loc.GetString("stat-melee-attack-rate"), Loc.GetString("stat-melee-dps"), Loc.GetString("stat-melee-structural-damage"), Loc.GetString("stat-melee-structural-wield-damage"), }, Values = values, }; return state; } private StatValuesEuiMessage GetLatheMessage() { var values = new List(); var priceSystem = _entManager.System(); foreach (var proto in _proto.EnumeratePrototypes()) { var cost = 0.0; foreach (var (material, count) in proto.Materials) { var materialPrice = _proto.Index(material).Price; cost += materialPrice * count; } var sell = priceSystem.GetLatheRecipePrice(proto); values.Add(new[] { proto.ID, $"{cost:0}", $"{sell:0}", }); } var state = new StatValuesEuiMessage() { Title = Loc.GetString("stat-lathe-values"), Headers = new List() { Loc.GetString("stat-lathe-id"), Loc.GetString("stat-lathe-cost"), Loc.GetString("stat-lathe-sell"), }, Values = values, }; return state; } private StatValuesEuiMessage GetDrawRateMessage() { var values = new List(); var powerName = _entManager.ComponentFactory.GetComponentName(); foreach (var proto in _proto.EnumeratePrototypes()) { if (proto.Abstract || !proto.Components.TryGetValue(powerName, out var powerConsumer)) { continue; } var comp = (ApcPowerReceiverComponent) powerConsumer.Component; if (comp.Load == 0) continue; values.Add(new[] { proto.ID, comp.Load.ToString(CultureInfo.InvariantCulture), }); } var state = new StatValuesEuiMessage { Title = Loc.GetString("stat-drawrate-values"), Headers = new List { Loc.GetString("stat-drawrate-id"), Loc.GetString("stat-drawrate-rate"), }, Values = values, }; return state; } }