diff --git a/Content.Server/Armor/ArmorSystem.cs b/Content.Server/Armor/ArmorSystem.cs index 477fd60cea..084d31b3e5 100644 --- a/Content.Server/Armor/ArmorSystem.cs +++ b/Content.Server/Armor/ArmorSystem.cs @@ -77,20 +77,11 @@ namespace Content.Server.Armor if (armorModifiers == null) return; - var verb = new ExamineVerb() - { - Act = () => - { - var markup = GetArmorExamine(armorModifiers); - _examine.SendExamineTooltip(args.User, uid, markup, false, false); - }, - Text = Loc.GetString("armor-examinable-verb-text"), - Message = Loc.GetString("armor-examinable-verb-message"), - Category = VerbCategory.Examine, - IconTexture = "/Textures/Interface/VerbIcons/dot.svg.192dpi.png" - }; + var examineMarkup = GetArmorExamine(armorModifiers); - args.Verbs.Add(verb); + _examine.AddDetailedExamineVerb(args, component, examineMarkup, Loc.GetString("armor-examinable-verb-text"), "/Textures/Interface/VerbIcons/dot.svg.192dpi.png", Loc.GetString("armor-examinable-verb-message")); + + return; } private static FormattedMessage GetArmorExamine(DamageModifierSet armorModifiers) diff --git a/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs b/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs index 80b6132e17..d75310ea00 100644 --- a/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs +++ b/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs @@ -118,18 +118,8 @@ public sealed class ClothingSpeedModifierSystem : EntitySystem } } - var verb = new ExamineVerb() - { - Act = () => - { - _examine.SendExamineTooltip(args.User, uid, msg, false, false); - }, - Text = Loc.GetString("clothing-speed-examinable-verb-text"), - Message = Loc.GetString("clothing-speed-examinable-verb-message"), - Category = VerbCategory.Examine, - IconTexture = "/Textures/Interface/VerbIcons/outfit.svg.192dpi.png" - }; + _examine.AddDetailedExamineVerb(args, component, msg, Loc.GetString("clothing-speed-examinable-verb-text"), "/Textures/Interface/VerbIcons/outfit.svg.192dpi.png", Loc.GetString("clothing-speed-examinable-verb-message")); - args.Verbs.Add(verb); + return; } } diff --git a/Content.Shared/Examine/ExamineSystemShared.Group.cs b/Content.Shared/Examine/ExamineSystemShared.Group.cs new file mode 100644 index 0000000000..a959f35b3b --- /dev/null +++ b/Content.Shared/Examine/ExamineSystemShared.Group.cs @@ -0,0 +1,175 @@ +using Robust.Shared.Utility; +using Content.Shared.Verbs; + +namespace Content.Shared.Examine +{ + public abstract partial class ExamineSystemShared : EntitySystem + { + [Dependency] private readonly IComponentFactory _componentFactory = default!; + + public const string DefaultIconTexture = "/Textures/Interface/examine-star.png"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(OnGroupExamineVerb); + } + + /// + /// Called when getting verbs on an object with the GroupExamine component.
+ /// This checks if any of the ExamineGroups are relevant (has 1 or more of the relevant components on the entity) + /// and if so, creates an ExamineVerb details button for the ExamineGroup. + ///
+ private void OnGroupExamineVerb(EntityUid uid, GroupExamineComponent component, GetVerbsEvent args) + { + foreach (var group in component.ExamineGroups) + { + if (!EntityHasComponent(uid, group.Components)) + continue; + + var examineVerb = new ExamineVerb() + { + Act = () => + { + SendExamineGroup(args.User, args.Target, group); + group.Entries.Clear(); + }, + Text = group.ContextText, + Message = group.HoverMessage, + Category = VerbCategory.Examine, + IconTexture = group.Icon + }; + + args.Verbs.Add(examineVerb); + } + } + + /// + /// Checks if the entity has any of the listed . + /// + public bool EntityHasComponent(EntityUid uid, List components) + { + foreach (var comp in components) + { + if (!_componentFactory.TryGetRegistration(comp, out var componentRegistration)) + continue; + + if (!HasComp(uid, componentRegistration.Type)) + continue; + + return true; + } + return false; + } + + /// + /// Sends an ExamineTooltip based on the contents of + /// + public void SendExamineGroup(EntityUid user, EntityUid target, ExamineGroup group) + { + var message = new FormattedMessage(); + + if (group.Title != null) + { + message.AddMarkup(Loc.GetString(group.Title)); + message.PushNewline(); + } + message.AddMessage(GetFormattedMessageFromExamineEntries(group.Entries)); + + SendExamineTooltip(user, target, message, false, false); + } + + /// A FormattedMessage based on all , sorted. + public static FormattedMessage GetFormattedMessageFromExamineEntries(List entries) + { + var formattedMessage = new FormattedMessage(); + entries.Sort((a, b) => (b.Priority.CompareTo(a.Priority))); + + var first = true; + + foreach (var entry in entries) + { + if (!first) + { + formattedMessage.PushNewline(); + } + else + { + first = false; + } + + formattedMessage.AddMessage(entry.Message); + } + + return formattedMessage; + } + + /// + /// Either sends the details to a GroupExamineComponent if it finds one, or adds a details examine verb itself. + /// + public void AddDetailedExamineVerb(GetVerbsEvent verbsEvent, Component component, List entries, string verbText, string iconTexture = DefaultIconTexture, string hoverMessage = "") + { + // If the entity has the GroupExamineComponent + if (TryComp(verbsEvent.Target, out var groupExamine)) + { + // Make sure we have the component name as a string + var componentName = _componentFactory.GetComponentName(component.GetType()); + + foreach (var examineGroup in groupExamine.ExamineGroups) + { + // If any of the examine groups list of components contain this componentname + if (examineGroup.Components.Contains(componentName)) + { + foreach (var entry in examineGroup.Entries) + { + // If any of the entries already are from your component, dont do anything else - no doubles! + if (entry.ComponentName == componentName) + return; + } + + foreach (var entry in entries) + { + // Otherwise, just add all information to the examine groups entries, and stop there. + examineGroup.Entries.Add(entry); + } + return; + } + } + } + + var formattedMessage = GetFormattedMessageFromExamineEntries(entries); + + var examineVerb = new ExamineVerb() + { + Act = () => + { + SendExamineTooltip(verbsEvent.User, verbsEvent.Target, formattedMessage, false, false); + }, + Text = verbText, + Message = hoverMessage, + Category = VerbCategory.Examine, + IconTexture = iconTexture + }; + + verbsEvent.Verbs.Add(examineVerb); + } + + /// + /// Either adds a details examine verb, or sends the details to a GroupExamineComponent if it finds one. + /// + public void AddDetailedExamineVerb(GetVerbsEvent verbsEvent, Component component, ExamineEntry entry, string verbText, string iconTexture = DefaultIconTexture, string hoverMessage = "") + { + AddDetailedExamineVerb(verbsEvent, component, new List { entry }, verbText, iconTexture, hoverMessage); + } + + /// + /// Either adds a details examine verb, or sends the details to a GroupExamineComponent if it finds one. + /// + public void AddDetailedExamineVerb(GetVerbsEvent verbsEvent, Component component, FormattedMessage message, string verbText, string iconTexture = DefaultIconTexture, string hoverMessage = "") + { + var componentName = _componentFactory.GetComponentName(component.GetType()); + AddDetailedExamineVerb(verbsEvent, component, new ExamineEntry(componentName, 0f, message), verbText, iconTexture, hoverMessage); + } + } +} diff --git a/Content.Shared/Examine/ExamineSystemShared.cs b/Content.Shared/Examine/ExamineSystemShared.cs index 18662ddab8..c7738750c0 100644 --- a/Content.Shared/Examine/ExamineSystemShared.cs +++ b/Content.Shared/Examine/ExamineSystemShared.cs @@ -13,7 +13,7 @@ using static Content.Shared.Interaction.SharedInteractionSystem; namespace Content.Shared.Examine { - public abstract class ExamineSystemShared : EntitySystem + public abstract partial class ExamineSystemShared : EntitySystem { [Dependency] private readonly SharedContainerSystem _containerSystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; diff --git a/Content.Shared/Examine/GroupExamineComponent.cs b/Content.Shared/Examine/GroupExamineComponent.cs new file mode 100644 index 0000000000..626619343d --- /dev/null +++ b/Content.Shared/Examine/GroupExamineComponent.cs @@ -0,0 +1,103 @@ +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.Examine +{ + /// + /// This component groups examine messages together + /// + [RegisterComponent] + public sealed class GroupExamineComponent : Component + { + /// + /// A list of ExamineGroups. + /// + [DataField("group")] + public List ExamineGroups = new() + { + new ExamineGroup() + { + Components = new() + { + "Armor", + "ClothingSpeedModifier", + }, + }, + }; + } + + [DataDefinition] + public sealed class ExamineGroup + { + /// + /// The title of the Examine Group, the . + /// + [DataField("title")] + [ViewVariables(VVAccess.ReadWrite)] + public string? Title; + + /// + /// A list of ExamineEntries, containing which component it belongs to, which priority it has, and what FormattedMessage it holds. + /// + [DataField("entries")] + public List Entries = new(); + + /// + /// A list of all components this ExamineGroup encompasses. + /// + [DataField("components")] + public List Components = new(); + + /// + /// The icon path for the Examine Group. + /// + [DataField("icon")] + public string Icon = "/Textures/Interface/examine-star.png"; + + /// + /// The text shown in the context verb menu. + /// + [DataField("contextText")] + public string ContextText = string.Empty; + + /// + /// Details shown when hovering over the button. + /// + [DataField("hoverMessage")] + public string HoverMessage = string.Empty; + } + + /// + /// An entry used when showing examine details + /// + [Serializable, NetSerializable] + public sealed class ExamineEntry + { + /// + /// Which component does this entry relate to? + /// + [DataField("component")] + public string ComponentName = string.Empty; + + /// + /// What priority has this entry - entries are sorted high to low. + /// + [DataField("priority")] + public float Priority = 0f; + + /// + /// The FormattedMessage of this entry. + /// + [DataField("message")] + public FormattedMessage Message = new(); + + /// Should be set to _componentFactory.GetComponentName(component.GetType()) to properly function. + public ExamineEntry(string componentName, float priority, FormattedMessage message) + { + ComponentName = componentName; + Priority = priority; + Message = message; + } + } + +} diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml index 6b917d34ef..5c80c73793 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml @@ -130,3 +130,4 @@ modifiers: coefficients: Heat: 0.95 + - type: GroupExamine diff --git a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml index 74d56b482c..a8c30202c3 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml @@ -142,6 +142,7 @@ Piercing: 0.95 Heat: 0.90 Radiation: 0.25 + - type: GroupExamine - type: IngestionBlocker - type: Tag tags: diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml index 857047d7cf..bc4245e927 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml @@ -53,6 +53,7 @@ Slash: 0.5 Piercing: 0.6 Heat: 0.5 + - type: GroupExamine - type: entity parent: ClothingOuterBaseLarge @@ -72,6 +73,7 @@ Piercing: 0.2 Heat: 0.5 Radiation: 0 + - type: GroupExamine - type: entity parent: ClothingOuterArmorHeavy @@ -136,6 +138,7 @@ Heat: 0.9 - type: ExplosionResistance damageCoefficient: 0.9 + - type: GroupExamine - type: entity parent: ClothingOuterBaseLarge @@ -156,6 +159,7 @@ Heat: 0.9 - type: ExplosionResistance damageCoefficient: 0.8 + - type: GroupExamine - type: entity parent: ClothingOuterBaseLarge @@ -176,7 +180,8 @@ Heat: 0.5 - type: ExplosionResistance damageCoefficient: 0.65 - + - type: GroupExamine + - type: entity parent: ClothingOuterBaseLarge id: ClothingOuterArmorChangeling @@ -200,3 +205,4 @@ sprintModifier: 0.65 - type: ExplosionResistance damageCoefficient: 0.5 + - type: GroupExamine diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml index 29bb48452b..af41be3c40 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml @@ -73,6 +73,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + - type: GroupExamine - type: entity abstract: true diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml index b15c77a736..133607c34b 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml @@ -481,6 +481,7 @@ Slash: 0.95 Heat: 0.90 Radiation: 0.75 + - type: GroupExamine - type: entity parent: ClothingOuterHardsuitBase diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml index d4c3a79636..5ec3c1ebaf 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml @@ -16,7 +16,8 @@ Piercing: 0.9 Heat: 0.75 - type: ExplosionResistance - damageCoefficient: 0.65 + damageCoefficient: 0.65 + - type: GroupExamine - type: entity parent: ClothingOuterEVASuitBase @@ -59,6 +60,7 @@ sprintModifier: 0.7 - type: TemperatureProtection coefficient: 0.01 + - type: GroupExamine - type: entity parent: [ClothingOuterBaseLarge, GeigerCounterClothing] @@ -75,6 +77,7 @@ Radiation: 0.05 - type: Clothing sprite: Clothing/OuterClothing/Suits/rad.rsi + - type: GroupExamine - type: entity parent: ClothingOuterBase @@ -150,3 +153,4 @@ - type: ClothingSpeedModifier walkModifier: 0.7 sprintModifier: 0.75 + - type: GroupExamine diff --git a/Resources/Textures/Interface/examine-star.png b/Resources/Textures/Interface/examine-star.png new file mode 100644 index 0000000000..0524bd544c Binary files /dev/null and b/Resources/Textures/Interface/examine-star.png differ diff --git a/Resources/Textures/Interface/examine-star.png.yml b/Resources/Textures/Interface/examine-star.png.yml new file mode 100644 index 0000000000..dabd6601f7 --- /dev/null +++ b/Resources/Textures/Interface/examine-star.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true