diff --git a/Content.Server/Nyanotrasen/Abilities/Felinid/CoughingUpHairballComponent.cs b/Content.Server/Nyanotrasen/Abilities/Felinid/CoughingUpHairballComponent.cs
new file mode 100644
index 0000000000..1bfa0809b6
--- /dev/null
+++ b/Content.Server/Nyanotrasen/Abilities/Felinid/CoughingUpHairballComponent.cs
@@ -0,0 +1,11 @@
+namespace Content.Server.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/Nyanotrasen/Abilities/Felinid/FelinidComponent.cs b/Content.Server/Nyanotrasen/Abilities/Felinid/FelinidComponent.cs
new file mode 100644
index 0000000000..a3e389409f
--- /dev/null
+++ b/Content.Server/Nyanotrasen/Abilities/Felinid/FelinidComponent.cs
@@ -0,0 +1,26 @@
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Prototypes;
+using Content.Shared.Actions;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Abilities.Felinid;
+
+[RegisterComponent]
+public sealed partial class FelinidComponent : Component
+{
+ ///
+ /// The hairball prototype to use.
+ ///
+ [DataField("hairballPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))]
+ public string HairballPrototype = "Hairball";
+
+ //[DataField("hairballAction", customTypeSerializer: typeof(PrototypeIdSerializer))]
+ //public string HairballAction = "ActionHairball";
+
+ [DataField("hairballAction")]
+ public EntityUid? HairballAction = null;
+
+ public EntityUid? EatActionTarget = null;
+
+ public EntityUid? EatAction = null;
+}
diff --git a/Content.Server/Nyanotrasen/Abilities/Felinid/FelinidFoodComponent.cs b/Content.Server/Nyanotrasen/Abilities/Felinid/FelinidFoodComponent.cs
new file mode 100644
index 0000000000..5a6069ffab
--- /dev/null
+++ b/Content.Server/Nyanotrasen/Abilities/Felinid/FelinidFoodComponent.cs
@@ -0,0 +1,5 @@
+namespace Content.Server.Abilities.Felinid;
+
+[RegisterComponent]
+public sealed partial class FelinidFoodComponent : Component
+{}
diff --git a/Content.Server/Nyanotrasen/Abilities/Felinid/FelinidSystem.cs b/Content.Server/Nyanotrasen/Abilities/Felinid/FelinidSystem.cs
new file mode 100644
index 0000000000..969a19103f
--- /dev/null
+++ b/Content.Server/Nyanotrasen/Abilities/Felinid/FelinidSystem.cs
@@ -0,0 +1,191 @@
+using Content.Shared.Actions;
+using Content.Shared.Actions.Events;
+using Content.Shared.Audio;
+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.Nutrition.Components;
+using Content.Server.Chemistry.EntitySystems;
+using Content.Server.Popups;
+using Robust.Shared.Audio;
+using Robust.Shared.Player;
+using Robust.Shared.Random;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Abilities.Felinid;
+
+public sealed partial 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 IPrototypeManager _prototypeManager = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(OnHairball);
+ SubscribeLocalEvent(OnEatMouse);
+ SubscribeLocalEvent(OnEquipped);
+ SubscribeLocalEvent(OnUnequipped);
+ SubscribeLocalEvent(OnHairballHit);
+ SubscribeLocalEvent(OnHairballPickupAttempt);
+ }
+
+ private Queue RemQueue = new();
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+ foreach (var cat in RemQueue)
+ {
+ RemComp(cat);
+ }
+ RemQueue.Clear();
+
+ foreach (var (hairballComp, catComp) in EntityQuery())
+ {
+ hairballComp.Accumulator += frameTime;
+ if (hairballComp.Accumulator < hairballComp.CoughUpTime.TotalSeconds)
+ continue;
+
+ hairballComp.Accumulator = 0;
+ SpawnHairball(hairballComp.Owner, catComp);
+ RemQueue.Enqueue(hairballComp.Owner);
+ }
+ }
+
+ private void OnInit(EntityUid uid, FelinidComponent component, ComponentInit args)
+ {
+ if (component.HairballAction != null)
+ return;
+
+ component.HairballAction = Spawn("ActionHairball");
+ _actionsSystem.AddAction(uid, component.HairballAction.Value, uid);
+ }
+
+ private void OnEquipped(EntityUid uid, FelinidComponent component, DidEquipHandEvent args)
+ {
+ if (!HasComp(args.Equipped))
+ return;
+
+ component.EatActionTarget = args.Equipped;
+
+ component.EatAction = Spawn("ActionEatMouse");
+ _actionsSystem.AddAction(uid, component.EatAction.Value, null);
+ }
+
+ private void OnUnequipped(EntityUid uid, FelinidComponent component, DidUnequipHandEvent args)
+ {
+ if (args.Unequipped == component.EatActionTarget)
+ {
+ component.EatActionTarget = null;
+ if (component.EatAction != null)
+ _actionsSystem.RemoveAction(uid, component.EatAction.Value);
+ }
+ }
+
+ 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);
+ SoundSystem.Play("/Audio/Nyanotrasen/Effects/Species/hairball.ogg", Filter.Pvs(uid), uid, AudioHelpers.WithVariation(0.15f));
+
+ EnsureComp(uid);
+ args.Handled = true;
+ }
+
+ private void OnEatMouse(EntityUid uid, FelinidComponent component, EatMouseActionEvent args)
+ {
+ if (component.EatActionTarget == 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)
+ {
+ _actionsSystem.SetCharges(component.HairballAction, 1); // You get the charge back and that's it. Tough.
+ _actionsSystem.SetEnabled(component.HairballAction, true);
+ }
+ Del(component.EatActionTarget.Value);
+ component.EatActionTarget = null;
+
+ SoundSystem.Play("/Audio/Items/eatfood.ogg", Filter.Pvs(uid), uid, AudioHelpers.WithVariation(0.15f));
+
+ _hungerSystem.ModifyHunger(uid, 50f, hunger);
+
+ if (component.EatAction != null)
+ _actionsSystem.RemoveAction(uid, component.EatAction.Value);
+ }
+
+ 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/Nyanotrasen/Abilities/Felinid/HairballComponent.cs b/Content.Server/Nyanotrasen/Abilities/Felinid/HairballComponent.cs
new file mode 100644
index 0000000000..01c01dbc2e
--- /dev/null
+++ b/Content.Server/Nyanotrasen/Abilities/Felinid/HairballComponent.cs
@@ -0,0 +1,7 @@
+namespace Content.Server.Abilities.Felinid;
+
+[RegisterComponent]
+public sealed partial class HairballComponent : Component
+{
+ public string SolutionName = "hairball";
+}
diff --git a/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemComponent.cs b/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemComponent.cs
new file mode 100644
index 0000000000..ccc1342c81
--- /dev/null
+++ b/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemComponent.cs
@@ -0,0 +1,15 @@
+
+namespace Content.Server.Item.PseudoItem;
+
+ ///
+ /// For entities that behave like an item under certain conditions,
+ /// but not under most conditions.
+ ///
+[RegisterComponent]
+public sealed partial class PseudoItemComponent : Component
+{
+ [DataField("size")]
+ public int Size = 120;
+
+ public bool Active = false;
+}
diff --git a/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs b/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs
new file mode 100644
index 0000000000..babfa5bd1a
--- /dev/null
+++ b/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs
@@ -0,0 +1,154 @@
+using System.Threading;
+using Content.Shared.Verbs;
+using Content.Shared.Item;
+using Content.Shared.Hands;
+using Content.Shared.DoAfter;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Item.PseudoItem;
+using Content.Server.Storage.Components;
+using Content.Server.Storage.EntitySystems;
+using Content.Server.DoAfter;
+using Robust.Shared.Containers;
+
+namespace Content.Server.Item.PseudoItem;
+public sealed partial class PseudoItemSystem : EntitySystem
+{
+ [Dependency] private readonly StorageSystem _storageSystem = default!;
+ [Dependency] private readonly ItemSystem _itemSystem = default!;
+ [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent>(AddInsertVerb);
+ SubscribeLocalEvent>(AddInsertAltVerb);
+ SubscribeLocalEvent(OnEntRemoved);
+ SubscribeLocalEvent(OnGettingPickedUpAttempt);
+ SubscribeLocalEvent(OnDropAttempt);
+ SubscribeLocalEvent(OnDoAfter);
+ }
+
+ private void AddInsertVerb(EntityUid uid, PseudoItemComponent component, GetVerbsEvent args)
+ {
+ if (!args.CanInteract || !args.CanAccess)
+ return;
+
+ if (component.Active)
+ return;
+
+ if (!TryComp(args.Target, out var targetStorage))
+ return;
+
+ if (component.Size > targetStorage.StorageCapacityMax - targetStorage.StorageUsed)
+ return;
+
+ if (Transform(args.Target).ParentUid == uid)
+ return;
+
+ InnateVerb verb = new()
+ {
+ Act = () =>
+ {
+ TryInsert(args.Target, uid, component, targetStorage);
+ },
+ Text = Loc.GetString("action-name-insert-self"),
+ Priority = 2
+ };
+ args.Verbs.Add(verb);
+ }
+
+ private void AddInsertAltVerb(EntityUid uid, PseudoItemComponent component, GetVerbsEvent args)
+ {
+ if (!args.CanInteract || !args.CanAccess)
+ return;
+
+ if (args.User == args.Target)
+ return;
+
+ if (args.Hands == null)
+ return;
+
+ if (!TryComp(args.Hands.ActiveHandEntity, out var targetStorage))
+ return;
+
+ AlternativeVerb verb = new()
+ {
+ Act = () =>
+ {
+ StartInsertDoAfter(args.User, uid, args.Hands.ActiveHandEntity.Value, component);
+ },
+ Text = Loc.GetString("action-name-insert-other", ("target", Identity.Entity(args.Target, EntityManager))),
+ Priority = 2
+ };
+ args.Verbs.Add(verb);
+ }
+
+ private void OnEntRemoved(EntityUid uid, PseudoItemComponent component, EntGotRemovedFromContainerMessage args)
+ {
+ if (!component.Active)
+ return;
+
+ RemComp(uid);
+ component.Active = false;
+ }
+
+ private void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component, GettingPickedUpAttemptEvent args)
+ {
+ if (args.User == args.Item)
+ return;
+
+ Transform(uid).AttachToGridOrMap();
+ args.Cancel();
+ }
+
+ private void OnDropAttempt(EntityUid uid, PseudoItemComponent component, DropAttemptEvent args)
+ {
+ if (component.Active)
+ args.Cancel();
+ }
+ private void OnDoAfter(EntityUid uid, PseudoItemComponent component, DoAfterEvent args)
+ {
+ if (args.Handled || args.Cancelled || args.Args.Used == null)
+ return;
+
+ args.Handled = TryInsert(args.Args.Used.Value, uid, component);
+ }
+
+ public bool TryInsert(EntityUid storageUid, EntityUid toInsert, PseudoItemComponent component, ServerStorageComponent? storage = null)
+ {
+ if (!Resolve(storageUid, ref storage))
+ return false;
+
+ if (component.Size > storage.StorageCapacityMax - storage.StorageUsed)
+ return false;
+
+ var item = EnsureComp(toInsert);
+ _itemSystem.SetSize(toInsert, component.Size, item);
+
+ if (!_storageSystem.Insert(storageUid, toInsert, storage))
+ {
+ component.Active = false;
+ RemComp(toInsert);
+ return false;
+ } else
+ {
+ component.Active = true;
+ Transform(storageUid).AttachToGridOrMap();
+ return true;
+ }
+ }
+ private void StartInsertDoAfter(EntityUid inserter, EntityUid toInsert, EntityUid storageEntity, PseudoItemComponent? pseudoItem = null)
+ {
+ if (!Resolve(toInsert, ref pseudoItem))
+ return;
+
+ var ev = new PseudoItemInsertDoAfterEvent();
+ var args = new DoAfterArgs(inserter, 5f, ev, toInsert, target: toInsert, used: storageEntity)
+ {
+ BreakOnTargetMove = true,
+ BreakOnUserMove = true,
+ NeedHand = true
+ };
+
+ _doAfter.TryStartDoAfter(args);
+ }
+}
diff --git a/Content.Shared/Nyanotrasen/Actions/Events/EatMouseActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/EatMouseActionEvent.cs
new file mode 100644
index 0000000000..63573febb5
--- /dev/null
+++ b/Content.Shared/Nyanotrasen/Actions/Events/EatMouseActionEvent.cs
@@ -0,0 +1,3 @@
+namespace Content.Shared.Actions.Events;
+
+public sealed partial class EatMouseActionEvent : InstantActionEvent {}
diff --git a/Content.Shared/Nyanotrasen/Actions/Events/HairballActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/HairballActionEvent.cs
new file mode 100644
index 0000000000..6cf55a224e
--- /dev/null
+++ b/Content.Shared/Nyanotrasen/Actions/Events/HairballActionEvent.cs
@@ -0,0 +1,3 @@
+namespace Content.Shared.Actions.Events;
+
+public sealed partial class HairballActionEvent : InstantActionEvent {}
diff --git a/Content.Shared/Nyanotrasen/Item/PseudoItemInsertDoAfterEvent.cs b/Content.Shared/Nyanotrasen/Item/PseudoItemInsertDoAfterEvent.cs
new file mode 100644
index 0000000000..daff55d277
--- /dev/null
+++ b/Content.Shared/Nyanotrasen/Item/PseudoItemInsertDoAfterEvent.cs
@@ -0,0 +1,11 @@
+using Robust.Shared.Serialization;
+using Content.Shared.DoAfter;
+
+namespace Content.Shared.Item.PseudoItem;
+
+
+[Serializable, NetSerializable]
+public sealed partial class PseudoItemInsertDoAfterEvent : SimpleDoAfterEvent
+{
+}
+
diff --git a/Resources/Audio/Nyanotrasen/Effects/Species/hairball.ogg b/Resources/Audio/Nyanotrasen/Effects/Species/hairball.ogg
new file mode 100644
index 0000000000..f7fb40de60
Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Effects/Species/hairball.ogg differ
diff --git a/Resources/Audio/Nyanotrasen/Effects/Species/license.txt b/Resources/Audio/Nyanotrasen/Effects/Species/license.txt
new file mode 100644
index 0000000000..0370cf25c6
--- /dev/null
+++ b/Resources/Audio/Nyanotrasen/Effects/Species/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/Audio/Nyanotrasen/Voice/Felinid/attributions.yml b/Resources/Audio/Nyanotrasen/Voice/Felinid/attributions.yml
new file mode 100644
index 0000000000..bca921c51d
--- /dev/null
+++ b/Resources/Audio/Nyanotrasen/Voice/Felinid/attributions.yml
@@ -0,0 +1,24 @@
+- files: ["cat_hiss1.ogg", "cat_hiss2.ogg"]
+ license: "CC-BY-4.0"
+ copyright: "Original sound by https://freesound.org/people/secondbody/ - cut out two clips of cat hissing, cleaned up, and converted to ogg."
+ source: "https://freesound.org/people/secondbody/sounds/50357/"
+
+- files: ["cat_meow1.ogg", "cat_meow2.ogg", "cat_meow3.ogg"]
+ license: "CC-BY-3.0"
+ copyright: "Original sound by https://freesound.org/people/ignotus/ - cut out three clips of cat meowing, cleaned up, and converted to ogg."
+ source: "https://freesound.org/people/ignotus/sounds/26104/"
+
+- files: ["cat_mew1.ogg", "cat_mew2.ogg"]
+ license: "CC0-1.0"
+ copyright: "Original sound by https://freesound.org/people/videog/ - cut out two clips of kittens mewing, cleaned up, and converted to ogg."
+ source: "https://freesound.org/people/videog/sounds/149191/"
+
+- files: ["cat_purr1.ogg"]
+ license: "CC-BY-4.0"
+ copyright: "Original sound by https://freesound.org/people/klangstrand/ - cut out short clip, fade in and out, converted to ogg."
+ source: "https://freesound.org/people/klangstrand/sounds/213951/"
+
+- files: ["cat_growl1.ogg"]
+ license: "CC0-1.0"
+ copyright: "Original sound by https://freesound.org/people/Zabuhailo/ - fade in and out, converted to ogg."
+ source: "https://freesound.org/people/Zabuhailo/sounds/146968/"
diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_growl1.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_growl1.ogg
new file mode 100644
index 0000000000..f7c5b43cee
Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_growl1.ogg differ
diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_hiss1.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_hiss1.ogg
new file mode 100644
index 0000000000..10cfe9670d
Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_hiss1.ogg differ
diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_hiss2.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_hiss2.ogg
new file mode 100644
index 0000000000..faaa953ee1
Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_hiss2.ogg differ
diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow1.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow1.ogg
new file mode 100644
index 0000000000..6ed99f0d58
Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow1.ogg differ
diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow2.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow2.ogg
new file mode 100644
index 0000000000..34bb375fe5
Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow2.ogg differ
diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow3.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow3.ogg
new file mode 100644
index 0000000000..0af0cb0e07
Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow3.ogg differ
diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_mew1.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_mew1.ogg
new file mode 100644
index 0000000000..e41650e0fb
Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_mew1.ogg differ
diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_mew2.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_mew2.ogg
new file mode 100644
index 0000000000..d82657b9a4
Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_mew2.ogg differ
diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_purr1.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_purr1.ogg
new file mode 100644
index 0000000000..d7ef89fc50
Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_purr1.ogg differ
diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream1.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream1.ogg
new file mode 100644
index 0000000000..2ecc1cf64d
Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream1.ogg differ
diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream2.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream2.ogg
new file mode 100644
index 0000000000..abb19bcba1
Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream2.ogg differ
diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream3.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream3.ogg
new file mode 100644
index 0000000000..5e29752046
Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream3.ogg differ
diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_wilhelm.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_wilhelm.ogg
new file mode 100644
index 0000000000..45934ebeb3
Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_wilhelm.ogg differ
diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/license.txt b/Resources/Audio/Nyanotrasen/Voice/Felinid/license.txt
new file mode 100644
index 0000000000..89905a5d65
--- /dev/null
+++ b/Resources/Audio/Nyanotrasen/Voice/Felinid/license.txt
@@ -0,0 +1,4 @@
+cat_scream1.ogg licensed under CC0 1.0 taken from Queen_Westeros at https://freesound.org/people/queen_westeros/sounds/222590/
+cat_scream2.ogg licensed under CC4.0 taken from InspectorJ at https://freesound.org/people/InspectorJ/sounds/415209/
+cat_scream3.ogg licensed under CC sampling plus 1.0 taken from Hamface at https://freesound.org/people/Hamface/sounds/98669/
+cat_wilhelm.ogg used with apologies to type moon
diff --git a/Resources/Locale/en-US/nyanotrasen/abilities/felinid.ftl b/Resources/Locale/en-US/nyanotrasen/abilities/felinid.ftl
new file mode 100644
index 0000000000..9218119416
--- /dev/null
+++ b/Resources/Locale/en-US/nyanotrasen/abilities/felinid.ftl
@@ -0,0 +1,8 @@
+action-name-hairball = Cough Up Hairball
+action-description-hairball = Purge some of your chemstream, and gain a cool hairball to throw at people.
+
+hairball-mask = Take off your {$mask} first.
+hairball-cough = {CAPITALIZE(THE($name))} starts coughing up a hairball!
+
+action-name-eat-mouse = Eat Mouse
+action-description-eat-mouse = Eat the mouse in your hand, gaining nutriment and a hairball charge.
diff --git a/Resources/Locale/en-US/nyanotrasen/markings/felinid.ftl b/Resources/Locale/en-US/nyanotrasen/markings/felinid.ftl
new file mode 100644
index 0000000000..d2e7abae4f
--- /dev/null
+++ b/Resources/Locale/en-US/nyanotrasen/markings/felinid.ftl
@@ -0,0 +1,56 @@
+marking-FelinidEarsBasic = Basic Ears
+marking-FelinidEarsBasic-basic_outer = Outer ear
+marking-FelinidEarsBasic-basic_inner = Inner ear
+
+marking-FelinidEarsCurled = Curled Ears
+marking-FelinidEarsCurled-curled_outer = Outer ear
+marking-FelinidEarsCurled-curled_inner = Inner ear
+
+marking-FelinidEarsDroopy = Droopy Ears
+marking-FelinidEarsDroopy-droopy_outer = Outer ear
+marking-FelinidEarsDroopy-droopy_inner = Inner ear
+
+marking-FelinidEarsFuzzy = Fuzzy Ears
+marking-FelinidEarsFuzzy-basic_outer = Outer ear
+marking-FelinidEarsFuzzy-fuzzy_inner = Ear fuzz
+
+marking-FelinidEarsStubby = Stubby Ears
+marking-FelinidEarsStubby-stubby_outer = Outer ear
+marking-FelinidEarsStubby-stubby_inner = Inner ear
+
+marking-FelinidEarsTall = Tall Ears
+marking-FelinidEarsTall-tall_outer = Outer ear
+marking-FelinidEarsTall-tall_inner = Inner ear
+marking-FelinidEarsTall-tall_fuzz = Ear fuzz
+
+marking-FelinidEarsTorn = Torn Ears
+marking-FelinidEarsTorn-torn_outer = Outer ear
+marking-FelinidEarsTorn-torn_inner = Inner ear
+
+marking-FelinidEarsWide = Wide Ears
+marking-FelinidEarsWide-wide_outer = Outer ear
+marking-FelinidEarsWide-wide_inner = Inner ear
+
+marking-FelinidTailBasic = Basic Tail
+marking-FelinidTailBasic-basic_tail_tip = Tail tip
+marking-FelinidTailBasic-basic_tail_stripes_even = Tail stripes, even
+marking-FelinidTailBasic-basic_tail_stripes_odd = Tail stripes, odd
+
+marking-FelinidTailBasicWithBow = Basic Tail with Bow
+marking-FelinidTailBasicWithBow-basic_tail_tip = Tail tip
+marking-FelinidTailBasicWithBow-basic_tail_stripes_even = Tail stripes, even
+marking-FelinidTailBasicWithBow-basic_tail_stripes_odd = Tail stripes, odd
+marking-FelinidTailBasicWithBow-basic_bow = Bow
+
+marking-FelinidTailBasicWithBell = Basic Tail with Bell
+marking-FelinidTailBasicWithBell-basic_tail_tip = Tail tip
+marking-FelinidTailBasicWithBell-basic_tail_stripes_even = Tail stripes, even
+marking-FelinidTailBasicWithBell-basic_tail_stripes_odd = Tail stripes, odd
+marking-FelinidTailBasicWithBell-basic_bell = Bell
+
+marking-FelinidTailBasicWithBowAndBell = Basic Tail with Bow & Bell
+marking-FelinidTailBasicWithBowAndBell-basic_tail_tip = Tail tip
+marking-FelinidTailBasicWithBowAndBell-basic_tail_stripes_even = Tail stripes, even
+marking-FelinidTailBasicWithBowAndBell-basic_tail_stripes_odd = Tail stripes, odd
+marking-FelinidTailBasicWithBowAndBell-basic_bow = Bow
+marking-FelinidTailBasicWithBowAndBell-basic_bell = Bell
diff --git a/Resources/Locale/en-US/nyanotrasen/species/species.ftl b/Resources/Locale/en-US/nyanotrasen/species/species.ftl
new file mode 100644
index 0000000000..b67fa19141
--- /dev/null
+++ b/Resources/Locale/en-US/nyanotrasen/species/species.ftl
@@ -0,0 +1 @@
+species-name-felinid = Felinid
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
index 7ff922bc01..0db7838183 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
@@ -1014,6 +1014,7 @@
price: 50
- type: Puller
needsHands: true
+ - type: FelinidFood # Nyanotrasen - Felinid, ability to eat mice, see Content.Server/Nyanotrasen/Abilities/Felinid/FelinidSystem.cs
- type: entity
parent: MobMouse
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml
index d41cb8a879..15f3bffbdc 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml
@@ -275,6 +275,7 @@
- type: GuideHelp
guides:
- MinorAntagonists
+ - type: FelinidFood # Nyanotrasen - Felinid, ability to eat rat, see Content.Server/Nyanotrasen/Abilities/Felinid/FelinidSystem.cs
- type: entity
id: ActionRatKingRaiseArmy
diff --git a/Resources/Prototypes/Nyanotrasen/Actions/types.yml b/Resources/Prototypes/Nyanotrasen/Actions/types.yml
new file mode 100644
index 0000000000..3c6c8519a1
--- /dev/null
+++ b/Resources/Prototypes/Nyanotrasen/Actions/types.yml
@@ -0,0 +1,21 @@
+- type: entity
+ id: ActionEatMouse
+ name: action-name-eat-mouse
+ description: action-description-eat-mouse
+ noSpawn: true
+ components:
+ - type: InstantAction
+ icon: Nyanotrasen/Icons/verbiconfangs.png
+ event: !type:EatMouseActionEvent
+
+- type: entity
+ id: ActionHairball
+ name: action-name-hairball
+ description: action-description-hairball
+ noSpawn: true
+ components:
+ - type: InstantAction
+ charges: 1
+ icon: { sprite: Nyanotrasen/Objects/Specific/Species/felinid.rsi, state: icon }
+ useDelay: 30
+ event: !type:HairballActionEvent
diff --git a/Resources/Prototypes/Nyanotrasen/Damage/modifier_sets.yml b/Resources/Prototypes/Nyanotrasen/Damage/modifier_sets.yml
index 5ee9013a88..a92c387bd0 100644
--- a/Resources/Prototypes/Nyanotrasen/Damage/modifier_sets.yml
+++ b/Resources/Prototypes/Nyanotrasen/Damage/modifier_sets.yml
@@ -1,3 +1,10 @@
+- type: damageModifierSet
+ id: Felinid
+ coefficients:
+ Blunt: 1.15
+ Slash: 1.15
+ Piercing: 1.15
+
# For airlocks, etc, to prevent cheesing.
- type: damageModifierSet
id: MetallicStructure
diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Body/Prototypes/felinid.yml b/Resources/Prototypes/Nyanotrasen/Entities/Body/Prototypes/felinid.yml
new file mode 100644
index 0000000000..f0c98ff83e
--- /dev/null
+++ b/Resources/Prototypes/Nyanotrasen/Entities/Body/Prototypes/felinid.yml
@@ -0,0 +1,49 @@
+- type: body
+ id: Felinid
+ name: "felinid"
+ root: torso
+ slots:
+ head:
+ part: HeadHuman
+ connections:
+ - torso
+ organs:
+ brain: OrganHumanBrain
+ eyes: OrganHumanEyes
+ torso:
+ part: TorsoHuman
+ connections:
+ - left arm
+ - right arm
+ - left leg
+ - right leg
+ organs:
+ heart: OrganAnimalHeart
+ lungs: OrganHumanLungs
+ stomach: OrganReptilianStomach
+ liver: OrganAnimalLiver
+ kidneys: OrganHumanKidneys
+ right arm:
+ part: RightArmHuman
+ connections:
+ - right hand
+ left arm:
+ part: LeftArmHuman
+ connections:
+ - left hand
+ right hand:
+ part: RightHandHuman
+ left hand:
+ part: LeftHandHuman
+ right leg:
+ part: RightLegHuman
+ connections:
+ - right foot
+ left leg:
+ part: LeftLegHuman
+ connections:
+ - left foot
+ right foot:
+ part: RightFootHuman
+ left foot:
+ part: LeftFootHuman
diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Customization/Markings/felinid.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Customization/Markings/felinid.yml
new file mode 100644
index 0000000000..e27a7f8be1
--- /dev/null
+++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Customization/Markings/felinid.yml
@@ -0,0 +1,153 @@
+# Felinid Ears
+
+- type: marking
+ id: FelinidEarsBasic
+ bodyPart: HeadTop
+ markingCategory: HeadTop
+ speciesRestriction: [Felinid]
+ sprites:
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: basic_outer
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: basic_inner
+
+- type: marking
+ id: FelinidEarsCurled
+ bodyPart: HeadTop
+ markingCategory: HeadTop
+ speciesRestriction: [Felinid]
+ sprites:
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: curled_outer
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: curled_inner
+
+- type: marking
+ id: FelinidEarsDroopy
+ bodyPart: HeadTop
+ markingCategory: HeadTop
+ speciesRestriction: [Felinid]
+ sprites:
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: droopy_outer
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: droopy_inner
+
+- type: marking
+ id: FelinidEarsFuzzy
+ bodyPart: HeadTop
+ markingCategory: HeadTop
+ speciesRestriction: [Felinid]
+ sprites:
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: basic_outer
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: fuzzy_inner
+
+- type: marking
+ id: FelinidEarsStubby
+ bodyPart: HeadTop
+ markingCategory: HeadTop
+ speciesRestriction: [Felinid]
+ sprites:
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: stubby_outer
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: stubby_inner
+
+- type: marking
+ id: FelinidEarsTall
+ bodyPart: HeadTop
+ markingCategory: HeadTop
+ speciesRestriction: [Felinid]
+ sprites:
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: tall_outer
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: tall_inner
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: tall_fuzz
+
+- type: marking
+ id: FelinidEarsTorn
+ bodyPart: HeadTop
+ markingCategory: HeadTop
+ speciesRestriction: [Felinid]
+ sprites:
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: torn_outer
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: torn_inner
+
+- type: marking
+ id: FelinidEarsWide
+ bodyPart: HeadTop
+ markingCategory: HeadTop
+ speciesRestriction: [Felinid]
+ sprites:
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: wide_outer
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_ears.rsi
+ state: wide_inner
+
+# Felinid Tails
+
+- type: marking
+ id: FelinidTailBasic
+ bodyPart: Tail
+ markingCategory: Tail
+ speciesRestriction: [Felinid]
+ sprites:
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_tail_tip
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_tail_stripes_even
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_tail_stripes_odd
+
+- type: marking
+ id: FelinidTailBasicWithBow
+ bodyPart: Tail
+ markingCategory: Tail
+ speciesRestriction: [Felinid]
+ sprites:
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_tail_tip
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_tail_stripes_even
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_tail_stripes_odd
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_bow
+
+- type: marking
+ id: FelinidTailBasicWithBell
+ bodyPart: Tail
+ markingCategory: Tail
+ speciesRestriction: [Felinid]
+ sprites:
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_tail_tip
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_tail_stripes_even
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_tail_stripes_odd
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_bell
+
+- type: marking
+ id: FelinidTailBasicWithBowAndBell
+ bodyPart: Tail
+ markingCategory: Tail
+ speciesRestriction: [Felinid]
+ sprites:
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_tail_tip
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_tail_stripes_even
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_tail_stripes_odd
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_bow
+ - sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi
+ state: basic_bell
diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml
new file mode 100644
index 0000000000..2f743e5a9f
--- /dev/null
+++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml
@@ -0,0 +1,35 @@
+- type: entity
+ save: false
+ name: Urist McFelinid
+ parent: MobFelinidBase
+ id: MobFelinid
+ components:
+ - type: CombatMode
+ - type: InteractionPopup
+ successChance: 1
+ interactSuccessString: hugging-success-generic
+ interactSuccessSound: /Audio/Effects/thudswoosh.ogg
+ messagePerceivedByOthers: hugging-success-generic-others
+ - type: MindContainer
+ showExamineInfo: true
+ - type: Input
+ context: "human"
+ - type: MobMover
+ - type: InputMover
+ - type: Respirator
+ damage:
+ types:
+ Asphyxiation: 1.0
+ damageRecovery:
+ types:
+ Asphyxiation: -1.0
+ - type: Alerts
+ - type: Actions
+ - type: Eye
+ - type: CameraRecoil
+ - type: Examiner
+ - type: CanHostGuardian
+ - type: NpcFactionMember
+ factions:
+ - NanoTrasen
+# - type: PotentialPsionic
diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml
new file mode 100644
index 0000000000..35c1bc73a0
--- /dev/null
+++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml
@@ -0,0 +1,68 @@
+- type: entity
+ save: false
+ name: Urist McFelinid
+ parent: BaseMobHuman
+ id: MobFelinidBase
+ abstract: true
+ components:
+ - type: Sprite
+ scale: 0.8, 0.8
+ - type: HumanoidAppearance
+ species: Felinid
+ - type: Fixtures
+ fixtures: # TODO: This needs a second fixture just for mob collisions.
+ fix1:
+ shape:
+ !type:PhysShapeCircle
+ radius: 0.28
+ density: 140
+ restitution: 0.0
+ mask:
+ - MobMask
+ layer:
+ - MobLayer
+ - type: Body
+ prototype: Felinid
+ - type: Damageable
+ damageModifierSet: Felinid
+ - type: MeleeWeapon
+ soundHit:
+ collection: Punch
+ animation: WeaponArcClaw
+ damage:
+ types:
+ Blunt: 1
+ Slash: 5
+# - type: DiseaseCarrier
+# naturalImmunities:
+# - OwOnavirus
+ - type: Thieving
+ stealthy: true
+ stripTimeReduction: 1
+ - type: Speech
+ speechSounds: Alto
+ - type: DamageOnHighSpeedImpact
+ damage:
+ types:
+ Blunt: 1
+ - type: Stamina
+ critThreshold: 85
+ - type: PseudoItem
+ - type: Vocal
+ wilhelm: "/Audio/Nyanotrasen/Voice/Felinid/cat_wilhelm.ogg"
+ sounds:
+ Male: MaleFelinid
+ Female: FemaleFelinid
+ Unsexed: MaleFelinid
+ - type: Felinid
+
+- type: entity
+ save: false
+ name: Urist McHands
+ parent: MobHumanDummy
+ id: MobFelinidDummy
+ noSpawn: true
+ description: A dummy felinid meant to be used in character setup.
+ components:
+ - type: HumanoidAppearance
+ species: Felinid
diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Species/felinid.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Species/felinid.yml
new file mode 100644
index 0000000000..29bdc2c483
--- /dev/null
+++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Species/felinid.yml
@@ -0,0 +1,23 @@
+- type: entity
+ parent: BaseItem
+ id: Hairball
+ name: hairball
+ description: Felinids, man... Placeholder sprite.
+ components:
+ - type: Sprite
+ sprite: Nyanotrasen/Objects/Specific/Species/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
diff --git a/Resources/Prototypes/Nyanotrasen/SoundCollections/felinid.yml b/Resources/Prototypes/Nyanotrasen/SoundCollections/felinid.yml
new file mode 100644
index 0000000000..c0aaa6c1b7
--- /dev/null
+++ b/Resources/Prototypes/Nyanotrasen/SoundCollections/felinid.yml
@@ -0,0 +1,35 @@
+- type: soundCollection
+ id: FelinidScreams
+ files:
+ - /Audio/Nyanotrasen/Voice/Felinid/cat_scream1.ogg
+ - /Audio/Nyanotrasen/Voice/Felinid/cat_scream2.ogg
+ - /Audio/Nyanotrasen/Voice/Felinid/cat_scream3.ogg
+
+- type: soundCollection
+ id: FelinidHisses
+ files:
+ - /Audio/Nyanotrasen/Voice/Felinid/cat_hiss1.ogg
+ - /Audio/Nyanotrasen/Voice/Felinid/cat_hiss2.ogg
+
+- type: soundCollection
+ id: FelinidMeows
+ files:
+ - /Audio/Nyanotrasen/Voice/Felinid/cat_meow1.ogg
+ - /Audio/Nyanotrasen/Voice/Felinid/cat_meow2.ogg
+ - /Audio/Nyanotrasen/Voice/Felinid/cat_meow3.ogg
+
+- type: soundCollection
+ id: FelinidMews
+ files:
+ - /Audio/Nyanotrasen/Voice/Felinid/cat_mew1.ogg
+ - /Audio/Nyanotrasen/Voice/Felinid/cat_mew2.ogg
+
+- type: soundCollection
+ id: FelinidGrowls
+ files:
+ - /Audio/Nyanotrasen/Voice/Felinid/cat_growl1.ogg
+
+- type: soundCollection
+ id: FelinidPurrs
+ files:
+ - /Audio/Nyanotrasen/Voice/Felinid/cat_purr1.ogg
diff --git a/Resources/Prototypes/Nyanotrasen/Species/felinid.yml b/Resources/Prototypes/Nyanotrasen/Species/felinid.yml
new file mode 100644
index 0000000000..754a4ae906
--- /dev/null
+++ b/Resources/Prototypes/Nyanotrasen/Species/felinid.yml
@@ -0,0 +1,36 @@
+- type: species
+ id: Felinid
+ name: species-name-felinid
+ roundStart: true
+ prototype: MobFelinid
+ sprites: MobHumanSprites
+ markingLimits: MobFelinidMarkingLimits
+ dollPrototype: MobFelinidDummy
+ skinColoration: HumanToned
+
+- type: markingPoints
+ id: MobFelinidMarkingLimits
+ points:
+ Hair:
+ points: 1
+ required: false
+ FacialHair:
+ points: 1
+ required: false
+ Tail:
+ points: 1
+ required: true
+ defaultMarkings: [ FelinidTailBasic ]
+ HeadTop:
+ points: 1
+ required: true
+ defaultMarkings: [ FelinidEarsBasic ]
+ Chest:
+ points: 1
+ required: false
+ Legs:
+ points: 2
+ required: false
+ Arms:
+ points: 2
+ required: false
diff --git a/Resources/Prototypes/Nyanotrasen/Voice/speech_emote_sounds.yml b/Resources/Prototypes/Nyanotrasen/Voice/speech_emote_sounds.yml
index ac0827618a..3dcc68250d 100644
--- a/Resources/Prototypes/Nyanotrasen/Voice/speech_emote_sounds.yml
+++ b/Resources/Prototypes/Nyanotrasen/Voice/speech_emote_sounds.yml
@@ -1,3 +1,60 @@
+# species
+- type: emoteSounds
+ id: MaleFelinid
+ params:
+ variation: 0.125
+ sounds:
+ Scream:
+ collection: FelinidScreams
+ Laugh:
+ collection: MaleLaugh
+ Sneeze:
+ collection: MaleSneezes
+ Cough:
+ collection: MaleCoughs
+ Crying:
+ collection: MaleCry
+ Whistle:
+ collection: Whistles
+ Hiss:
+ collection: FelinidHisses
+ Meow:
+ collection: FelinidMeows
+ Mew:
+ collection: FelinidMews
+ Growl:
+ collection: FelinidGrowls
+ Purr:
+ collection: FelinidPurrs
+
+- type: emoteSounds
+ id: FemaleFelinid
+ params:
+ variation: 0.125
+ sounds:
+ Scream:
+ collection: FelinidScreams
+ Laugh:
+ collection: FemaleLaugh
+ Sneeze:
+ collection: FemaleSneezes
+ Cough:
+ collection: FemaleCoughs
+ Crying:
+ collection: FemaleCry
+ Whistle:
+ collection: Whistles
+ Hiss:
+ collection: FelinidHisses
+ Meow:
+ collection: FelinidMeows
+ Mew:
+ collection: FelinidMews
+ Growl:
+ collection: FelinidGrowls
+ Purr:
+ collection: FelinidPurrs
+
# mobs
- type: emoteSounds
id: Mothroach
diff --git a/Resources/Prototypes/Nyanotrasen/Voice/speech_emotes.yml b/Resources/Prototypes/Nyanotrasen/Voice/speech_emotes.yml
new file mode 100644
index 0000000000..21bb428029
--- /dev/null
+++ b/Resources/Prototypes/Nyanotrasen/Voice/speech_emotes.yml
@@ -0,0 +1,68 @@
+# vocal emotes
+- type: emote
+ id: Hiss
+ category: Vocal
+ chatMessages: [hisses.]
+ chatTriggers:
+ - hiss
+ - hisses
+ - hisses.
+ - hisses!
+ - hissing
+ - hissed
+
+- type: emote
+ id: Meow
+ category: Vocal
+ chatMessages: [meows.]
+ chatTriggers:
+ - meow
+ - meows
+ - meows.
+ - meows!
+ - meowing
+ - meowed
+ - miau
+ - miaus
+ - miaus.
+ - miaus!
+ - nya
+ - nyas
+ - nyas.
+ - nyas!
+
+- type: emote
+ id: Mew
+ category: Vocal
+ chatMessages: [mews.]
+ chatTriggers:
+ - mew
+ - mews
+ - mews.
+ - mews!
+ - mewing
+ - mewed
+
+- type: emote
+ id: Growl
+ category: Vocal
+ chatMessages: [growls.]
+ chatTriggers:
+ - growl
+ - growls
+ - growls.
+ - growls!
+ - growling
+ - growled
+
+- type: emote
+ id: Purr
+ category: Vocal
+ chatMessages: [purrs.]
+ chatTriggers:
+ - purr
+ - purrs
+ - purrs.
+ - purrs!
+ - purring
+ - purred
diff --git a/Resources/Prototypes/Species/human.yml b/Resources/Prototypes/Species/human.yml
index 979e226c81..956c0d1946 100644
--- a/Resources/Prototypes/Species/human.yml
+++ b/Resources/Prototypes/Species/human.yml
@@ -30,6 +30,8 @@
RLeg: MobHumanRLeg
LFoot: MobHumanLFoot
RFoot: MobHumanRFoot
+ Tail: MobHumanoidAnyMarking # Nyanotrasen - Felinid
+ HeadTop: MobHumanoidAnyMarking # Nyanotrasen - Felinid & Oni
- type: markingPoints
id: MobHumanMarkingLimits
diff --git a/Resources/Prototypes/Species/species_weights.yml b/Resources/Prototypes/Species/species_weights.yml
index 63c196b95d..ca66b529d1 100644
--- a/Resources/Prototypes/Species/species_weights.yml
+++ b/Resources/Prototypes/Species/species_weights.yml
@@ -5,4 +5,6 @@
Human: 5
Reptilian: 4
SlimePerson: 4
+ Felinid: 4 # Nyanotrasen - Felinid, see Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml
+ Vulpkanin: 3 # DeltaV - Vulpkanin, see Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml
Diona: 2
diff --git a/Resources/Textures/Nyanotrasen/Icons/verbiconfangs.png b/Resources/Textures/Nyanotrasen/Icons/verbiconfangs.png
new file mode 100644
index 0000000000..4511cbd21f
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Icons/verbiconfangs.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/basic_inner.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/basic_inner.png
new file mode 100644
index 0000000000..15d3feceeb
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/basic_inner.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/basic_outer.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/basic_outer.png
new file mode 100644
index 0000000000..dacfe6dcef
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/basic_outer.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/curled_inner.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/curled_inner.png
new file mode 100644
index 0000000000..40c07fafc7
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/curled_inner.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/curled_outer.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/curled_outer.png
new file mode 100644
index 0000000000..1fc78f303f
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/curled_outer.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/droopy_inner.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/droopy_inner.png
new file mode 100644
index 0000000000..ffbca7bd46
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/droopy_inner.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/droopy_outer.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/droopy_outer.png
new file mode 100644
index 0000000000..268e8ec732
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/droopy_outer.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/fuzzy_inner.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/fuzzy_inner.png
new file mode 100644
index 0000000000..5af32433c6
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/fuzzy_inner.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/meta.json b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/meta.json
new file mode 100644
index 0000000000..9d6aeb1ae9
--- /dev/null
+++ b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/meta.json
@@ -0,0 +1,75 @@
+{
+ "version": 1,
+ "copyright": "@Vordenburg",
+ "license": "CC-BY-SA-4.0",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "basic_inner",
+ "directions": 4
+ },
+ {
+ "name": "basic_outer",
+ "directions": 4
+ },
+ {
+ "name": "curled_inner",
+ "directions": 4
+ },
+ {
+ "name": "curled_outer",
+ "directions": 4
+ },
+ {
+ "name": "fuzzy_inner",
+ "directions": 4
+ },
+ {
+ "name": "tall_outer",
+ "directions": 4
+ },
+ {
+ "name": "tall_inner",
+ "directions": 4
+ },
+ {
+ "name": "tall_fuzz",
+ "directions": 4
+ },
+ {
+ "name": "torn_outer",
+ "directions": 4
+ },
+ {
+ "name": "torn_inner",
+ "directions": 4
+ },
+ {
+ "name": "stubby_outer",
+ "directions": 4
+ },
+ {
+ "name": "stubby_inner",
+ "directions": 4
+ },
+ {
+ "name": "droopy_outer",
+ "directions": 4
+ },
+ {
+ "name": "droopy_inner",
+ "directions": 4
+ },
+ {
+ "name": "wide_inner",
+ "directions": 4
+ },
+ {
+ "name": "wide_outer",
+ "directions": 4
+ }
+ ]
+}
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/stubby_inner.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/stubby_inner.png
new file mode 100644
index 0000000000..dd417e8a85
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/stubby_inner.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/stubby_outer.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/stubby_outer.png
new file mode 100644
index 0000000000..317166f934
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/stubby_outer.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/tall_fuzz.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/tall_fuzz.png
new file mode 100644
index 0000000000..e17ef18cae
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/tall_fuzz.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/tall_inner.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/tall_inner.png
new file mode 100644
index 0000000000..7e9a8897e3
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/tall_inner.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/tall_outer.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/tall_outer.png
new file mode 100644
index 0000000000..3847b37b9c
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/tall_outer.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/torn_inner.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/torn_inner.png
new file mode 100644
index 0000000000..54c8ca35bf
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/torn_inner.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/torn_outer.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/torn_outer.png
new file mode 100644
index 0000000000..56fab2c36f
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/torn_outer.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/wide_inner.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/wide_inner.png
new file mode 100644
index 0000000000..2be9161aa0
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/wide_inner.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/wide_outer.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/wide_outer.png
new file mode 100644
index 0000000000..bfd0042ff5
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_ears.rsi/wide_outer.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/basic_bell.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/basic_bell.png
new file mode 100644
index 0000000000..8a6ca94940
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/basic_bell.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/basic_bow.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/basic_bow.png
new file mode 100644
index 0000000000..bfb8843413
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/basic_bow.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/basic_tail_stripes_even.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/basic_tail_stripes_even.png
new file mode 100644
index 0000000000..4950c72d7f
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/basic_tail_stripes_even.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/basic_tail_stripes_odd.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/basic_tail_stripes_odd.png
new file mode 100644
index 0000000000..10e47bbd2b
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/basic_tail_stripes_odd.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/basic_tail_tip.png b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/basic_tail_tip.png
new file mode 100644
index 0000000000..0248320764
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/basic_tail_tip.png differ
diff --git a/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/meta.json b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/meta.json
new file mode 100644
index 0000000000..a333d23a8e
--- /dev/null
+++ b/Resources/Textures/Nyanotrasen/Mobs/Customization/felinid_tails.rsi/meta.json
@@ -0,0 +1,181 @@
+{
+ "version": 1,
+ "copyright": "@Vordenburg",
+ "license": "CC-BY-SA-4.0",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "basic_tail_tip",
+ "directions": 4,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "basic_tail_stripes_even",
+ "directions": 4,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "basic_tail_stripes_odd",
+ "directions": 4,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "basic_bow",
+ "directions": 4,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "basic_bell",
+ "directions": 4,
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ],
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ }
+ ]
+}
diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Species/felinid.rsi/icon.png b/Resources/Textures/Nyanotrasen/Objects/Specific/Species/felinid.rsi/icon.png
new file mode 100644
index 0000000000..f9483ac80f
Binary files /dev/null and b/Resources/Textures/Nyanotrasen/Objects/Specific/Species/felinid.rsi/icon.png differ
diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Species/felinid.rsi/meta.json b/Resources/Textures/Nyanotrasen/Objects/Specific/Species/felinid.rsi/meta.json
new file mode 100644
index 0000000000..aaad7ca5b9
--- /dev/null
+++ b/Resources/Textures/Nyanotrasen/Objects/Specific/Species/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"
+ }
+ ]
+}