diff --git a/Content.Shared/Alert/AlertPrototype.cs b/Content.Shared/Alert/AlertPrototype.cs
index 465a1959f0..e57d4e6bbc 100644
--- a/Content.Shared/Alert/AlertPrototype.cs
+++ b/Content.Shared/Alert/AlertPrototype.cs
@@ -1,4 +1,4 @@
-using System.Globalization;
+using System.Globalization;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
@@ -21,14 +21,11 @@ namespace Content.Shared.Alert
public AlertType AlertType { get; private set; }
///
- /// Path to the icon (png) to show in alert bar. If severity levels are supported,
- /// this should be the path to the icon without the severity number
- /// (i.e. hot.png if there is hot1.png and hot2.png). Use
- /// to get the correct icon path for a particular severity level.
+ /// List of icons to use for this alert. Each entry corresponds to a different severity level, starting from the
+ /// minimum and incrementing upwards. If severities are not supported, the first entry is used.
///
- [ViewVariables]
- [DataField("icon")]
- public SpriteSpecifier Icon { get; private set; } = SpriteSpecifier.Invalid;
+ [DataField("icons", required: true)]
+ public readonly List Icons = new();
///
/// Name to show in tooltip window. Accepts formatting.
@@ -103,8 +100,14 @@ namespace Content.Shared.Alert
throw new InvalidOperationException($"This alert ({AlertKey}) does not support severity");
}
+ var minIcons = SupportsSeverity
+ ? MaxSeverity - MinSeverity : 1;
+
+ if (Icons.Count < minIcons)
+ throw new InvalidOperationException($"Insufficient number of icons given for alert {AlertType}");
+
if (!SupportsSeverity)
- return Icon;
+ return Icons[0];
if (severity == null)
{
@@ -121,20 +124,7 @@ namespace Content.Shared.Alert
throw new ArgumentOutOfRangeException(nameof(severity), $"Severity above maximum severity in {AlertKey}.");
}
- var severityText = severity.Value.ToString(CultureInfo.InvariantCulture);
- switch (Icon)
- {
- case SpriteSpecifier.EntityPrototype entityPrototype:
- throw new InvalidOperationException($"Severity not supported for EntityPrototype icon in {AlertKey}");
- case SpriteSpecifier.Rsi rsi:
- return new SpriteSpecifier.Rsi(rsi.RsiPath, rsi.RsiState + severityText);
- case SpriteSpecifier.Texture texture:
- var newName = texture.TexturePath.FilenameWithoutExtension + severityText;
- return new SpriteSpecifier.Texture(
- texture.TexturePath.WithName(newName + "." + texture.TexturePath.Extension));
- default:
- throw new ArgumentOutOfRangeException(nameof(Icon));
- }
+ return Icons[severity.Value - _minSeverity];
}
}
}
diff --git a/Content.Tests/Shared/Alert/AlertManagerTests.cs b/Content.Tests/Shared/Alert/AlertManagerTests.cs
index 3edf34bd20..71046ff70c 100644
--- a/Content.Tests/Shared/Alert/AlertManagerTests.cs
+++ b/Content.Tests/Shared/Alert/AlertManagerTests.cs
@@ -16,11 +16,13 @@ namespace Content.Tests.Shared.Alert
const string PROTOTYPES = @"
- type: alert
id: LowPressure
- icon: /Textures/Interface/Alerts/Pressure/lowpressure.png
+ icons:
+ - /Textures/Interface/Alerts/Pressure/lowpressure.png
- type: alert
id: HighPressure
- icon: /Textures/Interface/Alerts/Pressure/highpressure.png
+ icons:
+ - /Textures/Interface/Alerts/Pressure/highpressure.png
";
[Test]
@@ -37,14 +39,14 @@ namespace Content.Tests.Shared.Alert
prototypeManager.LoadFromStream(new StringReader(PROTOTYPES));
Assert.That(EntitySystem.Get().TryGet(AlertType.LowPressure, out var lowPressure));
- Assert.That(lowPressure.Icon, Is.EqualTo(new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Alerts/Pressure/lowpressure.png"))));
+ Assert.That(lowPressure.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Alerts/Pressure/lowpressure.png"))));
Assert.That(EntitySystem.Get().TryGet(AlertType.HighPressure, out var highPressure));
- Assert.That(highPressure.Icon, Is.EqualTo(new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Alerts/Pressure/highpressure.png"))));
+ Assert.That(highPressure.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Alerts/Pressure/highpressure.png"))));
Assert.That(EntitySystem.Get().TryGet(AlertType.LowPressure, out lowPressure));
- Assert.That(lowPressure.Icon, Is.EqualTo(new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Alerts/Pressure/lowpressure.png"))));
+ Assert.That(lowPressure.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Alerts/Pressure/lowpressure.png"))));
Assert.That(EntitySystem.Get().TryGet(AlertType.HighPressure, out highPressure));
- Assert.That(highPressure.Icon, Is.EqualTo(new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Alerts/Pressure/highpressure.png"))));
+ Assert.That(highPressure.Icons[0], Is.EqualTo(new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Alerts/Pressure/highpressure.png"))));
}
}
}
diff --git a/Content.Tests/Shared/Alert/AlertOrderPrototypeTests.cs b/Content.Tests/Shared/Alert/AlertOrderPrototypeTests.cs
index d1c2a429c5..fe293b050d 100644
--- a/Content.Tests/Shared/Alert/AlertOrderPrototypeTests.cs
+++ b/Content.Tests/Shared/Alert/AlertOrderPrototypeTests.cs
@@ -26,35 +26,44 @@ namespace Content.Tests.Shared.Alert
- type: alert
id: LowPressure
+ icons: []
category: Pressure
- type: alert
id: HighPressure
+ icons: []
category: Pressure
- type: alert
id: Peckish
+ icons: []
category: Hunger
- type: alert
id: Stun
+ icons: []
- type: alert
id: Handcuffed
+ icons: []
- type: alert
id: Hot
+ icons: []
category: Temperature
- type: alert
id: Cold
+ icons: []
category: Temperature
- type: alert
id: Weightless
+ icons: []
- type: alert
id: PilotingShuttle
+ icons: []
";
[Test]
diff --git a/Content.Tests/Shared/Alert/AlertPrototypeTests.cs b/Content.Tests/Shared/Alert/AlertPrototypeTests.cs
index 096107cf16..dffab940cc 100644
--- a/Content.Tests/Shared/Alert/AlertPrototypeTests.cs
+++ b/Content.Tests/Shared/Alert/AlertPrototypeTests.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.IO;
using Content.Shared.Alert;
using NUnit.Framework;
@@ -17,7 +17,14 @@ namespace Content.Tests.Shared.Alert
- type: alert
id: HumanHealth
category: Health
- icon: /Textures/Interface/Alerts/Human/human.rsi/human.png
+ icons:
+ - /Textures/Interface/Alerts/Human/human.rsi/human0.png
+ - /Textures/Interface/Alerts/Human/human.rsi/human1.png
+ - /Textures/Interface/Alerts/Human/human.rsi/human2.png
+ - /Textures/Interface/Alerts/Human/human.rsi/human3.png
+ - /Textures/Interface/Alerts/Human/human.rsi/human4.png
+ - /Textures/Interface/Alerts/Human/human.rsi/human5.png
+ - /Textures/Interface/Alerts/Human/human.rsi/human6.png
name: Health
description: ""[color=green]Green[/color] good. [color=red]Red[/color] bad.""
minSeverity: 0
diff --git a/Content.YAMLLinter/Program.cs b/Content.YAMLLinter/Program.cs
index 0a49e59517..99c61071b6 100644
--- a/Content.YAMLLinter/Program.cs
+++ b/Content.YAMLLinter/Program.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -88,15 +88,28 @@ namespace Content.YAMLLinter
foreach (var (key, val) in serverErrors)
{
+ // Include all server errors marked as always relevant
var newErrors = val.Where(n => n.AlwaysRelevant).ToHashSet();
- if (clientErrors.TryGetValue(key, out var clientVal))
- {
- newErrors.UnionWith(val.Intersect(clientVal));
- newErrors.UnionWith(clientVal.Where(n => n.AlwaysRelevant));
- }
- if (newErrors.Count == 0) continue;
- allErrors[key] = newErrors;
+ // We include sometimes-relevant errors if they exist both for the client & server
+ if (clientErrors.TryGetValue(key, out var clientVal))
+ newErrors.UnionWith(val.Intersect(clientVal));
+
+ if (newErrors.Count != 0)
+ allErrors[key] = newErrors;
+ }
+
+ // Finally add any always-relevant client errors.
+ foreach (var (key, val) in clientErrors)
+ {
+ var newErrors = val.Where(n => n.AlwaysRelevant).ToHashSet();
+ if (newErrors.Count == 0)
+ continue;
+
+ if (allErrors.TryGetValue(key, out var errors))
+ errors.UnionWith(val.Where(n => n.AlwaysRelevant));
+ else
+ allErrors[key] = newErrors;
}
return allErrors;
diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml
index e2c53944ad..fffafc48fd 100644
--- a/Resources/Prototypes/Alerts/alerts.yml
+++ b/Resources/Prototypes/Alerts/alerts.yml
@@ -23,8 +23,8 @@
- type: alert
id: LowOxygen
category: Breathing
- icon:
- sprite: /Textures/Interface/Alerts/breathing.rsi
+ icons:
+ - sprite: /Textures/Interface/Alerts/breathing.rsi
state: not_enough_oxy
name: "[color=red]Low Oxygen[/color]"
description: "There is [color=red]not enough oxygen[/color] in the air you are breathing. Put on [color=green]internals[/color]."
@@ -32,8 +32,8 @@
- type: alert
id: Toxins
category: Toxins
- icon:
- sprite: /Textures/Interface/Alerts/breathing.rsi
+ icons:
+ - sprite: /Textures/Interface/Alerts/breathing.rsi
state: too_much_tox
name: "[color=red]High Toxin Level[/color]"
description: "There are [color=red]too many toxins[/color] in the air you are breathing. Put on [color=green]internals[/color] or get away."
@@ -41,9 +41,11 @@
- type: alert
id: LowPressure
category: Pressure
- icon:
- sprite: /Textures/Interface/Alerts/pressure.rsi
- state: lowpressure
+ icons:
+ - sprite: /Textures/Interface/Alerts/pressure.rsi
+ state: lowpressure1
+ - sprite: /Textures/Interface/Alerts/pressure.rsi
+ state: lowpressure2
maxSeverity: 2
name: "[color=red]Low Pressure[/color]"
description: "The air around you is [color=red]hazardously thin[/color]. A [color=green]space suit[/color] would protect you."
@@ -51,16 +53,18 @@
- type: alert
id: HighPressure
category: Pressure
- icon:
- sprite: /Textures/Interface/Alerts/pressure.rsi
- state: highpressure
+ icons:
+ - sprite: /Textures/Interface/Alerts/pressure.rsi
+ state: highpressure1
+ - sprite: /Textures/Interface/Alerts/pressure.rsi
+ state: highpressure2
maxSeverity: 2
name: "[color=red]High Pressure[/color]"
description: "The air around you is [color=red]hazardously thick[/color]. A [color=green]pressurized suit[/color] would be enough protect you"
- type: alert
id: Fire
- icon: /Textures/Interface/Alerts/Fire/fire.png
+ icons: [ /Textures/Interface/Alerts/Fire/fire.png ]
onClick: !type:ResistFire { }
name: "[color=red]On Fire[/color]"
description: "You're [color=red]on fire[/color]. Click the alert to stop, drop and roll to put the fire out or move to a vacuum area."
@@ -69,9 +73,13 @@
- type: alert
id: Cold
category: Temperature
- icon:
- sprite: /Textures/Interface/Alerts/temperature.rsi
- state: cold
+ icons:
+ - sprite: /Textures/Interface/Alerts/temperature.rsi
+ state: cold1
+ - sprite: /Textures/Interface/Alerts/temperature.rsi
+ state: cold2
+ - sprite: /Textures/Interface/Alerts/temperature.rsi
+ state: cold3
maxSeverity: 3
name: "[color=cyan]Too Cold[/color]"
description: "You're [color=cyan]freezing cold![/color] Get somewhere warmer and take off any insulating clothing like a space suit."
@@ -79,16 +87,20 @@
- type: alert
id: Hot
category: Temperature
- icon:
- sprite: /Textures/Interface/Alerts/temperature.rsi
- state: hot
+ icons:
+ - sprite: /Textures/Interface/Alerts/temperature.rsi
+ state: hot1
+ - sprite: /Textures/Interface/Alerts/temperature.rsi
+ state: hot2
+ - sprite: /Textures/Interface/Alerts/temperature.rsi
+ state: hot3
maxSeverity: 3
name: "[color=red]Too Hot[/color]"
description: "It's [color=red]too hot![/color] Get somewhere colder, take off any insulating clothing like a space suit, or at least get away from the flames."
- type: alert
id: Weightless
- icon: /Textures/Interface/Alerts/Weightless/weightless.png
+ icons: [ /Textures/Interface/Alerts/Weightless/weightless.png ]
name: Weightless
description: >
Gravity has ceased affecting you, and you're floating around aimlessly. Find something sturdy to hold onto, or throw or shoot something in a direction opposite of you.
@@ -96,14 +108,14 @@
- type: alert
id: Stun
- icon: /Textures/Objects/Weapons/Melee/stunbaton.rsi/stunbaton_off.png #Should probably draw a proper icon
+ icons: [ /Textures/Objects/Weapons/Melee/stunbaton.rsi/stunbaton_off.png ] #Should probably draw a proper icon
name: "[color=yellow]Stunned[/color]"
description: "You're [color=yellow]stunned[/color]! Something is impairing your ability to move or interact with objects"
- type: alert
id: Handcuffed
onClick: !type:RemoveCuffs { }
- icon: /Textures/Interface/Alerts/Handcuffed/Handcuffed.png
+ icons: [ /Textures/Interface/Alerts/Handcuffed/Handcuffed.png ]
name: "[color=yellow]Handcuffed[/color]"
description: "You're [color=yellow]handcuffed[/color] and can't use your hands. If anyone drags you, you won't be able to resist."
@@ -111,15 +123,15 @@
id: Buckled
category: Buckled
onClick: !type:Unbuckle { }
- icon: /Textures/Interface/Alerts/Buckle/buckled.png
+ icons: [ /Textures/Interface/Alerts/Buckle/buckled.png ]
name: "[color=yellow]Buckled[/color]"
description: "You've been [color=yellow]buckled[/color] to something. Click the alert to unbuckle unless you're [color=yellow]handcuffed.[/color]"
- type: alert
id: HumanCrit
category: Health
- icon:
- sprite: /Textures/Interface/Alerts/human_health.rsi
+ icons:
+ - sprite: /Textures/Interface/Alerts/human_health.rsi
state: health6
name: "[color=red]Critical Condition[/color]"
description: "You're severely injured and unconscious."
@@ -127,8 +139,8 @@
- type: alert
id: HumanDead
category: Health
- icon:
- sprite: /Textures/Interface/Alerts/human_health.rsi
+ icons:
+ - sprite: /Textures/Interface/Alerts/human_health.rsi
state: health7
name: Dead
description: You're dead, note that you can still be revived!
@@ -136,9 +148,23 @@
- type: alert
id: HumanHealth
category: Health
- icon:
- sprite: /Textures/Interface/Alerts/human_health.rsi
- state: health
+ icons:
+ - sprite: /Textures/Interface/Alerts/human_health.rsi
+ state: health0
+ - sprite: /Textures/Interface/Alerts/human_health.rsi
+ state: health1
+ - sprite: /Textures/Interface/Alerts/human_health.rsi
+ state: health2
+ - sprite: /Textures/Interface/Alerts/human_health.rsi
+ state: health3
+ - sprite: /Textures/Interface/Alerts/human_health.rsi
+ state: health4
+ - sprite: /Textures/Interface/Alerts/human_health.rsi
+ state: health5
+ - sprite: /Textures/Interface/Alerts/human_health.rsi
+ state: health6
+ - sprite: /Textures/Interface/Alerts/human_health.rsi
+ state: health7
name: Health
description: "[color=green]Green[/color] good. [color=red]Red[/color] bad."
minSeverity: 0
@@ -148,9 +174,13 @@
id: Internals
category: Internals
onClick: !type:ToggleInternals {}
- icon:
- sprite: /Textures/Interface/Alerts/internals.rsi
- state: internal
+ icons:
+ - sprite: /Textures/Interface/Alerts/internals.rsi
+ state: internal0
+ - sprite: /Textures/Interface/Alerts/internals.rsi
+ state: internal1
+ - sprite: /Textures/Interface/Alerts/internals.rsi
+ state: internal2
name: Toggle internals
description: "Toggles your gas tank internals on or off."
minSeverity: 0
@@ -160,23 +190,35 @@
id: PilotingShuttle
category: Piloting
onClick: !type:StopPiloting { }
- icon: /Textures/Interface/Alerts/piloting.png
+ icons: [ /Textures/Interface/Alerts/piloting.png ]
name: Piloting Shuttle
description: You are piloting a shuttle. Click the alert to stop.
- type: alert
id: Peckish
category: Hunger
- icon: /Textures/Interface/Alerts/Hunger/Peckish.png
+ icons: [ /Textures/Interface/Alerts/Hunger/Peckish.png ]
name: "[color=yellow]Peckish[/color]"
description: Some food would be good right about now.
- type: alert
id: Stamina
category: Stamina
- icon:
- sprite: /Textures/Interface/Alerts/stamina.rsi
- state: stamina
+ icons:
+ - sprite: /Textures/Interface/Alerts/stamina.rsi
+ state: stamina0
+ - sprite: /Textures/Interface/Alerts/stamina.rsi
+ state: stamina1
+ - sprite: /Textures/Interface/Alerts/stamina.rsi
+ state: stamina2
+ - sprite: /Textures/Interface/Alerts/stamina.rsi
+ state: stamina3
+ - sprite: /Textures/Interface/Alerts/stamina.rsi
+ state: stamina4
+ - sprite: /Textures/Interface/Alerts/stamina.rsi
+ state: stamina5
+ - sprite: /Textures/Interface/Alerts/stamina.rsi
+ state: stamina6
name: Stamina
description: "Stuns you if it is too low."
minSeverity: 0
@@ -185,90 +227,90 @@
- type: alert
id: Starving
category: Hunger
- icon: /Textures/Interface/Alerts/Hunger/Starving.png
+ icons: [ /Textures/Interface/Alerts/Hunger/Starving.png ]
name: "[color=red]Starving[/color]"
description: You're severely malnourished. The hunger pains make moving around a chore.
- type: alert
id: Thirsty
category: Thirst
- icon: /Textures/Interface/Alerts/Thirst/Thirsty.png
+ icons: [ /Textures/Interface/Alerts/Thirst/Thirsty.png ]
name: "[color=yellow]Thirsty[/color]"
description: Something to drink would be good right about now.
- type: alert
id: Parched
category: Thirst
- icon: /Textures/Interface/Alerts/Thirst/Parched.png
+ icons: [ /Textures/Interface/Alerts/Thirst/Parched.png ]
name: "[color=red]Parched[/color]"
description: You're severely thirsty. The thirst makes moving around a chore.
- type: alert
id: Muted
- icon: /Textures/Interface/Alerts/Abilities/silenced.png
+ icons: [ /Textures/Interface/Alerts/Abilities/silenced.png ]
name: Muted
description: You have lost the ability to speak.
- type: alert
id: VowOfSilence
- icon: /Textures/Interface/Alerts/Abilities/silenced.png
+ icons: [ /Textures/Interface/Alerts/Abilities/silenced.png ]
name: Vow of Silence
onClick: !type:BreakVow { }
description: You have taken a vow of silence as part of initiation into the Mystiko Tagma Mimon. Click to break your vow.
- type: alert
id: VowBroken
- icon: /Textures/Interface/Actions/scream.png
+ icons: [ /Textures/Interface/Actions/scream.png ]
name: Vow Broken
onClick: !type:RetakeVow { }
description: You've broken your vows to Mimes everywhere. You can speak, but you've lost your powers for at least 5 entire minutes!!! Click to try and retake your vow.
- type: alert
id: Pulled
- icon: /Textures/Interface/Alerts/Pull/pulled.png
+ icons: [ /Textures/Interface/Alerts/Pull/pulled.png ]
onClick: !type:StopBeingPulled { }
name: Pulled
description: You're being pulled. Move to break free.
- type: alert
id: Pulling
- icon: /Textures/Interface/Alerts/Pull/pulling.png
+ icons: [ /Textures/Interface/Alerts/Pull/pulling.png ]
onClick: !type:StopPulling { }
name: Pulling
description: You're pulling something. Click the alert to stop.
- type: alert
id: Debug1
- icon: /Textures/Interface/Alerts/human_health.rsi/health1.png
+ icons: [ /Textures/Interface/Alerts/human_health.rsi/health1.png ]
name: Debug1
description: Debug
- type: alert
id: Debug2
- icon: /Textures/Interface/Alerts/human_health.rsi/health2.png
+ icons: [ /Textures/Interface/Alerts/human_health.rsi/health2.png ]
name: Debug2
description: Debug
- type: alert
id: Debug3
- icon: /Textures/Interface/Alerts/human_health.rsi/health3.png
+ icons: [ /Textures/Interface/Alerts/human_health.rsi/health3.png ]
name: Debug3
description: Debug
- type: alert
id: Debug4
- icon: /Textures/Interface/Alerts/human_health.rsi/health4.png
+ icons: [ /Textures/Interface/Alerts/human_health.rsi/health4.png ]
name: Debug4
description: Debug
- type: alert
id: Debug5
- icon: /Textures/Interface/Alerts/human_health.rsi/health5.png
+ icons: [ /Textures/Interface/Alerts/human_health.rsi/health5.png ]
name: Debug5
description: Debug
- type: alert
id: Debug6
- icon: /Textures/Interface/Alerts/human_health.rsi/health6.png
+ icons: [ /Textures/Interface/Alerts/human_health.rsi/health6.png ]
name: Debug6
description: Debug
diff --git a/Resources/Prototypes/Alerts/magboots.yml b/Resources/Prototypes/Alerts/magboots.yml
index d2e7464184..ea7dfa7eb6 100644
--- a/Resources/Prototypes/Alerts/magboots.yml
+++ b/Resources/Prototypes/Alerts/magboots.yml
@@ -1,5 +1,7 @@
- type: alert
id: Magboots
- icon: { sprite: "/Textures/Clothing/Shoes/Boots/magboots.rsi", state: "icon-on" }
+ icons:
+ - sprite: /Textures/Clothing/Shoes/Boots/magboots.rsi
+ state: icon-on
name: "Magboots"
- description: You are immume to airflow, but slightly slower.
+ description: You are immune to airflow, but slightly slower.
diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/cat_parts.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/cat_parts.yml
index 1098c39fe8..d779d71bd3 100644
--- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/cat_parts.yml
+++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/cat_parts.yml
@@ -4,9 +4,9 @@
markingCategory: HeadTop
speciesRestriction: [Human]
sprites:
- - sprite: Mobs/Customization/cat_parts.rsi/
+ - sprite: Mobs/Customization/cat_parts.rsi
state: ears_cat_outer
- - sprite: Mobs/Customization/cat_parts.rsi/
+ - sprite: Mobs/Customization/cat_parts.rsi
state: ears_cat_inner
- type: marking
@@ -15,5 +15,5 @@
markingCategory: Tail
speciesRestriction: [Human]
sprites:
- - sprite: Mobs/Customization/cat_parts.rsi/
+ - sprite: Mobs/Customization/cat_parts.rsi
state: tail_cat