Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Content.Shared/_CE/Health/CESharedDamageableSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,11 +302,17 @@ public CEHealthInfo GetHealthInfo(EntityUid uid)
remainingUntilDeath = Math.Max(0, maxHp + destr.DestroyThreshold - damage.Damage.Total);
}

float ratio = -1;
if (!mobState.Critical) // if mob isn't crit we use currentHp
ratio = maxHp > 0 ? Math.Clamp((float)currentHp / maxHp, 0f, 1f) : 0f;
else if (mobState.Critical && destroyThreshold != null) // if he is crit, though, we use damage total
ratio = Math.Clamp(1f - ((float)damage.Damage.Total - maxHp) / destroyThreshold.Value, 0f, 1f);

return new CEHealthInfo
{
CurrentHp = currentHp,
MaxHp = maxHp,
Ratio = maxHp > 0 ? Math.Clamp((float) currentHp / maxHp, 0f, 1f) : 0f,
Ratio = ratio,
Critical = mobState.Critical,
HasMobState = true,
DestroyThreshold = destroyThreshold,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Content.Shared._CE.Health.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;

namespace Content.Shared._CE.HealthExaminable;

[RegisterComponent, Access(typeof(CEHealthExaminableComponent))]
public sealed partial class CEHealthExaminableComponent : Component
{
// REVIEW: could be interesting to expose this to prototypes; would allow for different thresholds for different mobs.
public List<FixedPoint2> Thresholds = new()
{ FixedPoint2.New(100), FixedPoint2.New(90), FixedPoint2.New(70), FixedPoint2.New(40), FixedPoint2.New(10) };

/// <summary>
/// Health examine text is automatically generated through creating loc string IDs, in the form:
/// `health-examinable-[prefix]-[type]-[threshold]`
/// This part determines the prefix.
/// </summary>
[DataField]
public string LocPrefix = "default";
}
101 changes: 101 additions & 0 deletions Content.Shared/_CE/HealthExaminable/CEHealthExaminableSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using Content.Shared._CE.Health;
using Content.Shared._CE.Health.Components;
using Content.Shared.Damage.Components;
using Content.Shared.Examine;
using Content.Shared.FixedPoint;
using Content.Shared.IdentityManagement;
using Content.Shared.Verbs;
using Robust.Shared.Utility;

namespace Content.Shared._CE.HealthExaminable;

public sealed partial class CEHealthExaminableSystem : EntitySystem
{
[Dependency] private ExamineSystemShared _examineSystem = default!;
[Dependency] private CESharedDamageableSystem _damageable = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<CEHealthExaminableComponent, GetVerbsEvent<ExamineVerb>>(OnGetExamineVerbs);
}

private void OnGetExamineVerbs(EntityUid uid, CEHealthExaminableComponent component, GetVerbsEvent<ExamineVerb> args)
{
// as to not show health examine verbs if target is not damageable
if (!TryComp<CEDamageableComponent>(uid, out var _))
return;

var detailsRange = _examineSystem.IsInDetailsRange(args.User, uid);

var verb = new ExamineVerb()
{
Act = () =>
{
var markup = CreateMarkup(uid, component);
_examineSystem.SendExamineTooltip(args.User, uid, markup, false, false);
},
Text = Loc.GetString("health-examinable-verb-text"),
Category = VerbCategory.Examine,
Disabled = !detailsRange,
Message = detailsRange ? null : Loc.GetString("health-examinable-verb-disabled"),
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/rejuvenate.svg.192dpi.png"))
};

args.Verbs.Add(verb);
}

public FormattedMessage CreateMarkup(EntityUid uid, CEHealthExaminableComponent component)
{
var msg = new FormattedMessage();
var baseLocStr = $"health-examinable-{component.LocPrefix}";

var healthInfo = _damageable.GetHealthInfo(uid);

// REVIEW: Will this component be applied to structures/destructibles too? If so, then this entire method will need a few changes.
if (!healthInfo.HasMobState)
return msg;

var closest = FixedPoint2.MaxValue;
var chosenLocStr = string.Empty;
var ratio = healthInfo.Ratio * 100;

baseLocStr = $"{baseLocStr}-{(healthInfo.Critical ? "critical-" : "")}";

foreach (var threshold in component.Thresholds)
{
var str = $"{baseLocStr}{threshold}";
var tempLocStr = Loc.GetString(str, ("target", Identity.Entity(uid, EntityManager)));

// string doesn't exist in localization (loc.getstring returns the used messageId; in this case, str)
if (tempLocStr == str)
continue;

if (ratio <= threshold && threshold <= closest)
{
chosenLocStr = tempLocStr;
closest = threshold;
}
}
msg.AddMarkupOrThrow(chosenLocStr);

if (msg.IsEmpty)
msg.AddMarkupOrThrow(Loc.GetString($"health-examinable-{component.LocPrefix}-100")); // use 100% health in case somehow the message came up empty

// Anything else want to add on to this?
RaiseLocalEvent(uid, new CEHealthBeingExaminedEvent(msg), true);

return msg;
}
}

/// <summary>
/// A class raised on an entity whose health is being examined
/// in order to add special text that is not handled by the
/// damage thresholds.
/// </summary>
public sealed class CEHealthBeingExaminedEvent(FormattedMessage message)
{
public FormattedMessage Message = message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
health-examinable-default-100 = There are no obvious wounds to be seen.
health-examinable-default-90 = [color=#FFDDDD]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-HAVE($target) } a few small wounds.[/color]
health-examinable-default-70 = [color=#FFBBBB]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-HAVE($target) } some shallow wounds.[/color]
health-examinable-default-40 = [color=#FF9999]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-HAVE($target) } serious, deep wounds.[/color]
health-examinable-default-10 = [color=#FF7777]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-BE($target) } badly beaten up!.[/color]

health-examinable-default-critical-100 = [color=#FF6666]{ CAPITALIZE(POSS-ADJ($target)) } body is disfigured and extensively wounded.[/color]
health-examinable-default-critical-90 = [color=#FF5555]{ CAPITALIZE(POSS-ADJ($target)) } body is disfigured and extensively wounded, with slightly pale skin.[/color]
health-examinable-default-critical-70 = [color=#FF4444]{ CAPITALIZE(POSS-ADJ($target)) } body is disfigured and extensively wounded, with pale skin. [/color]
health-examinable-default-critical-40 = [color=#FF2020]{ CAPITALIZE(POSS-ADJ($target)) } body is disfigured and extensively wounded, with very pale skin. [/color]
health-examinable-default-critical-10 = [color=#FF0000]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-BE($target) } blanking and seems about to pass out![/color]
2 changes: 2 additions & 0 deletions Resources/Prototypes/_CE/Entities/Mobs/base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
components:
- type: CEDamageable
- type: CEDamagePopup
- type: CEHealthExaminable
locPrefix: default
- type: CEDestructible
destroyThreshold: 100
destroySound:
Expand Down
Loading