]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Examine prediction (#23565)
authorKara <lunarautomaton6@gmail.com>
Sat, 6 Jan 2024 06:53:13 +0000 (23:53 -0700)
committerGitHub <noreply@github.com>
Sat, 6 Jan 2024 06:53:13 +0000 (17:53 +1100)
* Initial prediction

* new group handling

* groups for all examines that use multiple rn

* compile

* why was it doing this??

* handle newlines with sorting properly

41 files changed:
Content.Client/Construction/ConstructionSystem.cs
Content.Client/Examine/ExamineSystem.cs
Content.Server/Atmos/EntitySystems/GasTankSystem.cs
Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs
Content.Server/Botany/Systems/BotanySystem.Seed.cs
Content.Server/Botany/Systems/PlantHolderSystem.cs
Content.Server/Construction/Conditions/MachineFrameComplete.cs
Content.Server/Construction/ConstructionSystem.Guided.cs
Content.Server/Defusable/Systems/DefusableSystem.cs
Content.Server/Fluids/EntitySystems/DrainSystem.cs
Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs
Content.Server/Fluids/EntitySystems/PuddleSystem.cs
Content.Server/Ghost/Roles/ToggleableGhostRoleSystem.cs
Content.Server/Holiday/Christmas/RandomGiftSystem.cs
Content.Server/Holosign/HolosignSystem.cs
Content.Server/Labels/Label/LabelSystem.cs
Content.Server/Light/EntitySystems/EmergencyLightSystem.cs
Content.Server/Light/EntitySystems/LightReplacerSystem.cs
Content.Server/Medical/CryoPodSystem.cs
Content.Server/Morgue/CrematoriumSystem.cs
Content.Server/Nutrition/EntitySystems/DrinkSystem.cs
Content.Server/Paper/PaperSystem.cs
Content.Server/Payload/EntitySystems/PayloadSystem.cs
Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs
Content.Server/Shuttles/Systems/ThrusterSystem.cs
Content.Server/Stunnable/Systems/StunbatonSystem.cs
Content.Server/Tools/ToolSystem.Welder.cs
Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs
Content.Shared/Charges/Systems/SharedChargesSystem.cs
Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs
Content.Shared/Construction/MachinePartSystem.cs
Content.Shared/Construction/Steps/ArbitraryInsertConstructionGraphStep.cs
Content.Shared/Construction/Steps/ComponentConstructionGraphStep.cs
Content.Shared/Construction/Steps/MaterialConstructionGraphStep.cs
Content.Shared/Dice/SharedDiceSystem.cs
Content.Shared/Examine/ExamineSystemShared.cs
Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs
Content.Shared/Item/SharedItemSystem.cs
Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.ChamberMagazine.cs
Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Interactions.cs
Content.Shared/Wires/SharedWiresSystem.cs

index 4035c68cc7581db7e01ae6abdb74cc4bf80bc836..940538670c09cb88cb9a3c62439c3ee9ff8ed6b0 100644 (file)
@@ -81,27 +81,30 @@ namespace Content.Client.Construction
 
         private void HandleConstructionGhostExamined(EntityUid uid, ConstructionGhostComponent component, ExaminedEvent args)
         {
-            if (component.Prototype == null) return;
+            if (component.Prototype == null)
+                return;
 
-            args.PushMarkup(Loc.GetString(
-                "construction-ghost-examine-message",
-                ("name", component.Prototype.Name)));
+            using (args.PushGroup(nameof(ConstructionGhostComponent)))
+            {
+                args.PushMarkup(Loc.GetString(
+                    "construction-ghost-examine-message",
+                    ("name", component.Prototype.Name)));
 
-            if (!_prototypeManager.TryIndex(component.Prototype.Graph, out ConstructionGraphPrototype? graph))
-                return;
+                if (!_prototypeManager.TryIndex(component.Prototype.Graph, out ConstructionGraphPrototype? graph))
+                    return;
 
-            var startNode = graph.Nodes[component.Prototype.StartNode];
+                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;
-            }
+                if (!graph.TryPath(component.Prototype.StartNode, component.Prototype.TargetNode, out var path) ||
+                    !startNode.TryGetEdge(path[0].Name, out var edge))
+                {
+                    return;
+                }
 
-            foreach (ConstructionGraphStep step in edge.Steps)
-            {
-                args.Message.PushNewline();
-                step.DoExamine(args);
+                foreach (var step in edge.Steps)
+                {
+                    step.DoExamine(args);
+                }
             }
         }
 
index 59460cec561694084891851d77ae7fe5b50c294e..32ba78085ae624eaeebd9dfd44a3af940d25f284 100644 (file)
@@ -366,13 +366,14 @@ namespace Content.Client.Examine
             var canSeeClearly = !HasComp<BlurryVisionComponent>(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;
         }
 
index bdd60d6d05289e65e6eb76cb2393a487a5b58615..e00990f5944070e655604cd5d88c0e13cb85067f 100644 (file)
@@ -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)
index fb35ddc34693518b8115471e3c23625840b994b0..6a4a16eac3df0aa82872e8b8575f22f253035ce8 100644 (file)
@@ -51,20 +51,23 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
                 return;
             }
 
