diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 1d0a90ee68..02e6a6c9b7 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -11,6 +11,7 @@ namespace Content.Client.Entry "IngestionBlocker", "Charger", "CloningPod", + "HealthExaminable", "Destructible", "Temperature", "AtmosExposed", diff --git a/Content.Server/HealthExaminable/HealthExaminableComponent.cs b/Content.Server/HealthExaminable/HealthExaminableComponent.cs new file mode 100644 index 0000000000..97b8bf8ee5 --- /dev/null +++ b/Content.Server/HealthExaminable/HealthExaminableComponent.cs @@ -0,0 +1,23 @@ +using Content.Shared.Damage.Prototypes; +using Content.Shared.FixedPoint; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; + +namespace Content.Server.HealthExaminable; + +[RegisterComponent] +public sealed class HealthExaminableComponent : Component +{ + public List Thresholds = new() + { FixedPoint2.New(10), FixedPoint2.New(25), FixedPoint2.New(50), FixedPoint2.New(75) }; + + [DataField("examinableTypes", required: true, customTypeSerializer:typeof(PrototypeIdHashSetSerializer))] + public HashSet ExaminableTypes = default!; + + /// + /// Health examine text is automatically generated through creating loc string IDs, in the form: + /// `health-examine-[prefix]-[type]-[threshold]` + /// This part determines the prefix. + /// + [DataField("locPrefix")] + public string LocPrefix = "carbon"; +} diff --git a/Content.Server/HealthExaminable/HealthExaminableSystem.cs b/Content.Server/HealthExaminable/HealthExaminableSystem.cs new file mode 100644 index 0000000000..58526031bc --- /dev/null +++ b/Content.Server/HealthExaminable/HealthExaminableSystem.cs @@ -0,0 +1,98 @@ +using Content.Server.Examine; +using Content.Shared.Damage; +using Content.Shared.Examine; +using Content.Shared.FixedPoint; +using Content.Shared.Ghost.Roles; +using Content.Shared.Verbs; +using Robust.Shared.Utility; + +namespace Content.Server.HealthExaminable; + +public sealed class HealthExaminableSystem : EntitySystem +{ + [Dependency] private readonly ExamineSystemShared _examineSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(OnGetExamineVerbs); + } + + private void OnGetExamineVerbs(EntityUid uid, HealthExaminableComponent component, GetVerbsEvent args) + { + if (!TryComp(uid, out var damage)) + return; + + var detailsRange = _examineSystem.IsInDetailsRange(args.User, uid); + + var verb = new ExamineVerb() + { + Act = () => + { + var markup = CreateMarkup(uid, component, damage); + _examineSystem.SendExamineTooltip(args.User, uid, markup, false, false); + }, + Text = Loc.GetString("health-examinable-verb-text"), + Category = VerbCategory.Examine, + Disabled = !detailsRange, + Message = Loc.GetString("health-examinable-verb-disabled"), + IconTexture = "/Textures/Interface/VerbIcons/plus.svg.192dpi.png" + }; + + args.Verbs.Add(verb); + } + + private FormattedMessage CreateMarkup(EntityUid uid, HealthExaminableComponent component, DamageableComponent damage) + { + var msg = new FormattedMessage(); + + var first = true; + foreach (var type in component.ExaminableTypes) + { + if (!damage.Damage.DamageDict.TryGetValue(type, out var dmg)) + continue; + + if (dmg == FixedPoint2.Zero) + continue; + + FixedPoint2 closest = FixedPoint2.Zero; + + string chosenLocStr = string.Empty; + foreach (var threshold in component.Thresholds) + { + var str = $"health-examinable-{component.LocPrefix}-{type}-{threshold}"; + var tempLocStr = Loc.GetString($"health-examinable-{component.LocPrefix}-{type}-{threshold}", ("target", uid)); + + // i.e., this string doesn't exist, because theres nothing for that threshold + if (tempLocStr == str) + continue; + + chosenLocStr = tempLocStr; + + if (dmg > threshold && threshold > closest) + closest = threshold; + } + + if (closest == FixedPoint2.Zero) + continue; + + if (!first) + { + msg.PushNewline(); + } + else + { + first = false; + } + msg.AddMarkup(chosenLocStr); + } + + if (msg.IsEmpty) + { + msg.AddMarkup(Loc.GetString($"health-examinable-{component.LocPrefix}-none")); + } + + return msg; + } +} diff --git a/Resources/Locale/en-US/health-examinable/health-examinable-carbon.ftl b/Resources/Locale/en-US/health-examinable/health-examinable-carbon.ftl new file mode 100644 index 0000000000..7f7b540601 --- /dev/null +++ b/Resources/Locale/en-US/health-examinable/health-examinable-carbon.ftl @@ -0,0 +1,18 @@ +health-examinable-carbon-none = There are no obvious wounds to be seen. + +health-examinable-carbon-Blunt-25 = [color=red]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-HAVE($target) } minor contusions across { POSS-ADJ($target) } body.[/color] +health-examinable-carbon-Blunt-50 = [color=crimson]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-HAVE($target) } major bruises all over { POSS-ADJ($target) } body![/color] +health-examinable-carbon-Blunt-75 = [color=crimson]{ CAPITALIZE(POSS-ADJ($target)) } body is completely covered in lesions![/color] + +health-examinable-carbon-Slash-10 = [color=red]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-HAVE($target) } some minor cuts.[/color] +health-examinable-carbon-Slash-25 = [color=red]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-HAVE($target) } lacerations across { POSS-ADJ($target) } body.[/color] +health-examinable-carbon-Slash-50 = [color=crimson]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-HAVE($target) } major gashes all over { POSS-ADJ($target) } body![/color] +health-examinable-carbon-Slash-75 = [color=crimson]{ CAPITALIZE(POSS-ADJ($target)) } body is completely mauled![/color] + +health-examinable-carbon-Piercing-50 = [color=crimson]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-HAVE($target) } deep wounds all over { POSS-ADJ($target) } body![/color] + +health-examinable-carbon-Heat-25 = [color=orange]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-HAVE($target) } minor burns across { POSS-ADJ($target) } body.[/color] +health-examinable-carbon-Heat-50 = [color=orange]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-HAVE($target) } major burns across { POSS-ADJ($target) } body.[/color] +health-examinable-carbon-Heat-75 = [color=orange]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-HAVE($target) } severe third-degree burns across { POSS-ADJ($target) } body![/color] + +health-examinable-carbon-Shock-50 = [color=lightgoldenrod]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-HAVE($target) } electrical shock marks across { POSS-ADJ($target) } body![/color] diff --git a/Resources/Locale/en-US/health-examinable/health-examinable-comp.ftl b/Resources/Locale/en-US/health-examinable/health-examinable-comp.ftl new file mode 100644 index 0000000000..844a84a457 --- /dev/null +++ b/Resources/Locale/en-US/health-examinable/health-examinable-comp.ftl @@ -0,0 +1,2 @@ +health-examinable-verb-text = Health +health-examinable-verb-disabled = Not close enough diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index d956ca3a28..58408d5e20 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -22,6 +22,13 @@ - type: MovementSpeedModifier baseWalkSpeed : 4 baseSprintSpeed : 4 + - type: HealthExaminable + examinableTypes: + - Blunt + - Slash + - Piercing + - Heat + - Shock - type: MovedByPressure - type: DamageOnHighSpeedImpact damage: diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 6ac8e820b3..f436310a86 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -48,6 +48,13 @@ solution: chemicals - type: DrawableSolution solution: bloodstream + - type: HealthExaminable + examinableTypes: + - Blunt + - Slash + - Piercing + - Heat + - Shock - type: Bloodstream bloodlossDamage: types: diff --git a/Resources/Textures/Interface/VerbIcons/plus.svg b/Resources/Textures/Interface/VerbIcons/plus.svg new file mode 100644 index 0000000000..78a56e3570 --- /dev/null +++ b/Resources/Textures/Interface/VerbIcons/plus.svg @@ -0,0 +1,39 @@ + + + + + + diff --git a/Resources/Textures/Interface/VerbIcons/plus.svg.192dpi.png b/Resources/Textures/Interface/VerbIcons/plus.svg.192dpi.png new file mode 100644 index 0000000000..3c97eb8473 Binary files /dev/null and b/Resources/Textures/Interface/VerbIcons/plus.svg.192dpi.png differ diff --git a/Resources/Textures/Interface/VerbIcons/plus.svg.192dpi.png.yml b/Resources/Textures/Interface/VerbIcons/plus.svg.192dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/Interface/VerbIcons/plus.svg.192dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true