From 0ae3858b69b695697ea9300609460f8ddb70ebbf Mon Sep 17 00:00:00 2001 From: Kara Date: Fri, 5 Jan 2024 23:53:13 -0700 Subject: [PATCH] Examine prediction (#23565) * Initial prediction * new group handling * groups for all examines that use multiple rn * compile * why was it doing this?? * handle newlines with sorting properly --- .../Construction/ConstructionSystem.cs | 37 +-- Content.Client/Examine/ExamineSystem.cs | 16 +- .../Atmos/EntitySystems/GasTankSystem.cs | 1 + .../Binary/EntitySystems/GasRecyclerSystem.cs | 23 +- .../Botany/Systems/BotanySystem.Seed.cs | 11 +- .../Botany/Systems/PlantHolderSystem.cs | 83 +++---- .../Conditions/MachineFrameComplete.cs | 25 +- .../Construction/ConstructionSystem.Guided.cs | 70 +++--- .../Defusable/Systems/DefusableSystem.cs | 29 +-- .../Fluids/EntitySystems/DrainSystem.cs | 2 +- .../EntitySystems/PuddleSystem.Spillable.cs | 9 +- .../Fluids/EntitySystems/PuddleSystem.cs | 28 ++- .../Ghost/Roles/ToggleableGhostRoleSystem.cs | 2 +- .../Holiday/Christmas/RandomGiftSystem.cs | 3 +- Content.Server/Holosign/HolosignSystem.cs | 11 +- Content.Server/Labels/Label/LabelSystem.cs | 37 +-- .../EntitySystems/EmergencyLightSystem.cs | 37 +-- .../EntitySystems/LightReplacerSystem.cs | 34 +-- Content.Server/Medical/CryoPodSystem.cs | 9 +- Content.Server/Morgue/CrematoriumSystem.cs | 27 ++- .../Nutrition/EntitySystems/DrinkSystem.cs | 8 +- Content.Server/Paper/PaperSystem.cs | 30 +-- .../Payload/EntitySystems/PayloadSystem.cs | 25 +- .../Radio/EntitySystems/RadioDeviceSystem.cs | 9 +- .../Shuttles/Systems/ThrusterSystem.cs | 26 ++- .../Stunnable/Systems/StunbatonSystem.cs | 8 +- Content.Server/Tools/ToolSystem.Welder.cs | 33 +-- .../Systems/TraversalDistorterSystem.cs | 3 +- .../Charges/Systems/SharedChargesSystem.cs | 10 +- .../SharedSolutionContainerSystem.cs | 104 +++++---- .../Construction/MachinePartSystem.cs | 61 ++--- .../ArbitraryInsertConstructionGraphStep.cs | 2 +- .../Steps/ComponentConstructionGraphStep.cs | 2 +- .../Steps/MaterialConstructionGraphStep.cs | 2 +- Content.Shared/Dice/SharedDiceSystem.cs | 8 +- Content.Shared/Examine/ExamineSystemShared.cs | 217 +++++++++++++++--- .../SharedHandsSystem.Interactions.cs | 27 ++- Content.Shared/Item/SharedItemSystem.cs | 3 +- .../SharedGunSystem.ChamberMagazine.cs | 20 +- .../Systems/SharedGunSystem.Interactions.cs | 9 +- Content.Shared/Wires/SharedWiresSystem.cs | 23 +- 41 files changed, 693 insertions(+), 431 deletions(-) diff --git a/Content.Client/Construction/ConstructionSystem.cs b/Content.Client/Construction/ConstructionSystem.cs index 4035c68cc7..940538670c 100644 --- a/Content.Client/Construction/ConstructionSystem.cs +++ b/Content.Client/Construction/ConstructionSystem.cs @@ -81,27 +81,30 @@ namespace Content.Client.Construction private void HandleConstructionGhostExamined(EntityUid uid, ConstructionGhostComponent component, ExaminedEvent args) { - if (component.Prototype == null) return; - - args.PushMarkup(Loc.GetString( - "construction-ghost-examine-message", - ("name", component.Prototype.Name))); - - if (!_prototypeManager.TryIndex(component.Prototype.Graph, out ConstructionGraphPrototype? graph)) + if (component.Prototype == null) return; - var startNode = graph.Nodes[component.Prototype.StartNode]; - - if (!graph.TryPath(component.Prototype.StartNode, component.Prototype.TargetNode, out var path) || - !startNode.TryGetEdge(path[0].Name, out var edge)) + using (args.PushGroup(nameof(ConstructionGhostComponent))) { - return; - } + args.PushMarkup(Loc.GetString( + "construction-ghost-examine-message", + ("name", component.Prototype.Name))); - foreach (ConstructionGraphStep step in edge.Steps) - { - args.Message.PushNewline(); - step.DoExamine(args); + if (!_prototypeManager.TryIndex(component.Prototype.Graph, out ConstructionGraphPrototype? graph)) + return; + + var startNode = graph.Nodes[component.Prototype.StartNode]; + + if (!graph.TryPath(component.Prototype.StartNode, component.Prototype.TargetNode, out var path) || + !startNode.TryGetEdge(path[0].Name, out var edge)) + { + return; + } + + foreach (var step in edge.Steps) + { + step.DoExamine(args); + } } } diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs index 59460cec56..32ba78085a 100644 --- a/Content.Client/Examine/ExamineSystem.cs +++ b/Content.Client/Examine/ExamineSystem.cs @@ -366,13 +366,14 @@ namespace Content.Client.Examine var canSeeClearly = !HasComp(playerEnt); OpenTooltip(playerEnt.Value, entity, centeredOnCursor, false, knowTarget: canSeeClearly); - if (IsClientSide(entity) - || _client.RunLevel == ClientRunLevel.SinglePlayerGame) // i.e. a replay - { - message = GetExamineText(entity, playerEnt); - UpdateTooltipInfo(playerEnt.Value, entity, message); - } - else + + // Always update tooltip info from client first. + // If we get it wrong, server will correct us later anyway. + // This will usually be correct (barring server-only components, which generally only adds, not replaces text) + message = GetExamineText(entity, playerEnt); + UpdateTooltipInfo(playerEnt.Value, entity, message); + + if (!IsClientSide(entity)) { // Ask server for extra examine info. if (entity != _lastExaminedEntity) @@ -383,7 +384,6 @@ namespace Content.Client.Examine } RaiseLocalEvent(entity, new ClientExaminedEvent(entity, playerEnt.Value)); - _lastExaminedEntity = entity; } diff --git a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs index bdd60d6d05..e00990f594 100644 --- a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs @@ -110,6 +110,7 @@ namespace Content.Server.Atmos.EntitySystems private void OnExamined(EntityUid uid, GasTankComponent component, ExaminedEvent args) { + using(args.PushGroup(nameof(GasTankComponent))); if (args.IsInDetailsRange) args.PushMarkup(Loc.GetString("comp-gas-tank-examine", ("pressure", Math.Round(component.Air?.Pressure ?? 0)))); if (component.IsConnected) diff --git a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs index fb35ddc346..6a4a16eac3 100644 --- a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs +++ b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs @@ -51,20 +51,23 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems return; } - if (comp.Reacting) + using (args.PushGroup(nameof(GasRecyclerComponent))) { - args.PushMarkup(Loc.GetString("gas-recycler-reacting")); - } - else - { - if (inlet.Air.Pressure < comp.MinPressure) + if (comp.Reacting) { - args.PushMarkup(Loc.GetString("gas-recycler-low-pressure")); + args.PushMarkup(Loc.GetString("gas-recycler-reacting")); } - - if (inlet.Air.Temperature < comp.MinTemp) + else { - args.PushMarkup(Loc.GetString("gas-recycler-low-temperature")); + if (inlet.Air.Pressure < comp.MinPressure) + { + args.PushMarkup(Loc.GetString("gas-recycler-low-pressure")); + } + + if (inlet.Air.Temperature < comp.MinTemp) + { + args.PushMarkup(Loc.GetString("gas-recycler-low-temperature")); + } } } } diff --git a/Content.Server/Botany/Systems/BotanySystem.Seed.cs b/Content.Server/Botany/Systems/BotanySystem.Seed.cs index 1fb6952e91..c9389f832e 100644 --- a/Content.Server/Botany/Systems/BotanySystem.Seed.cs +++ b/Content.Server/Botany/Systems/BotanySystem.Seed.cs @@ -90,10 +90,13 @@ public sealed partial class BotanySystem : EntitySystem if (!TryGetSeed(component, out var seed)) return; - var name = Loc.GetString(seed.DisplayName); - args.PushMarkup(Loc.GetString($"seed-component-description", ("seedName", name))); - args.PushMarkup(Loc.GetString($"seed-component-plant-yield-text", ("seedYield", seed.Yield))); - args.PushMarkup(Loc.GetString($"seed-component-plant-potency-text", ("seedPotency", seed.Potency))); + using (args.PushGroup(nameof(SeedComponent))) + { + var name = Loc.GetString(seed.DisplayName); + args.PushMarkup(Loc.GetString($"seed-component-description", ("seedName", name))); + args.PushMarkup(Loc.GetString($"seed-component-plant-yield-text", ("seedYield", seed.Yield))); + args.PushMarkup(Loc.GetString($"seed-component-plant-potency-text", ("seedPotency", seed.Potency))); + } } #region SeedPrototype prototype stuff diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs index 96fde08118..0bef58a293 100644 --- a/Content.Server/Botany/Systems/PlantHolderSystem.cs +++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs @@ -77,59 +77,62 @@ public sealed class PlantHolderSystem : EntitySystem var (_, component) = entity; - if (component.Seed == null) + using (args.PushGroup(nameof(PlantHolderComponent))) { - args.PushMarkup(Loc.GetString("plant-holder-component-nothing-planted-message")); - } - else if (!component.Dead) - { - var displayName = Loc.GetString(component.Seed.DisplayName); - args.PushMarkup(Loc.GetString("plant-holder-component-something-already-growing-message", + if (component.Seed == null) + { + args.PushMarkup(Loc.GetString("plant-holder-component-nothing-planted-message")); + } + else if (!component.Dead) + { + var displayName = Loc.GetString(component.Seed.DisplayName); + args.PushMarkup(Loc.GetString("plant-holder-component-something-already-growing-message", ("seedName", displayName), ("toBeForm", displayName.EndsWith('s') ? "are" : "is"))); - if (component.Health <= component.Seed.Endurance / 2) - { - args.PushMarkup(Loc.GetString( - "plant-holder-component-something-already-growing-low-health-message", - ("healthState", - Loc.GetString(component.Age > component.Seed.Lifespan - ? "plant-holder-component-plant-old-adjective" - : "plant-holder-component-plant-unhealthy-adjective")))); + if (component.Health <= component.Seed.Endurance / 2) + { + args.PushMarkup(Loc.GetString( + "plant-holder-component-something-already-growing-low-health-message", + ("healthState", + Loc.GetString(component.Age > component.Seed.Lifespan + ? "plant-holder-component-plant-old-adjective" + : "plant-holder-component-plant-unhealthy-adjective")))); + } + } + else + { + args.PushMarkup(Loc.GetString("plant-holder-component-dead-plant-matter-message")); } - } - else - { - args.PushMarkup(Loc.GetString("plant-holder-component-dead-plant-matter-message")); - } - if (component.WeedLevel >= 5) - args.PushMarkup(Loc.GetString("plant-holder-component-weed-high-level-message")); + if (component.WeedLevel >= 5) + args.PushMarkup(Loc.GetString("plant-holder-component-weed-high-level-message")); - if (component.PestLevel >= 5) - args.PushMarkup(Loc.GetString("plant-holder-component-pest-high-level-message")); + if (component.PestLevel >= 5) + args.PushMarkup(Loc.GetString("plant-holder-component-pest-high-level-message")); - args.PushMarkup(Loc.GetString($"plant-holder-component-water-level-message", - ("waterLevel", (int) component.WaterLevel))); - args.PushMarkup(Loc.GetString($"plant-holder-component-nutrient-level-message", - ("nutritionLevel", (int) component.NutritionLevel))); + args.PushMarkup(Loc.GetString($"plant-holder-component-water-level-message", + ("waterLevel", (int) component.WaterLevel))); + args.PushMarkup(Loc.GetString($"plant-holder-component-nutrient-level-message", + ("nutritionLevel", (int) component.NutritionLevel))); - if (component.DrawWarnings) - { - if (component.Toxins > 40f) - args.PushMarkup(Loc.GetString("plant-holder-component-toxins-high-warning")); + if (component.DrawWarnings) + { + if (component.Toxins > 40f) + args.PushMarkup(Loc.GetString("plant-holder-component-toxins-high-warning")); - if (component.ImproperLight) - args.PushMarkup(Loc.GetString("plant-holder-component-light-improper-warning")); + if (component.ImproperLight) + args.PushMarkup(Loc.GetString("plant-holder-component-light-improper-warning")); - if (component.ImproperHeat) - args.PushMarkup(Loc.GetString("plant-holder-component-heat-improper-warning")); + if (component.ImproperHeat) + args.PushMarkup(Loc.GetString("plant-holder-component-heat-improper-warning")); - if (component.ImproperPressure) - args.PushMarkup(Loc.GetString("plant-holder-component-pressure-improper-warning")); + if (component.ImproperPressure) + args.PushMarkup(Loc.GetString("plant-holder-component-pressure-improper-warning")); - if (component.MissingGas > 0) - args.PushMarkup(Loc.GetString("plant-holder-component-gas-missing-warning")); + if (component.MissingGas > 0) + args.PushMarkup(Loc.GetString("plant-holder-component-gas-missing-warning")); + } } } diff --git a/Content.Server/Construction/Conditions/MachineFrameComplete.cs b/Content.Server/Construction/Conditions/MachineFrameComplete.cs index 9fa4098ec2..fbba5eca99 100644 --- a/Content.Server/Construction/Conditions/MachineFrameComplete.cs +++ b/Content.Server/Construction/Conditions/MachineFrameComplete.cs @@ -46,7 +46,7 @@ namespace Content.Server.Construction.Conditions if (entityManager.EntitySysManager.GetEntitySystem().IsComplete(machineFrame)) return false; - args.Message.AddMarkup(Loc.GetString("construction-condition-machine-frame-requirement-label") + "\n"); + args.PushMarkup(Loc.GetString("construction-condition-machine-frame-requirement-label")); foreach (var (part, required) in machineFrame.Requirements) { var amount = required - machineFrame.Progress[part]; @@ -54,10 +54,9 @@ namespace Content.Server.Construction.Conditions if(amount == 0) continue; - args.Message.AddMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry", + args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry", ("amount", amount), - ("elementName", Loc.GetString(part))) - + "\n"); + ("elementName", Loc.GetString(part)))); } foreach (var (material, required) in machineFrame.MaterialRequirements) @@ -67,10 +66,9 @@ namespace Content.Server.Construction.Conditions if(amount == 0) continue; - args.Message.AddMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry", + args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry", ("amount", amount), - ("elementName", Loc.GetString(material))) - + "\n"); + ("elementName", Loc.GetString(material)))); } foreach (var (compName, info) in machineFrame.ComponentRequirements) @@ -80,10 +78,9 @@ namespace Content.Server.Construction.Conditions if(amount == 0) continue; - args.Message.AddMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry", + args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry", ("amount", info.Amount), - ("elementName", Loc.GetString(info.ExamineName))) - + "\n"); + ("elementName", Loc.GetString(info.ExamineName)))); } foreach (var (tagName, info) in machineFrame.TagRequirements) @@ -93,10 +90,10 @@ namespace Content.Server.Construction.Conditions if(amount == 0) continue; - args.Message.AddMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry", - ("amount", info.Amount), - ("elementName", Loc.GetString(info.ExamineName))) - + "\n"); + args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry", + ("amount", info.Amount), + ("elementName", Loc.GetString(info.ExamineName))) + + "\n"); } return true; diff --git a/Content.Server/Construction/ConstructionSystem.Guided.cs b/Content.Server/Construction/ConstructionSystem.Guided.cs index cf6ea4da51..84028b6b7f 100644 --- a/Content.Server/Construction/ConstructionSystem.Guided.cs +++ b/Content.Server/Construction/ConstructionSystem.Guided.cs @@ -67,46 +67,50 @@ namespace Content.Server.Construction private void HandleConstructionExamined(EntityUid uid, ConstructionComponent component, ExaminedEvent args) { - if (GetTargetNode(uid, component) is {} target) + using (args.PushGroup(nameof(ConstructionComponent))) { - if (target.Name == component.DeconstructionNode) + if (GetTargetNode(uid, component) is {} target) { - args.PushMarkup(Loc.GetString("deconstruction-header-text") + "\n"); + if (target.Name == component.DeconstructionNode) + { + args.PushMarkup(Loc.GetString("deconstruction-header-text") + "\n"); + } + else + { + args.PushMarkup(Loc.GetString( + "construction-component-to-create-header", + ("targetName", target.Name)) + "\n"); + } } - else + + if (component.EdgeIndex == null && GetTargetEdge(uid, component) is {} targetEdge) { - args.PushMarkup(Loc.GetString( - "construction-component-to-create-header", - ("targetName", target.Name)) + "\n"); + var preventStepExamine = false; + + foreach (var condition in targetEdge.Conditions) + { + preventStepExamine |= condition.DoExamine(args); + } + + if (!preventStepExamine) + targetEdge.Steps[0].DoExamine(args); + return; + } + + if (GetCurrentEdge(uid, component) is {} edge) + { + var preventStepExamine = false; + + foreach (var condition in edge.Conditions) + { + preventStepExamine |= condition.DoExamine(args); + } + + if (!preventStepExamine && component.StepIndex < edge.Steps.Count) + edge.Steps[component.StepIndex].DoExamine(args); } } - if (component.EdgeIndex == null && GetTargetEdge(uid, component) is {} targetEdge) - { - var preventStepExamine = false; - - foreach (var condition in targetEdge.Conditions) - { - preventStepExamine |= condition.DoExamine(args); - } - - if (!preventStepExamine) - targetEdge.Steps[0].DoExamine(args); - return; - } - - if (GetCurrentEdge(uid, component) is {} edge) - { - var preventStepExamine = false; - - foreach (var condition in edge.Conditions) - { - preventStepExamine |= condition.DoExamine(args); - } - - if (!preventStepExamine && component.StepIndex < edge.Steps.Count) - edge.Steps[component.StepIndex].DoExamine(args); - } } diff --git a/Content.Server/Defusable/Systems/DefusableSystem.cs b/Content.Server/Defusable/Systems/DefusableSystem.cs index c450fd04fb..6927df256a 100644 --- a/Content.Server/Defusable/Systems/DefusableSystem.cs +++ b/Content.Server/Defusable/Systems/DefusableSystem.cs @@ -66,26 +66,29 @@ public sealed class DefusableSystem : SharedDefusableSystem if (!args.IsInDetailsRange) return; - if (!comp.Usable) + using (args.PushGroup(nameof(DefusableComponent))) { - args.PushMarkup(Loc.GetString("defusable-examine-defused", ("name", uid))); - } - else if (comp.Activated && TryComp(uid, out var activeComp)) - { - if (comp.DisplayTime) + if (!comp.Usable) { - args.PushMarkup(Loc.GetString("defusable-examine-live", ("name", uid), - ("time", MathF.Floor(activeComp.TimeRemaining)))); + args.PushMarkup(Loc.GetString("defusable-examine-defused", ("name", uid))); + } + else if (comp.Activated && TryComp(uid, out var activeComp)) + { + if (comp.DisplayTime) + { + args.PushMarkup(Loc.GetString("defusable-examine-live", ("name", uid), + ("time", MathF.Floor(activeComp.TimeRemaining)))); + } + else + { + args.PushMarkup(Loc.GetString("defusable-examine-live-display-off", ("name", uid))); + } } else { - args.PushMarkup(Loc.GetString("defusable-examine-live-display-off", ("name", uid))); + args.PushMarkup(Loc.GetString("defusable-examine-inactive", ("name", uid))); } } - else - { - args.PushMarkup(Loc.GetString("defusable-examine-inactive", ("name", uid))); - } args.PushMarkup(Loc.GetString("defusable-examine-bolts", ("down", comp.Bolted))); } diff --git a/Content.Server/Fluids/EntitySystems/DrainSystem.cs b/Content.Server/Fluids/EntitySystems/DrainSystem.cs index 726ed0b13c..19cb650db7 100644 --- a/Content.Server/Fluids/EntitySystems/DrainSystem.cs +++ b/Content.Server/Fluids/EntitySystems/DrainSystem.cs @@ -216,7 +216,7 @@ public sealed class DrainSystem : SharedDrainSystem var text = drainSolution.AvailableVolume != 0 ? Loc.GetString("drain-component-examine-volume", ("volume", drainSolution.AvailableVolume)) : Loc.GetString("drain-component-examine-hint-full"); - args.Message.AddMarkup($"\n\n{text}"); + args.PushMarkup(text); } private void OnInteract(Entity entity, ref AfterInteractUsingEvent args) diff --git a/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs b/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs index 083d15c22e..177391de22 100644 --- a/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs +++ b/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs @@ -43,10 +43,13 @@ public sealed partial class PuddleSystem private void OnExamined(Entity entity, ref ExaminedEvent args) { - args.PushMarkup(Loc.GetString("spill-examine-is-spillable")); + using (args.PushGroup(nameof(SpillableComponent))) + { + args.PushMarkup(Loc.GetString("spill-examine-is-spillable")); - if (HasComp(entity)) - args.PushMarkup(Loc.GetString("spill-examine-spillable-weapon")); + if (HasComp(entity)) + args.PushMarkup(Loc.GetString("spill-examine-spillable-weapon")); + } } private void OnOverflow(Entity entity, ref SolutionContainerOverflowEvent args) diff --git a/Content.Server/Fluids/EntitySystems/PuddleSystem.cs b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs index 844f43a5e4..52948f2afb 100644 --- a/Content.Server/Fluids/EntitySystems/PuddleSystem.cs +++ b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs @@ -361,23 +361,27 @@ public sealed partial class PuddleSystem : SharedPuddleSystem private void HandlePuddleExamined(Entity entity, ref ExaminedEvent args) { - if (TryComp(entity, out var slippery) && slippery.Active) + using (args.PushGroup(nameof(PuddleComponent))) { - args.PushMarkup(Loc.GetString("puddle-component-examine-is-slipper-text")); - } + if (TryComp(entity, out var slippery) && slippery.Active) + { + args.PushMarkup(Loc.GetString("puddle-component-examine-is-slipper-text")); + } - if (HasComp(entity) && - _solutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution)) - { - if (CanFullyEvaporate(solution)) - args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating")); - else if (solution.GetTotalPrototypeQuantity(EvaporationReagents) > FixedPoint2.Zero) - args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-partial")); + if (HasComp(entity) && + _solutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.SolutionName, + ref entity.Comp.Solution, out var solution)) + { + if (CanFullyEvaporate(solution)) + args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating")); + else if (solution.GetTotalPrototypeQuantity(EvaporationReagents) > FixedPoint2.Zero) + args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-partial")); + else + args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-no")); + } else args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-no")); } - else - args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-no")); } private void OnAnchorChanged(Entity entity, ref AnchorStateChangedEvent args) diff --git a/Content.Server/Ghost/Roles/ToggleableGhostRoleSystem.cs b/Content.Server/Ghost/Roles/ToggleableGhostRoleSystem.cs index 4adfdfc576..9bab229c00 100644 --- a/Content.Server/Ghost/Roles/ToggleableGhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/ToggleableGhostRoleSystem.cs @@ -59,7 +59,7 @@ public sealed class ToggleableGhostRoleSystem : EntitySystem { if (!args.IsInDetailsRange) return; - + if (TryComp(uid, out var mind) && mind.HasMind) { args.PushMarkup(Loc.GetString(component.ExamineTextMindPresent)); diff --git a/Content.Server/Holiday/Christmas/RandomGiftSystem.cs b/Content.Server/Holiday/Christmas/RandomGiftSystem.cs index 33d5d0d234..9e56d0a493 100644 --- a/Content.Server/Holiday/Christmas/RandomGiftSystem.cs +++ b/Content.Server/Holiday/Christmas/RandomGiftSystem.cs @@ -44,8 +44,7 @@ public sealed class RandomGiftSystem : EntitySystem return; var name = _prototype.Index(component.SelectedEntity).Name; - args.Message.PushNewline(); - args.Message.AddText(Loc.GetString("gift-packin-contains", ("name", name))); + args.PushText(Loc.GetString("gift-packin-contains", ("name", name))); } private void OnUseInHand(EntityUid uid, RandomGiftComponent component, UseInHandEvent args) diff --git a/Content.Server/Holosign/HolosignSystem.cs b/Content.Server/Holosign/HolosignSystem.cs index 67f4cec43d..f0fc3bbe14 100644 --- a/Content.Server/Holosign/HolosignSystem.cs +++ b/Content.Server/Holosign/HolosignSystem.cs @@ -25,11 +25,14 @@ public sealed class HolosignSystem : EntitySystem var charges = UsesRemaining(component, battery); var maxCharges = MaxUses(component, battery); - args.PushMarkup(Loc.GetString("limited-charges-charges-remaining", ("charges", charges))); - - if (charges > 0 && charges == maxCharges) + using (args.PushGroup(nameof(HolosignProjectorComponent))) { - args.PushMarkup(Loc.GetString("limited-charges-max-charges")); + args.PushMarkup(Loc.GetString("limited-charges-charges-remaining", ("charges", charges))); + + if (charges > 0 && charges == maxCharges) + { + args.PushMarkup(Loc.GetString("limited-charges-max-charges")); + } } } diff --git a/Content.Server/Labels/Label/LabelSystem.cs b/Content.Server/Labels/Label/LabelSystem.cs index 23f2851665..d20295d072 100644 --- a/Content.Server/Labels/Label/LabelSystem.cs +++ b/Content.Server/Labels/Label/LabelSystem.cs @@ -100,25 +100,28 @@ namespace Content.Server.Labels if (comp.LabelSlot.Item is not {Valid: true} item) return; - if (!args.IsInDetailsRange) + using (args.PushGroup(nameof(PaperLabelComponent))) { - args.PushMarkup(Loc.GetString("comp-paper-label-has-label-cant-read")); - return; + if (!args.IsInDetailsRange) + { + args.PushMarkup(Loc.GetString("comp-paper-label-has-label-cant-read")); + return; + } + + if (!EntityManager.TryGetComponent(item, out PaperComponent? paper)) + // Assuming yaml has the correct entity whitelist, this should not happen. + return; + + if (string.IsNullOrWhiteSpace(paper.Content)) + { + args.PushMarkup(Loc.GetString("comp-paper-label-has-label-blank")); + return; + } + + args.PushMarkup(Loc.GetString("comp-paper-label-has-label")); + var text = paper.Content; + args.PushMarkup(text.TrimEnd()); } - - if (!EntityManager.TryGetComponent(item, out PaperComponent? paper)) - // Assuming yaml has the correct entity whitelist, this should not happen. - return; - - if (string.IsNullOrWhiteSpace(paper.Content)) - { - args.PushMarkup(Loc.GetString("comp-paper-label-has-label-blank")); - return; - } - - args.PushMarkup(Loc.GetString("comp-paper-label-has-label")); - var text = paper.Content; - args.PushMarkup(text.TrimEnd()); } private void OnContainerModified(EntityUid uid, PaperLabelComponent label, ContainerModifiedMessage args) diff --git a/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs b/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs index bc3e7e83e2..0b60787dfb 100644 --- a/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs +++ b/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs @@ -47,28 +47,31 @@ public sealed class EmergencyLightSystem : SharedEmergencyLightSystem private void OnEmergencyExamine(EntityUid uid, EmergencyLightComponent component, ExaminedEvent args) { - args.PushMarkup( - Loc.GetString("emergency-light-component-on-examine", - ("batteryStateText", - Loc.GetString(component.BatteryStateText[component.State])))); + using (args.PushGroup(nameof(EmergencyLightComponent))) + { + args.PushMarkup( + Loc.GetString("emergency-light-component-on-examine", + ("batteryStateText", + Loc.GetString(component.BatteryStateText[component.State])))); - // Show alert level on the light itself. - if (!TryComp(_station.GetOwningStation(uid), out var alerts)) - return; + // Show alert level on the light itself. + if (!TryComp(_station.GetOwningStation(uid), out var alerts)) + return; - if (alerts.AlertLevels == null) - return; + if (alerts.AlertLevels == null) + return; - var name = alerts.CurrentLevel; + var name = alerts.CurrentLevel; - var color = Color.White; - if (alerts.AlertLevels.Levels.TryGetValue(alerts.CurrentLevel, out var details)) - color = details.Color; + var color = Color.White; + if (alerts.AlertLevels.Levels.TryGetValue(alerts.CurrentLevel, out var details)) + color = details.Color; - args.PushMarkup( - Loc.GetString("emergency-light-component-on-examine-alert", - ("color", color.ToHex()), - ("level", name))); + args.PushMarkup( + Loc.GetString("emergency-light-component-on-examine-alert", + ("color", color.ToHex()), + ("level", name))); + } } private void OnEmergencyLightEvent(EntityUid uid, EmergencyLightComponent component, EmergencyLightEvent args) diff --git a/Content.Server/Light/EntitySystems/LightReplacerSystem.cs b/Content.Server/Light/EntitySystems/LightReplacerSystem.cs index c34fcfe3a2..f1cb5373c0 100644 --- a/Content.Server/Light/EntitySystems/LightReplacerSystem.cs +++ b/Content.Server/Light/EntitySystems/LightReplacerSystem.cs @@ -33,23 +33,27 @@ public sealed class LightReplacerSystem : EntitySystem private void OnExamined(EntityUid uid, LightReplacerComponent component, ExaminedEvent args) { - if (!component.InsertedBulbs.ContainedEntities.Any()) + using (args.PushGroup(nameof(LightReplacerComponent))) { - args.PushMarkup(Loc.GetString("comp-light-replacer-no-lights")); - return; - } - args.PushMarkup(Loc.GetString("comp-light-replacer-has-lights")); - var groups = new Dictionary(); - var metaQuery = GetEntityQuery(); - foreach (var bulb in component.InsertedBulbs.ContainedEntities) - { - var metaData = metaQuery.GetComponent(bulb); - groups[metaData.EntityName] = groups.GetValueOrDefault(metaData.EntityName) + 1; - } + if (!component.InsertedBulbs.ContainedEntities.Any()) + { + args.PushMarkup(Loc.GetString("comp-light-replacer-no-lights")); + return; + } - foreach (var (name, amount) in groups) - { - args.PushMarkup(Loc.GetString("comp-light-replacer-light-listing", ("amount", amount), ("name", name))); + args.PushMarkup(Loc.GetString("comp-light-replacer-has-lights")); + var groups = new Dictionary(); + var metaQuery = GetEntityQuery(); + foreach (var bulb in component.InsertedBulbs.ContainedEntities) + { + var metaData = metaQuery.GetComponent(bulb); + groups[metaData.EntityName] = groups.GetValueOrDefault(metaData.EntityName) + 1; + } + + foreach (var (name, amount) in groups) + { + args.PushMarkup(Loc.GetString("comp-light-replacer-light-listing", ("amount", amount), ("name", name))); + } } } diff --git a/Content.Server/Medical/CryoPodSystem.cs b/Content.Server/Medical/CryoPodSystem.cs index 19cb62f836..3fcec3a824 100644 --- a/Content.Server/Medical/CryoPodSystem.cs +++ b/Content.Server/Medical/CryoPodSystem.cs @@ -208,10 +208,13 @@ public sealed partial class CryoPodSystem : SharedCryoPodSystem var container = _itemSlotsSystem.GetItemOrNull(entity.Owner, entity.Comp.SolutionContainerName); if (args.IsInDetailsRange && container != null && _solutionContainerSystem.TryGetFitsInDispenser(container.Value, out _, out var containerSolution)) { - args.PushMarkup(Loc.GetString("cryo-pod-examine", ("beaker", Name(container.Value)))); - if (containerSolution.Volume == 0) + using (args.PushGroup(nameof(CryoPodComponent))) { - args.PushMarkup(Loc.GetString("cryo-pod-empty-beaker")); + args.PushMarkup(Loc.GetString("cryo-pod-examine", ("beaker", Name(container.Value)))); + if (containerSolution.Volume == 0) + { + args.PushMarkup(Loc.GetString("cryo-pod-empty-beaker")); + } } } } diff --git a/Content.Server/Morgue/CrematoriumSystem.cs b/Content.Server/Morgue/CrematoriumSystem.cs index 1f0682774f..54b47cff84 100644 --- a/Content.Server/Morgue/CrematoriumSystem.cs +++ b/Content.Server/Morgue/CrematoriumSystem.cs @@ -47,17 +47,24 @@ public sealed class CrematoriumSystem : EntitySystem if (!TryComp(uid, out var appearance)) return; - if (_appearance.TryGetData(uid, CrematoriumVisuals.Burning, out var isBurning, appearance) && isBurning) + using (args.PushGroup(nameof(CrematoriumComponent))) { - args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-is-burning", ("owner", uid))); - } - if (_appearance.TryGetData(uid, StorageVisuals.HasContents, out var hasContents, appearance) && hasContents) - { - args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-has-contents")); - } - else - { - args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-empty")); + if (_appearance.TryGetData(uid, CrematoriumVisuals.Burning, out var isBurning, appearance) && + isBurning) + { + args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-is-burning", + ("owner", uid))); + } + + if (_appearance.TryGetData(uid, StorageVisuals.HasContents, out var hasContents, appearance) && + hasContents) + { + args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-has-contents")); + } + else + { + args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-empty")); + } } } diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index b32f5a7f63..f75dd93e09 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -135,19 +135,19 @@ public sealed class DrinkSystem : EntitySystem return; // put Empty / Xu after Opened, or start a new line - args.Message.AddMarkup(hasOpenable ? " - " : "\n"); + args.AddMarkup(hasOpenable ? " - " : "\n"); var empty = IsEmpty(entity, entity.Comp); if (empty) { - args.Message.AddMarkup(Loc.GetString("drink-component-on-examine-is-empty")); + args.AddMarkup(Loc.GetString("drink-component-on-examine-is-empty")); return; } if (TryComp(entity, out var comp)) { //provide exact measurement for beakers - args.Message.AddMarkup(Loc.GetString("drink-component-on-examine-exact-volume", ("amount", DrinkVolume(entity, entity.Comp)))); + args.AddMarkup(Loc.GetString("drink-component-on-examine-exact-volume", ("amount", DrinkVolume(entity, entity.Comp)))); } else { @@ -159,7 +159,7 @@ public sealed class DrinkSystem : EntitySystem > 33 => HalfEmptyOrHalfFull(args), _ => "drink-component-on-examine-is-mostly-empty", }; - args.Message.AddMarkup(Loc.GetString(remainingString)); + args.AddMarkup(Loc.GetString(remainingString)); } } diff --git a/Content.Server/Paper/PaperSystem.cs b/Content.Server/Paper/PaperSystem.cs index 22f39cd35b..7525ad7cf3 100644 --- a/Content.Server/Paper/PaperSystem.cs +++ b/Content.Server/Paper/PaperSystem.cs @@ -79,20 +79,24 @@ namespace Content.Server.Paper if (!args.IsInDetailsRange) return; - if (paperComp.Content != "") - args.PushMarkup( - Loc.GetString( - "paper-component-examine-detail-has-words", ("paper", uid) - ) - ); - - if (paperComp.StampedBy.Count > 0) + using (args.PushGroup(nameof(PaperComponent))) { - var commaSeparated = string.Join(", ", paperComp.StampedBy.Select(s => Loc.GetString(s.StampedName))); - args.PushMarkup( - Loc.GetString( - "paper-component-examine-detail-stamped-by", ("paper", uid), ("stamps", commaSeparated)) - ); + if (paperComp.Content != "") + args.PushMarkup( + Loc.GetString( + "paper-component-examine-detail-has-words", ("paper", uid) + ) + ); + + if (paperComp.StampedBy.Count > 0) + { + var commaSeparated = + string.Join(", ", paperComp.StampedBy.Select(s => Loc.GetString(s.StampedName))); + args.PushMarkup( + Loc.GetString( + "paper-component-examine-detail-stamped-by", ("paper", uid), ("stamps", commaSeparated)) + ); + } } } diff --git a/Content.Server/Payload/EntitySystems/PayloadSystem.cs b/Content.Server/Payload/EntitySystems/PayloadSystem.cs index 78a2de22d1..85cf303d5d 100644 --- a/Content.Server/Payload/EntitySystems/PayloadSystem.cs +++ b/Content.Server/Payload/EntitySystems/PayloadSystem.cs @@ -121,19 +121,22 @@ public sealed class PayloadSystem : EntitySystem private void OnExamined(EntityUid uid, PayloadCaseComponent component, ExaminedEvent args) { - if (!args.IsInDetailsRange) + using (args.PushGroup(nameof(PayloadCaseComponent))) { - args.PushMarkup(Loc.GetString("payload-case-not-close-enough", ("ent", uid))); - return; - } + if (!args.IsInDetailsRange) + { + args.PushMarkup(Loc.GetString("payload-case-not-close-enough", ("ent", uid))); + return; + } - if (GetAllPayloads(uid).Any()) - { - args.PushMarkup(Loc.GetString("payload-case-has-payload", ("ent", uid))); - } - else - { - args.PushMarkup(Loc.GetString("payload-case-does-not-have-payload", ("ent", uid))); + if (GetAllPayloads(uid).Any()) + { + args.PushMarkup(Loc.GetString("payload-case-has-payload", ("ent", uid))); + } + else + { + args.PushMarkup(Loc.GetString("payload-case-does-not-have-payload", ("ent", uid))); + } } } diff --git a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs index 6a6c644fbb..2cd3777c52 100644 --- a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs @@ -172,8 +172,13 @@ public sealed class RadioDeviceSystem : EntitySystem return; var proto = _protoMan.Index(component.BroadcastChannel); - args.PushMarkup(Loc.GetString("handheld-radio-component-on-examine", ("frequency", proto.Frequency))); - args.PushMarkup(Loc.GetString("handheld-radio-component-chennel-examine", ("channel", proto.LocalizedName))); + + using (args.PushGroup(nameof(RadioMicrophoneComponent))) + { + args.PushMarkup(Loc.GetString("handheld-radio-component-on-examine", ("frequency", proto.Frequency))); + args.PushMarkup(Loc.GetString("handheld-radio-component-chennel-examine", + ("channel", proto.LocalizedName))); + } } private void OnListen(EntityUid uid, RadioMicrophoneComponent component, ListenEvent args) diff --git a/Content.Server/Shuttles/Systems/ThrusterSystem.cs b/Content.Server/Shuttles/Systems/ThrusterSystem.cs index ed7208ab47..09607381c7 100644 --- a/Content.Server/Shuttles/Systems/ThrusterSystem.cs +++ b/Content.Server/Shuttles/Systems/ThrusterSystem.cs @@ -64,22 +64,26 @@ public sealed class ThrusterSystem : EntitySystem // Powered is already handled by other power components var enabled = Loc.GetString(component.Enabled ? "thruster-comp-enabled" : "thruster-comp-disabled"); - args.PushMarkup(enabled); - - if (component.Type == ThrusterType.Linear && - EntityManager.TryGetComponent(uid, out TransformComponent? xform) && - xform.Anchored) + using (args.PushGroup(nameof(ThrusterComponent))) { - var nozzleDir = Loc.GetString("thruster-comp-nozzle-direction", - ("direction", xform.LocalRotation.Opposite().ToWorldVec().GetDir().ToString().ToLowerInvariant())); + args.PushMarkup(enabled); - args.PushMarkup(nozzleDir); + if (component.Type == ThrusterType.Linear && + EntityManager.TryGetComponent(uid, out TransformComponent? xform) && + xform.Anchored) + { + var nozzleDir = Loc.GetString("thruster-comp-nozzle-direction", + ("direction", xform.LocalRotation.Opposite().ToWorldVec().GetDir().ToString().ToLowerInvariant())); - var exposed = NozzleExposed(xform); + args.PushMarkup(nozzleDir); - var nozzleText = Loc.GetString(exposed ? "thruster-comp-nozzle-exposed" : "thruster-comp-nozzle-not-exposed"); + var exposed = NozzleExposed(xform); - args.PushMarkup(nozzleText); + var nozzleText = + Loc.GetString(exposed ? "thruster-comp-nozzle-exposed" : "thruster-comp-nozzle-not-exposed"); + + args.PushMarkup(nozzleText); + } } } diff --git a/Content.Server/Stunnable/Systems/StunbatonSystem.cs b/Content.Server/Stunnable/Systems/StunbatonSystem.cs index 436559e43b..053a293689 100644 --- a/Content.Server/Stunnable/Systems/StunbatonSystem.cs +++ b/Content.Server/Stunnable/Systems/StunbatonSystem.cs @@ -25,7 +25,7 @@ namespace Content.Server.Stunnable.Systems { base.Initialize(); - SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnSolutionChange); SubscribeLocalEvent(OnStaminaHitAttempt); SubscribeLocalEvent(TryTurnOn); @@ -47,16 +47,12 @@ namespace Content.Server.Stunnable.Systems } } - private void OnExamined(Entity entity, ref ExaminedEvent args) + private void OnExamined(Entity entity, ref ExaminedEvent args) { var onMsg = _itemToggle.IsActivated(entity.Owner) ? Loc.GetString("comp-stunbaton-examined-on") : Loc.GetString("comp-stunbaton-examined-off"); args.PushMarkup(onMsg); - - var chargeMessage = Loc.GetString("stunbaton-component-on-examine-charge", - ("charge", (int) (entity.Comp.CurrentCharge / entity.Comp.MaxCharge * 100))); - args.PushMarkup(chargeMessage); } private void ToggleDone(Entity entity, ref ItemToggledEvent args) diff --git a/Content.Server/Tools/ToolSystem.Welder.cs b/Content.Server/Tools/ToolSystem.Welder.cs index 510dd3bfe0..6f8fb54d72 100644 --- a/Content.Server/Tools/ToolSystem.Welder.cs +++ b/Content.Server/Tools/ToolSystem.Welder.cs @@ -99,24 +99,27 @@ namespace Content.Server.Tools private void OnWelderExamine(Entity entity, ref ExaminedEvent args) { - if (_itemToggle.IsActivated(entity.Owner)) + using (args.PushGroup(nameof(WelderComponent))) { - args.PushMarkup(Loc.GetString("welder-component-on-examine-welder-lit-message")); - } - else - { - args.PushMarkup(Loc.GetString("welder-component-on-examine-welder-not-lit-message")); - } + if (_itemToggle.IsActivated(entity.Owner)) + { + args.PushMarkup(Loc.GetString("welder-component-on-examine-welder-lit-message")); + } + else + { + args.PushMarkup(Loc.GetString("welder-component-on-examine-welder-not-lit-message")); + } - if (args.IsInDetailsRange) - { - var (fuel, capacity) = GetWelderFuelAndCapacity(entity.Owner, entity.Comp); + if (args.IsInDetailsRange) + { + var (fuel, capacity) = GetWelderFuelAndCapacity(entity.Owner, entity.Comp); - args.PushMarkup(Loc.GetString("welder-component-on-examine-detailed-message", - ("colorName", fuel < capacity / FixedPoint2.New(4f) ? "darkorange" : "orange"), - ("fuelLeft", fuel), - ("fuelCapacity", capacity), - ("status", string.Empty))); // Lit status is handled above + args.PushMarkup(Loc.GetString("welder-component-on-examine-detailed-message", + ("colorName", fuel < capacity / FixedPoint2.New(4f) ? "darkorange" : "orange"), + ("fuelLeft", fuel), + ("fuelCapacity", capacity), + ("status", string.Empty))); // Lit status is handled above + } } } diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs index 5a1639de4c..d313f9a25b 100644 --- a/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs @@ -73,7 +73,8 @@ public sealed class TraversalDistorterSystem : EntitySystem examine = Loc.GetString("traversal-distorter-desc-out"); break; } - args.Message.AddMarkup(examine); + + args.PushMarkup(examine); } private void OnRefreshParts(EntityUid uid, TraversalDistorterComponent component, RefreshPartsEvent args) diff --git a/Content.Shared/Charges/Systems/SharedChargesSystem.cs b/Content.Shared/Charges/Systems/SharedChargesSystem.cs index be1526d3d3..653a7f22a5 100644 --- a/Content.Shared/Charges/Systems/SharedChargesSystem.cs +++ b/Content.Shared/Charges/Systems/SharedChargesSystem.cs @@ -17,11 +17,13 @@ public abstract class SharedChargesSystem : EntitySystem if (!args.IsInDetailsRange) return; - args.PushMarkup(Loc.GetString("limited-charges-charges-remaining", ("charges", comp.Charges))); - if (comp.Charges == comp.MaxCharges) + using (args.PushGroup(nameof(LimitedChargesComponent))) { - args.PushMarkup(Loc.GetString("limited-charges-max-charges")); - return; + args.PushMarkup(Loc.GetString("limited-charges-charges-remaining", ("charges", comp.Charges))); + if (comp.Charges == comp.MaxCharges) + { + args.PushMarkup(Loc.GetString("limited-charges-max-charges")); + } } } diff --git a/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs b/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs index fc83d46f4b..32ecdd4ba6 100644 --- a/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs +++ b/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs @@ -742,61 +742,65 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem .ToHexNoAlpha(); //TODO: If the chem has a dark color, the examine text becomes black on a black background, which is unreadable. var messageString = "shared-solution-container-component-on-examine-main-text"; - args.PushMarkup(Loc.GetString(messageString, - ("color", colorHex), - ("wordedAmount", Loc.GetString(solution.Contents.Count == 1 - ? "shared-solution-container-component-on-examine-worded-amount-one-reagent" - : "shared-solution-container-component-on-examine-worded-amount-multiple-reagents")), - ("desc", primary.LocalizedPhysicalDescription))); - - var reagentPrototypes = solution.GetReagentPrototypes(PrototypeManager); - - // Sort the reagents by amount, descending then alphabetically - var sortedReagentPrototypes = reagentPrototypes - .OrderByDescending(pair => pair.Value.Value) - .ThenBy(pair => pair.Key.LocalizedName); - - // Add descriptions of immediately recognizable reagents, like water or beer - var recognized = new List(); - foreach (var keyValuePair in sortedReagentPrototypes) + using (args.PushGroup(nameof(ExaminableSolutionComponent))) { - var proto = keyValuePair.Key; - if (!proto.Recognizable) + args.PushMarkup(Loc.GetString(messageString, + ("color", colorHex), + ("wordedAmount", Loc.GetString(solution.Contents.Count == 1 + ? "shared-solution-container-component-on-examine-worded-amount-one-reagent" + : "shared-solution-container-component-on-examine-worded-amount-multiple-reagents")), + ("desc", primary.LocalizedPhysicalDescription))); + + var reagentPrototypes = solution.GetReagentPrototypes(PrototypeManager); + + // Sort the reagents by amount, descending then alphabetically + var sortedReagentPrototypes = reagentPrototypes + .OrderByDescending(pair => pair.Value.Value) + .ThenBy(pair => pair.Key.LocalizedName); + + // Add descriptions of immediately recognizable reagents, like water or beer + var recognized = new List(); + foreach (var keyValuePair in sortedReagentPrototypes) { - continue; + var proto = keyValuePair.Key; + if (!proto.Recognizable) + { + continue; + } + + recognized.Add(proto); } - recognized.Add(proto); + // Skip if there's nothing recognizable + if (recognized.Count == 0) + return; + + var msg = new StringBuilder(); + foreach (var reagent in recognized) + { + string part; + if (reagent == recognized[0]) + { + part = "examinable-solution-recognized-first"; + } + else if (reagent == recognized[^1]) + { + // this loc specifically requires space to be appended, fluent doesnt support whitespace + msg.Append(' '); + part = "examinable-solution-recognized-last"; + } + else + { + part = "examinable-solution-recognized-next"; + } + + msg.Append(Loc.GetString(part, ("color", reagent.SubstanceColor.ToHexNoAlpha()), + ("chemical", reagent.LocalizedName))); + } + + args.PushMarkup(Loc.GetString("examinable-solution-has-recognizable-chemicals", + ("recognizedString", msg.ToString()))); } - - // Skip if there's nothing recognizable - if (recognized.Count == 0) - return; - - var msg = new StringBuilder(); - foreach (var reagent in recognized) - { - string part; - if (reagent == recognized[0]) - { - part = "examinable-solution-recognized-first"; - } - else if (reagent == recognized[^1]) - { - // this loc specifically requires space to be appended, fluent doesnt support whitespace - msg.Append(' '); - part = "examinable-solution-recognized-last"; - } - else - { - part = "examinable-solution-recognized-next"; - } - - msg.Append(Loc.GetString(part, ("color", reagent.SubstanceColor.ToHexNoAlpha()), - ("chemical", reagent.LocalizedName))); - } - - args.PushMarkup(Loc.GetString("examinable-solution-has-recognizable-chemicals", ("recognizedString", msg.ToString()))); } private void OnSolutionExaminableVerb(Entity entity, ref GetVerbsEvent args) diff --git a/Content.Shared/Construction/MachinePartSystem.cs b/Content.Shared/Construction/MachinePartSystem.cs index b13dc20c6d..01db7fbade 100644 --- a/Content.Shared/Construction/MachinePartSystem.cs +++ b/Content.Shared/Construction/MachinePartSystem.cs @@ -28,33 +28,37 @@ namespace Content.Shared.Construction { if (!args.IsInDetailsRange) return; - args.PushMarkup(Loc.GetString("machine-board-component-on-examine-label")); - foreach (var (part, amount) in component.Requirements) - { - args.PushMarkup(Loc.GetString("machine-board-component-required-element-entry-text", - ("amount", amount), - ("requiredElement", Loc.GetString(_prototype.Index(part).Name)))); - } - foreach (var (material, amount) in component.MaterialRequirements) + using (args.PushGroup(nameof(MachineBoardComponent))) { - args.PushMarkup(Loc.GetString("machine-board-component-required-element-entry-text", - ("amount", amount), - ("requiredElement", Loc.GetString(material.Name)))); - } + args.PushMarkup(Loc.GetString("machine-board-component-on-examine-label")); + foreach (var (part, amount) in component.Requirements) + { + args.PushMarkup(Loc.GetString("machine-board-component-required-element-entry-text", + ("amount", amount), + ("requiredElement", Loc.GetString(_prototype.Index(part).Name)))); + } - foreach (var (_, info) in component.ComponentRequirements) - { - args.PushMarkup(Loc.GetString("machine-board-component-required-element-entry-text", - ("amount", info.Amount), - ("requiredElement", Loc.GetString(info.ExamineName)))); - } + foreach (var (material, amount) in component.MaterialRequirements) + { + args.PushMarkup(Loc.GetString("machine-board-component-required-element-entry-text", + ("amount", amount), + ("requiredElement", Loc.GetString(material.Name)))); + } - foreach (var (_, info) in component.TagRequirements) - { - args.PushMarkup(Loc.GetString("machine-board-component-required-element-entry-text", - ("amount", info.Amount), - ("requiredElement", Loc.GetString(info.ExamineName)))); + foreach (var (_, info) in component.ComponentRequirements) + { + args.PushMarkup(Loc.GetString("machine-board-component-required-element-entry-text", + ("amount", info.Amount), + ("requiredElement", Loc.GetString(info.ExamineName)))); + } + + foreach (var (_, info) in component.TagRequirements) + { + args.PushMarkup(Loc.GetString("machine-board-component-required-element-entry-text", + ("amount", info.Amount), + ("requiredElement", Loc.GetString(info.ExamineName)))); + } } } @@ -62,9 +66,14 @@ namespace Content.Shared.Construction { if (!args.IsInDetailsRange) return; - args.PushMarkup(Loc.GetString("machine-part-component-on-examine-rating-text", ("rating", component.Rating))); - args.PushMarkup(Loc.GetString("machine-part-component-on-examine-type-text", ("type", - Loc.GetString(_prototype.Index(component.PartType).Name)))); + + using (args.PushGroup(nameof(MachinePartComponent))) + { + args.PushMarkup(Loc.GetString("machine-part-component-on-examine-rating-text", + ("rating", component.Rating))); + args.PushMarkup(Loc.GetString("machine-part-component-on-examine-type-text", ("type", + Loc.GetString(_prototype.Index(component.PartType).Name)))); + } } public Dictionary GetMachineBoardMaterialCost(Entity entity, int coefficient = 1) diff --git a/Content.Shared/Construction/Steps/ArbitraryInsertConstructionGraphStep.cs b/Content.Shared/Construction/Steps/ArbitraryInsertConstructionGraphStep.cs index 8e3dcd4f4d..49d429aa26 100644 --- a/Content.Shared/Construction/Steps/ArbitraryInsertConstructionGraphStep.cs +++ b/Content.Shared/Construction/Steps/ArbitraryInsertConstructionGraphStep.cs @@ -14,7 +14,7 @@ namespace Content.Shared.Construction.Steps if (string.IsNullOrEmpty(Name)) return; - examinedEvent.Message.AddMarkup(Loc.GetString("construction-insert-arbitrary-entity", ("stepName", Name))); + examinedEvent.PushMarkup(Loc.GetString("construction-insert-arbitrary-entity", ("stepName", Name))); } public override ConstructionGuideEntry GenerateGuideEntry() diff --git a/Content.Shared/Construction/Steps/ComponentConstructionGraphStep.cs b/Content.Shared/Construction/Steps/ComponentConstructionGraphStep.cs index a85a1ed472..5c28d3b05f 100644 --- a/Content.Shared/Construction/Steps/ComponentConstructionGraphStep.cs +++ b/Content.Shared/Construction/Steps/ComponentConstructionGraphStep.cs @@ -20,7 +20,7 @@ namespace Content.Shared.Construction.Steps public override void DoExamine(ExaminedEvent examinedEvent) { - examinedEvent.Message.AddMarkup(string.IsNullOrEmpty(Name) + examinedEvent.PushMarkup(string.IsNullOrEmpty(Name) ? Loc.GetString( "construction-insert-entity-with-component", ("componentName", Component))// Terrible. diff --git a/Content.Shared/Construction/Steps/MaterialConstructionGraphStep.cs b/Content.Shared/Construction/Steps/MaterialConstructionGraphStep.cs index d9f968d41a..eec8d89b80 100644 --- a/Content.Shared/Construction/Steps/MaterialConstructionGraphStep.cs +++ b/Content.Shared/Construction/Steps/MaterialConstructionGraphStep.cs @@ -20,7 +20,7 @@ namespace Content.Shared.Construction.Steps { var material = IoCManager.Resolve().Index(MaterialPrototypeId); - examinedEvent.Message.AddMarkup(Loc.GetString("construction-insert-material-entity", ("amount", Amount), ("materialName", material.Name))); + examinedEvent.PushMarkup(Loc.GetString("construction-insert-material-entity", ("amount", Amount), ("materialName", material.Name))); } public override bool EntityValid(EntityUid uid, IEntityManager entityManager, IComponentFactory compFactory) diff --git a/Content.Shared/Dice/SharedDiceSystem.cs b/Content.Shared/Dice/SharedDiceSystem.cs index 59a536e244..be3c912fb5 100644 --- a/Content.Shared/Dice/SharedDiceSystem.cs +++ b/Content.Shared/Dice/SharedDiceSystem.cs @@ -48,8 +48,12 @@ public abstract class SharedDiceSystem : EntitySystem private void OnExamined(EntityUid uid, DiceComponent dice, ExaminedEvent args) { //No details check, since the sprite updates to show the side. - args.PushMarkup(Loc.GetString("dice-component-on-examine-message-part-1", ("sidesAmount", dice.Sides))); - args.PushMarkup(Loc.GetString("dice-component-on-examine-message-part-2", ("currentSide", dice.CurrentValue))); + using (args.PushGroup(nameof(DiceComponent))) + { + args.PushMarkup(Loc.GetString("dice-component-on-examine-message-part-1", ("sidesAmount", dice.Sides))); + args.PushMarkup(Loc.GetString("dice-component-on-examine-message-part-2", + ("currentSide", dice.CurrentValue))); + } } public void SetCurrentSide(EntityUid uid, int side, DiceComponent? die = null) diff --git a/Content.Shared/Examine/ExamineSystemShared.cs b/Content.Shared/Examine/ExamineSystemShared.cs index f4d89ecf26..a06a4b7049 100644 --- a/Content.Shared/Examine/ExamineSystemShared.cs +++ b/Content.Shared/Examine/ExamineSystemShared.cs @@ -1,4 +1,7 @@ +using System.Diagnostics; +using System.Globalization; using System.Linq; +using System.Text; using Content.Shared.Eye.Blinding.Components; using Content.Shared.Interaction; using Content.Shared.Mobs.Components; @@ -238,42 +241,60 @@ namespace Content.Shared.Examine return message; } - var doNewline = false; + var hasDescription = false; //Add an entity description if one is declared if (!string.IsNullOrEmpty(EntityManager.GetComponent(entity).EntityDescription)) { message.AddText(EntityManager.GetComponent(entity).EntityDescription); - doNewline = true; + hasDescription = true; } message.PushColor(Color.DarkGray); // Raise the event and let things that subscribe to it change the message... var isInDetailsRange = IsInDetailsRange(examiner.Value, entity); - var examinedEvent = new ExaminedEvent(message, entity, examiner.Value, isInDetailsRange, doNewline); - RaiseLocalEvent(entity, examinedEvent, true); + var examinedEvent = new ExaminedEvent(message, entity, examiner.Value, isInDetailsRange, hasDescription); + RaiseLocalEvent(entity, examinedEvent); - message.Pop(); + var newMessage = examinedEvent.GetTotalMessage(); - return message; + // pop color tag + newMessage.Pop(); + + return newMessage; } } /// /// Raised when an entity is examined. + /// If you're pushing multiple messages that should be grouped together (or ordered in some way), + /// call before pushing and when finished. /// public sealed class ExaminedEvent : EntityEventArgs { /// /// The message that will be displayed as the examine text. - /// For most use cases, you probably want to use and similar instead to modify this, - /// since it handles newlines and such correctly. + /// You should use and similar instead to modify this, + /// since it handles newlines/priority and such correctly. /// /// /// /// - public FormattedMessage Message { get; } + /// + /// + /// + private FormattedMessage Message { get; } + + /// + /// Parts of the examine message that will later be sorted by priority and pushed onto . + /// + private List Parts { get; } = new(); + + /// + /// Whether the examiner is in range of the entity to get some extra details. + /// + public bool IsInDetailsRange { get; } /// /// The entity performing the examining. @@ -285,62 +306,206 @@ namespace Content.Shared.Examine /// public EntityUid Examined { get; } - /// - /// Whether the examiner is in range of the entity to get some extra details. - /// - public bool IsInDetailsRange { get; } + private bool _hasDescription; - private bool _doNewLine; + private ExamineMessagePart? _currentGroupPart; - public ExaminedEvent(FormattedMessage message, EntityUid examined, EntityUid examiner, bool isInDetailsRange, bool doNewLine) + public ExaminedEvent(FormattedMessage message, EntityUid examined, EntityUid examiner, bool isInDetailsRange, bool hasDescription) { Message = message; Examined = examined; Examiner = examiner; IsInDetailsRange = isInDetailsRange; - _doNewLine = doNewLine; + _hasDescription = hasDescription; + } + + /// + /// Returns with all appended according to their priority. + /// + public FormattedMessage GetTotalMessage() + { + int Comparison(ExamineMessagePart a, ExamineMessagePart b) + { + // Try sort by priority, then group, then by string contents + if (a.Priority != b.Priority) + { + // negative so that expected behavior is consistent with what makes sense + // i.e. a negative priority should mean its at the bottom of the list, right? + return -a.Priority.CompareTo(b.Priority); + } + + if (a.Group != b.Group) + { + return string.Compare(a.Group, b.Group, StringComparison.Ordinal); + } + + return string.Compare(a.Message.ToString(), b.Message.ToString(), StringComparison.Ordinal); + } + + // tolist/clone formatted message so calling this multiple times wont fuck shit up + // (if that happens for some reason) + var parts = Parts.ToList(); + var totalMessage = new FormattedMessage(Message); + parts.Sort(Comparison); + + if (_hasDescription) + { + totalMessage.PushNewline(); + } + + foreach (var part in parts) + { + totalMessage.AddMessage(part.Message); + if (part.DoNewLine && parts.Last() != part) + totalMessage.PushNewline(); + } + + return totalMessage; + } + + /// + /// Message group handling. Call this if you want the next set of examine messages that you're adding to have + /// a consistent order with regards to each other. This is done so that client & server will always + /// sort messages the same as well as grouped together properly, even if subscriptions are different. + /// You should wrap it in a using() block so popping automatically occurs. + /// + public ExamineGroupDisposable PushGroup(string groupName, int priority=0) + { + // Ensure that other examine events correctly ended their groups. + DebugTools.Assert(_currentGroupPart == null); + _currentGroupPart = new ExamineMessagePart(new FormattedMessage(), priority, false, groupName); + return new ExamineGroupDisposable(this); + } + + /// + /// Ends the current group and pushes its groups contents to the message. + /// This will be called automatically if in using a `using` block with . + /// + private void PopGroup() + { + DebugTools.Assert(_currentGroupPart != null); + if (_currentGroupPart != null) + Parts.Add(_currentGroupPart); + + _currentGroupPart = null; } /// /// Push another message into this examine result, on its own line. + /// End message will be grouped by , then by group if one was started + /// then by ordinal comparison. /// /// /// - public void PushMessage(FormattedMessage message) + public void PushMessage(FormattedMessage message, int priority=0) { if (message.Nodes.Count == 0) return; - if (_doNewLine) - Message.AddText("\n"); - - Message.AddMessage(message); - _doNewLine = true; + if (_currentGroupPart != null) + { + message.PushNewline(); + _currentGroupPart.Message.AddMessage(message); + } + else + { + Parts.Add(new ExamineMessagePart(message, priority, true, null)); + } } /// /// Push another message parsed from markup into this examine result, on its own line. + /// End message will be grouped by , then by group if one was started + /// then by ordinal comparison. /// /// /// - public void PushMarkup(string markup) + public void PushMarkup(string markup, int priority=0) { - PushMessage(FormattedMessage.FromMarkup(markup)); + PushMessage(FormattedMessage.FromMarkup(markup), priority); } /// /// Push another message containing raw text into this examine result, on its own line. + /// End message will be grouped by , then by group if one was started + /// then by ordinal comparison. /// /// /// - public void PushText(string text) + public void PushText(string text, int priority=0) { var msg = new FormattedMessage(); msg.AddText(text); - PushMessage(msg); + PushMessage(msg, priority); } + + /// + /// Adds a message directly without starting a newline after. + /// End message will be grouped by , then by group if one was started + /// then by ordinal comparison. + /// + /// + /// + public void AddMessage(FormattedMessage message, int priority = 0) + { + if (message.Nodes.Count == 0) + return; + + if (_currentGroupPart != null) + { + _currentGroupPart.Message.AddMessage(message); + } + else + { + Parts.Add(new ExamineMessagePart(message, priority, false, null)); + } + } + + /// + /// Adds markup directly without starting a newline after. + /// End message will be grouped by , then by group if one was started + /// then by ordinal comparison. + /// + /// + /// + public void AddMarkup(string markup, int priority=0) + { + AddMessage(FormattedMessage.FromMarkup(markup), priority); + } + + /// + /// Adds text directly without starting a newline after. + /// End message will be grouped by , then by group if one was started + /// then by ordinal comparison. + /// + /// + /// + public void AddText(string text, int priority=0) + { + var msg = new FormattedMessage(); + msg.AddText(text); + AddMessage(msg, priority); + } + + public struct ExamineGroupDisposable : IDisposable + { + private ExaminedEvent _event; + + public ExamineGroupDisposable(ExaminedEvent @event) + { + _event = @event; + } + + public void Dispose() + { + _event.PopGroup(); + } + } + + private record ExamineMessagePart(FormattedMessage Message, int Priority, bool DoNewLine, string? Group); } + /// /// Event raised directed at an entity that someone is attempting to examine /// diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs index 712e0192db..c5dd7c6e35 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs @@ -185,19 +185,22 @@ public abstract partial class SharedHandsSystem : EntitySystem var held = EnumerateHeld(uid, handsComp) .Where(x => !HasComp(x)).ToList(); - if (!held.Any()) + using (args.PushGroup(nameof(HandsComponent))) { - args.PushText(Loc.GetString("comp-hands-examine-empty", - ("user", Identity.Entity(uid, EntityManager)))); - return; + if (!held.Any()) + { + args.PushText(Loc.GetString("comp-hands-examine-empty", + ("user", Identity.Entity(uid, EntityManager)))); + return; + } + + var heldList = ContentLocalizationManager.FormatList(held + .Select(x => Loc.GetString("comp-hands-examine-wrapper", + ("item", Identity.Entity(x, EntityManager)))).ToList()); + + args.PushMarkup(Loc.GetString("comp-hands-examine", + ("user", Identity.Entity(uid, EntityManager)), + ("items", heldList))); } - - var heldList = ContentLocalizationManager.FormatList(held - .Select(x => Loc.GetString("comp-hands-examine-wrapper", - ("item", Identity.Entity(x, EntityManager)))).ToList()); - - args.PushMarkup(Loc.GetString("comp-hands-examine", - ("user", Identity.Entity(uid, EntityManager)), - ("items", heldList))); } } diff --git a/Content.Shared/Item/SharedItemSystem.cs b/Content.Shared/Item/SharedItemSystem.cs index 13b98d8533..c6a86c5011 100644 --- a/Content.Shared/Item/SharedItemSystem.cs +++ b/Content.Shared/Item/SharedItemSystem.cs @@ -112,8 +112,9 @@ public abstract class SharedItemSystem : EntitySystem private void OnExamine(EntityUid uid, ItemComponent component, ExaminedEvent args) { + // show at end of message generally args.PushMarkup(Loc.GetString("item-component-on-examine-size", - ("size", GetItemSizeLocale(component.Size)))); + ("size", GetItemSizeLocale(component.Size))), priority: -1); } public ItemSizePrototype GetSizePrototype(ProtoId id) diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.ChamberMagazine.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.ChamberMagazine.cs index 7220faa4d2..1b124cd51d 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.ChamberMagazine.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.ChamberMagazine.cs @@ -265,16 +265,20 @@ public abstract partial class SharedGunSystem var (count, _) = GetChamberMagazineCountCapacity(uid, component); string boltState; - if (component.BoltClosed != null) + using (args.PushGroup(nameof(ChamberMagazineAmmoProviderComponent))) { - if (component.BoltClosed == true) - boltState = Loc.GetString("gun-chamber-bolt-open-state"); - else - boltState = Loc.GetString("gun-chamber-bolt-closed-state"); - args.PushMarkup(Loc.GetString("gun-chamber-bolt", ("bolt", boltState), ("color", component.BoltClosed.Value ? Color.FromHex("#94e1f2") : Color.FromHex("#f29d94")))); - } + if (component.BoltClosed != null) + { + if (component.BoltClosed == true) + boltState = Loc.GetString("gun-chamber-bolt-open-state"); + else + boltState = Loc.GetString("gun-chamber-bolt-closed-state"); + args.PushMarkup(Loc.GetString("gun-chamber-bolt", ("bolt", boltState), + ("color", component.BoltClosed.Value ? Color.FromHex("#94e1f2") : Color.FromHex("#f29d94")))); + } - args.PushMarkup(Loc.GetString("gun-magazine-examine", ("color", AmmoExamineColor), ("count", count))); + args.PushMarkup(Loc.GetString("gun-magazine-examine", ("color", AmmoExamineColor), ("count", count))); + } } private bool TryTakeChamberEntity(EntityUid uid, [NotNullWhen(true)] out EntityUid? entity) diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Interactions.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Interactions.cs index 1600dec0e4..852096a386 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Interactions.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Interactions.cs @@ -14,8 +14,13 @@ public abstract partial class SharedGunSystem if (!args.IsInDetailsRange || !component.ShowExamineText) return; - args.PushMarkup(Loc.GetString("gun-selected-mode-examine", ("color", ModeExamineColor), ("mode", GetLocSelector(component.SelectedMode)))); - args.PushMarkup(Loc.GetString("gun-fire-rate-examine", ("color", FireRateExamineColor), ("fireRate", $"{component.FireRate:0.0}"))); + using (args.PushGroup(nameof(GunComponent))) + { + args.PushMarkup(Loc.GetString("gun-selected-mode-examine", ("color", ModeExamineColor), + ("mode", GetLocSelector(component.SelectedMode)))); + args.PushMarkup(Loc.GetString("gun-fire-rate-examine", ("color", FireRateExamineColor), + ("fireRate", $"{component.FireRate:0.0}"))); + } } private string GetLocSelector(SelectiveFire mode) diff --git a/Content.Shared/Wires/SharedWiresSystem.cs b/Content.Shared/Wires/SharedWiresSystem.cs index 50465537e8..a5b43da482 100644 --- a/Content.Shared/Wires/SharedWiresSystem.cs +++ b/Content.Shared/Wires/SharedWiresSystem.cs @@ -15,18 +15,21 @@ public abstract class SharedWiresSystem : EntitySystem private void OnExamine(EntityUid uid, WiresPanelComponent component, ExaminedEvent args) { - if (!component.Open) + using (args.PushGroup(nameof(WiresPanelComponent))) { - args.PushMarkup(Loc.GetString("wires-panel-component-on-examine-closed")); - } - else - { - args.PushMarkup(Loc.GetString("wires-panel-component-on-examine-open")); - - if (TryComp(uid, out var wiresPanelSecurity) && - wiresPanelSecurity.Examine != null) + if (!component.Open) { - args.PushMarkup(Loc.GetString(wiresPanelSecurity.Examine)); + args.PushMarkup(Loc.GetString("wires-panel-component-on-examine-closed")); + } + else + { + args.PushMarkup(Loc.GetString("wires-panel-component-on-examine-open")); + + if (TryComp(uid, out var wiresPanelSecurity) && + wiresPanelSecurity.Examine != null) + { + args.PushMarkup(Loc.GetString(wiresPanelSecurity.Examine)); + } } } }