-            if (comp.Reacting)
-            {
-                args.PushMarkup(Loc.GetString("gas-recycler-reacting"));
-            }
-            else
+            using (args.PushGroup(nameof(GasRecyclerComponent)))
             {
-                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"));
+                    }
                 }
             }
         }
index 1fb6952e913aebcc0f3928df4612c7c12c88bcd9..c9389f832e71ecbe9280e260cb3bf41c2a441582 100644 (file)
@@ -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
index 96fde08118d17d77f41636b533bac34da8af1096..0bef58a293ab99ebd0faa0ceb81e5e090eb47636 100644 (file)
@@ -77,59 +77,62 @@ public sealed class PlantHolderSystem : EntitySystem
 
         var (_, component) = entity;
 
-        if (component.Seed == null)
-        {
-            args.PushMarkup(Loc.GetString("plant-holder-component-nothing-planted-message"));
-        }
-        else if (!component.Dead)
+        using (args.PushGroup(nameof(PlantHolderComponent)))
         {
-            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)
+                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-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"))));
+                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"));
+            }
         }
     }
 
index 9fa4098ec21fa36b0ed962dbee6b1e8862046bc8..fbba5eca996f9531c79f0e641fba21ac0e87c496 100644 (file)
@@ -46,7 +46,7 @@ namespace Content.Server.Construction.Conditions
             if (entityManager.EntitySysManager.GetEntitySystem<MachineFrameSystem>().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;
index cf6ea4da5169cbb1636d50b2acf8ed226c55cd6e..84028b6b7f4c10d3654a236800aa56b3a8393c87 100644 (file)
@@ -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;
 
-            if (component.EdgeIndex == null && GetTargetEdge(uid, component) is {} targetEdge)
-            {
-                var preventStepExamine = false;
+                    foreach (var condition in targetEdge.Conditions)
+                    {
+                        preventStepExamine |= condition.DoExamine(args);
+                    }
 
-                foreach (var condition in targetEdge.Conditions)
-                {
-                    preventStepExamine |= condition.DoExamine(args);
+                    if (!preventStepExamine)
+                        targetEdge.Steps[0].DoExamine(args);
+                    return;
                 }
 
-                if (!preventStepExamine)
-                    targetEdge.Steps[0].DoExamine(args);
-                return;
-            }
+                if (GetCurrentEdge(uid, component) is {} edge)
+                {
+                    var preventStepExamine = false;
 
-            if (GetCurrentEdge(uid, component) is {} edge)
-            {
-                var preventStepExamine = false;
+                    foreach (var condition in edge.Conditions)
+                    {
+                        preventStepExamine |= condition.DoExamine(args);
+                    }
 
-                foreach (var condition in edge.Conditions)
-                {
-                    preventStepExamine |= condition.DoExamine(args);
+                    if (!preventStepExamine && component.StepIndex < edge.Steps.Count)
+                        edge.Steps[component.StepIndex].DoExamine(args);
                 }
-
-                if (!preventStepExamine && component.StepIndex < edge.Steps.Count)
-                    edge.Steps[component.StepIndex].DoExamine(args);
             }
+
         }
 
 
index c450fd04fb1d673ecc701723d383a1792a068749..6927df256a473d8cc8c168702b142c83bac9cb02 100644 (file)
@@ -66,26 +66,29 @@ public sealed class DefusableSystem : SharedDefusableSystem
         if (!args.IsInDetailsRange)
             return;
 
-        if (!comp.Usable)
-        {
-            args.PushMarkup(Loc.GetString("defusable-examine-defused", ("name", uid)));
-        }
-        else if (comp.Activated && TryComp<ActiveTimerTriggerComponent>(uid, out var activeComp))
+        using (args.PushGroup(nameof(DefusableComponent)))
         {
-            if (comp.DisplayTime)
+            if (!comp.Usable)
+            {
+                args.PushMarkup(Loc.GetString("defusable-examine-defused", ("name", uid)));
+            }
+            else if (comp.Activated && TryComp<ActiveTimerTriggerComponent>(uid, out var activeComp))
             {
-                args.PushMarkup(Loc.GetString("defusable-examine-live", ("name", uid),
-                    ("time", MathF.Floor(activeComp.TimeRemaining))));
+                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)));
     }
