diff --git a/Content.Client/Damage/DamageVisualizer.cs b/Content.Client/Damage/DamageVisualizer.cs new file mode 100644 index 0000000000..d89911d313 --- /dev/null +++ b/Content.Client/Damage/DamageVisualizer.cs @@ -0,0 +1,867 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; +using Robust.Client.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Maths; +using Robust.Shared.Prototypes; +using Robust.Shared.Reflection; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Utility; + +// apologies in advance for all the != null checks, +// my IDE wouldn't stop complaining about these + +namespace Content.Client.Damage +{ + /// + /// A simple visualizer for any entity with a DamageableComponent + /// to display the status of how damaged it is. + /// + /// Can either be an overlay for an entity, or target multiple + /// layers on the same entity. + /// + /// This can be disabled dynamically by passing into SetData, + /// key DamageVisualizerKeys.Disabled, value bool + /// (DamageVisualizerKeys lives in Content.Shared.Damage) + /// + /// Damage layers, if targetting layers, can also be dynamically + /// disabled if needed by passing into SetData, the name/enum + /// of the sprite layer, and then passing in a bool value + /// (true to enable, false to disable). + /// + public class DamageVisualizer : AppearanceVisualizer + { + [Dependency] IPrototypeManager _prototypeManager = default!; + [Dependency] IEntityManager _entityManager = default!; + + private const string _name = "DamageVisualizer"; + /// + /// Damage thresholds between damage state changes. + /// + /// If there are any negative thresholds, or there is + /// less than one threshold, the visualizer is marked + /// as invalid. + /// + /// + /// A 'zeroth' threshold is automatically added, + /// and this list is automatically sorted for + /// efficiency beforehand. As such, the zeroth + /// threshold is not required - and negative + /// thresholds are automatically caught as + /// invalid. The zeroth threshold automatically + /// sets all layers to invisible, so a sprite + /// isn't required for it. + /// + [DataField("thresholds", required: true)] + private List _thresholds = new(); + + /// + /// Layers to target, by layerMapKey. + /// If a target layer map key is invalid + /// (in essence, undefined), then the target + /// layer is removed from the list for efficiency. + /// + /// If no layers are valid, then the visualizer + /// is marked as invalid. + /// + /// If this is not defined, however, the visualizer + /// instead adds an overlay to the sprite. + /// + /// + /// Layers can be disabled here by passing + /// the layer's name as a key to SetData, + /// and passing in a bool set to either 'false' + /// to disable it, or 'true' to enable it. + /// Setting the layer as disabled will make it + /// completely invisible. + /// + [DataField("targetLayers")] + private List? _targetLayers; + + /// + /// The actual sprites for every damage group + /// that the entity should display visually. + /// + /// This is keyed by a damage group identifier + /// (for example, Brute), and has a value + /// of a DamageVisualizerSprite (see below) + /// + [DataField("damageOverlayGroups")] + private readonly Dictionary? _damageOverlayGroups; + + /// + /// Sets if you want sprites to overlay the + /// entity when damaged, or if you would + /// rather have each target layer's state + /// replaced by a different state + /// within its RSI. + /// + /// This cannot be set to false if: + /// - There are no target layers + /// - There is no damage group + /// + [DataField("overlay")] + private readonly bool _overlay = true; + + /// + /// A single damage group to target. + /// This should only be defined if + /// overlay is set to false. + /// If this is defined with damageSprites, + /// this will be ignored. + /// + /// + /// This is here because otherwise, + /// you would need several permutations + /// of group sprites depending on + /// what kind of damage combination + /// you would want, on which threshold. + /// + [DataField("damageGroup")] + private readonly string? _damageGroup; + + /// + /// Set this if you want incoming damage to be + /// divided. + /// + /// + /// This is more useful if you have similar + /// damage sprites inbetween entities, + /// but with different damage thresholds + /// and you want to avoid duplicating + /// these sprites. + /// + [DataField("damageDivisor")] + private float _divisor = 1; + + /// + /// Set this to track all damage, instead of specific groups. + /// + /// + /// This will only work if you have damageOverlay + /// defined - otherwise, it will not work. + /// + [DataField("trackAllDamage")] + private readonly bool _trackAllDamage = false; + /// + /// This is the overlay sprite used, if _trackAllDamage is + /// enabled. Supports no complex per-group layering, + /// just an actually simple damage overlay. See + /// DamageVisualizerSprite for more information. + /// + [DataField("damageOverlay")] + private readonly DamageVisualizerSprite? _damageOverlay; + + // deals with the edge case of human damage visuals not + // being in color without making a Dict + /// The RSI path for the damage visualizer + /// group overlay. + /// + /// + /// States in here will require one of four + /// forms: + /// + /// If tracking damage groups: + /// - {base_state}_{group}_{threshold} if targetting + /// a static layer on a sprite (either as an + /// overlay or as a state change) + /// - DamageOverlay_{group}_{threshold} if not + /// targetting a layer on a sprite. + /// + /// If not tracking damage groups: + /// - {base_state}_{threshold} if it is targetting + /// a layer + /// - DamageOverlay_{threshold} if not targetting + /// a layer. + /// + [DataField("sprite", required: true)] + public readonly string Sprite = default!; + + /// + /// The color of this sprite overlay. + /// Supports only hexadecimal format. + /// + [DataField("color")] + public readonly string? Color; + } + + public override void InitializeEntity(IEntity entity) + { + base.InitializeEntity(entity); + + IoCManager.InjectDependencies(this); + + var damageData = _entityManager.EnsureComponent(entity); + VerifyVisualizerSetup(entity, damageData); + if (damageData.Valid) + InitializeVisualizer(entity, damageData); + } + + private void VerifyVisualizerSetup(IEntity entity, DamageVisualizerDataComponent damageData) + { + if (_thresholds.Count < 1) + { + Logger.ErrorS(_name, $"Thresholds were invalid for entity {entity}. Thresholds: {_thresholds}"); + damageData.Valid = false; + return; + } + + if (_divisor == 0) + { + Logger.ErrorS(_name, $"Divisor for {entity} is set to zero."); + damageData.Valid = false; + return; + } + + if (_overlay) + { + if (_damageOverlayGroups == null && _damageOverlay == null) + { + Logger.ErrorS(_name, $"Enabled overlay without defined damage overlay sprites on {entity}."); + damageData.Valid = false; + return; + } + + if (_trackAllDamage && _damageOverlay == null) + { + Logger.ErrorS(_name, $"Enabled all damage tracking without a damage overlay sprite on {entity}."); + damageData.Valid = false; + return; + } + + if (!_trackAllDamage && _damageOverlay != null) + { + Logger.WarningS(_name, $"Disabled all damage tracking with a damage overlay sprite on {entity}."); + damageData.Valid = false; + return; + } + + + if (_trackAllDamage && _damageOverlayGroups != null) + { + Logger.WarningS(_name, $"Enabled all damage tracking with damage overlay groups on {entity}."); + damageData.Valid = false; + return; + } + } + else if (!_overlay) + { + if (_targetLayers == null) + { + Logger.ErrorS(_name, $"Disabled overlay without target layers on {entity}."); + damageData.Valid = false; + return; + } + + if (_damageOverlayGroups != null || _damageOverlay != null) + { + Logger.ErrorS(_name, $"Disabled overlay with defined damage overlay sprites on {entity}."); + damageData.Valid = false; + return; + } + + if (_damageGroup == null) + { + Logger.ErrorS(_name, $"Disabled overlay without defined damage group on {entity}."); + damageData.Valid = false; + return; + } + } + + if (_damageOverlayGroups != null && _damageGroup != null) + { + Logger.WarningS(_name, $"Damage overlay sprites and damage group are both defined on {entity}."); + } + + if (_damageOverlay != null && _damageGroup != null) + { + Logger.WarningS(_name, $"Damage overlay sprites and damage group are both defined on {entity}."); + } + } + + private void InitializeVisualizer(IEntity entity, DamageVisualizerDataComponent damageData) + { + if (!entity.TryGetComponent(out SpriteComponent? spriteComponent) + || !entity.TryGetComponent(out var damageComponent) + || !entity.TryGetComponent(out var appearanceComponent)) + return; + + _thresholds.Add(0); + _thresholds.Sort(); + + if (_thresholds[0] != 0) + { + Logger.ErrorS(_name, $"Thresholds were invalid for entity {entity}. Thresholds: {_thresholds}"); + damageData.Valid = false; + return; + } + + // If the damage container on our entity's DamageableComponent + // is not null, we can try to check through its groups. + if (damageComponent.DamageContainerID != null + && _prototypeManager.TryIndex(damageComponent.DamageContainerID, out var damageContainer)) + { + // Are we using damage overlay sprites by group? + // Check if the container matches the supported groups, + // and start cacheing the last threshold. + if (_damageOverlayGroups != null) + { + foreach (string damageType in _damageOverlayGroups.Keys) + { + if (!damageContainer.SupportedGroups.Contains(damageType)) + { + Logger.ErrorS(_name, $"Damage key {damageType} was invalid for entity {entity}."); + damageData.Valid = false; + return; + } + + damageData.LastThresholdPerGroup.Add(damageType, 0); + } + } + // Are we tracking a single damage group without overlay instead? + // See if that group is in our entity's damage container. + else if (!_overlay && _damageGroup != null) + { + if (!damageContainer.SupportedGroups.Contains(_damageGroup)) + { + Logger.ErrorS(_name, $"Damage keys were invalid for entity {entity}."); + damageData.Valid = false; + return; + } + + damageData.LastThresholdPerGroup.Add(_damageGroup, 0); + } + } + // Ditto above, but instead we go through every group. + else // oh boy! time to enumerate through every single group! + { + var damagePrototypeIdList = _prototypeManager.EnumeratePrototypes() + .Select((p, _) => p.ID) + .ToList(); + if (_damageOverlayGroups != null) + foreach (string damageType in _damageOverlayGroups.Keys) + { + if (!damagePrototypeIdList.Contains(damageType)) + { + Logger.ErrorS(_name, $"Damage keys were invalid for entity {entity}."); + damageData.Valid = false; + return; + } + damageData.LastThresholdPerGroup.Add(damageType, 0); + } + else if (_damageGroup != null) + { + if (!damagePrototypeIdList.Contains(_damageGroup)) + { + Logger.ErrorS(_name, $"Damage keys were invalid for entity {entity}."); + damageData.Valid = false; + return; + } + + damageData.LastThresholdPerGroup.Add(_damageGroup, 0); + } + } + + // If we're targetting any layers, and the amount of + // layers is greater than zero, we start reserving + // all the layers needed to track damage groups + // on the entity. + if (_targetLayers != null && _targetLayers.Count > 0) + { + // This should ensure that the layers we're targetting + // are valid for the visualizer's use. + // + // If the layer doesn't have a base state, or + // the layer key just doesn't exist, we skip it. + foreach (var keyString in _targetLayers) + { + object key; + if (IoCManager.Resolve().TryParseEnumReference(keyString, out var @enum)) + { + key = @enum; + } + else + { + key = keyString; + } + + if (!spriteComponent.LayerMapTryGet(key, out int index) + || spriteComponent.LayerGetState(index).ToString() == null) + { + Logger.WarningS(_name, $"Layer at key {key} was invalid for entity {entity}."); + continue; + } + + damageData.TargetLayerMapKeys.Add(key); + }; + + // Similar to damage overlay groups, if none of the targetted + // sprite layers could be used, we display an error and + // invalidate the visualizer without crashing. + if (damageData.TargetLayerMapKeys.Count == 0) + { + Logger.ErrorS(_name, $"Target layers were invalid for entity {entity}."); + damageData.Valid = false; + return; + } + + // Otherwise, we start reserving layers. Since the filtering + // loop above ensures that all of these layers are not null, + // and have valid state IDs, there should be no issues. + foreach (object layer in damageData.TargetLayerMapKeys) + { + int layerCount = spriteComponent.AllLayers.Count(); + int index = spriteComponent.LayerMapGet(layer); + string layerState = spriteComponent.LayerGetState(index)!.ToString()!; + + if (index + 1 != layerCount) + { + index += 1; + } + + damageData.LayerMapKeyStates.Add(layer, layerState); + + // If we're an overlay, and we're targetting groups, + // we reserve layers per damage group. + if (_overlay && _damageOverlayGroups != null) + { + foreach (var (group, sprite) in _damageOverlayGroups) + { + AddDamageLayerToSprite(spriteComponent, + sprite, + $"{layerState}_{group}_{_thresholds[1]}", + $"{layer}{group}", + index); + } + damageData.DisabledLayers.Add(layer, false); + } + // If we're not targetting groups, and we're still + // using an overlay, we instead just add a general + // overlay that reflects on how much damage + // was taken. + else if (_damageOverlay != null) + { + AddDamageLayerToSprite(spriteComponent, + _damageOverlay, + $"{layerState}_{_thresholds[1]}", + $"{layer}trackDamage", + index); + damageData.DisabledLayers.Add(layer, false); + } + } + } + // If we're not targetting layers, however, + // we should ensure that we instead + // reserve it as an overlay. + else + { + if (_damageOverlayGroups != null) + { + foreach (var (group, sprite) in _damageOverlayGroups) + { + AddDamageLayerToSprite(spriteComponent, + sprite, + $"DamageOverlay_{group}_{_thresholds[1]}", + $"DamageOverlay{group}"); + damageData.TopMostLayerKey = $"DamageOverlay{group}"; + } + } + else if (_damageOverlay != null) + { + AddDamageLayerToSprite(spriteComponent, + _damageOverlay, + $"DamageOverlay_{_thresholds[1]}", + "DamageOverlay"); + damageData.TopMostLayerKey = $"DamageOverlay"; + } + } + } + + /// + /// Adds a damage tracking layer to a given sprite component. + /// + private void AddDamageLayerToSprite(SpriteComponent spriteComponent, DamageVisualizerSprite sprite, string state, string mapKey, int? index = null) + { + int newLayer = spriteComponent.AddLayer( + new SpriteSpecifier.Rsi( + new ResourcePath(sprite.Sprite), state + ), index); + spriteComponent.LayerMapSet(mapKey, newLayer); + if (sprite.Color != null) + spriteComponent.LayerSetColor(newLayer, Color.FromHex(sprite.Color)); + spriteComponent.LayerSetVisible(newLayer, false); + } + + public override void OnChangeData(AppearanceComponent component) + { + if (!component.Owner.TryGetComponent(out var damageData)) + return; + + if (!damageData.Valid) + return; + + // If this was passed into the component, we update + // the data to ensure that the current disabled + // bool matches. + if (component.TryGetData(DamageVisualizerKeys.Disabled, out var disabledStatus)) + if (disabledStatus != damageData.Disabled) + damageData.Disabled = disabledStatus; + + if (damageData.Disabled) + return; + + HandleDamage(component, damageData); + } + + private void HandleDamage(AppearanceComponent component, DamageVisualizerDataComponent damageData) + { + if (!component.Owner.TryGetComponent(out var spriteComponent) + || !component.Owner.TryGetComponent(out var damageComponent)) + return; + + if (_targetLayers != null && _damageOverlayGroups != null) + UpdateDisabledLayers(spriteComponent, component, damageData); + + if (_overlay && _damageOverlayGroups != null && _targetLayers == null) + CheckOverlayOrdering(spriteComponent, damageData); + + if (component.TryGetData(DamageVisualizerKeys.ForceUpdate, out bool update) + && update) + { + ForceUpdateLayers(damageComponent, spriteComponent, damageData); + return; + } + + if (_trackAllDamage) + { + UpdateDamageVisuals(damageComponent, spriteComponent, damageData); + } + else if (component.TryGetData>(DamageVisualizerKeys.DamageUpdateGroups, out List? delta)) + { + UpdateDamageVisuals(delta, damageComponent, spriteComponent, damageData); + } + } + + /// + /// Checks if any layers were disabled in the last + /// data update. Disabled layers mean that the + /// layer will no longer be visible, or obtain + /// any damage updates. + /// + private void UpdateDisabledLayers(SpriteComponent spriteComponent, AppearanceComponent component, DamageVisualizerDataComponent damageData) + { + foreach (object layer in damageData.TargetLayerMapKeys) + { + bool? layerStatus = null; + switch (layer) + { + case Enum layerEnum: + if (component.TryGetData(layerEnum, out var layerStateEnum)) + layerStatus = layerStateEnum; + break; + case string layerString: + if (component.TryGetData(layerString, out var layerStateString)) + layerStatus = layerStateString; + break; + } + + if (layerStatus == null) + continue; + + if (damageData.DisabledLayers[layer] != (bool) layerStatus) + { + damageData.DisabledLayers[layer] = (bool) layerStatus; + if (!_trackAllDamage && _damageOverlayGroups != null) + foreach (string damageGroup in _damageOverlayGroups!.Keys) + spriteComponent.LayerSetVisible($"{layer}{damageGroup}", damageData.DisabledLayers[layer]); + else if (_trackAllDamage) + spriteComponent.LayerSetVisible($"{layer}trackDamage", damageData.DisabledLayers[layer]); + } + } + } + + /// + /// Checks the overlay ordering on the current + /// sprite component, compared to the + /// data for the visualizer. If the top + /// most layer doesn't match, the sprite + /// layers are recreated and placed on top. + /// + private void CheckOverlayOrdering(SpriteComponent spriteComponent, DamageVisualizerDataComponent damageData) + { + if (spriteComponent[damageData.TopMostLayerKey] != spriteComponent[spriteComponent.AllLayers.Count() - 1]) + { + if (!_trackAllDamage && _damageOverlayGroups != null) + { + foreach (var (damageGroup, sprite) in _damageOverlayGroups) + { + int threshold = damageData.LastThresholdPerGroup[damageGroup]; + ReorderOverlaySprite(spriteComponent, + damageData, + sprite, + $"DamageOverlay{damageGroup}", + $"DamageOverlay_{damageGroup}", + threshold); + } + } + else if (_trackAllDamage && _damageOverlay != null) + { + ReorderOverlaySprite(spriteComponent, + damageData, + _damageOverlay, + $"DamageOverlay", + $"DamageOverlay", + damageData.LastDamageThreshold); + } + } + } + + private void ReorderOverlaySprite(SpriteComponent spriteComponent, DamageVisualizerDataComponent damageData, DamageVisualizerSprite sprite, string key, string statePrefix, int threshold) + { + spriteComponent.LayerMapTryGet(key, out int spriteLayer); + bool visibility = spriteComponent[spriteLayer].Visible; + spriteComponent.RemoveLayer(spriteLayer); + if (threshold == 0) // these should automatically be invisible + threshold = _thresholds[1]; + spriteLayer = spriteComponent.AddLayer( + new SpriteSpecifier.Rsi( + new ResourcePath(sprite.Sprite), + $"{statePrefix}_{threshold}" + ), + spriteLayer); + spriteComponent.LayerMapSet(key, spriteLayer); + spriteComponent.LayerSetVisible(spriteLayer, visibility); + // this is somewhat iffy since it constantly reallocates + damageData.TopMostLayerKey = key; + } + + /// + /// Updates damage visuals without tracking + /// any damage groups. + /// + private void UpdateDamageVisuals(DamageableComponent damageComponent, SpriteComponent spriteComponent, DamageVisualizerDataComponent damageData) + { + if (!CheckThresholdBoundary(damageComponent.TotalDamage, damageData.LastDamageThreshold, out int threshold)) + return; + + damageData.LastDamageThreshold = threshold; + + if (_targetLayers != null) + { + foreach (var layerMapKey in damageData.TargetLayerMapKeys) + UpdateTargetLayer(spriteComponent, damageData, layerMapKey, threshold); + } + else + { + UpdateOverlay(spriteComponent, threshold); + } + } + + /// + /// Updates damage visuals by damage group, + /// according to the list of damage groups + /// passed into it. + /// + private void UpdateDamageVisuals(List delta, DamageableComponent damageComponent, SpriteComponent spriteComponent, DamageVisualizerDataComponent damageData) + { + foreach (var damageGroup in delta) + { + if (!_overlay && damageGroup != _damageGroup) + continue; + + if (!_prototypeManager.TryIndex(damageGroup, out var damageGroupPrototype) + || !damageComponent.Damage.TryGetDamageInGroup(damageGroupPrototype, out int damageTotal)) + continue; + + if (!damageData.LastThresholdPerGroup.TryGetValue(damageGroup, out int lastThreshold) + || !CheckThresholdBoundary(damageTotal, lastThreshold, out int threshold)) + continue; + + damageData.LastThresholdPerGroup[damageGroup] = threshold; + + if (_targetLayers != null) + { + foreach (var layerMapKey in damageData.TargetLayerMapKeys) + UpdateTargetLayer(spriteComponent, damageData, layerMapKey, damageGroup, threshold); + } + else + { + UpdateOverlay(spriteComponent, damageGroup, threshold); + } + } + + } + + /// + /// Checks if a threshold boundary was passed. + /// + private bool CheckThresholdBoundary(int damageTotal, int lastThreshold, out int threshold) + { + threshold = 0; + damageTotal = (int) Math.Floor(damageTotal / _divisor); + int thresholdIndex = _thresholds.BinarySearch(damageTotal); + + if (thresholdIndex < 0) + { + thresholdIndex = ~thresholdIndex; + threshold = _thresholds[thresholdIndex - 1]; + } + else + { + threshold = _thresholds[thresholdIndex]; + } + + if (threshold == lastThreshold) + return false; + + return true; + } + + /// + /// This is the entry point for + /// forcing an update on all damage layers. + /// Does different things depending on + /// the configuration of the visualizer. + /// + private void ForceUpdateLayers(DamageableComponent damageComponent, SpriteComponent spriteComponent, DamageVisualizerDataComponent damageData) + { + if (_damageOverlayGroups != null) + { + UpdateDamageVisuals(_damageOverlayGroups.Keys.ToList(), damageComponent, spriteComponent, damageData); + } + else if (_damageGroup != null) + { + UpdateDamageVisuals(new List(){ _damageGroup }, damageComponent, spriteComponent, damageData); + } + else if (_damageOverlay != null) + { + UpdateDamageVisuals(damageComponent, spriteComponent, damageData); + } + } + + /// + /// Updates a target layer. Without a damage group passed in, + /// it assumes you're updating a layer that is tracking all + /// damage. + /// + private void UpdateTargetLayer(SpriteComponent spriteComponent, DamageVisualizerDataComponent damageData, object layerMapKey, int threshold) + { + if (_overlay && _damageOverlayGroups != null) + { + if (!damageData.DisabledLayers[layerMapKey]) + { + string layerState = damageData.LayerMapKeyStates[layerMapKey]; + spriteComponent.LayerMapTryGet($"{layerMapKey}trackDamage", out int spriteLayer); + + UpdateDamageLayerState(spriteComponent, + spriteLayer, + $"{layerState}", + threshold); + } + } + else if (!_overlay) + { + string layerState = damageData.LayerMapKeyStates[layerMapKey]; + spriteComponent.LayerMapTryGet(layerMapKey, out int spriteLayer); + + UpdateDamageLayerState(spriteComponent, + spriteLayer, + $"{layerState}", + threshold); + } + } + + /// + /// Updates a target layer by damage group. + /// + private void UpdateTargetLayer(SpriteComponent spriteComponent, DamageVisualizerDataComponent damageData, object layerMapKey, string damageGroup, int threshold) + { + if (_overlay && _damageOverlayGroups != null) + { + if (_damageOverlayGroups.ContainsKey(damageGroup) && !damageData.DisabledLayers[layerMapKey]) + { + string layerState = damageData.LayerMapKeyStates[layerMapKey]; + spriteComponent.LayerMapTryGet($"{layerMapKey}{damageGroup}", out int spriteLayer); + + UpdateDamageLayerState(spriteComponent, + spriteLayer, + $"{layerState}_{damageGroup}", + threshold); + } + } + else if (!_overlay) + { + string layerState = damageData.LayerMapKeyStates[layerMapKey]; + spriteComponent.LayerMapTryGet(layerMapKey, out int spriteLayer); + + UpdateDamageLayerState(spriteComponent, + spriteLayer, + $"{layerState}_{damageGroup}", + threshold); + } + } + + /// + /// Updates an overlay that is tracking all damage. + /// + private void UpdateOverlay(SpriteComponent spriteComponent, int threshold) + { + spriteComponent.LayerMapTryGet($"DamageOverlay", out int spriteLayer); + + UpdateDamageLayerState(spriteComponent, + spriteLayer, + $"DamageOverlay", + threshold); + } + + /// + /// Updates an overlay based on damage group. + /// + private void UpdateOverlay(SpriteComponent spriteComponent, string damageGroup, int threshold) + { + if (_damageOverlayGroups != null) + { + if (_damageOverlayGroups.ContainsKey(damageGroup)) + { + spriteComponent.LayerMapTryGet($"DamageOverlay{damageGroup}", out int spriteLayer); + + UpdateDamageLayerState(spriteComponent, + spriteLayer, + $"DamageOverlay_{damageGroup}", + threshold); + } + } + } + + /// + /// Updates a layer on the sprite by what + /// prefix it has (calculated by whatever + /// function calls it), and what threshold + /// was passed into it. + /// + private void UpdateDamageLayerState(SpriteComponent spriteComponent, int spriteLayer, string statePrefix, int threshold) + { + if (threshold == 0) + { + spriteComponent.LayerSetVisible(spriteLayer, false); + } + else + { + if (!spriteComponent[spriteLayer].Visible) + { + spriteComponent.LayerSetVisible(spriteLayer, true); + } + spriteComponent.LayerSetState(spriteLayer, $"{statePrefix}_{threshold}"); + } + } + } +} diff --git a/Content.Client/Damage/DamageVisualizerComponent.cs b/Content.Client/Damage/DamageVisualizerComponent.cs new file mode 100644 index 0000000000..01b648dc4b --- /dev/null +++ b/Content.Client/Damage/DamageVisualizerComponent.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Robust.Shared.GameObjects; + +namespace Content.Client.Damage +{ + // Stores all the data for a DamageVisualizer. + // + // Storing it inside of the AppearanceComponent's data + // dictionary was too messy, but at least we can + // store it in the entity itself as a separate, + // dynamically added component. + [RegisterComponent] + public class DamageVisualizerDataComponent : Component + { + public override string Name => "DamageVisualizerData"; + + public List TargetLayerMapKeys = new(); + public bool Disabled = false; + public bool Valid = true; + public int LastDamageThreshold = 0; + public Dictionary DisabledLayers = new(); + public Dictionary LayerMapKeyStates = new(); + public Dictionary LastThresholdPerGroup = new(); + public string TopMostLayerKey = default!; + } +} diff --git a/Content.Client/Window/WindowVisualizer.cs b/Content.Client/Window/WindowVisualizer.cs deleted file mode 100644 index c3c9a85e30..0000000000 --- a/Content.Client/Window/WindowVisualizer.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using Content.Shared.Rounding; -using Content.Shared.Window; -using JetBrains.Annotations; -using Robust.Client.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.Log; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Utility; - -namespace Content.Client.Window -{ - [UsedImplicitly] - public sealed class WindowVisualizer : AppearanceVisualizer - { - [DataField("crackRsi")] - public ResourcePath CrackRsi { get; } = new ("/Textures/Structures/Windows/cracks.rsi"); - - public override void InitializeEntity(IEntity entity) - { - if (!entity.TryGetComponent(out ISpriteComponent? sprite)) - return; - - sprite.LayerMapReserveBlank(WindowDamageLayers.Layer); - sprite.LayerSetVisible(WindowDamageLayers.Layer, false); - sprite.LayerSetRSI(WindowDamageLayers.Layer, CrackRsi); - } - - public override void OnChangeData(AppearanceComponent component) - { - base.OnChangeData(component); - - if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite)) - return; - - if (component.TryGetData(WindowVisuals.Damage, out float fraction)) - { - var level = Math.Min(ContentHelpers.RoundToLevels(fraction, 1, 5), 3); - - if (level == 0) - { - sprite.LayerSetVisible(WindowDamageLayers.Layer, false); - return; - } - - sprite.LayerSetVisible(WindowDamageLayers.Layer, true); - sprite.LayerSetState(WindowDamageLayers.Layer, $"{level}"); - } - } - - public enum WindowDamageLayers : byte - { - Layer, - } - } -} diff --git a/Content.Server/Window/WindowComponent.cs b/Content.Server/Window/WindowComponent.cs index 11c9791376..52c84a1c7b 100644 --- a/Content.Server/Window/WindowComponent.cs +++ b/Content.Server/Window/WindowComponent.cs @@ -42,23 +42,6 @@ namespace Content.Server.Window [DataField("knockSound")] private SoundSpecifier _knockSound = new SoundPathSpecifier("/Audio/Effects/glass_knock.ogg"); - public void UpdateVisuals(int currentDamage) - { - if (Owner.TryGetComponent(out AppearanceComponent? appearance) && - Owner.TryGetComponent(out DestructibleComponent? destructible)) - { - foreach (var threshold in destructible.Thresholds) - { - if (threshold.Trigger is not DamageTrigger trigger) - { - continue; - } - - appearance.SetData(WindowVisuals.Damage, (float) currentDamage / trigger.Damage); - } - } - } - void IExamine.Examine(FormattedMessage message, bool inDetailsRange) { if (!Owner.TryGetComponent(out DamageableComponent? damageable) || diff --git a/Content.Server/Window/WindowSystem.cs b/Content.Server/Window/WindowSystem.cs deleted file mode 100644 index b711b9ea0a..0000000000 --- a/Content.Server/Window/WindowSystem.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Shared.Damage; -using Robust.Shared.GameObjects; - -namespace Content.Server.Window -{ - public class WindowSystem : EntitySystem - { - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(UpdateVisuals); - } - - public void UpdateVisuals(EntityUid _, WindowComponent component, DamageChangedEvent args) - { - component.UpdateVisuals(args.Damageable.TotalDamage); - } - } -} diff --git a/Content.Shared/Damage/DamageVisualizerKeys.cs b/Content.Shared/Damage/DamageVisualizerKeys.cs new file mode 100644 index 0000000000..dfaa598589 --- /dev/null +++ b/Content.Shared/Damage/DamageVisualizerKeys.cs @@ -0,0 +1,14 @@ +using System; +using Robust.Shared.Serialization; + +namespace Content.Shared.Damage +{ + [Serializable, NetSerializable] + public enum DamageVisualizerKeys + { + Disabled, + DamageSpecifierDelta, + DamageUpdateGroups, + ForceUpdate + } +} diff --git a/Content.Shared/Damage/DamageableSystem.cs b/Content.Shared/Damage/DamageableSystem.cs index 797173879b..8cd4680718 100644 --- a/Content.Shared/Damage/DamageableSystem.cs +++ b/Content.Shared/Damage/DamageableSystem.cs @@ -83,6 +83,9 @@ namespace Content.Shared.Damage component.DamagePerGroup = component.Damage.GetDamagePerGroup(); component.TotalDamage = component.Damage.Total; component.Dirty(); + + if (EntityManager.TryGetComponent(component.Owner.Uid, out var appearance) && damageDelta != null) + appearance.SetData(DamageVisualizerKeys.DamageUpdateGroups, damageDelta.GetDamagePerGroup().Keys.ToList()); RaiseLocalEvent(component.Owner.Uid, new DamageChangedEvent(component, damageDelta), false); } diff --git a/Content.Shared/Window/WindowVisuals.cs b/Content.Shared/Window/WindowVisuals.cs deleted file mode 100644 index 20081129a4..0000000000 --- a/Content.Shared/Window/WindowVisuals.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization; - -namespace Content.Shared.Window -{ - [Serializable, NetSerializable] - public enum WindowVisuals - { - Damage - } -} diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index a6920120ad..0491907b2a 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -245,6 +245,21 @@ - type: CreamPiedVisualizer state: creampie_human - type: HandsVisualizer + - type: DamageVisualizer + thresholds: [20, 40, 100] + targetLayers: + - "enum.HumanoidVisualLayers.Chest" + - "enum.HumanoidVisualLayers.Head" + - "enum.HumanoidVisualLayers.LArm" + - "enum.HumanoidVisualLayers.LLeg" + - "enum.HumanoidVisualLayers.RArm" + - "enum.HumanoidVisualLayers.RLeg" + damageOverlayGroups: + Brute: + sprite: Mobs/Effects/brute_damage.rsi + color: "#FF0000" + Burn: + sprite: Mobs/Effects/burn_damage.rsi - type: CombatMode - type: Climbing - type: Cuffable diff --git a/Resources/Prototypes/Entities/Structures/Windows/plasma.yml b/Resources/Prototypes/Entities/Structures/Windows/plasma.yml index c86e770b03..9a6ad92a42 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/plasma.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/plasma.yml @@ -32,3 +32,11 @@ - type: Construction graph: window node: plasmaWindow + - type: Appearance + visuals: + - type: DamageVisualizer + thresholds: [4, 8, 12] + damageDivisor: 12 + trackAllDamage: true + damageOverlay: + sprite: Structures/Windows/cracks.rsi diff --git a/Resources/Prototypes/Entities/Structures/Windows/reinforced.yml b/Resources/Prototypes/Entities/Structures/Windows/reinforced.yml index 44836e9a9a..c67cc7975c 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/reinforced.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/reinforced.yml @@ -32,3 +32,11 @@ - type: Construction graph: window node: reinforcedWindow + - type: Appearance + visuals: + - type: DamageVisualizer + thresholds: [4, 8, 12] + damageDivisor: 10 + trackAllDamage: true + damageOverlay: + sprite: Structures/Windows/cracks.rsi diff --git a/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml b/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml index 98ea983dd2..d7aae424fa 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml @@ -35,3 +35,11 @@ - type: Construction graph: window node: reinforcedPlasmaWindow + - type: Appearance + visuals: + - type: DamageVisualizer + thresholds: [4, 8, 12] + damageDivisor: 36 + trackAllDamage: true + damageOverlay: + sprite: Structures/Windows/cracks.rsi diff --git a/Resources/Prototypes/Entities/Structures/Windows/window.yml b/Resources/Prototypes/Entities/Structures/Windows/window.yml index a9b265f156..cd8ae21fbd 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/window.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/window.yml @@ -60,4 +60,8 @@ node: window - type: Appearance visuals: - - type: WindowVisualizer + - type: DamageVisualizer + thresholds: [4, 8, 12] + trackAllDamage: true + damageOverlay: + sprite: Structures/Windows/cracks.rsi diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_f_Brute_100.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_f_Brute_100.png new file mode 100644 index 0000000000..f1de5695c5 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_f_Brute_100.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_f_Brute_20.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_f_Brute_20.png new file mode 100644 index 0000000000..2166220992 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_f_Brute_20.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_f_Brute_40.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_f_Brute_40.png new file mode 100644 index 0000000000..33f624d503 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_f_Brute_40.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_m_Brute_100.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_m_Brute_100.png new file mode 100644 index 0000000000..f1de5695c5 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_m_Brute_100.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_m_Brute_20.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_m_Brute_20.png new file mode 100644 index 0000000000..2166220992 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_m_Brute_20.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_m_Brute_40.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_m_Brute_40.png new file mode 100644 index 0000000000..33f624d503 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/head_m_Brute_40.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_arm_Brute_100.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_arm_Brute_100.png new file mode 100644 index 0000000000..488040b0c1 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_arm_Brute_100.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_arm_Brute_20.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_arm_Brute_20.png new file mode 100644 index 0000000000..1bcf2e505a Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_arm_Brute_20.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_arm_Brute_40.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_arm_Brute_40.png new file mode 100644 index 0000000000..8429e7fb18 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_arm_Brute_40.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_leg_Brute_100.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_leg_Brute_100.png new file mode 100644 index 0000000000..6c1a2dcc19 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_leg_Brute_100.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_leg_Brute_20.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_leg_Brute_20.png new file mode 100644 index 0000000000..2670821090 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_leg_Brute_20.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_leg_Brute_40.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_leg_Brute_40.png new file mode 100644 index 0000000000..680ec3009a Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/l_leg_Brute_40.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/meta.json b/Resources/Textures/Mobs/Effects/brute_damage.rsi/meta.json new file mode 100644 index 0000000000..23e2b0a3d3 --- /dev/null +++ b/Resources/Textures/Mobs/Effects/brute_damage.rsi/meta.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/Citadel-Station-13/Citadel-Station-13/blob/971ddef989f7f4f365a714ef3d4df5dea2d53d9a/icons/mob/dam_mob.dmi", + "size": {"x": 32, "y": 32}, + "states": [ + {"name": "head_m_Brute_20", "directions": 4}, + {"name": "head_f_Brute_20", "directions": 4}, + {"name": "l_arm_Brute_20", "directions": 4}, + {"name": "l_leg_Brute_20", "directions": 4}, + {"name": "r_arm_Brute_20", "directions": 4}, + {"name": "r_leg_Brute_20", "directions": 4}, + {"name": "torso_m_Brute_20", "directions": 4}, + {"name": "torso_f_Brute_20", "directions": 4}, + {"name": "head_m_Brute_40", "directions": 4}, + {"name": "head_f_Brute_40", "directions": 4}, + {"name": "l_arm_Brute_40", "directions": 4}, + {"name": "l_leg_Brute_40", "directions": 4}, + {"name": "r_arm_Brute_40", "directions": 4}, + {"name": "r_leg_Brute_40", "directions": 4}, + {"name": "torso_m_Brute_40", "directions": 4}, + {"name": "torso_f_Brute_40", "directions": 4}, + {"name": "head_m_Brute_100", "directions": 4}, + {"name": "head_f_Brute_100", "directions": 4}, + {"name": "l_arm_Brute_100", "directions": 4}, + {"name": "l_leg_Brute_100", "directions": 4}, + {"name": "r_arm_Brute_100", "directions": 4}, + {"name": "r_leg_Brute_100", "directions": 4}, + {"name": "torso_m_Brute_100", "directions": 4}, + {"name": "torso_f_Brute_100", "directions": 4} + ] +} diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_arm_Brute_100.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_arm_Brute_100.png new file mode 100644 index 0000000000..6b99414d0b Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_arm_Brute_100.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_arm_Brute_20.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_arm_Brute_20.png new file mode 100644 index 0000000000..2e4a031f15 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_arm_Brute_20.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_arm_Brute_40.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_arm_Brute_40.png new file mode 100644 index 0000000000..f68f0c4f15 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_arm_Brute_40.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_leg_Brute_100.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_leg_Brute_100.png new file mode 100644 index 0000000000..6899cccab7 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_leg_Brute_100.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_leg_Brute_20.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_leg_Brute_20.png new file mode 100644 index 0000000000..44aae3c0bf Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_leg_Brute_20.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_leg_Brute_40.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_leg_Brute_40.png new file mode 100644 index 0000000000..ef4dbcb08b Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/r_leg_Brute_40.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_f_Brute_100.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_f_Brute_100.png new file mode 100644 index 0000000000..f0d9b5741b Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_f_Brute_100.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_f_Brute_20.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_f_Brute_20.png new file mode 100644 index 0000000000..43c92d9fa4 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_f_Brute_20.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_f_Brute_40.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_f_Brute_40.png new file mode 100644 index 0000000000..404f932b64 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_f_Brute_40.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_m_Brute_100.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_m_Brute_100.png new file mode 100644 index 0000000000..f0d9b5741b Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_m_Brute_100.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_m_Brute_20.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_m_Brute_20.png new file mode 100644 index 0000000000..43c92d9fa4 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_m_Brute_20.png differ diff --git a/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_m_Brute_40.png b/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_m_Brute_40.png new file mode 100644 index 0000000000..404f932b64 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_m_Brute_40.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_f_Burn_100.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_f_Burn_100.png new file mode 100644 index 0000000000..36b9114816 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_f_Burn_100.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_f_Burn_20.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_f_Burn_20.png new file mode 100644 index 0000000000..f91dae519a Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_f_Burn_20.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_f_Burn_40.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_f_Burn_40.png new file mode 100644 index 0000000000..90d69223b6 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_f_Burn_40.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_m_Burn_100.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_m_Burn_100.png new file mode 100644 index 0000000000..36b9114816 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_m_Burn_100.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_m_Burn_20.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_m_Burn_20.png new file mode 100644 index 0000000000..f91dae519a Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_m_Burn_20.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_m_Burn_40.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_m_Burn_40.png new file mode 100644 index 0000000000..90d69223b6 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/head_m_Burn_40.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_arm_Burn_100.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_arm_Burn_100.png new file mode 100644 index 0000000000..2dc5377659 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_arm_Burn_100.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_arm_Burn_20.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_arm_Burn_20.png new file mode 100644 index 0000000000..bc8f7bf9ca Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_arm_Burn_20.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_arm_Burn_40.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_arm_Burn_40.png new file mode 100644 index 0000000000..7c8da637ca Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_arm_Burn_40.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_leg_Burn_100.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_leg_Burn_100.png new file mode 100644 index 0000000000..c18e554541 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_leg_Burn_100.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_leg_Burn_20.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_leg_Burn_20.png new file mode 100644 index 0000000000..70b91d8824 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_leg_Burn_20.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_leg_Burn_40.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_leg_Burn_40.png new file mode 100644 index 0000000000..a84ef88da8 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/l_leg_Burn_40.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/meta.json b/Resources/Textures/Mobs/Effects/burn_damage.rsi/meta.json new file mode 100644 index 0000000000..43b27495b0 --- /dev/null +++ b/Resources/Textures/Mobs/Effects/burn_damage.rsi/meta.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/Citadel-Station-13/Citadel-Station-13/blob/971ddef989f7f4f365a714ef3d4df5dea2d53d9a/icons/mob/dam_mob.dmi", + "size": {"x": 32, "y": 32}, + "states": [ + {"name": "head_m_Burn_20", "directions": 4}, + {"name": "head_f_Burn_20", "directions": 4}, + {"name": "l_arm_Burn_20", "directions": 4}, + {"name": "l_leg_Burn_20", "directions": 4}, + {"name": "r_arm_Burn_20", "directions": 4}, + {"name": "r_leg_Burn_20", "directions": 4}, + {"name": "torso_m_Burn_20", "directions": 4}, + {"name": "torso_f_Burn_20", "directions": 4}, + {"name": "head_m_Burn_40", "directions": 4}, + {"name": "head_f_Burn_40", "directions": 4}, + {"name": "l_arm_Burn_40", "directions": 4}, + {"name": "l_leg_Burn_40", "directions": 4}, + {"name": "r_arm_Burn_40", "directions": 4}, + {"name": "r_leg_Burn_40", "directions": 4}, + {"name": "torso_m_Burn_40", "directions": 4}, + {"name": "torso_f_Burn_40", "directions": 4}, + {"name": "head_m_Burn_100", "directions": 4}, + {"name": "head_f_Burn_100", "directions": 4}, + {"name": "l_arm_Burn_100", "directions": 4}, + {"name": "l_leg_Burn_100", "directions": 4}, + {"name": "r_arm_Burn_100", "directions": 4}, + {"name": "r_leg_Burn_100", "directions": 4}, + {"name": "torso_m_Burn_100", "directions": 4}, + {"name": "torso_f_Burn_100", "directions": 4} + ] +} diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_arm_Burn_100.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_arm_Burn_100.png new file mode 100644 index 0000000000..77e463d02f Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_arm_Burn_100.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_arm_Burn_20.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_arm_Burn_20.png new file mode 100644 index 0000000000..96b96b566b Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_arm_Burn_20.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_arm_Burn_40.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_arm_Burn_40.png new file mode 100644 index 0000000000..1d0c791e38 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_arm_Burn_40.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_leg_Burn_100.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_leg_Burn_100.png new file mode 100644 index 0000000000..978e01358f Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_leg_Burn_100.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_leg_Burn_20.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_leg_Burn_20.png new file mode 100644 index 0000000000..5f5d390792 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_leg_Burn_20.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_leg_Burn_40.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_leg_Burn_40.png new file mode 100644 index 0000000000..efe3ad8b53 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/r_leg_Burn_40.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_f_Burn_100.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_f_Burn_100.png new file mode 100644 index 0000000000..2ca0dc67f1 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_f_Burn_100.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_f_Burn_20.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_f_Burn_20.png new file mode 100644 index 0000000000..bb29391fa0 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_f_Burn_20.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_f_Burn_40.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_f_Burn_40.png new file mode 100644 index 0000000000..5a68c6fd86 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_f_Burn_40.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_m_Burn_100.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_m_Burn_100.png new file mode 100644 index 0000000000..2ca0dc67f1 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_m_Burn_100.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_m_Burn_20.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_m_Burn_20.png new file mode 100644 index 0000000000..bb29391fa0 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_m_Burn_20.png differ diff --git a/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_m_Burn_40.png b/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_m_Burn_40.png new file mode 100644 index 0000000000..5a68c6fd86 Binary files /dev/null and b/Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_m_Burn_40.png differ diff --git a/Resources/Textures/Structures/Windows/cracks.rsi/DamageOverlay_12.png b/Resources/Textures/Structures/Windows/cracks.rsi/DamageOverlay_12.png new file mode 100644 index 0000000000..201698af28 Binary files /dev/null and b/Resources/Textures/Structures/Windows/cracks.rsi/DamageOverlay_12.png differ diff --git a/Resources/Textures/Structures/Windows/cracks.rsi/DamageOverlay_4.png b/Resources/Textures/Structures/Windows/cracks.rsi/DamageOverlay_4.png new file mode 100644 index 0000000000..067b895315 Binary files /dev/null and b/Resources/Textures/Structures/Windows/cracks.rsi/DamageOverlay_4.png differ diff --git a/Resources/Textures/Structures/Windows/cracks.rsi/DamageOverlay_8.png b/Resources/Textures/Structures/Windows/cracks.rsi/DamageOverlay_8.png new file mode 100644 index 0000000000..0a88d182ec Binary files /dev/null and b/Resources/Textures/Structures/Windows/cracks.rsi/DamageOverlay_8.png differ diff --git a/Resources/Textures/Structures/Windows/cracks.rsi/meta.json b/Resources/Textures/Structures/Windows/cracks.rsi/meta.json index 84375c566b..9d0cc9a505 100644 --- a/Resources/Textures/Structures/Windows/cracks.rsi/meta.json +++ b/Resources/Textures/Structures/Windows/cracks.rsi/meta.json @@ -7,17 +7,8 @@ "license": "CC-BY-SA-3.0", "copyright": "Taken from https://github.com/tgstation/tgstation at commit e06b82a7f4b2b09216fb28fd384c95a2e1dc50e5", "states": [ - { - "name": "1", - "directions": 1, - }, - { - "name": "2", - "directions": 1, - }, - { - "name": "3", - "directions": 1, - }, + {"name": "DamageOverlay_4", "directions": 1}, + {"name": "DamageOverlay_8", "directions": 1}, + {"name": "DamageOverlay_12", "directions": 1} ] }