diff --git a/Content.Server/ADT/Abilities/Felinid/CoughingUpHairballComponent.cs b/Content.Server/ADT/Abilities/Felinid/CoughingUpHairballComponent.cs
new file mode 100644
index 00000000000..9e97f837deb
--- /dev/null
+++ b/Content.Server/ADT/Abilities/Felinid/CoughingUpHairballComponent.cs
@@ -0,0 +1,11 @@
+namespace Content.Server.ADT.Abilities.Felinid;
+
+[RegisterComponent]
+public sealed partial class CoughingUpHairballComponent : Component
+{
+ [DataField("accumulator")]
+ public float Accumulator = 0f;
+
+ [DataField("coughUpTime")]
+ public TimeSpan CoughUpTime = TimeSpan.FromSeconds(2.15); // length of hairball.ogg
+}
diff --git a/Content.Server/ADT/Abilities/Felinid/FelinidComponent.cs b/Content.Server/ADT/Abilities/Felinid/FelinidComponent.cs
new file mode 100644
index 00000000000..c953034595d
--- /dev/null
+++ b/Content.Server/ADT/Abilities/Felinid/FelinidComponent.cs
@@ -0,0 +1,20 @@
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.ADT.Abilities.Felinid;
+
+[RegisterComponent]
+public sealed partial class FelinidComponent : Component
+{
+ ///
+ /// The hairball prototype to use.
+ ///
+ [DataField("hairballPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))]
+ public string HairballPrototype = "Hairball";
+
+ public EntityUid? HairballAction = null;
+
+ public EntityUid? EatMouse = null;
+
+ public EntityUid? PotentialTarget = null;
+}
diff --git a/Content.Server/ADT/Abilities/Felinid/FelinidFoodComponent.cs b/Content.Server/ADT/Abilities/Felinid/FelinidFoodComponent.cs
new file mode 100644
index 00000000000..3bb3fff9317
--- /dev/null
+++ b/Content.Server/ADT/Abilities/Felinid/FelinidFoodComponent.cs
@@ -0,0 +1,5 @@
+namespace Content.Server.ADT.Abilities.Felinid;
+
+[RegisterComponent]
+public sealed partial class FelinidFoodComponent : Component
+{}
diff --git a/Content.Server/ADT/Abilities/Felinid/FelinidSystem.cs b/Content.Server/ADT/Abilities/Felinid/FelinidSystem.cs
new file mode 100644
index 00000000000..bc8469faac7
--- /dev/null
+++ b/Content.Server/ADT/Abilities/Felinid/FelinidSystem.cs
@@ -0,0 +1,197 @@
+using Content.Server.Actions;
+using Content.Shared.Actions;
+using Content.Shared.StatusEffect;
+using Content.Shared.Throwing;
+using Content.Shared.Item;
+using Content.Shared.Inventory;
+using Content.Shared.Hands;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Nutrition.EntitySystems;
+using Content.Server.Body.Components;
+using Content.Server.Medical;
+using Content.Server.Nutrition.EntitySystems;
+using Content.Server.Popups;
+using Content.Shared.ADT.Psionics.Events;
+using Content.Shared.Chemistry.EntitySystems;
+using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Random;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+using Content.Server.Clothing;
+
+namespace Content.Server.ADT.Abilities.Felinid;
+
+public sealed class FelinidSystem : EntitySystem
+{
+
+ [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
+ [Dependency] private readonly HungerSystem _hungerSystem = default!;
+ [Dependency] private readonly VomitSystem _vomitSystem = default!;
+ [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!;
+ [Dependency] private readonly IRobustRandom _robustRandom = default!;
+ [Dependency] private readonly PopupSystem _popupSystem = default!;
+ [Dependency] private readonly InventorySystem _inventorySystem = default!;
+ [Dependency] private readonly ActionsSystem _actions = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(OnHairball);
+ SubscribeLocalEvent(OnEatMouse);
+ SubscribeLocalEvent(OnEquipped);
+ SubscribeLocalEvent(OnUnequipped);
+ SubscribeLocalEvent(OnHairballHit);
+ SubscribeLocalEvent(OnHairballPickupAttempt);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var entityUid, out var hairballComp, out var catComp))
+ {
+ hairballComp.Accumulator += frameTime;
+ if (hairballComp.Accumulator < hairballComp.CoughUpTime.TotalSeconds)
+ continue;
+
+ hairballComp.Accumulator = 0;
+ SpawnHairball(entityUid, catComp);
+ RemCompDeferred(entityUid);
+ }
+ }
+
+ [ValidatePrototypeId] private const string ActionHairball = "ActionHairball";
+ [ValidatePrototypeId] private const string ActionEatMouse = "ActionEatMouse";
+
+ private void OnInit(EntityUid uid, FelinidComponent component, ComponentInit args)
+ {
+ _actions.AddAction(uid, ref component.HairballAction, ActionHairball);
+
+ if (_actions.TryGetActionData(component.HairballAction, out var action) && action?.UseDelay != null)
+ {
+ _actions.SetCooldown(component.HairballAction,
+ _gameTiming.CurTime, _gameTiming.CurTime + (TimeSpan) action?.UseDelay!);
+ }
+ }
+
+ private void OnEquipped(EntityUid uid, FelinidComponent component, DidEquipHandEvent args)
+ {
+ if (!HasComp(args.Equipped))
+ return;
+
+ component.PotentialTarget = args.Equipped;
+
+ _actions.AddAction(uid, ref component.EatMouse, ActionEatMouse);
+ }
+
+ private void OnUnequipped(EntityUid uid, FelinidComponent component, DidUnequipHandEvent args)
+ {
+ if (args.Unequipped == component.PotentialTarget)
+ {
+ component.PotentialTarget = null;
+
+ _actions.RemoveAction(uid, component.EatMouse);
+ }
+ }
+
+ private static readonly SoundSpecifier HairballPlay = new SoundPathSpecifier("/Audio/ADT/Felinid/hairball.ogg",
+ AudioParams.Default.WithVariation(0.15f));
+
+ private void OnHairball(EntityUid uid, FelinidComponent component, HairballActionEvent args)
+ {
+ //if (_inventorySystem.TryGetSlotEntity(uid, "mask", out var maskUid) &&
+ // EntityManager.TryGetComponent(maskUid, out var blocker) &&
+ // blocker.Enabled)
+ //{
+ // _popupSystem.PopupEntity(Loc.GetString("hairball-mask", ("mask", maskUid)), uid, uid);
+ // return;
+ //}
+
+ _popupSystem.PopupEntity(Loc.GetString("hairball-cough", ("name", Identity.Entity(uid, EntityManager))), uid);
+ _audio.PlayPredicted(HairballPlay, uid, null);
+
+ EnsureComp(uid);
+ args.Handled = true;
+ }
+
+ private static readonly SoundSpecifier EatMousePlay = new SoundPathSpecifier("/Audio/Items/eatfood.ogg",
+ AudioParams.Default.WithVariation(0.15f));
+
+ private void OnEatMouse(EntityUid uid, FelinidComponent component, EatMouseActionEvent args)
+ {
+ if (component.PotentialTarget == null)
+ return;
+
+ if (!TryComp(uid, out var hunger))
+ return;
+
+ if (hunger.CurrentThreshold == Shared.Nutrition.Components.HungerThreshold.Overfed)
+ {
+ _popupSystem.PopupEntity(Loc.GetString("food-system-you-cannot-eat-any-more"), uid, uid, Shared.Popups.PopupType.SmallCaution);
+ return;
+ }
+
+ //if (_inventorySystem.TryGetSlotEntity(uid, "mask", out var maskUid) &&
+ // EntityManager.TryGetComponent(maskUid, out var blocker) &&
+ // blocker.Enabled)
+ //{
+ // _popupSystem.PopupEntity(Loc.GetString("hairball-mask", ("mask", maskUid)), uid, uid, Shared.Popups.PopupType.SmallCaution);
+ // return;
+ //}
+
+ if (component.HairballAction != null && _actions.TryGetActionData(component.HairballAction, out var skill))
+ {
+ _actionsSystem.SetCharges(component.HairballAction, skill?.Charges + 1);
+ _actionsSystem.SetEnabled(component.HairballAction, true);
+ }
+ Del(component.PotentialTarget.Value);
+ component.PotentialTarget = null;
+
+ _audio.PlayPredicted(EatMousePlay, uid, null);
+
+ _hungerSystem.ModifyHunger(uid, 70f, hunger);
+
+ _actions.RemoveAction(uid, component.EatMouse);
+ }
+
+ private void SpawnHairball(EntityUid uid, FelinidComponent component)
+ {
+ var hairball = EntityManager.SpawnEntity(component.HairballPrototype, Transform(uid).Coordinates);
+ var hairballComp = Comp(hairball);
+
+ if (TryComp(uid, out var bloodstream))
+ {
+ var temp = bloodstream.ChemicalSolution.SplitSolution(20);
+
+ if (_solutionSystem.TryGetSolution(hairball, hairballComp.SolutionName, out var hairballSolution))
+ {
+ _solutionSystem.TryAddSolution(hairball, hairballSolution, temp);
+ }
+ }
+ }
+ private void OnHairballHit(EntityUid uid, HairballComponent component, ThrowDoHitEvent args)
+ {
+ if (HasComp(args.Target) || !HasComp(args.Target))
+ return;
+ if (_robustRandom.Prob(0.2f))
+ _vomitSystem.Vomit(args.Target);
+ }
+
+ private void OnHairballPickupAttempt(EntityUid uid, HairballComponent component, GettingPickedUpAttemptEvent args)
+ {
+ if (HasComp(args.User) || !HasComp(args.User))
+ return;
+
+ if (_robustRandom.Prob(0.2f))
+ {
+ _vomitSystem.Vomit(args.User);
+ args.Cancel();
+ }
+ }
+}
diff --git a/Content.Server/ADT/Abilities/Felinid/HairballComponent.cs b/Content.Server/ADT/Abilities/Felinid/HairballComponent.cs
new file mode 100644
index 00000000000..82f02db2ec2
--- /dev/null
+++ b/Content.Server/ADT/Abilities/Felinid/HairballComponent.cs
@@ -0,0 +1,7 @@
+namespace Content.Server.ADT.Abilities.Felinid;
+
+[RegisterComponent]
+public sealed partial class HairballComponent : Component
+{
+ public string SolutionName = "hairball";
+}
diff --git a/Content.Server/Clothing/MaskSystem.cs b/Content.Server/Clothing/MaskSystem.cs
new file mode 100644
index 00000000000..2154f3b5765
--- /dev/null
+++ b/Content.Server/Clothing/MaskSystem.cs
@@ -0,0 +1,124 @@
+//using Content.Server.Actions;
+//using Content.Server.Atmos.Components;
+//using Content.Server.Atmos.EntitySystems;
+//using Content.Server.Body.Components;
+//using Content.Server.Body.Systems;
+//using Content.Server.Clothing.Components;
+//using Content.Server.IdentityManagement;
+//using Content.Server.Nutrition.EntitySystems;
+//using Content.Server.Popups;
+//using Content.Server.VoiceMask;
+//using Content.Shared.Actions;
+//using Content.Shared.Clothing;
+//using Content.Shared.Clothing.Components;
+//using Content.Shared.Clothing.EntitySystems;
+//using Content.Shared.IdentityManagement.Components;
+//using Content.Shared.Inventory;
+//using Content.Shared.Inventory.Events;
+
+//namespace Content.Server.Clothing
+//{
+// public sealed class MaskSystem : EntitySystem
+// {
+// [Dependency] private readonly ActionsSystem _actionSystem = default!;
+// [Dependency] private readonly AtmosphereSystem _atmos = default!;
+// [Dependency] private readonly InternalsSystem _internals = default!;
+// [Dependency] private readonly InventorySystem _inventorySystem = default!;
+// [Dependency] private readonly PopupSystem _popupSystem = default!;
+// [Dependency] private readonly IdentitySystem _identity = default!;
+// [Dependency] private readonly ClothingSystem _clothing = default!;
+
+// public override void Initialize()
+// {
+// base.Initialize();
+
+// SubscribeLocalEvent(OnToggleMask);
+// SubscribeLocalEvent(OnGetActions);
+// SubscribeLocalEvent(OnGotUnequipped);
+// }
+
+// private void OnGetActions(EntityUid uid, MaskComponent component, GetItemActionsEvent args)
+// {
+// if (!args.InHands)
+// args.AddAction(ref component.ToggleActionEntity, component.ToggleAction);
+// }
+
+// private void OnToggleMask(Entity ent, ref ToggleMaskEvent args)
+// {
+// var (uid, mask) = ent;
+// if (mask.ToggleActionEntity == null)
+// return;
+
+// if (!_inventorySystem.TryGetSlotEntity(args.Performer, "mask", out var existing) || !uid.Equals(existing))
+// return;
+
+// //mask.IsToggled ^= true;
+// //_actionSystem.SetToggled(mask.ToggleActionEntity, mask.IsToggled);
+
+// // Pulling mask down can change identity, so we want to update that
+// _identity.QueueIdentityUpdate(args.Performer);
+
+// if (mask.IsToggled)
+// _popupSystem.PopupEntity(Loc.GetString("action-mask-pull-down-popup-message", ("mask", uid)), args.Performer, args.Performer);
+// else
+// _popupSystem.PopupEntity(Loc.GetString("action-mask-pull-up-popup-message", ("mask", uid)), args.Performer, args.Performer);
+
+// ToggleMaskComponents(uid, mask, args.Performer);
+// }
+
+// // set to untoggled when unequipped, so it isn't left in a 'pulled down' state
+// private void OnGotUnequipped(EntityUid uid, MaskComponent mask, GotUnequippedEvent args)
+// {
+// if (mask.ToggleActionEntity == null)
+// return;
+
+// //mask.IsToggled = false;
+// //_actionSystem.SetToggled(mask.ToggleActionEntity, mask.IsToggled);
+
+// ToggleMaskComponents(uid, mask, args.Equipee, true);
+// }
+
+// private void ToggleMaskComponents(EntityUid uid, MaskComponent mask, EntityUid wearer, bool isEquip = false)
+// {
+// // toggle visuals
+// if (TryComp(uid, out var clothing))
+// {
+// //TODO: sprites for 'pulled down' state. defaults to invisible due to no sprite with this prefix
+// _clothing.SetEquippedPrefix(uid, mask.IsToggled ? "toggled" : null, clothing);
+// }
+
+// // shouldn't this be an event?
+
+// // toggle ingestion blocking
+// if (TryComp(uid, out var blocker))
+// blocker.Enabled = !mask.IsToggled;
+
+// // toggle identity
+// if (TryComp(uid, out var identity))
+// identity.Enabled = !mask.IsToggled;
+
+// // toggle voice masking
+// if (TryComp(wearer, out var voiceMask))
+// voiceMask.Enabled = !mask.IsToggled;
+
+// // toggle breath tool connection (skip during equip since that is handled in LungSystem)
+// if (isEquip || !TryComp(uid, out var breathTool))
+// return;
+
+// if (mask.IsToggled)
+// {
+// _atmos.DisconnectInternals(breathTool);
+// }
+// else
+// {
+// breathTool.IsFunctional = true;
+
+// if (TryComp(wearer, out InternalsComponent? internals))
+// {
+// breathTool.ConnectedInternalsEntity = wearer;
+// _internals.ConnectBreathTool((wearer, internals), uid);
+// }
+// }
+// }
+// }
+//}
diff --git a/Content.Shared/ADT/Psionics/Events.cs b/Content.Shared/ADT/Psionics/Events.cs
new file mode 100644
index 00000000000..1a475afe4ad
--- /dev/null
+++ b/Content.Shared/ADT/Psionics/Events.cs
@@ -0,0 +1,52 @@
+using Content.Shared.Actions;
+using Robust.Shared.Serialization;
+using Content.Shared.DoAfter;
+
+namespace Content.Shared.ADT.Psionics.Events;
+
+[Serializable, NetSerializable]
+public sealed partial class PsionicRegenerationDoAfterEvent : DoAfterEvent
+{
+ [DataField("startedAt", required: true)]
+ public TimeSpan StartedAt;
+
+ private PsionicRegenerationDoAfterEvent()
+ {
+ }
+
+ public PsionicRegenerationDoAfterEvent(TimeSpan startedAt)
+ {
+ StartedAt = startedAt;
+ }
+
+ public override DoAfterEvent Clone() => this;
+}
+
+[Serializable, NetSerializable]
+public sealed partial class GlimmerWispDrainDoAfterEvent : SimpleDoAfterEvent
+{
+}
+
+public sealed partial class PsionicInvisibilityPowerActionEvent : InstantActionEvent {}
+public sealed partial class PsionicInvisibilityPowerOffActionEvent : InstantActionEvent {}
+public sealed partial class HairballActionEvent : InstantActionEvent {}
+public sealed partial class EatMouseActionEvent : InstantActionEvent {}
+public sealed partial class MetapsionicPowerActionEvent : InstantActionEvent {}
+public sealed partial class TelegnosisPowerReturnActionEvent : InstantActionEvent {}
+public sealed partial class TelegnosisPowerActionEvent : InstantActionEvent {}
+public sealed partial class PsionicRegenerationPowerActionEvent : InstantActionEvent {}
+public sealed partial class NoosphericZapPowerActionEvent : EntityTargetActionEvent {}
+public sealed partial class DispelPowerActionEvent : EntityTargetActionEvent {}
+
+public sealed partial class DispelledEvent : HandledEntityEventArgs {}
+public sealed partial class MindSwapPowerActionEvent : EntityTargetActionEvent
+{
+
+}
+
+public sealed partial class MindSwapPowerReturnActionEvent : InstantActionEvent
+{
+
+}
+public sealed partial class PyrokinesisPowerActionEvent : EntityTargetActionEvent {}
+public sealed partial class PsychokinesisPowerActionEvent : WorldTargetActionEvent {}
diff --git a/Content.Shared/ADT/Psionics/PsychokinesisComponent.cs b/Content.Shared/ADT/Psionics/PsychokinesisComponent.cs
new file mode 100644
index 00000000000..674a3d28e16
--- /dev/null
+++ b/Content.Shared/ADT/Psionics/PsychokinesisComponent.cs
@@ -0,0 +1,18 @@
+using Robust.Shared.Audio;
+
+namespace Content.Shared.ADT.Abilities.Psionics;
+
+[RegisterComponent]
+public sealed partial class PsychokinesisPowerComponent : Component
+{
+ public EntityUid? PsychokinesisPowerAction = null;
+
+ [DataField("waveSound")]
+ public SoundSpecifier WaveSound = new SoundPathSpecifier("/Audio/Nyanotrasen/Mobs/SilverGolem/wave.ogg");
+
+ ///
+ /// Volume control for the spell.
+ ///
+ [DataField("waveVolume")]
+ public float WaveVolume = 5f;
+}
diff --git a/Resources/Audio/ADT/Felinid/hairball.ogg b/Resources/Audio/ADT/Felinid/hairball.ogg
new file mode 100644
index 00000000000..f7fb40de608
Binary files /dev/null and b/Resources/Audio/ADT/Felinid/hairball.ogg differ
diff --git a/Resources/Audio/ADT/Felinid/license.txt b/Resources/Audio/ADT/Felinid/license.txt
new file mode 100644
index 00000000000..0370cf25c61
--- /dev/null
+++ b/Resources/Audio/ADT/Felinid/license.txt
@@ -0,0 +1 @@
+hairball.ogg taken from https://en.wikipedia.org/wiki/File:Common_house_cat_coughing_hairball.ogv CC-BY-SA-3.0
diff --git a/Resources/Locale/ru-RU/ADT/Felinid/abilities/felinid.ftl b/Resources/Locale/ru-RU/ADT/Felinid/abilities/felinid.ftl
new file mode 100644
index 00000000000..5a4df0fbd42
--- /dev/null
+++ b/Resources/Locale/ru-RU/ADT/Felinid/abilities/felinid.ftl
@@ -0,0 +1,8 @@
+hairball-action = Откашляться
+hairball-action-desc = Очистите свою пищеварительную систему и получите крутой комок волос, который можно бросить в людей.
+#hairball-mask = Снемите свою { $mask } для начала.
+#hairball-cough = { CAPITALIZE(THE($name)) } начинает кашлять комком шерсти!
+ent-Hairball = комок шерсти
+ .desc = Фелиниды, чувак...
+action-name-eat-mouse = Сожрать мышку
+action-description-eat-mouse = Съешьте мышь, которую держите в руке, и получите питательные вещества и заряд шерсти.
diff --git a/Resources/Prototypes/ADT/Felinid/Mobs/Species/felinid.yml b/Resources/Prototypes/ADT/Felinid/Mobs/Species/felinid.yml
index 734fd1dfc72..ce93ceb6e9e 100644
--- a/Resources/Prototypes/ADT/Felinid/Mobs/Species/felinid.yml
+++ b/Resources/Prototypes/ADT/Felinid/Mobs/Species/felinid.yml
@@ -5,11 +5,12 @@
id: MobFelinidBase
abstract: true
components:
+ - type: Felinid
- type: Sprite
netsync: false
noRot: true
drawdepth: Mobs
- scale: 0.8, 0.8
+ scale: 0.95, 0.95
layers:
- map: [ "enum.HumanoidVisualLayers.Chest" ]
- map: [ "enum.HumanoidVisualLayers.Head" ]
@@ -167,3 +168,52 @@
- map: [ "head" ]
- map: [ "pocket1" ]
- map: [ "pocket2" ]
+
+
+- type: entity
+ parent: BaseItem
+ id: Hairball
+ name: hairball
+ description: Felinids, man... Placeholder sprite.
+ components:
+ - type: Sprite
+ sprite: ADT/Objects/Specific/felinid.rsi
+ state: icon
+ - type: Hairball
+ - type: SolutionContainerManager
+ solutions:
+ hairball:
+ maxVol: 25
+ reagents:
+ - ReagentId: Protein
+ Quantity: 2
+ - type: Extractable
+ grindableSolutionName: hairball
+ # - type: Tag
+ # tags:
+ # - Recyclable
+ # - Trash
+
+- type: entity
+ id: ActionHairball
+ name: hairball-action
+ description: hairball-action-desc
+ noSpawn: true
+ components:
+ - type: InstantAction
+# icon: { sprite: Backmen/Structures/web.rsi, state: web1 }
+ priority: -10
+ event: !type:HairballActionEvent
+ charges: 100 #костыль
+ useDelay: 30
+
+- type: entity
+ id: ActionEatMouse
+ name: action-name-eat-mouse
+ description: action-description-eat-mouse
+ noSpawn: true
+ components:
+ - type: InstantAction
+ useDelay: 10
+ icon: ADT/Icons/verbiconfangs.png
+ event: !type:EatMouseActionEvent
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
index 16683b56644..88e6451f0ad 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
@@ -1175,6 +1175,7 @@
id: MobMouse
description: Squeak!
components:
+ - type: FelinidFood
- type: Body
prototype: Mouse
- type: GhostRole
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml
index 5e691fb3e23..5f02d2e062b 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml
@@ -155,6 +155,7 @@
description: He's da mini rat. He don't make da roolz.
noSpawn: true #Must be configured to a King or the AI breaks.
components:
+ - type: FelinidFood
- type: CombatMode
- type: MovementSpeedModifier
baseWalkSpeed : 2.5 #Value was 3.5 (Nerf since 20.12.2022)
diff --git a/Resources/Textures/ADT/Icons/verbiconfangs.png b/Resources/Textures/ADT/Icons/verbiconfangs.png
new file mode 100644
index 00000000000..4511cbd21fd
Binary files /dev/null and b/Resources/Textures/ADT/Icons/verbiconfangs.png differ
diff --git a/Resources/Textures/ADT/Objects/Specific/felinid.rsi/icon.png b/Resources/Textures/ADT/Objects/Specific/felinid.rsi/icon.png
new file mode 100644
index 00000000000..f9483ac80fc
Binary files /dev/null and b/Resources/Textures/ADT/Objects/Specific/felinid.rsi/icon.png differ
diff --git a/Resources/Textures/ADT/Objects/Specific/felinid.rsi/meta.json b/Resources/Textures/ADT/Objects/Specific/felinid.rsi/meta.json
new file mode 100644
index 00000000000..aaad7ca5b98
--- /dev/null
+++ b/Resources/Textures/ADT/Objects/Specific/felinid.rsi/meta.json
@@ -0,0 +1,14 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Made by QuizzyQuin",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ }
+ ]
+}