index 726ed0b13c50648adb2b482b75c4c8484099122f..19cb650db7cb9fae213c88c66dbed8e488c8c843 100644 (file)
@@ -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<DrainComponent> entity, ref AfterInteractUsingEvent args)
index 083d15c22e5aba9b62dcde891335d92e23c4972a..177391de22d280e8393759850e437775abe8da72 100644 (file)
@@ -43,10 +43,13 @@ public sealed partial class PuddleSystem
 
     private void OnExamined(Entity<SpillableComponent> 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<MeleeWeaponComponent>(entity))
-            args.PushMarkup(Loc.GetString("spill-examine-spillable-weapon"));
+            if (HasComp<MeleeWeaponComponent>(entity))
+                args.PushMarkup(Loc.GetString("spill-examine-spillable-weapon"));
+        }
     }
 
     private void OnOverflow(Entity<SpillableComponent> entity, ref SolutionContainerOverflowEvent args)
index 844f43a5e4e1d46d3136ea05668a03b14bd1d41a..52948f2afb3763dbf1bf3559651aef5de2e8cb4a 100644 (file)
@@ -361,23 +361,27 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
 
     private void HandlePuddleExamined(Entity<PuddleComponent> entity, ref ExaminedEvent args)
     {
-        if (TryComp<StepTriggerComponent>(entity, out var slippery) && slippery.Active)
+        using (args.PushGroup(nameof(PuddleComponent)))
         {
-            args.PushMarkup(Loc.GetString("puddle-component-examine-is-slipper-text"));
-        }
+            if (TryComp<StepTriggerComponent>(entity, out var slippery) && slippery.Active)
+            {
+                args.PushMarkup(Loc.GetString("puddle-component-examine-is-slipper-text"));
+            }
 
-        if (HasComp<EvaporationComponent>(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<EvaporationComponent>(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<PuddleComponent> entity, ref AnchorStateChangedEvent args)
index 4adfdfc576a20af14e1f0afa4496501f8dacf276..9bab229c0019db180fecffaee0a8a7811834e185 100644 (file)
@@ -59,7 +59,7 @@ public sealed class ToggleableGhostRoleSystem : EntitySystem
     {
         if (!args.IsInDetailsRange)
             return;
-
+        
         if (TryComp<MindContainerComponent>(uid, out var mind) && mind.HasMind)
         {
             args.PushMarkup(Loc.GetString(component.ExamineTextMindPresent));
index 33d5d0d234c396aa03de0f34978ca4f974aeb8bc..9e56d0a4937777598526255db8072d1a22d6c0b5 100644 (file)
@@ -44,8 +44,7 @@ public sealed class RandomGiftSystem : EntitySystem
             return;
 
         var name = _prototype.Index<EntityPrototype>(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)
index 67f4cec43d52b7fefc0805a5141e52c00e00cd82..f0fc3bbe142df990bb0b6b0b324394b7a477f771 100644 (file)
@@ -25,11 +25,14 @@ public sealed class HolosignSystem : EntitySystem
         var charges = UsesRemaining(component, battery);\r
         var maxCharges = MaxUses(component, battery);\r
 \r
-        args.PushMarkup(Loc.GetString("limited-charges-charges-remaining", ("charges", charges)));\r
-\r
-        if (charges > 0 && charges == maxCharges)\r
+        using (args.PushGroup(nameof(HolosignProjectorComponent)))\r
         {\r
-            args.PushMarkup(Loc.GetString("limited-charges-max-charges"));\r
+            args.PushMarkup(Loc.GetString("limited-charges-charges-remaining", ("charges", charges)));\r
+\r
+            if (charges > 0 && charges == maxCharges)\r
+            {\r
+                args.PushMarkup(Loc.GetString("limited-charges-max-charges"));\r
+            }\r
         }\r
     }\r
 \r
index 23f2851665fb9e2c49ac3eb9c5071ff5efaa0ade..d20295d072718a48bc601cbf6313ae136caef651 100644 (file)
@@ -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 (!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;
-            }
+                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());
+                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)
index bc3e7e83e282932607f0e710502d51c3fd1c8fe1..0b60787dfb6cce4f404eae95ba916e20a1853ce4 100644 (file)
@@ -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<AlertLevelComponent>(_station.GetOwningStation(uid), out var alerts))
-            return;
+            // Show alert level on the light itself.
+            if (!TryComp<AlertLevelComponent>(_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)
index c34fcfe3a24458c5b3fa0a073bbc1adfe43f9ce3..f1cb5373c04c56d5b80c6400f88b3083cfc10e6c 100644 (file)
@@ -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<string, int>();
-        var metaQuery = GetEntityQuery<MetaDataComponent>();
-        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<string, int>();
+            var metaQuery = GetEntityQuery<MetaDataComponent>();
+            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)));
+            }
         }
     }
 
