From 91b4ce6f24ab7a4d60a45ecf05a7eb6bf4018678 Mon Sep 17 00:00:00 2001 From: Flipp Syder <76629141+vulppine@users.noreply.github.com> Date: Fri, 29 Oct 2021 01:04:24 -0700 Subject: [PATCH] Adds generic damage visualizer to the game (#4893) --- Content.Client/Damage/DamageVisualizer.cs | 867 ++++++++++++++++++ .../Damage/DamageVisualizerComponent.cs | 26 + Content.Client/Window/WindowVisualizer.cs | 56 -- Content.Server/Window/WindowComponent.cs | 17 - Content.Server/Window/WindowSystem.cs | 19 - Content.Shared/Damage/DamageVisualizerKeys.cs | 14 + Content.Shared/Damage/DamageableSystem.cs | 3 + Content.Shared/Window/WindowVisuals.cs | 12 - .../Entities/Mobs/Species/human.yml | 15 + .../Entities/Structures/Windows/plasma.yml | 8 + .../Structures/Windows/reinforced.yml | 8 + .../Entities/Structures/Windows/rplasma.yml | 8 + .../Entities/Structures/Windows/window.yml | 6 +- .../brute_damage.rsi/head_f_Brute_100.png | Bin 0 -> 604 bytes .../brute_damage.rsi/head_f_Brute_20.png | Bin 0 -> 148 bytes .../brute_damage.rsi/head_f_Brute_40.png | Bin 0 -> 207 bytes .../brute_damage.rsi/head_m_Brute_100.png | Bin 0 -> 604 bytes .../brute_damage.rsi/head_m_Brute_20.png | Bin 0 -> 148 bytes .../brute_damage.rsi/head_m_Brute_40.png | Bin 0 -> 207 bytes .../brute_damage.rsi/l_arm_Brute_100.png | Bin 0 -> 363 bytes .../brute_damage.rsi/l_arm_Brute_20.png | Bin 0 -> 148 bytes .../brute_damage.rsi/l_arm_Brute_40.png | Bin 0 -> 178 bytes .../brute_damage.rsi/l_leg_Brute_100.png | Bin 0 -> 518 bytes .../brute_damage.rsi/l_leg_Brute_20.png | Bin 0 -> 161 bytes .../brute_damage.rsi/l_leg_Brute_40.png | Bin 0 -> 5504 bytes .../Mobs/Effects/brute_damage.rsi/meta.json | 32 + .../brute_damage.rsi/r_arm_Brute_100.png | Bin 0 -> 6173 bytes .../brute_damage.rsi/r_arm_Brute_20.png | Bin 0 -> 145 bytes .../brute_damage.rsi/r_arm_Brute_40.png | Bin 0 -> 5587 bytes .../brute_damage.rsi/r_leg_Brute_100.png | Bin 0 -> 465 bytes .../brute_damage.rsi/r_leg_Brute_20.png | Bin 0 -> 163 bytes .../brute_damage.rsi/r_leg_Brute_40.png | Bin 0 -> 5575 bytes .../brute_damage.rsi/torso_f_Brute_100.png | Bin 0 -> 755 bytes .../brute_damage.rsi/torso_f_Brute_20.png | Bin 0 -> 172 bytes .../brute_damage.rsi/torso_f_Brute_40.png | Bin 0 -> 358 bytes .../brute_damage.rsi/torso_m_Brute_100.png | Bin 0 -> 755 bytes .../brute_damage.rsi/torso_m_Brute_20.png | Bin 0 -> 172 bytes .../brute_damage.rsi/torso_m_Brute_40.png | Bin 0 -> 358 bytes .../burn_damage.rsi/head_f_Burn_100.png | Bin 0 -> 299 bytes .../burn_damage.rsi/head_f_Burn_20.png | Bin 0 -> 135 bytes .../burn_damage.rsi/head_f_Burn_40.png | Bin 0 -> 163 bytes .../burn_damage.rsi/head_m_Burn_100.png | Bin 0 -> 299 bytes .../burn_damage.rsi/head_m_Burn_20.png | Bin 0 -> 135 bytes .../burn_damage.rsi/head_m_Burn_40.png | Bin 0 -> 163 bytes .../burn_damage.rsi/l_arm_Burn_100.png | Bin 0 -> 317 bytes .../Effects/burn_damage.rsi/l_arm_Burn_20.png | Bin 0 -> 146 bytes .../Effects/burn_damage.rsi/l_arm_Burn_40.png | Bin 0 -> 229 bytes .../burn_damage.rsi/l_leg_Burn_100.png | Bin 0 -> 337 bytes .../Effects/burn_damage.rsi/l_leg_Burn_20.png | Bin 0 -> 127 bytes .../Effects/burn_damage.rsi/l_leg_Burn_40.png | Bin 0 -> 177 bytes .../Mobs/Effects/burn_damage.rsi/meta.json | 32 + .../burn_damage.rsi/r_arm_Burn_100.png | Bin 0 -> 325 bytes .../Effects/burn_damage.rsi/r_arm_Burn_20.png | Bin 0 -> 167 bytes .../Effects/burn_damage.rsi/r_arm_Burn_40.png | Bin 0 -> 237 bytes .../burn_damage.rsi/r_leg_Burn_100.png | Bin 0 -> 241 bytes .../Effects/burn_damage.rsi/r_leg_Burn_20.png | Bin 0 -> 137 bytes .../Effects/burn_damage.rsi/r_leg_Burn_40.png | Bin 0 -> 180 bytes .../burn_damage.rsi/torso_f_Burn_100.png | Bin 0 -> 732 bytes .../burn_damage.rsi/torso_f_Burn_20.png | Bin 0 -> 135 bytes .../burn_damage.rsi/torso_f_Burn_40.png | Bin 0 -> 275 bytes .../burn_damage.rsi/torso_m_Burn_100.png | Bin 0 -> 732 bytes .../burn_damage.rsi/torso_m_Burn_20.png | Bin 0 -> 135 bytes .../burn_damage.rsi/torso_m_Burn_40.png | Bin 0 -> 275 bytes .../Windows/cracks.rsi/DamageOverlay_12.png | Bin 0 -> 311 bytes .../Windows/cracks.rsi/DamageOverlay_4.png | Bin 0 -> 167 bytes .../Windows/cracks.rsi/DamageOverlay_8.png | Bin 0 -> 247 bytes .../Structures/Windows/cracks.rsi/meta.json | 15 +- 67 files changed, 1021 insertions(+), 117 deletions(-) create mode 100644 Content.Client/Damage/DamageVisualizer.cs create mode 100644 Content.Client/Damage/DamageVisualizerComponent.cs delete mode 100644 Content.Client/Window/WindowVisualizer.cs delete mode 100644 Content.Server/Window/WindowSystem.cs create mode 100644 Content.Shared/Damage/DamageVisualizerKeys.cs delete mode 100644 Content.Shared/Window/WindowVisuals.cs create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/head_f_Brute_100.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/head_f_Brute_20.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/head_f_Brute_40.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/head_m_Brute_100.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/head_m_Brute_20.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/head_m_Brute_40.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/l_arm_Brute_100.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/l_arm_Brute_20.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/l_arm_Brute_40.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/l_leg_Brute_100.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/l_leg_Brute_20.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/l_leg_Brute_40.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/meta.json create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/r_arm_Brute_100.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/r_arm_Brute_20.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/r_arm_Brute_40.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/r_leg_Brute_100.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/r_leg_Brute_20.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/r_leg_Brute_40.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_f_Brute_100.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_f_Brute_20.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_f_Brute_40.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_m_Brute_100.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_m_Brute_20.png create mode 100644 Resources/Textures/Mobs/Effects/brute_damage.rsi/torso_m_Brute_40.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/head_f_Burn_100.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/head_f_Burn_20.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/head_f_Burn_40.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/head_m_Burn_100.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/head_m_Burn_20.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/head_m_Burn_40.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/l_arm_Burn_100.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/l_arm_Burn_20.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/l_arm_Burn_40.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/l_leg_Burn_100.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/l_leg_Burn_20.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/l_leg_Burn_40.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/meta.json create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/r_arm_Burn_100.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/r_arm_Burn_20.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/r_arm_Burn_40.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/r_leg_Burn_100.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/r_leg_Burn_20.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/r_leg_Burn_40.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_f_Burn_100.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_f_Burn_20.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_f_Burn_40.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_m_Burn_100.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_m_Burn_20.png create mode 100644 Resources/Textures/Mobs/Effects/burn_damage.rsi/torso_m_Burn_40.png create mode 100644 Resources/Textures/Structures/Windows/cracks.rsi/DamageOverlay_12.png create mode 100644 Resources/Textures/Structures/Windows/cracks.rsi/DamageOverlay_4.png create mode 100644 Resources/Textures/Structures/Windows/cracks.rsi/DamageOverlay_8.png 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 0000000000000000000000000000000000000000..f1de5695c5adc09012210e68ce278db3418f53e0 GIT binary patch literal 604 zcmV-i0;BzjP)0sdQqWRTaRScZHbr~@A0Um}oWUoca+%iB^96{MDiRVG@D`C- zv|4Ew=fUbp^EE_K!iqKVttVrSzYlpLjeT@8%HY ze!s(73n9c?MTjZ@=iCE8mSs2`4&a=7t@SRQV2trX2w#>Zs;WX+mTwhdbpgWSrIc(O zM*zU_ctqEAZ0jNzV|>#ztgdSSfVCE<(}@9yE&^Nge|3j4}RvKKpPC-z|?@$6p?oxx~beYFQzKpQZ_Enxd|2OM{}`@;SWOFU-jB;gZB-JInu3x)WxuR_{B<4%7$)bxV>J|7jIh%J#j8 q%vgKKWYwx`>nF^VwPplK<~(N<2@$UM6E>d>;(EIJxvXz@qLn`LHy=BdJSb>Kj;L30P zJI&cYWnNBE^eD*);W=BaEc7Y;L(+7nn$=s&-po7v`rPvlk6fqhw;dgG9&Ekl{m@wY z;_+9-Gu0Mzo))vO`aOMzxHixJxG2Ay&Ia0MKpPks4t##IPjXkOe1O-zUWZq?2VP!$ z{V|Yff6k##NBi%U^?twTwfg0km`!aD-pl~2fAfyl(4|h3<@jtK5Z}|)&t;ucLK6Uw C8dqun literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f1de5695c5adc09012210e68ce278db3418f53e0 GIT binary patch literal 604 zcmV-i0;BzjP)0sdQqWRTaRScZHbr~@A0Um}oWUoca+%iB^96{MDiRVG@D`C- zv|4Ew=fUbp^EE_K!iqKVttVrSzYlpLjeT@8%HY ze!s(73n9c?MTjZ@=iCE8mSs2`4&a=7t@SRQV2trX2w#>Zs;WX+mTwhdbpgWSrIc(O zM*zU_ctqEAZ0jNzV|>#ztgdSSfVCE<(}@9yE&^Nge|3j4}RvKKpPC-z|?@$6p?oxx~beYFQzKpQZ_Enxd|2OM{}`@;SWOFU-jB;gZB-JInu3x)WxuR_{B<4%7$)bxV>J|7jIh%J#j8 q%vgKKWYwx`>nF^VwPplK<~(N<2@$UM6E>d>;(EIJxvXz@qLn`LHy=BdJSb>Kj;L30P zJI&cYWnNBE^eD*);W=BaEc7Y;L(+7nn$=s&-po7v`rPvlk6fqhw;dgG9&Ekl{m@wY z;_+9-Gu0Mzo))vO`aOMzxHixJxG2Ay&Ia0MKpPks4t##IPjXkOe1O-zUWZq?2VP!$ z{V|Yff6k##NBi%U^?twTwfg0km`!aD-pl~2fAfyl(4|h3<@jtK5Z}|)&t;ucLK6Uw C8dqun literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..488040b0c143eaacdcf94171828526a59cd009a4 GIT binary patch literal 363 zcmV-x0hIoUP)mWTU%&h{fl94CvRRN|6XxsK7@|^s-t|l?3 zY5GY*(=-p0ps!%nusj;yZ|1MS)wm?Ux*2+IuPzCws>(E8Q52@}T&Zfx@9oh_$SXgN zWAgsUGN-EP=++fuyxzn`o-1cH@Jp^f000000002MQ9g_4x^DO1<6E5#mSrh>lW>a! zgb-w&XQ`@6>bl@5v!8+uhKDPU3|NROC006)z`v!x_nOBEXZsPy|002ov JPDHLkV1j2tqGJF6 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1bcf2e505a1897df1f36f5b3f381079d437dfd64 GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=(Vi}jAr*7p-q^_7V8FxT(EZo_ zr{bo_6)P8bu!xm2N37T1S0D;hcYkV%+RXgt6-#_Ctg_vDkNJ^q+kUyP~!- q&Sm?$D7OHpjDg|BL2Wq@y`z|ErJ~%-doj#cL0nH)KbLh*2~7b1+AwJV literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8429e7fb181826584994fc45ae9695dd661a7e03 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=m7Xq+Ar*7p-aN>ARDpvb;L>0H zHwOjQJ#hJVS$;w1Ewg>*42+_DLW~Ry2W)0q-jm4g7Uh($<(RMiuVSXdkx7Mfj2k;+*3=&m}^= zv{)=eiyd5mvMl+0K2zWK68XH;G!5l>{yE2CyWKJok+qin_f&Sf9XCzG{#zpEcW8j+ za(QpbGSGsdtnW|pLiqTr$^KI;x3B1+R#tSz5$&T$;aKi2&S z#u%P@^PY1~v;%_A?Bm6!wWh=2AbuAZWB7W#{`e=cPVf=XT9ekA(ljL^qG1@skHX)Z zkK>q>Qlyj;i5*;kVHij$MO9TrT5E}%o>EFtUDpva(_=RP0000000000006*ClfLFo zt!Wzea|vD7QB_s{y&sVY5&vdCw>;0IuIs36TUxDFqhg2@V6N$qN7hh(7U_V?>Y#*OX-$Wmy)qu4n;Hr;}v3r;;S0&1MrNNfNx~A7om{6uKUw761SM07*qo IM6N<$f-vysivR!s literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2670821090fb2abb277f7fdc72ac5351a697be70 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=8J;eVAr*7p-rUH0K!JzFVai|k zL=OHVd`rCK1Cl1}h(561d{Ln$P$3X7XRWo(4s%&@R%()H&TXbmDf_lvS6Y;wmv&mH zFJ1fTeWAYpe;_L5Hn&fH{aRu5mTO_|5yxYN8Z6D%EYY5%?+`x+q`=eF&t;ucLK6UH Czc#Y~ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..680ec3009a5017c345f61bb7d2ec136d38c22057 GIT binary patch literal 5504 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2e4a031f15166f01e7cfbdce1b3f580854ff090c GIT binary patch literal 145 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6899cccab7c7bcf89e236c21902a5eda67090f78 GIT binary patch literal 465 zcmV;?0WSWDP)aIM_v-PzXdAQ|gq+D|5s}zh5WM#>Hvp;( znCCez%L1UCOh^;A)?%LL^~WS4+0kB02q8NwU$qS`mrG7Rt2&?0*=!lPTL@@wPjhb-EP(GOB0cpCcoeB)#FiRKx>l+ z0OwrS_dS4>4?>^UR|uhrCxM8>)9Lj7UAqtbC;%aZI1B^Q82~_28IV4w&G{+k0U{zI zA|fIpA|fK9zbEV6{xnT-x7z_|t4j1VA`+)*s(MeLRAowC9R`5o@rc9W&{=D%mB?x?m_#`LXHYd?7dzXZhhupZ3500000NkvXX Hu0mjf9l6Tm literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..44aae3c0bfb91ebf9003c1a337b4e3bd96c7f5ec GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=S)MMAAr*7p-aN?JU?9M9AmRVg zB^}xtwnz3WbY$$AJhMI_(rkS;FHj`|!vhzWGwY3IzTUf@dMd&po4Z&y*Lro1s>_ix zyL`ebYp05;O?#Wo1XgMv@X({^YT5IP5|3ZXe6?_947ktdbwGR4;WHNJKngrv{an^L HB{Ts5vF$nj literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ef4dbcb08b862c651cd3e72dad503da7fe9b6010 GIT binary patch literal 5575 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 a!9vk$ii)D0!fO`mgSeiqelF{r5}E*XE+$<7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..90d69223b6d72eaa60f7cc6f0db1e382c471f0b7 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=S)MMAAr*7p-rC69V8Fw2Am_ML zPUC{}GA3_0YgM$CJaK&|_G5eWZjlE5Z>2la&&<0WnSJNk?(!_=^~tZp7Mzu`t7$so z2Q&u=K8SOE|JlMZ+!5aw3`tm>CnJl7G|{e*ykymKwM8(KbLh* G2~7afpFL6l literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..36b911481623f94d094667de464ded479f148ba2 GIT binary patch literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=pFCY0Ln`LHy=BPP z;kJ&BIsUy}9Wwk&D+SCiry2x{&U}{n$z+R`(PoA9X-_j31smzu{Y{WOeyObXiP^&n zjjdWndwH+>PoMS1EM}>eQR#LCf8pHSc2UPR`ZjpS+L!&R$uha3Z?Ny*W1nY^O+0)y z(m=ODz~}Q^M2>!(}fN+hJEVT!P^~odTaTfovc7@Kp=42zwnU%c7b!JBOEmt a!9vk$ii)D0!fO`mgSeiqelF{r5}E*XE+$<7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..90d69223b6d72eaa60f7cc6f0db1e382c471f0b7 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=S)MMAAr*7p-rC69V8Fw2Am_ML zPUC{}GA3_0YgM$CJaK&|_G5eWZjlE5Z>2la&&<0WnSJNk?(!_=^~tZp7Mzu`t7$so z2Q&u=K8SOE|JlMZ+!5aw3`tm>CnJl7G|{e*ykymKwM8(KbLh* G2~7afpFL6l literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2dc53776594eda8616ae777b1fe7f36586905cd8 GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEU}W)haSW-L^Y)f4*C7XiwucwP zX2%>Zu3*+>J$TBCN32PStBHfr)nnm=OU5^k9&_T>c6=M7?RY88%>K=9nFq;|X=UA! zf2z#g3JH_ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bc8f7bf9cae96ef3cea682d8565b29613da562f1 GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=k)AG&Ar*7p-t^>UP~>57n6ikK zRhudBM&o%cg;s|df)~Dc+52(=6*^s+^qbdkdR9EAw}p||ZkKg$N?-H$DF9`FU_+bu oFU>OlL(hMFFHxSSC&$3>jAw$4vH_dU0!xs*r>mdKI;Vst065GmP5=M^ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7c8da637caf5e57123777180ed856099f3d9a25f GIT binary patch literal 229 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=Ydl>XLn`LHy=BP97%0H@!2OQn z>ZeM2ijP>7qyld~+gf#1E$=Z?$)Z4uqpQ9#omqF{_de!7Gf%2qKC^^{fkEKGhB%Y| zGklj^&J(sYjFSdhu4C*-xzv1Lpn0hfg@wr5ep!X7#*i zz5DO0d+t1oE#eMJ&97%* b_;E*Ca+c+*#_&@wK?Zud`njxgN@xNAQ7&Fy literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c18e5545414a90ac4160410074cb2e2797069b53 GIT binary patch literal 337 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEV3hE5aSW-L^Y+$4E+$8ThKI46 zk19ljeh95$nEce>M54gKgaE${Gx%7ZK5D%9MEVCuTKAlzN*P<$-+lD5cenb#`^;y~ z@L6w9^wR_y2?7UxKhiK+m;J-VQOQH5_1P2^p@l&+d8(7nie)-%x4-oLbf{8@)});- zQUX8btu%cFlm(JnANJPt&Nebs$0 zW{8^@7$(F&_A`#ZoizK}=gMV=yVo3xvATC|op(cUe%aMowW~+D=HP8 de%W5WQAe(?DWX@r#n>HWgr}>Y%Q~loCIC+AjaUEx literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..70b91d8824484b7eb0d26e2f4a9204086bd1fc89 GIT binary patch literal 127 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=Zk{fVAr*7p-U{SoP!MoA=$$1d zEO98{+C{Fe2aRV$D&&9f76d8-f&-lwU;Wv5v@h=TXIGa`c=j~O1zCh+EpblIgT3ch8zjNh*Bo@$WlF*!E6p;+d5`L1UPmeOiU>-7&=_HBK= zm34(}UQve<_r)&5xHZ~JO6?kxZaoewlL1-{0uAypvqWo>ztlH8y0pN=y#6NRd*kxy z%Ur`&e*3mdKI;Vst0K3J75C8xG literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..96b96b566bfdcc8fe9294152e98b75b18f72bed0 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=d7dtgAr*7p-a5$HV8G*W@eu>x zL!NVwb><#WlsQ#e!sy6-RPDpPo$dY_dzUN)sz0!Jao+V8ZDEqLlTX>4)PH;Vc4KMW zuPZzgcFr|8w|7}Bv(a{DpfaE>EHnS}T)2Gy_H_gE^S5?NFflNwlrwk5$eFx$b^HTT N;pyt{O)K5hU2 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1d0c791e38813d1853da6e26dcb849dfb3583641 GIT binary patch literal 237 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=TRdGHLn`LHy|t0I$w8taFsVeg zT75$?laJ4vmut67xF&k%=s8xM5&`cu9y&e_MJEgIRGX^EmlvG%4+QEy;NO0CSLfXi z>l4mi-{tr2VRY@S^jLA5`0WO!f?qP<$27bx5>-)avwJ(2yP#gb>Bx}@MZYU-^p>aa zADnWV5oiEIgP>*DQihZ$W#2c?-Bb7eU*-Oj h#ez{c=d&^}OjyUi&A=p-f2ASV08dvxmvv4FO#t4~U0nbG literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..978e01358f9916f1bafbbe11dc05604611641e98 GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=J3L(+Ln`LHy=BOC$UuZG;bYOg zMHW-n$?A&6?BbetAbO|8uBvvcR6&+bv)XI#Z!_0B#c}M}9>oaM%5cCt_I3{t6+f>C z)486vn{)fmB@x>`8?;!r-0^um$MyPe-SnGFl+vP>t;;>OZ{4A(4)NisatAxU&KEgd zW+`___Ry1=m%duPef*bk(iGiC_N*X78Ga;Ae9qFd*4Senrv;tljbpuYZdO{b=tTrHRExcy~=h*u!e%GvDF8D^*Pt+R!0BH?38|0|7v&IXWokS YvU{Ah-&>p9ItxFVdQ&MBb@0AKk;DF6Tf literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2ca0dc67f153aeab95b43905762a2ef11afc6ea5 GIT binary patch literal 732 zcmV<20wev2P)OnB%y`~1$%+~T0GsP;T2?`|QX;pO6*VYtnS+&}qyAtC)}$oMGy*NZpsJM; zS*AfXDaqTL3gs=Z6b_?kr)k5sMJP0fZo7TuTu>cPmA6*6-9{)hhYi~nMLSJP;jr>} z!cPO=zJA8s%ws%TdXE12PrNDDmB+aWdCR2P`KS2N`i}0O-+1xz6=KQEWP5SH0q>sQ z-z{k02Hu)(FMd|webqOd967K|gUa=W*xxPCr(fr&A3CbT1Xe$EP_)zZ`pG;ZTLod6 z2Av$K_n~wK6zw!^%N?<>I12!XY!$?%bAYbXRxa#^_mJy4ZCpABh-?)E04yxdVq5O$ z*@|u>xm05CZ_v*P9JIa_HOSkGuKL#+(OtY+DNXl--v-mXe=x=vV~jDz7-Nhv#{OeL zf1kf=b+2(BOUrDWbUdQYr4l5w@!Ro8d{jBqU#5~z0H8l7Fe(=i z#Es0Hn-l2tE(R&CNy()W^ytAuxVeC!<0+j1>#F}ZicY5fA{LT1`yBF)z4*}Q$iB}5+Ev@ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5a68c6fd86142ec705e4a3fdfc31362539c99527 GIT binary patch literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=w>(`OLn`LHy`{+4Y#?&%W7_ls zNtT>_!U`TADnx{BPK#EpK6~z+$lO<#Civv2zMuN$|K}&aZ#kVkYzfrN@Z+G-*N10r zPQSxcdQ~Ru+3c9-b5^fD_KsnX#|Ndf3Vd}V3AH|2H64xAM(E) z=IDLc;`+>e8uO(RgH>~TSG3$c|8~OH?lX@o&sY8tcqXgk{zSFs`N0Yqp!x%ndl`KG VH|^bN+w=^i-_zC4Wt~$(69C9tdyoJC literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2ca0dc67f153aeab95b43905762a2ef11afc6ea5 GIT binary patch literal 732 zcmV<20wev2P)OnB%y`~1$%+~T0GsP;T2?`|QX;pO6*VYtnS+&}qyAtC)}$oMGy*NZpsJM; zS*AfXDaqTL3gs=Z6b_?kr)k5sMJP0fZo7TuTu>cPmA6*6-9{)hhYi~nMLSJP;jr>} z!cPO=zJA8s%ws%TdXE12PrNDDmB+aWdCR2P`KS2N`i}0O-+1xz6=KQEWP5SH0q>sQ z-z{k02Hu)(FMd|webqOd967K|gUa=W*xxPCr(fr&A3CbT1Xe$EP_)zZ`pG;ZTLod6 z2Av$K_n~wK6zw!^%N?<>I12!XY!$?%bAYbXRxa#^_mJy4ZCpABh-?)E04yxdVq5O$ z*@|u>xm05CZ_v*P9JIa_HOSkGuKL#+(OtY+DNXl--v-mXe=x=vV~jDz7-Nhv#{OeL zf1kf=b+2(BOUrDWbUdQYr4l5w@!Ro8d{jBqU#5~z0H8l7Fe(=i z#Es0Hn-l2tE(R&CNy()W^ytAuxVeC!<0+j1>#F}ZicY5fA{LT1`yBF)z4*}Q$iB}5+Ev@ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5a68c6fd86142ec705e4a3fdfc31362539c99527 GIT binary patch literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=w>(`OLn`LHy`{+4Y#?&%W7_ls zNtT>_!U`TADnx{BPK#EpK6~z+$lO<#Civv2zMuN$|K}&aZ#kVkYzfrN@Z+G-*N10r zPQSxcdQ~Ru+3c9-b5^fD_KsnX#|Ndf3Vd}V3AH|2H64xAM(E) z=IDLc;`+>e8uO(RgH>~TSG3$c|8~OH?lX@o&sY8tcqXgk{zSFs`N0Yqp!x%ndl`KG VH|^bN+w=^i-_zC4Wt~$(69C9tdyoJC literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..201698af28f4c1495f8180876285415b0098c35e GIT binary patch literal 311 zcmV-70m%M|P)1u?;DQ99n?ig zL?LPY04_CKmrm(piKkkuUmv*%o+A}-3StqlL9y=Ot_PGcF(1rI11_r z5JEOI6}+>dBM83V-q}NvQmKG;ECDeU5os&3ZAg&A!4s|)`qPsXfb-Y3A0kiK<|p8y zf!F-?Hl)a;B5u2&=M1%Fx7CKLpAs-~_fHoy15^Hp78Vvp@Bz3+&CR)OxaI%=002ov JPDHLkV1oEkfgb<> literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..067b895315e7351ffce6c28ea1d963c7598be78e GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJJWm(Lkcv6U2@AOk99`_7W5t2r+MP+OaFDw9h)R< zZ!G-M`#paD{R7L|mi0L%-CuA-C+kVm>?sxvNewQUEcrz002ovPDHLkV1iUGWwZbQ literal 0 HcmV?d00001 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} ] }