diff --git a/Content.Server/AME/Components/AMEPartComponent.cs b/Content.Server/AME/Components/AMEPartComponent.cs index 426a5a0563..6b9346aa46 100644 --- a/Content.Server/AME/Components/AMEPartComponent.cs +++ b/Content.Server/AME/Components/AMEPartComponent.cs @@ -1,7 +1,7 @@ using System.Linq; using System.Threading.Tasks; using Content.Server.Hands.Components; -using Content.Server.Tools.Components; +using Content.Server.Tools; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Sound; @@ -42,7 +42,7 @@ namespace Content.Server.AME.Components return false; } - if (!args.Using.TryGetComponent(out var tool) || !tool.Qualities.Contains(_qualityNeeded)) + if (!EntitySystem.Get().HasQuality(args.Using.Uid, _qualityNeeded)) return false; if (!_mapManager.TryGetGrid(args.ClickLocation.GetGridId(_serverEntityManager), out var mapGrid)) diff --git a/Content.Server/Power/EntitySystems/CableMultitoolSystem.cs b/Content.Server/Power/EntitySystems/CableMultitoolSystem.cs new file mode 100644 index 0000000000..c14982af77 --- /dev/null +++ b/Content.Server/Power/EntitySystems/CableMultitoolSystem.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Server.NodeContainer; +using Content.Server.NodeContainer.EntitySystems; +using Content.Server.Power.Components; +using Content.Server.Power.Nodes; +using Content.Server.Power.Pow3r; +using Content.Server.Power.NodeGroups; +using Content.Server.Power.EntitySystems; +using Content.Server.Hands.Components; +using Content.Server.Tools; +using Content.Shared.Wires; +using Content.Shared.Examine; +using Content.Shared.Hands.Components; +using JetBrains.Annotations; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Localization; + +namespace Content.Server.Power.EntitySystems +{ + [UsedImplicitly] + public sealed class CableMultitoolSystem : EntitySystem + { + [Dependency] private readonly ToolSystem _toolSystem = default!; + [Dependency] private readonly PowerNetSystem _pnSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamined); + } + + private void OnExamined(EntityUid uid, CableComponent component, ExaminedEvent args) + { + // Must be in details range to try this. + // Theoretically there should be a separate range at which a multitool works, but this does just fine. + if (args.IsInDetailsRange) + { + // Determine if they are holding a multitool. + if (args.Examiner.TryGetComponent(out var hands) && hands.TryGetActiveHand(out var hand)) + { + var held = hand.HeldEntity; + // Pulsing is hardcoded here because I don't think it needs to be more complex than that right now. + // Update if I'm wrong. + if ((held != null) && _toolSystem.HasQuality(held.Uid, "Pulsing")) + { + args.PushMarkup(GenerateCableMarkup(uid)); + // args.PushFancyUpdatingPowerGraphs(uid); + } + } + } + } + + private string GenerateCableMarkup(EntityUid uid, NodeContainerComponent? nodeContainer = null) + { + if (!Resolve(uid, ref nodeContainer)) + return Loc.GetString("cable-multitool-system-internal-error-missing-component"); + + foreach (var node in nodeContainer.Nodes) + { + if (!(node.Value.NodeGroup is IBasePowerNet)) + continue; + var p = (IBasePowerNet) node.Value.NodeGroup; + var ps = _pnSystem.GetNetworkStatistics(p.NetworkNode); + + float storageRatio = ps.InStorageCurrent / Math.Max(ps.InStorageMax, 1.0f); + float outStorageRatio = ps.OutStorageCurrent / Math.Max(ps.OutStorageMax, 1.0f); + return Loc.GetString("cable-multitool-system-statistics", + ("supplyc", ps.SupplyCurrent), + ("supplyb", ps.SupplyBatteries), + ("supplym", ps.SupplyTheoretical), + ("consumption", ps.Consumption), + ("storagec", ps.InStorageCurrent), + ("storager", storageRatio), + ("storagem", ps.InStorageMax), + ("storageoc", ps.OutStorageCurrent), + ("storageor", outStorageRatio), + ("storageom", ps.OutStorageMax) + ); + } + return Loc.GetString("cable-multitool-system-internal-error-no-power-node"); + } + } +} diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs index 74b6783e14..867a915593 100644 --- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Collections.Generic; using Content.Server.NodeContainer.EntitySystems; using Content.Server.Power.Components; @@ -148,6 +149,60 @@ namespace Content.Server.Power.EntitySystems }; } + public NetworkPowerStatistics GetNetworkStatistics(PowerState.Network network) + { + // Right, consumption. Now this is a big mess. + // Start by summing up consumer draw rates. + // Then deal with batteries. + // While for consumers we want to use their max draw rates, + // for batteries we ought to use their current draw rates, + // because there's all sorts of weirdness with them. + // A full battery will still have the same max draw rate, + // but will likely have deliberately limited current draw rate. + float consumptionW = network.Loads.Sum(s => _powerState.Loads[s].DesiredPower); + consumptionW += network.BatteriesCharging.Sum(s => _powerState.Batteries[s].CurrentReceiving); + + // This is interesting because LastMaxSupplySum seems to match LastAvailableSupplySum for some reason. + // I suspect it's accounting for current supply rather than theoretical supply. + float maxSupplyW = network.Supplies.Sum(s => _powerState.Supplies[s].MaxSupply); + + // Battery stuff is more complex. + // Without stealing PowerState, the most efficient way + // to grab the necessary discharge data is from + // PowerNetworkBatteryComponent (has Pow3r reference). + float supplyBatteriesW = 0.0f; + float storageCurrentJ = 0.0f; + float storageMaxJ = 0.0f; + foreach (var discharger in network.BatteriesDischarging) + { + var nb = _powerState.Batteries[discharger]; + supplyBatteriesW += nb.CurrentSupply; + storageCurrentJ += nb.CurrentStorage; + storageMaxJ += nb.Capacity; + maxSupplyW += nb.MaxSupply; + } + // And charging + float outStorageCurrentJ = 0.0f; + float outStorageMaxJ = 0.0f; + foreach (var charger in network.BatteriesCharging) + { + var nb = _powerState.Batteries[charger]; + outStorageCurrentJ += nb.CurrentStorage; + outStorageMaxJ += nb.Capacity; + } + return new() + { + SupplyCurrent = network.LastMaxSupplySum, + SupplyBatteries = supplyBatteriesW, + SupplyTheoretical = maxSupplyW, + Consumption = consumptionW, + InStorageCurrent = storageCurrentJ, + InStorageMax = storageMaxJ, + OutStorageCurrent = outStorageCurrentJ, + OutStorageMax = outStorageMaxJ + }; + } + public override void Update(float frameTime) { base.Update(frameTime); @@ -376,4 +431,17 @@ namespace Content.Server.Power.EntitySystems public int CountSupplies; public int CountBatteries; } + + public struct NetworkPowerStatistics + { + public float SupplyCurrent; + public float SupplyBatteries; + public float SupplyTheoretical; + public float Consumption; + public float InStorageCurrent; + public float InStorageMax; + public float OutStorageCurrent; + public float OutStorageMax; + } + } diff --git a/Content.Shared/Localizations/Localization.cs b/Content.Shared/Localizations/Localization.cs index cbdbd0868b..6b60cf5f8d 100644 --- a/Content.Shared/Localizations/Localization.cs +++ b/Content.Shared/Localizations/Localization.cs @@ -31,6 +31,8 @@ namespace Content.Shared.Localizations loc.LoadCulture(culture); loc.AddFunction(culture, "PRESSURE", FormatPressure); + loc.AddFunction(culture, "POWERWATTS", FormatPowerWatts); + loc.AddFunction(culture, "POWERJOULES", FormatPowerJoules); loc.AddFunction(culture, "TOSTRING", args => FormatToString(culture, args)); } @@ -46,7 +48,7 @@ namespace Content.Shared.Localizations return new LocValueString(obj?.ToString() ?? ""); } - private static ILocValue FormatPressure(LocArgs args) + private static ILocValue FormatUnitsGeneric(LocArgs args, string mode) { const int maxPlaces = 5; // Matches amount in _lib.ftl var pressure = ((LocValueNumber) args.Args[0]).Value; @@ -58,7 +60,22 @@ namespace Content.Shared.Localizations places += 1; } - return new LocValueString(Loc.GetString("zzzz-fmt-pressure", ("divided", pressure), ("places", places))); + return new LocValueString(Loc.GetString(mode, ("divided", pressure), ("places", places))); + } + + private static ILocValue FormatPressure(LocArgs args) + { + return FormatUnitsGeneric(args, "zzzz-fmt-pressure"); + } + + private static ILocValue FormatPowerWatts(LocArgs args) + { + return FormatUnitsGeneric(args, "zzzz-fmt-power-watts"); + } + + private static ILocValue FormatPowerJoules(LocArgs args) + { + return FormatUnitsGeneric(args, "zzzz-fmt-power-joules"); } } } diff --git a/Resources/Locale/en-US/_lib.ftl b/Resources/Locale/en-US/_lib.ftl index 666505b621..b2e858b452 100644 --- a/Resources/Locale/en-US/_lib.ftl +++ b/Resources/Locale/en-US/_lib.ftl @@ -10,6 +10,28 @@ zzzz-fmt-pressure = { TOSTRING($divided, "G3") } { $places -> *[5] ??? } +# Used internally by the POWERWATTS() function. +zzzz-fmt-power-watts = { TOSTRING($divided, "G3") } { $places -> + [0] W + [1] kW + [2] MW + [3] GW + [4] TW + *[5] ??? +} + +# Used internally by the POWERJOULES() function. +# Reminder: 1 joule = 1 watt for 1 second (multiply watts by seconds to get joules). +# Therefore 1 kilowatt-hour is equal to 3,600,000 joules (3.6MJ) +zzzz-fmt-power-joules = { TOSTRING($divided, "G3") } { $places -> + [0] J + [1] kJ + [2] MJ + [3] GJ + [4] TJ + *[5] ??? +} + # Used internally by the THE() function. zzzz-the = { PROPER($ent) -> *[false] the { $ent } diff --git a/Resources/Locale/en-US/cable/cable-multitool-system.ftl b/Resources/Locale/en-US/cable/cable-multitool-system.ftl new file mode 100644 index 0000000000..6b15bcbd00 --- /dev/null +++ b/Resources/Locale/en-US/cable/cable-multitool-system.ftl @@ -0,0 +1,11 @@ +cable-multitool-system-internal-error-no-power-node = Your multitool reads, "INTERNAL ERROR: NOT A POWER CABLE". +cable-multitool-system-internal-error-missing-component = Your multitool reads, "INTERNAL ERROR: CABLE ABNORMAL". + +cable-multitool-system-statistics = Your multitool shows a list of statistics: + Current Supply: { POWERWATTS($supplyc) } + From Batteries: { POWERWATTS($supplyb) } + Theoretical Supply: { POWERWATTS($supplym) } + Ideal Consumption: { POWERWATTS($consumption) } + Input Storage: { POWERJOULES($storagec) } / { POWERJOULES($storagem) } ({ TOSTRING($storager, "P1") }) + Output Storage: { POWERJOULES($storageoc) } / { POWERJOULES($storageom) } ({ TOSTRING($storageor, "P1") }) +