index 19cb62f8365f0dcf3ad2579ca4d9b63486d151f6..3fcec3a824cbfa4ba60e793e4ae3f5ecd0ea9059 100644 (file)
@@ -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"));
+                }
             }
         }
     }
index 1f0682774ff21c8d6fa7dcc23395ac68fb2b90ff..54b47cff84adccef09a99bd842a722724ae7a2d3 100644 (file)
@@ -47,17 +47,24 @@ public sealed class CrematoriumSystem : EntitySystem
         if (!TryComp<AppearanceComponent>(uid, out var appearance))
             return;
 
-        if (_appearance.TryGetData<bool>(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<bool>(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<bool>(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<bool>(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"));
+            }
         }
     }
 
index b32f5a7f6358b7ae9531c218e9559df44a34339b..f75dd93e0979209f23bcb7d2dd3453d1bba42e05 100644 (file)
@@ -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<ExaminableSolutionComponent>(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));
         }
     }
 
index 22f39cd35b8aa4e82aa08793cadd863c1549c7cc..7525ad7cf39d95dc7fb34f017e57c0ef57f5e41e 100644 (file)
@@ -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))
+                    );
+                }
             }
         }
 
index 78a2de22d1c17678a177fd3250463d6051be1d15..85cf303d5d73cf59718a88f309b79cc4fb2dcdc1 100644 (file)
@@ -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)));
+            }
         }
     }
 
index 6a6c644fbbf034105263422d84e42efbf85d7af2..2cd3777c526de90eae8988e93f87e59c1a8eadfd 100644 (file)
@@ -172,8 +172,13 @@ public sealed class RadioDeviceSystem : EntitySystem
             return;
 
         var proto = _protoMan.Index<RadioChannelPrototype>(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)
index ed7208ab470b35010aca1c1b95d96cafe13bfce8..09607381c7afbca82899fc57cab24636eac3fba5 100644 (file)
@@ -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);
+
+            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()));
 
