-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
35 changed files
with
3,762 additions
and
22 deletions.
There are no files selected for viewing
13 changes: 13 additions & 0 deletions
13
Content.Client/_White/Medical/Wounds/WoundableVisualsComponent.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
namespace Content.Client._White.Medical.Wounds; | ||
|
||
[RegisterComponent] | ||
public sealed partial class WoundableVisualsComponent : Component | ||
{ | ||
[DataField] public Dictionary<string, string>? DamageOverlayGroups = new(); | ||
|
||
[DataField] public string BleedingOverlay; | ||
|
||
[DataField] public List<Enum>? TargetLayers; | ||
|
||
public List<Enum> TargetLayerMapKeys = []; | ||
} |
325 changes: 325 additions & 0 deletions
325
Content.Client/_White/Medical/Wounds/WoundsVisualsSystem.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,325 @@ | ||
using System.Linq; | ||
using Content.Shared.Body.Part; | ||
using Content.Shared.Body.Systems; | ||
using Content.Shared.FixedPoint; | ||
using Content.Shared.Humanoid; | ||
using Content.Shared._White.Medical.Wounds; | ||
using Content.Shared._White.Medical.Wounds.Components; | ||
using Robust.Client.GameObjects; | ||
using Robust.Shared.Random; | ||
using Robust.Shared.Utility; | ||
|
||
namespace Content.Client._White.Medical.Wounds; | ||
|
||
public sealed class WoundsVisualsSystem : EntitySystem | ||
{ | ||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; | ||
[Dependency] private readonly SharedBodySystem _body = default!; | ||
[Dependency] private readonly IRobustRandom _random = default!; | ||
|
||
private const float AltBleedingSpriteChance = 0.4f; | ||
|
||
private readonly HashSet<string> _damageGroups = ["Brute", "Burn", "Blyading"]; | ||
|
||
private readonly Dictionary<WoundableSeverity, FixedPoint2> _visualizerThresholds = new() | ||
{ | ||
{ WoundableSeverity.Moderate, 25 }, | ||
{ WoundableSeverity.Severe, 50 }, | ||
{ WoundableSeverity.Critical, 80 } | ||
}; | ||
|
||
private readonly Dictionary<BleedingSeverity, FixedPoint2> _bleedingThresholds = new() | ||
{ | ||
{ BleedingSeverity.Minor, 0.05 }, | ||
{ BleedingSeverity.Severe, 0.30} | ||
}; | ||
|
||
private readonly Dictionary<WoundSeverity, FixedPoint2> _severityPoints = new() | ||
{ | ||
{ WoundSeverity.Healed, 0}, | ||
{ WoundSeverity.Minor, 0.02 }, | ||
{ WoundSeverity.Moderate, 0.06 }, | ||
{ WoundSeverity.Severe, 0.08 }, | ||
{ WoundSeverity.Critical, 0.10 }, | ||
{ WoundSeverity.Loss, 0.20} | ||
}; | ||
|
||
public override void Initialize() | ||
{ | ||
base.Initialize(); | ||
|
||
SubscribeLocalEvent<WoundableVisualsComponent, ComponentInit>(InitializeEntity); | ||
|
||
SubscribeLocalEvent<BodyPartComponent, AppearanceChangeEvent>((uid, comp, _) => OnAppearanceChange(uid, comp)); | ||
|
||
SubscribeNetworkEvent<OnWoundableLossDeleteMessage>((msg, _) => OnBodyPartLossDelete(msg)); | ||
} | ||
|
||
private void InitializeEntity(EntityUid uid, WoundableVisualsComponent component, ComponentInit args) | ||
{ | ||
if (!TryComp(uid, out SpriteComponent? spriteComponent) | ||
|| component.TargetLayers is not { Count: > 0 }) | ||
return; | ||
|
||
foreach (var layer in component.TargetLayers.Where(layer => spriteComponent.LayerMapTryGet(layer, out _))) | ||
{ | ||
component.TargetLayerMapKeys.Add(layer); | ||
} | ||
|
||
foreach (var layer in component.TargetLayerMapKeys.Where(_ => component.DamageOverlayGroups != null)) | ||
{ | ||
var layerCount = spriteComponent.AllLayers.Count(); | ||
var index = spriteComponent.LayerMapGet(layer); | ||
|
||
if (index + 1 != layerCount) | ||
index += 1; | ||
|
||
foreach (var (group, sprite) in component.DamageOverlayGroups!) | ||
{ | ||
AddDamageLayerToSprite(spriteComponent, | ||
sprite, | ||
$"{layer}_{group}_{_visualizerThresholds[WoundableSeverity.Moderate]}", | ||
$"{layer}{group}", | ||
index); | ||
} | ||
} | ||
|
||
foreach (var layer in component.TargetLayerMapKeys.Where(_ => component.DamageOverlayGroups != null)) | ||
{ | ||
var layerCount = spriteComponent.AllLayers.Count(); | ||
var index = spriteComponent.LayerMapGet(layer); | ||
|
||
if (index + 1 != layerCount) | ||
index += 1; | ||
|
||
if (Equals(layer, HumanoidVisualLayers.LHand) | ||
|| Equals(layer, HumanoidVisualLayers.RHand) | ||
|| Equals(layer, HumanoidVisualLayers.LFoot) | ||
|| Equals(layer, HumanoidVisualLayers.RFoot)) | ||
continue; | ||
|
||
AddDamageLayerToSprite(spriteComponent, | ||
component.BleedingOverlay, | ||
$"{layer}_Minor", | ||
$"{layer}Blyading", | ||
index); | ||
} | ||
} | ||
|
||
private void OnAppearanceChange(EntityUid uid, BodyPartComponent bodyPart) | ||
{ | ||
if (bodyPart.Body == null | ||
|| !TryComp<SpriteComponent>(bodyPart.Body.Value, out var spriteComponent)) | ||
return; | ||
|
||
UpdateWoundableVisuals(uid, bodyPart, spriteComponent); | ||
} | ||
|
||
private void OnBodyPartLossDelete(OnWoundableLossDeleteMessage args) | ||
{ | ||
if (!TryComp<SpriteComponent>(GetEntity(args.Body), out var spriteComponent)) | ||
return; | ||
|
||
if (UpdateLostWoundableVisuals(args.Layer, spriteComponent, force: true)) | ||
RaiseNetworkEvent(new OnWoundableLossDeleteMessage(args.Woundable, args.Body, args.Layer)); | ||
} | ||
|
||
private void AddDamageLayerToSprite(SpriteComponent spriteComponent, string sprite, string state, string mapKey, int? index = null) | ||
{ | ||
var newLayer = spriteComponent.AddLayer( | ||
new SpriteSpecifier.Rsi( | ||
new ResPath(sprite), | ||
state | ||
), | ||
index); | ||
spriteComponent.LayerMapSet(mapKey, newLayer); | ||
spriteComponent.LayerSetVisible(newLayer, false); | ||
} | ||
|
||
private void UpdateWoundableVisuals(EntityUid uid, BodyPartComponent bodyPart, SpriteComponent sprite) | ||
{ | ||
if (!_appearance.TryGetData<WoundsVisualizerGroupData>(uid, WoundableVisualizerKeys.Wounds, out var data) | ||
|| !_appearance.TryGetData<WoundableSeverity>(uid, WoundableVisualizerKeys.Severity, out var severity)) | ||
return; | ||
|
||
var layer = bodyPart.ToHumanoidLayers(); | ||
if (layer == null | ||
|| UpdateLostWoundableVisuals(layer.Value, sprite, severity)) | ||
return; | ||
|
||
var damageGroups = new Dictionary<string, FixedPoint2>(); | ||
foreach (var wound in data.WoundsList) | ||
{ | ||
var woundDmgGroup = Comp<WoundComponent>(GetEntity(wound)).DamageGroup; | ||
if (damageGroups.TryGetValue(woundDmgGroup, out var value)) | ||
{ | ||
damageGroups[woundDmgGroup] = value + Comp<WoundComponent>(GetEntity(wound)).WoundSeverityPoint; | ||
} | ||
else | ||
{ | ||
damageGroups.Add(woundDmgGroup, Comp<WoundComponent>(GetEntity(wound)).WoundSeverityPoint); | ||
} | ||
} | ||
|
||
foreach (var damageGroup in damageGroups) | ||
{ | ||
sprite.LayerMapTryGet($"{layer}{damageGroup.Key}", out var damageLayer); | ||
|
||
UpdateDamageLayerState(sprite, damageLayer, $"{layer}_{damageGroup.Key}", GetThreshold(damageGroup.Value)); | ||
} | ||
|
||
UpdateBleeding(uid, layer.Value, bodyPart, sprite); | ||
} | ||
|
||
private bool UpdateLostWoundableVisuals(HumanoidVisualLayers key, SpriteComponent sprite, WoundableSeverity? severity = null, bool force = false) | ||
{ | ||
sprite.LayerMapTryGet(key, out var layer); | ||
if (severity != WoundableSeverity.Loss && !force) | ||
{ | ||
sprite.LayerSetVisible(layer, true); | ||
|
||
return false; | ||
} | ||
|
||
foreach (var damageGroup in _damageGroups) | ||
{ | ||
sprite.LayerMapTryGet($"{key}{damageGroup}", out var damageLayer); | ||
|
||
UpdateDamageLayerState(sprite, damageLayer, $"{key}_{damageGroup}", -100); | ||
} | ||
|
||
if (key == HumanoidVisualLayers.Head) | ||
{ | ||
sprite.LayerMapTryGet(HumanoidVisualLayers.Eyes, out var eyesLayer); | ||
sprite.LayerSetVisible(eyesLayer, false); | ||
|
||
sprite.LayerMapTryGet(HumanoidVisualLayers.Hair, out var hairLayer); | ||
sprite.LayerSetVisible(hairLayer, false); | ||
} | ||
|
||
sprite.LayerSetVisible(layer, false); | ||
return true; | ||
} | ||
|
||
private void UpdateBleeding(EntityUid uid, HumanoidVisualLayers layer, BodyPartComponent bodyPart, SpriteComponent sprite) | ||
{ | ||
if (bodyPart.PartType is BodyPartType.Foot or BodyPartType.Hand) | ||
{ | ||
if (!_body.TryGetParentBodyPart(uid, out var parentUid, out _) | ||
|| !_appearance.TryGetData<WoundsVisualizerGroupData>(parentUid.Value, WoundableVisualizerKeys.Wounds, out var parentData) | ||
|| !_appearance.TryGetData<WoundsVisualizerGroupData>(uid, WoundableVisualizerKeys.Wounds, out var data)) | ||
return; | ||
|
||
var woundsToProcess = parentData.WoundsList.ToList(); | ||
woundsToProcess.AddRange(data.WoundsList); | ||
|
||
var totalDamage = | ||
woundsToProcess.Select(wound => new { wound, woundComp = Comp<WoundComponent>(GetEntity(wound)) }) | ||
.Where(t => t.woundComp.CanBleed) | ||
.Select(t => t.wound) | ||
.Aggregate((FixedPoint2) 0, | ||
(current, wound) => current + Comp<WoundComponent>(GetEntity(wound)).WoundSeverityPoint | ||
* _severityPoints[Comp<WoundComponent>(GetEntity(wound)).WoundSeverity]); | ||
|
||
var symmetry = bodyPart.Symmetry == BodyPartSymmetry.Left ? "L" : "R"; | ||
var partType = bodyPart.PartType == BodyPartType.Foot ? "Leg" : "Arm"; | ||
|
||
var part = symmetry + partType; | ||
|
||
sprite.LayerMapTryGet($"{part}Blyading", out var parentBlyadingLayer); | ||
|
||
UpdateBleedingLayerState( | ||
sprite, | ||
parentBlyadingLayer, | ||
part, | ||
GetBleedingThreshold(totalDamage)); | ||
} | ||
else | ||
{ | ||
if (!_appearance.TryGetData<WoundsVisualizerGroupData>(uid, WoundableVisualizerKeys.Wounds, out var data)) | ||
return; | ||
|
||
var totalDamage = | ||
data.WoundsList.Aggregate((FixedPoint2) 0, (current, wound) => current + Comp<WoundComponent>(GetEntity(wound)).WoundSeverityPoint); | ||
|
||
sprite.LayerMapTryGet($"{layer}Blyading", out var blyadingLayer); | ||
|
||
UpdateBleedingLayerState(sprite, | ||
blyadingLayer, | ||
layer.ToString(), | ||
GetBleedingThreshold(totalDamage)); | ||
} | ||
} | ||
|
||
private FixedPoint2 GetThreshold(FixedPoint2 threshold) | ||
{ | ||
var nearestSeverity = (FixedPoint2) 0; | ||
|
||
foreach (var (_, value) in _visualizerThresholds.OrderByDescending(kv => kv.Value)) | ||
{ | ||
if (threshold < value) | ||
continue; | ||
|
||
nearestSeverity = value; | ||
break; | ||
} | ||
|
||
return nearestSeverity; | ||
} | ||
|
||
private BleedingSeverity GetBleedingThreshold(FixedPoint2 threshold) | ||
{ | ||
var nearestSeverity = BleedingSeverity.Minor; | ||
|
||
foreach (var (key, value) in _bleedingThresholds.OrderByDescending(kv => kv.Value)) | ||
{ | ||
if (threshold < value) | ||
continue; | ||
|
||
nearestSeverity = key; | ||
break; | ||
} | ||
|
||
return nearestSeverity; | ||
} | ||
|
||
private void UpdateBleedingLayerState(SpriteComponent spriteComponent, int spriteLayer, string statePrefix, BleedingSeverity threshold) | ||
{ | ||
if (threshold <= 0) | ||
{ | ||
spriteComponent.LayerSetVisible(spriteLayer, false); | ||
} | ||
else | ||
{ | ||
if (!spriteComponent[spriteLayer].Visible) | ||
spriteComponent.LayerSetVisible(spriteLayer, true); | ||
|
||
if (_random.Prob(AltBleedingSpriteChance)) | ||
{ | ||
var rsi = spriteComponent.LayerGetActualRSI(spriteLayer); | ||
|
||
if (rsi != null && rsi.TryGetState($"{statePrefix}_{threshold}_alt", out _)) | ||
spriteComponent.LayerSetState(spriteLayer, $"{statePrefix}_{threshold}_alt"); | ||
} | ||
else | ||
{ | ||
spriteComponent.LayerSetState(spriteLayer, $"{statePrefix}_{threshold}"); | ||
} | ||
} | ||
} | ||
|
||
private void UpdateDamageLayerState(SpriteComponent spriteComponent, int spriteLayer, string statePrefix, FixedPoint2 threshold) | ||
{ | ||
if (threshold <= 0) | ||
{ | ||
spriteComponent.LayerSetVisible(spriteLayer, false); | ||
} | ||
else | ||
{ | ||
if (!spriteComponent[spriteLayer].Visible) | ||
spriteComponent.LayerSetVisible(spriteLayer, true); | ||
|
||
spriteComponent.LayerSetState(spriteLayer, $"{statePrefix}_{threshold}"); | ||
} | ||
} | ||
} |
Oops, something went wrong.