-            args.PushMarkup(nozzleDir);
+                args.PushMarkup(nozzleDir);
 
-            var exposed = NozzleExposed(xform);
+                var exposed = NozzleExposed(xform);
 
-            var nozzleText = Loc.GetString(exposed ? "thruster-comp-nozzle-exposed" : "thruster-comp-nozzle-not-exposed");
+                var nozzleText =
+                    Loc.GetString(exposed ? "thruster-comp-nozzle-exposed" : "thruster-comp-nozzle-not-exposed");
 
-            args.PushMarkup(nozzleText);
+                args.PushMarkup(nozzleText);
+            }
         }
     }
 
index 436559e43b5943eb094cf28cb14b0e51d126778a..053a2936892eac386580e3b4d5b8ffb2cf4397b3 100644 (file)
@@ -25,7 +25,7 @@ namespace Content.Server.Stunnable.Systems
         {
             base.Initialize();
 
-            SubscribeLocalEvent<BatteryComponent, ExaminedEvent>(OnExamined);
+            SubscribeLocalEvent<StunbatonComponent, ExaminedEvent>(OnExamined);
             SubscribeLocalEvent<StunbatonComponent, SolutionContainerChangedEvent>(OnSolutionChange);
             SubscribeLocalEvent<StunbatonComponent, StaminaDamageOnHitAttemptEvent>(OnStaminaHitAttempt);
             SubscribeLocalEvent<StunbatonComponent, ItemToggleActivateAttemptEvent>(TryTurnOn);
@@ -47,16 +47,12 @@ namespace Content.Server.Stunnable.Systems
             }
         }
 
-        private void OnExamined(Entity<BatteryComponent> entity, ref ExaminedEvent args)
+        private void OnExamined(Entity<StunbatonComponent> 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<StunbatonComponent> entity, ref ItemToggledEvent args)
index 510dd3bfe0646b31a2a49f25b731821bd4022574..6f8fb54d721c48ba5341e5892ade2ef3c98764e0 100644 (file)
@@ -99,24 +99,27 @@ namespace Content.Server.Tools
 
         private void OnWelderExamine(Entity<WelderComponent> 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
+                }
             }
         }
 
index 5a1639de4c08dcab64f8cd9c79c648089e120140..d313f9a25bf3fb84e81421f79f3c3237cd814fcd 100644 (file)
@@ -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)
index be1526d3d3c82118051f35711035a3519315e633..653a7f22a5a3cce7fe090f5f0b966b07ea86a288 100644 (file)
@@ -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"));
+            }
         }
     }
 
index fc83d46f4b954deac60bb5a92ca528e970d6dda8..32ecdd4ba6fde0d8bc88cb4791603e724e51a174 100644 (file)
@@ -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<ReagentPrototype>();
-        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<ReagentPrototype>();
+            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;
+            // 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])
+            var msg = new StringBuilder();
+            foreach (var reagent in recognized)
             {
-                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";
+                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)));
             }
 
-            msg.Append(Loc.GetString(part, ("color", reagent.SubstanceColor.ToHexNoAlpha()),
-                ("chemical", reagent.LocalizedName)));
+            args.PushMarkup(Loc.GetString("examinable-solution-has-recognizable-chemicals",
+                ("recognizedString", msg.ToString())));
         }
-
-        args.PushMarkup(Loc.GetString("examinable-solution-has-recognizable-chemicals", ("recognizedString", msg.ToString())));
     }
 
     private void OnSolutionExaminableVerb(Entity<ExaminableSolutionComponent> entity, ref GetVerbsEvent<ExamineVerb> args)
index b13dc20c6d51393cefb01af61dd21c7ccb95c064..01db7fbade3dd27d7a302276dff6068271a0b72f 100644 (file)
@@ -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<MachinePartPrototype>(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<MachinePartPrototype>(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<MachinePartPrototype>(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<MachinePartPrototype>(component.PartType).Name))));
+            }
         }
 
         public Dictionary<string, int> GetMachineBoardMaterialCost(Entity<MachineBoardComponent> entity, int coefficient = 1)
index 8e3dcd4f4dc0cbbe2be8d97949e6946007e912dc..49d429aa2673bffe064b9f4d25d4930d474bef7d 100644 (file)
@@ -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()
index a85a1ed472dd03ea14cb64d48fda7d5bd989dbd1..5c28d3b05f33354806b8dad2d415fed59d47e162 100644 (file)
@@ -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.
index d9f968d41a6dabf9a0d0a2bccf0404840daea0e6..eec8d89b808b3b673fc047a7b9b0826a36cce35b 100644 (file)
@@ -20,7 +20,7 @@ namespace Content.Shared.Construction.Steps
         {
             var material = IoCManager.Resolve<IPrototypeManager>().Index<StackPrototype>(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)
index 59a536e244199ded42368ab7a6426096876ac008..be3c912fb54c00b17da8a17abd7cae7606245381 100644 (file)
@@ -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)
index f4d89ecf26b5e6c5c39278eb6380355aab17eb1a..a06a4b7049934f998db95ec76ca4bc88e2843b18 100644 (file)
@@ -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<MetaDataComponent>(entity).EntityDescription))
             {
                 message.AddText(EntityManager.GetComponent<MetaDataComponent>(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;
         }
     }
 
     /// <summary>
     ///     Raised when an entity is examined.
+    ///     If you're pushing multiple messages that should be grouped together (or ordered in some way),
+    ///     call <see cref="PushGroup"/> before pushing and <see cref="PopGroup"/> when finished.
     /// </summary>
     public sealed class ExaminedEvent : EntityEventArgs
     {
         /// <summary>
         ///     The message that will be displayed as the examine text.
-        /// For most use cases, you probably want to use <see cref="PushMarkup"/> and similar instead to modify this,
-        /// since it handles newlines and such correctly.
+        ///     You should use <see cref="PushMarkup"/> and similar instead to modify this,
+        ///     since it handles newlines/priority and such correctly.
         /// </summary>
         /// <seealso cref="PushMessage"/>
         /// <seealso cref="PushMarkup"/>
         /// <seealso cref="PushText"/>
-        public FormattedMessage Message { get; }
+        /// <seealso cref="AddMessage"/>
+        /// <seealso cref="AddMarkup"/>
+        /// <seealso cref="AddText"/>
+        private FormattedMessage Message { get; }
+
+        /// <summary>
+        ///     Parts of the examine message that will later be sorted by priority and pushed onto <see cref="Message"/>.
+        /// </summary>
+        private List<ExamineMessagePart> Parts { get; } = new();
+
+        /// <summary>
+        ///     Whether the examiner is in range of the entity to get some extra details.
+        /// </summary>
+        public bool IsInDetailsRange { get; }
 
         /// <summary>
         ///     The entity performing the examining.
@@ -285,62 +306,206 @@ namespace Content.Shared.Examine
         /// </summary>
         public EntityUid Examined { get; }
 
-        /// <summary>
-        ///     Whether the examiner is in range of the entity to get some extra details.
-        /// </summary>
-        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;
+        }
+
+        /// <summary>
+        ///     Returns <see cref="Message"/> with all <see cref="Parts"/> appended according to their priority.
+        /// </summary>
+        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;
+        }
+
+        /// <summary>
+        ///     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.
+        /// </summary>
+        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);
+        }
+
+        /// <summary>
+        ///     Ends the current group and pushes its groups contents to the message.
+        ///     This will be called automatically if in using a `using` block with <see cref="PushGroup"/>.
+        /// </summary>
+        private void PopGroup()
+        {
+            DebugTools.Assert(_currentGroupPart != null);
+            if (_currentGroupPart != null)
+                Parts.Add(_currentGroupPart);
+
+            _currentGroupPart = null;
         }
 
         /// <summary>
         /// Push another message into this examine result, on its own line.
+        /// End message will be grouped by <see cref="priority"/>, then by group if one was started
+        /// then by ordinal comparison.
         /// </summary>
         /// <seealso cref="PushMarkup"/>
         /// <seealso cref="PushText"/>
-        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));
+            }
         }
 
         /// <summary>
         /// Push another message parsed from markup into this examine result, on its own line.
+        /// End message will be grouped by <see cref="priority"/>, then by group if one was started
+        /// then by ordinal comparison.
         /// </summary>
         /// <seealso cref="PushText"/>
         /// <seealso cref="PushMessage"/>
-        public void PushMarkup(string markup)
+        public void PushMarkup(string markup, int priority=0)
         {
-            PushMessage(FormattedMessage.FromMarkup(markup));
+            PushMessage(FormattedMessage.FromMarkup(markup), priority);
         }
 
         /// <summary>
         /// Push another message containing raw text into this examine result, on its own line.
+        /// End message will be grouped by <see cref="priority"/>, then by group if one was started
+        /// then by ordinal comparison.
         /// </summary>
         /// <seealso cref="PushMarkup"/>
         /// <seealso cref="PushMessage"/>
-        public void PushText(string text)
+        public void PushText(string text, int priority=0)
+        {
+            var msg = new FormattedMessage();
+            msg.AddText(text);
+            PushMessage(msg, priority);
+        }
+
+        /// <summary>
+        /// Adds a message directly without starting a newline after.
+        /// End message will be grouped by <see cref="priority"/>, then by group if one was started
+        /// then by ordinal comparison.
+        /// </summary>
+        /// <seealso cref="AddMarkup"/>
+        /// <seealso cref="AddText"/>
+        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));
+            }
+        }
+
+        /// <summary>
+        /// Adds markup directly without starting a newline after.
+        /// End message will be grouped by <see cref="priority"/>, then by group if one was started
+        /// then by ordinal comparison.
+        /// </summary>
+        /// <seealso cref="AddText"/>
+        /// <seealso cref="AddMessage"/>
+        public void AddMarkup(string markup, int priority=0)
+        {
+            AddMessage(FormattedMessage.FromMarkup(markup), priority);
+        }
+
+        /// <summary>
+        /// Adds text directly without starting a newline after.
+        /// End message will be grouped by <see cref="priority"/>, then by group if one was started
+        /// then by ordinal comparison.
+        /// </summary>
+        /// <seealso cref="AddMarkup"/>
+        /// <seealso cref="AddMessage"/>
+        public void AddText(string text, int priority=0)
         {
             var msg = new FormattedMessage();
             msg.AddText(text);
-            PushMessage(msg);
+            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);
     }
 
+
     /// <summary>
     ///     Event raised directed at an entity that someone is attempting to examine
     /// </summary>
index 712e0192db0f3070929385cbfb0d7547ec873d2b..c5dd7c6e35981c49b89b04adbbf16ab8fe8e3985 100644 (file)
@@ -185,19 +185,22 @@ public abstract partial class SharedHandsSystem : EntitySystem
         var held = EnumerateHeld(uid, handsComp)
             .Where(x => !HasComp<HandVirtualItemComponent>(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)));
     }
 }
index 13b98d8533898bd002a79f6b1012fff3fd7eca3a..c6a86c5011fd59fdeda2197561ac0df6c9d1715f 100644 (file)
@@ -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<ItemSizePrototype> id)
index 7220faa4d2161c1ad21d70150e8e3ab3240dad99..1b124cd51dcfadf30c5478f70b77ed5325974bb3 100644 (file)
@@ -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)
index 1600dec0e48cad1a18efe3fd5116586651bc63dd..852096a386483d30e137a544021cde6d13c5b5da 100644 (file)
@@ -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)
index 50465537e8ccaa90ea343dae58edd72ca2048de1..a5b43da482eb8c14eec346a422ccb07e803724c1 100644 (file)
@@ -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<WiresPanelSecurityComponent>(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<WiresPanelSecurityComponent>(uid, out var wiresPanelSecurity) &&
+                    wiresPanelSecurity.Examine != null)
+                {
+                    args.PushMarkup(Loc.GetString(wiresPanelSecurity.Examine));
+                }
             }
         }
     }