diff --git a/Content.Client/Overlays/ColorTintOverlay.cs b/Content.Client/Overlays/ColorTintOverlay.cs
new file mode 100644
index 00000000000..f40a8d7342e
--- /dev/null
+++ b/Content.Client/Overlays/ColorTintOverlay.cs
@@ -0,0 +1,63 @@
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Shared.Enums;
+using Robust.Shared.Prototypes;
+using Content.Shared.Shadowkin;
+
+namespace Content.Client.Overlays;
+
+///
+/// A simple overlay that applies a colored tint to the screen.
+///
+public sealed class ColorTintOverlay : Overlay
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] IEntityManager _entityManager = default!;
+
+ public override bool RequestScreenTexture => true;
+ public override OverlaySpace Space => OverlaySpace.WorldSpace;
+ private readonly ShaderInstance _shader;
+
+ ///
+ /// The color to tint the screen to as RGB on a scale of 0-1.
+ ///
+ public Vector3? TintColor = null;
+ ///
+ /// The percent to tint the screen by on a scale of 0-1.
+ ///
+ public float? TintAmount = null;
+
+ public ColorTintOverlay()
+ {
+ IoCManager.InjectDependencies(this);
+ _shader = _prototype.Index("ColorTint").InstanceUnique();
+ }
+
+ protected override bool BeforeDraw(in OverlayDrawArgs args)
+ {
+ if (_player.LocalEntity is not { Valid: true } player
+ || !_entityManager.HasComponent(player))
+ return false;
+
+ return base.BeforeDraw(in args);
+ }
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ if (ScreenTexture is null)
+ return;
+
+ _shader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
+ if (TintColor != null)
+ _shader.SetParameter("tint_color", (Vector3) TintColor);
+ if (TintAmount != null)
+ _shader.SetParameter("tint_amount", (float) TintAmount);
+
+ var worldHandle = args.WorldHandle;
+ var viewport = args.WorldBounds;
+ worldHandle.SetTransform(Matrix3.Identity);
+ worldHandle.UseShader(_shader);
+ worldHandle.DrawRect(viewport, Color.White);
+ worldHandle.UseShader(null);
+ }
+}
\ No newline at end of file
diff --git a/Content.Client/Overlays/EtherealOverlay.cs b/Content.Client/Overlays/EtherealOverlay.cs
new file mode 100644
index 00000000000..3d771de8ceb
--- /dev/null
+++ b/Content.Client/Overlays/EtherealOverlay.cs
@@ -0,0 +1,48 @@
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Shared.Enums;
+using Robust.Shared.Prototypes;
+using Content.Shared.Shadowkin;
+
+namespace Content.Client.Overlays;
+
+public sealed class EtherealOverlay : Overlay
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] IEntityManager _entityManager = default!;
+
+ public override bool RequestScreenTexture => true;
+ public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
+ private readonly ShaderInstance _shader;
+
+ public EtherealOverlay()
+ {
+ IoCManager.InjectDependencies(this);
+ _shader = _prototype.Index("Ethereal").InstanceUnique();
+ }
+
+ protected override bool BeforeDraw(in OverlayDrawArgs args)
+ {
+ if (_player.LocalEntity is not { Valid: true } player
+ || !_entityManager.HasComponent(player))
+ return false;
+
+ return base.BeforeDraw(in args);
+ }
+
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ if (ScreenTexture is null)
+ return;
+
+ _shader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
+
+ var worldHandle = args.WorldHandle;
+ var viewport = args.WorldBounds;
+ worldHandle.SetTransform(Matrix3.Identity);
+ worldHandle.UseShader(_shader);
+ worldHandle.DrawRect(viewport, Color.White);
+ worldHandle.UseShader(null);
+ }
+}
\ No newline at end of file
diff --git a/Content.Client/Shadowkin/EtherealSystem.cs b/Content.Client/Shadowkin/EtherealSystem.cs
new file mode 100644
index 00000000000..cb289a87f12
--- /dev/null
+++ b/Content.Client/Shadowkin/EtherealSystem.cs
@@ -0,0 +1,52 @@
+using Content.Shared.Shadowkin;
+using Robust.Client.Graphics;
+using Robust.Shared.Player;
+using Content.Client.Overlays;
+
+namespace Content.Client.Shadowkin;
+
+public sealed partial class EtherealSystem : EntitySystem
+{
+ [Dependency] private readonly IOverlayManager _overlayMan = default!;
+ [Dependency] private readonly ISharedPlayerManager _playerMan = default!;
+
+ private EtherealOverlay _overlay = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(Onhutdown);
+ SubscribeLocalEvent(OnPlayerAttached);
+ SubscribeLocalEvent(OnPlayerDetached);
+
+ _overlay = new();
+ }
+
+ private void OnInit(EntityUid uid, EtherealComponent component, ComponentInit args)
+ {
+ if (uid != _playerMan.LocalEntity)
+ return;
+
+ _overlayMan.AddOverlay(_overlay);
+ }
+
+ private void Onhutdown(EntityUid uid, EtherealComponent component, ComponentShutdown args)
+ {
+ if (uid != _playerMan.LocalEntity)
+ return;
+
+ _overlayMan.RemoveOverlay(_overlay);
+ }
+
+ private void OnPlayerAttached(EntityUid uid, EtherealComponent component, LocalPlayerAttachedEvent args)
+ {
+ _overlayMan.AddOverlay(_overlay);
+ }
+
+ private void OnPlayerDetached(EntityUid uid, EtherealComponent component, LocalPlayerDetachedEvent args)
+ {
+ _overlayMan.RemoveOverlay(_overlay);
+ }
+}
diff --git a/Content.Client/Shadowkin/ShadowkinSystem.cs b/Content.Client/Shadowkin/ShadowkinSystem.cs
new file mode 100644
index 00000000000..d8e1b69fc71
--- /dev/null
+++ b/Content.Client/Shadowkin/ShadowkinSystem.cs
@@ -0,0 +1,114 @@
+using Content.Shared.Shadowkin;
+using Content.Shared.CCVar;
+using Robust.Client.Graphics;
+using Robust.Shared.Configuration;
+using Robust.Shared.Player;
+using Content.Shared.Humanoid;
+using Content.Shared.Abilities.Psionics;
+using Content.Client.Overlays;
+
+namespace Content.Client.Shadowkin;
+
+public sealed partial class ShadowkinSystem : EntitySystem
+{
+ [Dependency] private readonly IOverlayManager _overlayMan = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+ [Dependency] private readonly ISharedPlayerManager _playerMan = default!;
+
+ private ColorTintOverlay _overlay = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(Onhutdown);
+ SubscribeLocalEvent(OnPlayerAttached);
+ SubscribeLocalEvent(OnPlayerDetached);
+
+ Subs.CVar(_cfg, CCVars.NoVisionFilters, OnNoVisionFiltersChanged);
+
+ _overlay = new();
+ }
+
+ private void OnInit(EntityUid uid, ShadowkinComponent component, ComponentInit args)
+ {
+ if (uid != _playerMan.LocalEntity
+ || _cfg.GetCVar(CCVars.NoVisionFilters))
+ return;
+
+ _overlayMan.AddOverlay(_overlay);
+ }
+
+ private void Onhutdown(EntityUid uid, ShadowkinComponent component, ComponentShutdown args)
+ {
+ if (uid != _playerMan.LocalEntity)
+ return;
+
+ _overlayMan.RemoveOverlay(_overlay);
+ }
+
+ private void OnPlayerAttached(EntityUid uid, ShadowkinComponent component, LocalPlayerAttachedEvent args)
+ {
+ if (_cfg.GetCVar(CCVars.NoVisionFilters))
+ return;
+
+ _overlayMan.AddOverlay(_overlay);
+ }
+
+ private void OnPlayerDetached(EntityUid uid, ShadowkinComponent component, LocalPlayerDetachedEvent args)
+ {
+ _overlayMan.RemoveOverlay(_overlay);
+ }
+
+ private void OnNoVisionFiltersChanged(bool enabled)
+ {
+ if (enabled)
+ _overlayMan.RemoveOverlay(_overlay);
+ else
+ _overlayMan.AddOverlay(_overlay);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ if (_cfg.GetCVar(CCVars.NoVisionFilters))
+ return;
+
+ var uid = _playerMan.LocalEntity;
+ if (uid == null
+ || !TryComp(uid, out var comp)
+ || !TryComp(uid, out var humanoid))
+ return;
+
+ // 1/3 = 0.333...
+ // intensity = min + (power / max)
+ // intensity = intensity / 0.333
+ // intensity = clamp intensity min, max
+
+ var tintIntensity = 0.65f;
+ if (TryComp(uid, out var magic))
+ {
+ var min = 0.45f;
+ var max = 0.75f;
+ tintIntensity = Math.Clamp(min + (magic.Mana / magic.MaxMana) * 0.333f, min, max);
+ }
+
+ UpdateShader(new Vector3(humanoid.EyeColor.R, humanoid.EyeColor.G, humanoid.EyeColor.B), tintIntensity);
+ }
+
+ private void UpdateShader(Vector3? color, float? intensity)
+ {
+ while (_overlayMan.HasOverlay())
+ _overlayMan.RemoveOverlay(_overlay);
+
+ if (color != null)
+ _overlay.TintColor = color;
+ if (intensity != null)
+ _overlay.TintAmount = intensity;
+
+ if (!_cfg.GetCVar(CCVars.NoVisionFilters))
+ _overlayMan.AddOverlay(_overlay);
+ }
+}
diff --git a/Content.Server/Abilities/Psionics/Abilities/DarkSwapSystem.cs b/Content.Server/Abilities/Psionics/Abilities/DarkSwapSystem.cs
new file mode 100644
index 00000000000..fd394e0a228
--- /dev/null
+++ b/Content.Server/Abilities/Psionics/Abilities/DarkSwapSystem.cs
@@ -0,0 +1,58 @@
+using Content.Shared.Abilities.Psionics;
+using Content.Shared.Actions.Events;
+using Content.Shared.Shadowkin;
+using Content.Shared.Physics;
+using Content.Shared.Popups;
+using Content.Shared.Maps;
+using Robust.Server.GameObjects;
+
+namespace Content.Server.Abilities.Psionics
+{
+ public sealed class DarkSwapSystem : EntitySystem
+ {
+ [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly PhysicsSystem _physics = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnPowerUsed);
+ }
+
+ private void OnPowerUsed(DarkSwapActionEvent args)
+ {
+ if (TryComp(args.Performer, out var ethereal))
+ {
+ var tileref = Transform(args.Performer).Coordinates.GetTileRef();
+ if (tileref != null
+ && _physics.GetEntitiesIntersectingBody(args.Performer, (int) CollisionGroup.Impassable).Count > 0)
+ {
+ _popup.PopupEntity(Loc.GetString("revenant-in-solid"), args.Performer, args.Performer);
+ return;
+ }
+
+ if (_psionics.OnAttemptPowerUse(args.Performer, "DarkSwap", args.ManaCost / 2, args.CheckInsulation))
+ {
+ RemComp(args.Performer, ethereal);
+ args.Handled = true;
+ }
+ }
+ else if (_psionics.OnAttemptPowerUse(args.Performer, "DarkSwap", args.ManaCost, args.CheckInsulation))
+ {
+ var newethereal = EnsureComp(args.Performer);
+ newethereal.Darken = true;
+
+ SpawnAtPosition("ShadowkinShadow", Transform(args.Performer).Coordinates);
+ SpawnAtPosition("EffectFlashShadowkinDarkSwapOn", Transform(args.Performer).Coordinates);
+
+ args.Handled = true;
+ }
+
+ if (args.Handled)
+ _psionics.LogPowerUsed(args.Performer, "DarkSwap", 0, 0);
+ }
+ }
+}
+
+
diff --git a/Content.Server/Abilities/Psionics/Abilities/DispelPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/DispelPowerSystem.cs
index cdfda7c8013..ecffc86c76d 100644
--- a/Content.Server/Abilities/Psionics/Abilities/DispelPowerSystem.cs
+++ b/Content.Server/Abilities/Psionics/Abilities/DispelPowerSystem.cs
@@ -38,6 +38,9 @@ public override void Initialize()
private void OnPowerUsed(DispelPowerActionEvent args)
{
+ if (!_psionics.OnAttemptPowerUse(args.Performer, "dispel"))
+ return;
+
var ev = new DispelledEvent();
RaiseLocalEvent(args.Target, ev, false);
diff --git a/Content.Server/Abilities/Psionics/Abilities/HealOtherPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/HealOtherPowerSystem.cs
index 138a341ce85..6a2e90dd88d 100644
--- a/Content.Server/Abilities/Psionics/Abilities/HealOtherPowerSystem.cs
+++ b/Content.Server/Abilities/Psionics/Abilities/HealOtherPowerSystem.cs
@@ -44,7 +44,7 @@ public override void Initialize()
private void OnPowerUsed(EntityUid uid, PsionicComponent component, PsionicHealOtherPowerActionEvent args)
{
- if (component.DoAfter is not null)
+ if (!_psionics.OnAttemptPowerUse(args.Performer, args.PowerName))
return;
args.ModifiedAmplification = _psionics.ModifiedAmplification(uid, component);
diff --git a/Content.Server/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs
index 58d7d804da6..24ef344f638 100644
--- a/Content.Server/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs
+++ b/Content.Server/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs
@@ -19,6 +19,9 @@ public override void Initialize()
private void OnPowerUsed(EntityUid uid, MetapsionicPowerComponent component, MetapsionicPowerActionEvent args)
{
+ if (!_psionics.OnAttemptPowerUse(args.Performer, "metapsionic pulse"))
+ return;
+
foreach (var entity in _lookup.GetEntitiesInRange(uid, component.Range))
{
if (HasComp(entity) && entity != uid && !HasComp(entity) &&
diff --git a/Content.Server/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs
index 2d106706c67..869bf269ab6 100644
--- a/Content.Server/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs
+++ b/Content.Server/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs
@@ -38,7 +38,8 @@ public override void Initialize()
private void OnPowerUsed(MindSwapPowerActionEvent args)
{
- if (!(TryComp(args.Target, out var damageable) && damageable.DamageContainerID == "Biological"))
+ if (!_psionics.OnAttemptPowerUse(args.Performer, "mind swap")
+ || !(TryComp(args.Target, out var damageable) && damageable.DamageContainerID == "Biological"))
return;
Swap(args.Performer, args.Target);
@@ -116,8 +117,8 @@ private void OnGhostAttempt(GhostAttemptHandleEvent args)
private void OnSwapInit(EntityUid uid, MindSwappedComponent component, ComponentInit args)
{
- _actions.AddAction(uid, ref component.MindSwapReturnActionEntity, component.MindSwapReturnActionId );
- _actions.TryGetActionData( component.MindSwapReturnActionEntity, out var actionData );
+ _actions.AddAction(uid, ref component.MindSwapReturnActionEntity, component.MindSwapReturnActionId);
+ _actions.TryGetActionData(component.MindSwapReturnActionEntity, out var actionData);
if (actionData is { UseDelay: not null })
_actions.StartUseDelay(component.MindSwapReturnActionEntity);
}
@@ -132,11 +133,13 @@ public void Swap(EntityUid performer, EntityUid target, bool end = false)
MindComponent? targetMind = null;
// This is here to prevent missing MindContainerComponent Resolve errors.
- if(!_mindSystem.TryGetMind(performer, out var performerMindId, out performerMind)){
+ if (!_mindSystem.TryGetMind(performer, out var performerMindId, out performerMind))
+ {
performerMind = null;
};
- if(!_mindSystem.TryGetMind(target, out var targetMindId, out targetMind)){
+ if (!_mindSystem.TryGetMind(target, out var targetMindId, out targetMind))
+ {
targetMind = null;
};
//This is a terrible way to 'unattach' minds. I wanted to use UnVisit but in TransferTo's code they say
diff --git a/Content.Server/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs
index c2f59206392..22c4f2e5005 100644
--- a/Content.Server/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs
+++ b/Content.Server/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs
@@ -22,6 +22,9 @@ public override void Initialize()
private void OnPowerUsed(NoosphericZapPowerActionEvent args)
{
+ if (!_psionics.OnAttemptPowerUse(args.Performer, "noospheric zap"))
+ return;
+
_beam.TryCreateBeam(args.Performer, args.Target, "LightningNoospheric");
_stunSystem.TryParalyze(args.Target, TimeSpan.FromSeconds(5), false);
diff --git a/Content.Server/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs
index 19658629545..c6a01912a08 100644
--- a/Content.Server/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs
+++ b/Content.Server/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs
@@ -31,13 +31,14 @@ public override void Initialize()
private void OnPowerUsed(EntityUid uid, PsionicInvisibilityPowerComponent component, PsionicInvisibilityPowerActionEvent args)
{
- if (HasComp(uid))
+ if (!_psionics.OnAttemptPowerUse(args.Performer, "psionic invisibility")
+ || HasComp(uid))
return;
ToggleInvisibility(args.Performer);
var action = Spawn(PsionicInvisibilityUsedComponent.PsionicInvisibilityUsedActionPrototype);
_actions.AddAction(uid, action, action);
- _actions.TryGetActionData( action, out var actionData );
+ _actions.TryGetActionData(action, out var actionData);
if (actionData is { UseDelay: not null })
_actions.StartUseDelay(action);
@@ -93,7 +94,8 @@ public void ToggleInvisibility(EntityUid uid)
if (!HasComp(uid))
{
EnsureComp(uid);
- } else
+ }
+ else
{
RemComp(uid);
}
diff --git a/Content.Server/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs
index 17e9249e655..d7ad2d49aba 100644
--- a/Content.Server/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs
+++ b/Content.Server/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs
@@ -40,6 +40,9 @@ public override void Initialize()
private void OnPowerUsed(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationPowerActionEvent args)
{
+ if (!_psionics.OnAttemptPowerUse(args.Performer, "psionic regeneration"))
+ return;
+
var ev = new PsionicRegenerationDoAfterEvent(_gameTiming.CurTime);
var doAfterArgs = new DoAfterArgs(EntityManager, uid, component.UseDelay, ev, uid);
diff --git a/Content.Server/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs
index 3740822667b..4a750836020 100644
--- a/Content.Server/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs
+++ b/Content.Server/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs
@@ -19,6 +19,9 @@ public override void Initialize()
}
private void OnPowerUsed(PyrokinesisPowerActionEvent args)
{
+ if (!_psionics.OnAttemptPowerUse(args.Performer, "pyrokinesis"))
+ return;
+
if (!TryComp(args.Target, out var flammableComponent))
return;
diff --git a/Content.Server/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs
index 7a3f663a43f..abbbdfacc56 100644
--- a/Content.Server/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs
+++ b/Content.Server/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs
@@ -18,6 +18,9 @@ public override void Initialize()
private void OnPowerUsed(EntityUid uid, TelegnosisPowerComponent component, TelegnosisPowerActionEvent args)
{
+ if (!_psionics.OnAttemptPowerUse(args.Performer, "telegnosis"))
+ return;
+
var projection = Spawn(component.Prototype, Transform(uid).Coordinates);
Transform(projection).AttachToGridOrMap();
_mindSwap.Swap(uid, projection);
diff --git a/Content.Server/Abilities/Psionics/AnomalyPowerSystem.cs b/Content.Server/Abilities/Psionics/AnomalyPowerSystem.cs
index 73d5aae2102..ff9910c400d 100644
--- a/Content.Server/Abilities/Psionics/AnomalyPowerSystem.cs
+++ b/Content.Server/Abilities/Psionics/AnomalyPowerSystem.cs
@@ -49,8 +49,7 @@ public override void Initialize()
private void OnPowerUsed(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args)
{
- if (HasComp(uid)
- || HasComp(uid))
+ if (!_psionics.OnAttemptPowerUse(args.Performer, args.Settings.PowerName, args.Settings.ManaCost, args.Settings.CheckInsulation))
return;
var overcharged = args.Settings.DoSupercritical ? _glimmerSystem.Glimmer * component.CurrentAmplification
diff --git a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs
index 536235b6d63..841d923a24c 100644
--- a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs
+++ b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs
@@ -217,6 +217,10 @@ public void RemoveAllPsionicPowers(EntityUid uid, bool mindbreak = false)
RemComp(uid);
RemComp(uid);
+
+ var ev = new OnMindbreakEvent();
+ RaiseLocalEvent(uid, ref ev);
+
return;
}
RefreshPsionicModifiers(uid, psionic);
diff --git a/Content.Server/Body/Components/RespiratorImmuneComponent.cs b/Content.Server/Body/Components/RespiratorImmuneComponent.cs
new file mode 100644
index 00000000000..afc261eff24
--- /dev/null
+++ b/Content.Server/Body/Components/RespiratorImmuneComponent.cs
@@ -0,0 +1,4 @@
+namespace Content.Server.Body.Components;
+
+[RegisterComponent]
+public sealed partial class RespiratorImmuneComponent : Component { }
\ No newline at end of file
diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs
index 389e5fbab72..f6a17d32b65 100644
--- a/Content.Server/Body/Systems/RespiratorSystem.cs
+++ b/Content.Server/Body/Systems/RespiratorSystem.cs
@@ -66,6 +66,9 @@ public override void Update(float frameTime)
if (_mobState.IsDead(uid))
continue;
+ if (HasComp(uid))
+ continue;
+
UpdateSaturation(uid, -(float) respirator.UpdateInterval.TotalSeconds, respirator);
if (!_mobState.IsIncapacitated(uid)) // cannot breathe in crit.
diff --git a/Content.Server/Chat/EmpathyChatSystem.cs b/Content.Server/Chat/EmpathyChatSystem.cs
new file mode 100644
index 00000000000..b46bbdc34e9
--- /dev/null
+++ b/Content.Server/Chat/EmpathyChatSystem.cs
@@ -0,0 +1,83 @@
+using System.Linq;
+using Robust.Shared.Utility;
+using Content.Server.Chat.Managers;
+using Content.Server.Language;
+using Content.Server.Chat.Systems;
+using Content.Server.Administration.Managers;
+using Robust.Shared.Network;
+using Robust.Shared.Player;
+using Content.Shared.Chat;
+using Content.Shared.Language;
+using Robust.Shared.Prototypes;
+using Content.Shared.Language.Components;
+
+namespace Content.Server.Chat;
+
+public sealed partial class EmpathyChatSystem : EntitySystem
+{
+ [Dependency] private readonly IChatManager _chatManager = default!;
+ [Dependency] private readonly LanguageSystem _language = default!;
+ [Dependency] private readonly IAdminManager _adminManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnSpeak);
+ }
+
+ private void OnSpeak(EntityUid uid, LanguageSpeakerComponent component, EntitySpokeEvent args)
+ {
+ if (args.Source != uid
+ || !args.Language.SpeechOverride.EmpathySpeech
+ || args.IsWhisper)
+ return;
+
+ SendEmpathyChat(args.Source, args.Message, false);
+ }
+
+ ///
+ /// Send a Message in the Shadowkin Empathy Chat.
+ ///
+ /// The entity making the message
+ /// The contents of the message
+ /// Set the ChatTransmitRange
+ public void SendEmpathyChat(EntityUid source, string message, bool hideChat)
+ {
+ var clients = GetEmpathChatClients();
+ string wrappedMessage;
+
+ wrappedMessage = Loc.GetString("chat-manager-send-empathy-chat-wrap-message",
+ ("source", source),
+ ("message", FormattedMessage.EscapeText(message)));
+
+ _chatManager.ChatMessageToMany(ChatChannel.Telepathic, message, wrappedMessage, source, hideChat, true, clients.ToList(), Color.FromHex("#be3cc5"));
+ }
+
+ private IEnumerable GetEmpathChatClients()
+ {
+ return Filter.Empty()
+ .AddWhereAttachedEntity(entity =>
+ CanHearEmpathy(entity))
+ .Recipients
+ .Union(_adminManager.ActiveAdmins)
+ .Select(p => p.Channel);
+ }
+
+ ///
+ /// Check if an entity can hear Empathy.
+ /// (Admins will always be able to hear Empathy)
+ ///
+ /// The entity to check
+ public bool CanHearEmpathy(EntityUid entity)
+ {
+ var understood = _language.GetUnderstoodLanguages(entity);
+ for (int i = 0; i < understood.Count; i++)
+ {
+ var language = _prototype.Index(understood[i]);
+ if (language.SpeechOverride.EmpathySpeech)
+ return true;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/Content.Server/Shadowkin/EtherealLightComponent.cs b/Content.Server/Shadowkin/EtherealLightComponent.cs
new file mode 100644
index 00000000000..8f47d862002
--- /dev/null
+++ b/Content.Server/Shadowkin/EtherealLightComponent.cs
@@ -0,0 +1,15 @@
+namespace Content.Server.Shadowkin;
+
+[RegisterComponent]
+public sealed partial class EtherealLightComponent : Component
+{
+ public EntityUid AttachedEntity = EntityUid.Invalid;
+
+ public float OldRadius = 0f;
+
+ public bool OldRadiusEdited = false;
+
+ public float OldEnergy = 0f;
+
+ public bool OldEnergyEdited = false;
+}
\ No newline at end of file
diff --git a/Content.Server/Shadowkin/EtherealStunItemSystem.cs b/Content.Server/Shadowkin/EtherealStunItemSystem.cs
new file mode 100644
index 00000000000..b48b4d4fecf
--- /dev/null
+++ b/Content.Server/Shadowkin/EtherealStunItemSystem.cs
@@ -0,0 +1,44 @@
+using Content.Shared.Interaction.Events;
+using Content.Shared.Damage.Components;
+using Content.Shared.Damage.Systems;
+using Content.Shared.Shadowkin;
+using Content.Shared.Abilities.Psionics;
+using Content.Shared.Stacks;
+
+namespace Content.Server.Shadowkin;
+
+public sealed class EtherealStunItemSystem : EntitySystem
+{
+ [Dependency] private readonly StaminaSystem _stamina = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly SharedStackSystem _sharedStackSystem = default!;
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnUseInHand);
+ }
+
+ private void OnUseInHand(EntityUid uid, EtherealStunItemComponent component, UseInHandEvent args)
+ {
+ foreach (var ent in _lookup.GetEntitiesInRange(uid, component.Radius))
+ {
+ if (!TryComp(ent, out var ethereal))
+ continue;
+
+ RemComp(ent, ethereal);
+
+ if (TryComp(ent, out var stamina))
+ _stamina.TakeStaminaDamage(ent, stamina.CritThreshold, stamina, ent);
+
+ if (TryComp(ent, out var magic))
+ magic.Mana = 0;
+ }
+
+ if (!component.DeleteOnUse)
+ return;
+
+ if (TryComp(uid, out var stack))
+ _sharedStackSystem.Use(uid, 1, stack);
+ else
+ QueueDel(uid);
+ }
+}
diff --git a/Content.Server/Shadowkin/EtherealSystem.cs b/Content.Server/Shadowkin/EtherealSystem.cs
new file mode 100644
index 00000000000..2622547a3f6
--- /dev/null
+++ b/Content.Server/Shadowkin/EtherealSystem.cs
@@ -0,0 +1,216 @@
+using Content.Shared.Eye;
+using Content.Shared.Shadowkin;
+using Robust.Server.GameObjects;
+using Content.Server.Atmos.Components;
+using Content.Server.Temperature.Components;
+using Content.Shared.Movement.Components;
+using Content.Shared.Stealth;
+using Content.Shared.Stealth.Components;
+using Content.Server.Body.Components;
+using Content.Server.NPC.Components;
+using Content.Server.NPC.Systems;
+using System.Linq;
+using Content.Shared.Abilities.Psionics;
+using Robust.Shared.Random;
+using Content.Server.Light.Components;
+
+namespace Content.Server.Shadowkin;
+
+public sealed class EtherealSystem : SharedEtherealSystem
+{
+ [Dependency] private readonly VisibilitySystem _visibilitySystem = default!;
+ [Dependency] private readonly SharedStealthSystem _stealth = default!;
+ [Dependency] private readonly EyeSystem _eye = default!;
+ [Dependency] private readonly NpcFactionSystem _factions = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly SharedPointLightSystem _light = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+ public override void OnStartup(EntityUid uid, EtherealComponent component, MapInitEvent args)
+ {
+ base.OnStartup(uid, component, args);
+
+ var visibility = EnsureComp(uid);
+ _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false);
+ _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Ethereal, false);
+ _visibilitySystem.RefreshVisibility(uid, visibility);
+
+ if (TryComp(uid, out var eye))
+ _eye.SetVisibilityMask(uid, eye.VisibilityMask | (int) (VisibilityFlags.Ethereal), eye);
+
+ if (TryComp(uid, out var temp))
+ temp.AtmosTemperatureTransferEfficiency = 0;
+
+ var stealth = EnsureComp(uid);
+ _stealth.SetVisibility(uid, 0.8f, stealth);
+
+ SuppressFactions(uid, component, true);
+
+ EnsureComp(uid);
+ EnsureComp(uid);
+ EnsureComp(uid);
+
+ if (HasComp(uid))
+ RemComp(uid, component);
+ }
+
+ public override void OnShutdown(EntityUid uid, EtherealComponent component, ComponentShutdown args)
+ {
+ base.OnShutdown(uid, component, args);
+
+ if (TryComp(uid, out var visibility))
+ {
+ _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Normal, false);
+ _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Ethereal, false);
+ _visibilitySystem.RefreshVisibility(uid, visibility);
+ }
+
+ if (TryComp(uid, out var eye))
+ _eye.SetVisibilityMask(uid, (int) VisibilityFlags.Normal, eye);
+
+ if (TryComp(uid, out var temp))
+ temp.AtmosTemperatureTransferEfficiency = 0.1f;
+
+ SuppressFactions(uid, component, false);
+
+ RemComp(uid);
+ RemComp(uid);
+ RemComp(uid);
+ RemComp(uid);
+
+ SpawnAtPosition("ShadowkinShadow", Transform(uid).Coordinates);
+ SpawnAtPosition("EffectFlashShadowkinDarkSwapOff", Transform(uid).Coordinates);
+
+ foreach (var light in component.DarkenedLights.ToArray())
+ {
+ if (!TryComp(light, out var pointLight)
+ || !TryComp(light, out var etherealLight))
+ continue;
+
+ ResetLight(light, pointLight, etherealLight);
+ }
+ }
+
+ public void SuppressFactions(EntityUid uid, EtherealComponent component, bool set)
+ {
+ if (set)
+ {
+ if (!TryComp(uid, out var factions))
+ return;
+
+ component.SuppressedFactions = factions.Factions.ToList();
+
+ foreach (var faction in factions.Factions)
+ _factions.RemoveFaction(uid, faction);
+ }
+ else
+ {
+ foreach (var faction in component.SuppressedFactions)
+ _factions.AddFaction(uid, faction);
+
+ component.SuppressedFactions.Clear();
+ }
+ }
+
+ public void ResetLight(EntityUid uid, PointLightComponent light, EtherealLightComponent etherealLight)
+ {
+ etherealLight.AttachedEntity = EntityUid.Invalid;
+
+ if (etherealLight.OldRadiusEdited)
+ _light.SetRadius(uid, etherealLight.OldRadius);
+ etherealLight.OldRadiusEdited = false;
+
+ if (etherealLight.OldEnergyEdited)
+ _light.SetEnergy(uid, etherealLight.OldEnergy);
+ etherealLight.OldEnergyEdited = false;
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var component))
+ {
+ if (!component.Darken)
+ continue;
+
+ component.DarkenAccumulator += frameTime;
+
+ if (component.DarkenAccumulator <= 1)
+ continue;
+
+ component.DarkenAccumulator -= component.DarkenRate;
+
+ var darkened = new List();
+ var lightQuery = _lookup.GetEntitiesInRange(uid, component.DarkenRange, flags: LookupFlags.StaticSundries)
+ .Where(x => HasComp(x) && HasComp(x));
+
+ foreach (var entity in lightQuery)
+ if (!darkened.Contains(entity))
+ darkened.Add(entity);
+
+ _random.Shuffle(darkened);
+ component.DarkenedLights = darkened;
+
+ var playerPos = _transform.GetWorldPosition(uid);
+
+ foreach (var light in component.DarkenedLights.ToArray())
+ {
+ var lightPos = _transform.GetWorldPosition(light);
+ if (!TryComp(light, out var pointLight)
+ || !TryComp(light, out var etherealLight))
+ continue;
+
+ if (TryComp(light, out var powered) && !powered.On)
+ {
+ ResetLight(light, pointLight, etherealLight);
+ continue;
+ }
+
+ if (etherealLight.AttachedEntity == EntityUid.Invalid)
+ etherealLight.AttachedEntity = uid;
+
+ if (etherealLight.AttachedEntity != EntityUid.Invalid
+ && etherealLight.AttachedEntity != uid)
+ {
+ component.DarkenedLights.Remove(light);
+ continue;
+ }
+
+ if (etherealLight.AttachedEntity == uid
+ && _random.Prob(0.03f))
+ etherealLight.AttachedEntity = EntityUid.Invalid;
+
+ if (!etherealLight.OldRadiusEdited)
+ {
+ etherealLight.OldRadius = pointLight.Radius;
+ etherealLight.OldRadiusEdited = true;
+ }
+ if (!etherealLight.OldEnergyEdited)
+ {
+ etherealLight.OldEnergy = pointLight.Energy;
+ etherealLight.OldEnergyEdited = true;
+ }
+
+ var distance = (lightPos - playerPos).Length();
+ var radius = distance * 2f;
+ var energy = distance * 0.8f;
+
+ if (etherealLight.OldRadiusEdited && radius > etherealLight.OldRadius)
+ radius = etherealLight.OldRadius;
+ if (etherealLight.OldRadiusEdited && radius < etherealLight.OldRadius * 0.20f)
+ radius = etherealLight.OldRadius * 0.20f;
+
+ if (etherealLight.OldEnergyEdited && energy > etherealLight.OldEnergy)
+ energy = etherealLight.OldEnergy;
+ if (etherealLight.OldEnergyEdited && energy < etherealLight.OldEnergy * 0.20f)
+ energy = etherealLight.OldEnergy * 0.20f;
+
+ _light.SetRadius(light, radius);
+ _light.SetEnergy(light, energy);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Content.Server/Shadowkin/ShadowkinCuffSystem.cs b/Content.Server/Shadowkin/ShadowkinCuffSystem.cs
new file mode 100644
index 00000000000..ce2b2588174
--- /dev/null
+++ b/Content.Server/Shadowkin/ShadowkinCuffSystem.cs
@@ -0,0 +1,29 @@
+using Content.Shared.Inventory.Events;
+using Content.Shared.Clothing.Components;
+using Content.Shared.Shadowkin;
+
+namespace Content.Server.Shadowkin;
+
+public sealed class ShadowkinCuffSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnEquipped);
+ SubscribeLocalEvent(OnUnequipped);
+ }
+
+ private void OnEquipped(EntityUid uid, ShadowkinCuffComponent component, GotEquippedEvent args)
+ {
+ if (!TryComp(uid, out var clothing)
+ || !clothing.Slots.HasFlag(args.SlotFlags))
+ return;
+
+ EnsureComp(args.Equipee);
+ }
+
+ private void OnUnequipped(EntityUid uid, ShadowkinCuffComponent component, GotUnequippedEvent args)
+ {
+ RemComp(args.Equipee);
+ }
+}
\ No newline at end of file
diff --git a/Content.Server/Shadowkin/ShadowkinSystem.cs b/Content.Server/Shadowkin/ShadowkinSystem.cs
new file mode 100644
index 00000000000..83461e7a7fe
--- /dev/null
+++ b/Content.Server/Shadowkin/ShadowkinSystem.cs
@@ -0,0 +1,181 @@
+using Content.Shared.Examine;
+using Content.Shared.Abilities.Psionics;
+using Content.Shared.Humanoid;
+using Content.Shared.Psionics;
+using Content.Shared.Bed.Sleep;
+using Content.Shared.Damage.Components;
+using Content.Shared.Damage.Systems;
+using Content.Shared.Shadowkin;
+using Content.Shared.Rejuvenate;
+using Content.Shared.Alert;
+using Content.Shared.Rounding;
+using Content.Shared.Actions;
+using Robust.Shared.Prototypes;
+using Content.Server.Abilities.Psionics;
+
+namespace Content.Server.Shadowkin;
+
+public sealed class ShadowkinSystem : EntitySystem
+{
+ [Dependency] private readonly StaminaSystem _stamina = default!;
+ [Dependency] private readonly PsionicAbilitiesSystem _psionicAbilitiesSystem = default!;
+ [Dependency] private readonly AlertsSystem _alerts = default!;
+ [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ public const string ShadowkinSleepActionId = "ShadowkinActionSleep";
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(OnExamined);
+ SubscribeLocalEvent(OnMindbreak);
+ SubscribeLocalEvent(OnAttemptPowerUse);
+ SubscribeLocalEvent(OnManaUpdate);
+ SubscribeLocalEvent(OnRejuvenate);
+ SubscribeLocalEvent(OnEyeColorChange);
+ }
+
+ private void OnInit(EntityUid uid, ShadowkinComponent component, ComponentStartup args)
+ {
+ if (component.BlackeyeSpawn)
+ ApplyBlackEye(uid);
+
+ _actionsSystem.AddAction(uid, ref component.ShadowkinSleepAction, ShadowkinSleepActionId, uid);
+
+ UpdateShadowkinAlert(uid, component);
+ }
+
+ private void OnEyeColorChange(EntityUid uid, ShadowkinComponent component, EyeColorInitEvent args)
+ {
+ if (!TryComp(uid, out var humanoid)
+ || !component.BlackeyeSpawn
+ || humanoid.EyeColor == component.OldEyeColor)
+ return;
+
+ component.OldEyeColor = humanoid.EyeColor;
+ humanoid.EyeColor = component.BlackEyeColor;
+ Dirty(humanoid);
+ }
+
+ private void OnExamined(EntityUid uid, ShadowkinComponent component, ExaminedEvent args)
+ {
+ if (!args.IsInDetailsRange
+ || !TryComp(uid, out var magic)
+ || HasComp(uid))
+ return;
+
+ var severity = "shadowkin-power-" + ContentHelpers.RoundToLevels(magic.Mana, magic.MaxMana, 6);
+ var powerType = Loc.GetString(severity);
+
+ if (args.Examined == args.Examiner)
+ args.PushMarkup(Loc.GetString("shadowkin-power-examined-self",
+ ("power", Math.Floor(magic.Mana)),
+ ("powerMax", Math.Floor(magic.MaxMana)),
+ ("powerType", powerType)
+ ));
+ else
+ args.PushMarkup(Loc.GetString("shadowkin-power-examined-other",
+ ("target", uid),
+ ("powerType", powerType)
+ ));
+ }
+
+ ///
+ /// Update the Shadowkin Alert, if Blackeye will remove the Alert, if not will update to its current power status.
+ ///
+ public void UpdateShadowkinAlert(EntityUid uid, ShadowkinComponent component)
+ {
+ if (TryComp(uid, out var magic))
+ {
+ var severity = (short) ContentHelpers.RoundToLevels(magic.Mana, magic.MaxMana, 8);
+ _alerts.ShowAlert(uid, AlertType.ShadowkinPower, severity);
+ }
+ else
+ _alerts.ClearAlert(uid, AlertType.ShadowkinPower);
+ }
+
+ private void OnAttemptPowerUse(EntityUid uid, ShadowkinComponent component, OnAttemptPowerUseEvent args)
+ {
+ if (HasComp(uid))
+ args.Cancel();
+ }
+
+ private void OnManaUpdate(EntityUid uid, ShadowkinComponent component, ref OnManaUpdateEvent args)
+ {
+ if (!TryComp(uid, out var magic))
+ return;
+
+ if (component.SleepManaRegen
+ && TryComp(uid, out var sleep))
+ magic.ManaGainMultiplier = component.SleepManaRegenMultiplier;
+ else
+ magic.ManaGainMultiplier = 1;
+
+ if (magic.Mana <= component.BlackEyeMana)
+ ApplyBlackEye(uid);
+
+ Dirty(magic); // Update Shadowkin Overlay.
+ UpdateShadowkinAlert(uid, component);
+ }
+
+ ///
+ /// Blackeye the Shadowkin, its just a function to mindbreak the shadowkin but making sure "Removable" is checked true during it.
+ ///
+ ///
+ public void ApplyBlackEye(EntityUid uid)
+ {
+ if (!TryComp(uid, out var magic))
+ return;
+
+ magic.Removable = true;
+ _psionicAbilitiesSystem.MindBreak(uid);
+ }
+
+ private void OnMindbreak(EntityUid uid, ShadowkinComponent component, ref OnMindbreakEvent args)
+ {
+ if (TryComp(uid, out var mindbreak))
+ mindbreak.MindbrokenExaminationText = "examine-mindbroken-shadowkin-message";
+
+ if (TryComp(uid, out var humanoid))
+ {
+ component.OldEyeColor = humanoid.EyeColor;
+ humanoid.EyeColor = component.BlackEyeColor;
+ Dirty(humanoid);
+ }
+
+ if (component.BlackeyeSpawn)
+ return;
+
+ if (TryComp(uid, out var stamina))
+ _stamina.TakeStaminaDamage(uid, stamina.CritThreshold, stamina, uid);
+ }
+
+ private void OnRejuvenate(EntityUid uid, ShadowkinComponent component, RejuvenateEvent args)
+ {
+ if (component.BlackeyeSpawn
+ || !HasComp(uid))
+ return;
+
+ RemComp(uid);
+
+ if (TryComp(uid, out var humanoid))
+ {
+ humanoid.EyeColor = component.OldEyeColor;
+ Dirty(humanoid);
+ }
+
+ EnsureComp(uid, out var magic);
+ magic.Mana = 250;
+ magic.MaxMana = 250;
+ magic.ManaGain = 0.25f;
+ magic.BypassManaCheck = true;
+ magic.Removable = false;
+ magic.MindbreakingFeedback = "shadowkin-blackeye";
+
+ if (_prototypeManager.TryIndex("ShadowkinPowers", out var shadowkinPowers))
+ _psionicAbilitiesSystem.InitializePsionicPower(uid, shadowkinPowers);
+
+ UpdateShadowkinAlert(uid, component);
+ }
+}
diff --git a/Content.Server/Shadowkin/ShowEtherealSystem.cs b/Content.Server/Shadowkin/ShowEtherealSystem.cs
new file mode 100644
index 00000000000..151c379afbb
--- /dev/null
+++ b/Content.Server/Shadowkin/ShowEtherealSystem.cs
@@ -0,0 +1,88 @@
+using Content.Shared.Shadowkin;
+using Content.Shared.Eye;
+using Robust.Server.GameObjects;
+using Content.Shared.Inventory.Events;
+using Content.Shared.Interaction.Events;
+using Robust.Shared.Timing;
+using Content.Shared.Popups;
+using Content.Shared.Clothing.Components;
+
+namespace Content.Server.Shadowkin;
+public sealed class ShowEtherealSystem : EntitySystem
+{
+ [Dependency] private readonly EyeSystem _eye = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(OnShutdown);
+ SubscribeLocalEvent(OnEquipped);
+ SubscribeLocalEvent(OnUnequipped);
+ SubscribeLocalEvent(OnInteractionAttempt);
+ SubscribeLocalEvent(OnAttackAttempt);
+ }
+
+ private void OnInit(EntityUid uid, ShowEtherealComponent component, MapInitEvent args)
+ {
+ Toggle(uid, true);
+ }
+
+ public void OnShutdown(EntityUid uid, ShowEtherealComponent component, ComponentShutdown args)
+ {
+ Toggle(uid, false);
+ }
+
+ private void OnEquipped(EntityUid uid, ShowEtherealComponent component, GotEquippedEvent args)
+ {
+ if (!TryComp(uid, out var clothing)
+ || !clothing.Slots.HasFlag(args.SlotFlags))
+ return;
+
+ EnsureComp(args.Equipee);
+ }
+
+ private void OnUnequipped(EntityUid uid, ShowEtherealComponent component, GotUnequippedEvent args)
+ {
+ RemComp(args.Equipee);
+ }
+
+ private void Toggle(EntityUid uid, bool toggle)
+ {
+ if (!TryComp(uid, out var eye))
+ return;
+
+ if (toggle)
+ {
+ _eye.SetVisibilityMask(uid, eye.VisibilityMask | (int) (VisibilityFlags.Ethereal), eye);
+ return;
+ }
+ else if (HasComp(uid))
+ return;
+
+ _eye.SetVisibilityMask(uid, (int) VisibilityFlags.Normal, eye);
+ }
+
+ private void OnInteractionAttempt(EntityUid uid, ShowEtherealComponent component, InteractionAttemptEvent args)
+ {
+ if (HasComp(uid)
+ || !HasComp(args.Target))
+ return;
+
+ args.Cancel();
+ if (_gameTiming.InPrediction)
+ return;
+
+ _popup.PopupEntity(Loc.GetString("ethereal-pickup-fail"), args.Target.Value, uid);
+ }
+
+ private void OnAttackAttempt(EntityUid uid, ShowEtherealComponent component, AttackAttemptEvent args)
+ {
+ if (HasComp(uid)
+ || !HasComp(args.Target))
+ return;
+
+ args.Cancel();
+ }
+}
\ No newline at end of file
diff --git a/Content.Shared/Actions/Events/AnomalyPowerActionEvent.cs b/Content.Shared/Actions/Events/AnomalyPowerActionEvent.cs
index 7ef98a152c2..9184460153b 100644
--- a/Content.Shared/Actions/Events/AnomalyPowerActionEvent.cs
+++ b/Content.Shared/Actions/Events/AnomalyPowerActionEvent.cs
@@ -76,6 +76,10 @@ public partial record struct AnomalyPowerSettings()
{
public string PowerName;
+ public float ManaCost;
+
+ public bool CheckInsulation;
+
///
/// When casting above the Supercritical Threshold, if not 0, this will cause all powers to enter cooldown for the given duration.
///
diff --git a/Content.Shared/Actions/Events/DarkSwapActionEvent.cs b/Content.Shared/Actions/Events/DarkSwapActionEvent.cs
new file mode 100644
index 00000000000..ad60d0ede45
--- /dev/null
+++ b/Content.Shared/Actions/Events/DarkSwapActionEvent.cs
@@ -0,0 +1,9 @@
+namespace Content.Shared.Actions.Events;
+public sealed partial class DarkSwapActionEvent : InstantActionEvent
+{
+ [DataField]
+ public float ManaCost;
+
+ [DataField]
+ public bool CheckInsulation;
+}
\ No newline at end of file
diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs
index 0808114b9d5..bd8c1dbe257 100644
--- a/Content.Shared/Alert/AlertType.cs
+++ b/Content.Shared/Alert/AlertType.cs
@@ -71,6 +71,7 @@ public enum AlertType : byte
BorgCrit,
BorgDead,
Offer,
+ ShadowkinPower,
Deflecting,
}
diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs
index 679b99524ed..9f972b656d7 100644
--- a/Content.Shared/CCVar/CCVars.cs
+++ b/Content.Shared/CCVar/CCVars.cs
@@ -1994,6 +1994,12 @@ public static readonly CVarDef
public static readonly CVarDef ICShowSSDIndicator =
CVarDef.Create("ic.show_ssd_indicator", true, CVar.CLIENTONLY);
+ ///
+ /// Allow Ethereal Ent to PassThrough Walls/Objects while in Ethereal.
+ ///
+ public static readonly CVarDef EtherealPassThrough =
+ CVarDef.Create("ic.EtherealPassThrough", false, CVar.SERVER);
+
/*
* Salvage
*/
diff --git a/Content.Shared/Eye/VisibilityFlags.cs b/Content.Shared/Eye/VisibilityFlags.cs
index 7e2dd33d7d4..2e20b1de4f3 100644
--- a/Content.Shared/Eye/VisibilityFlags.cs
+++ b/Content.Shared/Eye/VisibilityFlags.cs
@@ -6,10 +6,11 @@ namespace Content.Shared.Eye
[FlagsFor(typeof(VisibilityMaskLayer))]
public enum VisibilityFlags : int
{
- None = 0,
+ None = 0,
Normal = 1 << 0,
- Ghost = 1 << 1,
+ Ghost = 1 << 1,
PsionicInvisibility = 1 << 2, //Nyano - Summary: adds Psionic Invisibility as a visibility layer. Currently does nothing.
- TelegnosticProjection = 5,
+ TelegnosticProjection = 5,
+ Ethereal = 1 << 3,
}
}
diff --git a/Content.Shared/Humanoid/EyeColor.cs b/Content.Shared/Humanoid/EyeColor.cs
new file mode 100644
index 00000000000..a39e090a86c
--- /dev/null
+++ b/Content.Shared/Humanoid/EyeColor.cs
@@ -0,0 +1,4 @@
+namespace Content.Shared.Humanoid;
+
+[ByRefEvent]
+public record struct EyeColorInitEvent();
\ No newline at end of file
diff --git a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
index dc1e6b736c2..a1e8bec2cd8 100644
--- a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
+++ b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
@@ -15,6 +15,7 @@
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
+using Content.Shared.Shadowkin;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Utility;
@@ -104,6 +105,12 @@ private void OnExamined(EntityUid uid, HumanoidAppearanceComponent component, Ex
var identity = Identity.Entity(uid, EntityManager);
var species = GetSpeciesRepresentation(component.Species, component.CustomSpecieName).ToLower();
var age = GetAgeRepresentation(component.Species, component.Age);
+ if (HasComp(uid))
+ {
+ var color = component.EyeColor.Name();
+ if (color != null)
+ age = Loc.GetString("identity-eye-shadowkin", ("color", color));
+ }
args.PushText(Loc.GetString("humanoid-appearance-component-examine", ("user", identity), ("age", age), ("species", species)));
}
@@ -362,6 +369,8 @@ public virtual void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile,
SetSpecies(uid, profile.Species, false, humanoid);
SetSex(uid, profile.Sex, false, humanoid);
humanoid.EyeColor = profile.Appearance.EyeColor;
+ var ev = new EyeColorInitEvent();
+ RaiseLocalEvent(uid, ref ev);
SetSkinColor(uid, profile.Appearance.SkinColor, false);
diff --git a/Content.Shared/Language/LanguagePrototype.cs b/Content.Shared/Language/LanguagePrototype.cs
index 2137e4e838f..3b2b24c4b25 100644
--- a/Content.Shared/Language/LanguagePrototype.cs
+++ b/Content.Shared/Language/LanguagePrototype.cs
@@ -53,6 +53,14 @@ public sealed partial class SpeechOverrideInfo
[DataField]
public bool AllowRadio = true;
+ ///
+ /// If true, the message will be relayed to the Empathy Chat and
+ /// anyone with that language will also hear Empathy Chat. (Unless user has ShadowkinBlackeyeComponent)
+ /// This is mostly only use for "Marish" but... fuckit modularity :p
+ ///
+ [DataField]
+ public bool EmpathySpeech = false;
+
///
/// If false, the entity can use this language even when it's unable to speak (i.e. muffled or muted),
/// and accents are not applied to messages in this language.
diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs
index 37d0a9a7ef4..5a3cce19ad3 100644
--- a/Content.Shared/Psionics/PsionicComponent.cs
+++ b/Content.Shared/Psionics/PsionicComponent.cs
@@ -5,9 +5,38 @@
namespace Content.Shared.Abilities.Psionics
{
- [RegisterComponent, NetworkedComponent]
+ [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
public sealed partial class PsionicComponent : Component
{
+ ///
+ /// Current Mana.
+ ///
+ [DataField, AutoNetworkedField]
+ public float Mana;
+
+ ///
+ /// Max Mana Possible.
+ ///
+ [DataField, AutoNetworkedField]
+ public float MaxMana = 100;
+
+ ///
+ /// How much energy is gained per second.
+ ///
+ [DataField]
+ public float ManaGain = 1;
+
+ ///
+ /// ManaGain Multiplier
+ ///
+ [DataField]
+ public float ManaGainMultiplier = 1;
+
+ public float ManaAccumulator;
+
+ [DataField]
+ public bool BypassManaCheck;
+
///
/// How close a Psion is to generating a new power. When Potentia reaches the NextPowerCost, it is "Spent" in order to "Buy" a random new power.
/// TODO: Psi-Potentiometry should be able to read how much Potentia a person has.
@@ -45,6 +74,10 @@ public sealed partial class PsionicComponent : Component
public string MindbreakingStutterAccent = "StutteringAccent";
+ ///
+ /// The message feedback given on mindbreak.
+ ///
+ [DataField]
public string MindbreakingFeedback = "mindbreaking-feedback";
///
@@ -163,6 +196,7 @@ private set
[ViewVariables(VVAccess.ReadWrite)]
public int PowerSlotsTaken;
+ ///
/// List of descriptors this entity will bring up for psychognomy. Used to remove
/// unneccesary subs for unique psionic entities like e.g. Oracle.
///
@@ -176,5 +210,8 @@ private set
/// Popup to play if a Psion attempts to start casting a power while already casting one
[DataField]
public string AlreadyCasting = "already-casting";
+
+ /// Popup to play if there no Mana left for a power to execute.
+ public string NoMana = "no-mana";
}
}
diff --git a/Content.Shared/Psionics/PsionicEvents.cs b/Content.Shared/Psionics/PsionicEvents.cs
index be3bf03af62..4d134417988 100644
--- a/Content.Shared/Psionics/PsionicEvents.cs
+++ b/Content.Shared/Psionics/PsionicEvents.cs
@@ -8,4 +8,10 @@ namespace Content.Shared.Psionics;
///
///
[ByRefEvent]
-public record struct OnSetPsionicStatsEvent(float AmplificationChangedAmount, float DampeningChangedAmount);
\ No newline at end of file
+public record struct OnSetPsionicStatsEvent(float AmplificationChangedAmount, float DampeningChangedAmount);
+
+[ByRefEvent]
+public record struct OnMindbreakEvent();
+
+[ByRefEvent]
+public record struct OnManaUpdateEvent();
\ No newline at end of file
diff --git a/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs b/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs
index 27928645684..b79dabbc416 100644
--- a/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs
+++ b/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs
@@ -1,9 +1,13 @@
using Content.Shared.Administration.Logs;
using Content.Shared.Contests;
using Content.Shared.Popups;
+using Content.Shared.Psionics;
using Content.Shared.Psionics.Glimmer;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.FixedPoint;
+using Content.Shared.Rejuvenate;
namespace Content.Shared.Abilities.Psionics
{
@@ -15,11 +19,54 @@ public sealed class SharedPsionicAbilitiesSystem : EntitySystem
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly ContestsSystem _contests = default!;
+ [Dependency] private readonly MobStateSystem _mobState = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnPowerUsed);
+ SubscribeLocalEvent(OnRejuvenate);
+ }
+
+ public bool OnAttemptPowerUse(EntityUid uid, string power, float? manacost = null, bool checkInsulation = true)
+ {
+ if (!TryComp(uid, out var component)
+ || HasComp(uid)
+ || checkInsulation
+ && HasComp(uid))
+ return false;
+
+ var tev = new OnAttemptPowerUseEvent(uid, power);
+ RaiseLocalEvent(uid, tev);
+
+ if (tev.Cancelled)
+ return false;
+
+ if (component.DoAfter is not null)
+ {
+ _popups.PopupEntity(Loc.GetString(component.AlreadyCasting), uid, uid, PopupType.LargeCaution);
+ return false;
+ }
+
+ if (manacost is null)
+ return true;
+
+ if (component.Mana >= manacost
+ || component.BypassManaCheck)
+ {
+ var newmana = component.Mana - manacost;
+ component.Mana = newmana ?? component.Mana;
+
+ var ev = new OnManaUpdateEvent();
+ RaiseLocalEvent(uid, ref ev);
+ }
+ else
+ {
+ _popups.PopupEntity(Loc.GetString(component.NoMana), uid, uid, PopupType.LargeCaution);
+ return false;
+ }
+
+ return true;
}
private void OnPowerUsed(EntityUid uid, PsionicComponent component, PsionicPowerUsedEvent args)
@@ -85,6 +132,45 @@ public float ModifiedDampening(EntityUid uid, PsionicComponent component)
{
return component.CurrentDampening / _contests.MoodContest(uid, true);
}
+
+ public void OnRejuvenate(EntityUid uid, PsionicComponent component, RejuvenateEvent args)
+ {
+ component.Mana = component.MaxMana;
+ var ev = new OnManaUpdateEvent();
+ RaiseLocalEvent(uid, ref ev);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var component))
+ {
+ if (_mobState.IsDead(uid))
+ continue;
+
+ component.ManaAccumulator += frameTime;
+
+ if (component.ManaAccumulator <= 1)
+ continue;
+
+ component.ManaAccumulator -= 1;
+
+ if (component.Mana > component.MaxMana)
+ component.Mana = component.MaxMana;
+
+ if (component.Mana < component.MaxMana)
+ {
+ var gainedmana = component.ManaGain * component.ManaGainMultiplier;
+ component.Mana += gainedmana;
+ FixedPoint2.Min(component.Mana, component.MaxMana);
+
+ var ev = new OnManaUpdateEvent();
+ RaiseLocalEvent(uid, ref ev);
+ }
+ }
+ }
}
public sealed class PsionicPowerUsedEvent : HandledEntityEventArgs
@@ -99,6 +185,18 @@ public PsionicPowerUsedEvent(EntityUid user, string power)
}
}
+ public sealed class OnAttemptPowerUseEvent : CancellableEntityEventArgs
+ {
+ public EntityUid User { get; }
+ public string Power = string.Empty;
+
+ public OnAttemptPowerUseEvent(EntityUid user, string power)
+ {
+ User = user;
+ Power = power;
+ }
+ }
+
[Serializable]
[NetSerializable]
public sealed class PsionicsChangedEvent : EntityEventArgs
diff --git a/Content.Shared/Shadowkin/EtherealComponent.cs b/Content.Shared/Shadowkin/EtherealComponent.cs
new file mode 100644
index 00000000000..0fc50c0f12e
--- /dev/null
+++ b/Content.Shared/Shadowkin/EtherealComponent.cs
@@ -0,0 +1,36 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Shadowkin;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class EtherealComponent : Component
+{
+ ///
+ /// Does the Ent, Dark lights around it?
+ ///
+ [DataField]
+ public bool Darken = false;
+
+ ///
+ /// Range of the Darken Effect.
+ ///
+ [DataField]
+ public float DarkenRange = 5;
+
+ ///
+ /// Darken Effect Rate.
+ ///
+ [DataField]
+ public float DarkenRate = 0.084f;
+
+ public List DarkenedLights = new();
+
+ public float DarkenAccumulator;
+
+ public int OldMobMask;
+
+ public int OldMobLayer;
+
+ public List SuppressedFactions = new();
+ public bool HasDoorBumpTag;
+}
\ No newline at end of file
diff --git a/Content.Shared/Shadowkin/EtherealStunItemComponent.cs b/Content.Shared/Shadowkin/EtherealStunItemComponent.cs
new file mode 100644
index 00000000000..053b5c11f67
--- /dev/null
+++ b/Content.Shared/Shadowkin/EtherealStunItemComponent.cs
@@ -0,0 +1,11 @@
+namespace Content.Shared.Shadowkin;
+
+[RegisterComponent]
+public sealed partial class EtherealStunItemComponent : Component
+{
+ [DataField]
+ public float Radius = 10;
+
+ [DataField]
+ public bool DeleteOnUse = true;
+}
\ No newline at end of file
diff --git a/Content.Shared/Shadowkin/ShadowkinComponent.cs b/Content.Shared/Shadowkin/ShadowkinComponent.cs
new file mode 100644
index 00000000000..b382f3112b7
--- /dev/null
+++ b/Content.Shared/Shadowkin/ShadowkinComponent.cs
@@ -0,0 +1,42 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Shadowkin;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ShadowkinComponent : Component
+{
+ ///
+ /// Apply the SleepManaRegenMultiplier on SleepComponent if true.
+ ///
+ [DataField]
+ public bool SleepManaRegen = true;
+
+ ///
+ /// What do edit the ManaRegenMultiplier when on Sleep.
+ ///
+ [DataField]
+ public float SleepManaRegenMultiplier = 4;
+
+ ///
+ /// On MapInitEvent, will Blackeye the Shadowkin.
+ ///
+ [DataField]
+ public bool BlackeyeSpawn;
+
+ ///
+ /// If mana is equal or lower then this value, blackeye the shadowkin.
+ ///
+ [DataField]
+ public float BlackEyeMana;
+
+ ///
+ /// Set the Black-Eye Color.
+ ///
+ [DataField]
+ public Color BlackEyeColor = Color.Black;
+
+ public Color OldEyeColor = Color.LimeGreen;
+
+ [DataField]
+ public EntityUid? ShadowkinSleepAction;
+}
\ No newline at end of file
diff --git a/Content.Shared/Shadowkin/ShadowkinCuffComponent.cs b/Content.Shared/Shadowkin/ShadowkinCuffComponent.cs
new file mode 100644
index 00000000000..b4c62d66647
--- /dev/null
+++ b/Content.Shared/Shadowkin/ShadowkinCuffComponent.cs
@@ -0,0 +1,4 @@
+namespace Content.Shared.Shadowkin;
+
+[RegisterComponent]
+public sealed partial class ShadowkinCuffComponent : Component { }
\ No newline at end of file
diff --git a/Content.Shared/Shadowkin/SharedEtherealSystem.cs b/Content.Shared/Shadowkin/SharedEtherealSystem.cs
new file mode 100644
index 00000000000..66196faf0a3
--- /dev/null
+++ b/Content.Shared/Shadowkin/SharedEtherealSystem.cs
@@ -0,0 +1,141 @@
+using Content.Shared.Physics;
+using Robust.Shared.Physics;
+using System.Linq;
+using Robust.Shared.Physics.Systems;
+using Content.Shared.Interaction.Events;
+using Robust.Shared.Timing;
+using Content.Shared.Popups;
+using Content.Shared.Throwing;
+using Content.Shared.Weapons.Ranged.Events;
+using Content.Shared.CombatMode.Pacification;
+using Content.Shared.Psionics;
+using Content.Shared.Mobs;
+using Content.Shared.CCVar;
+using Robust.Shared.Configuration;
+using Content.Shared.Abilities.Psionics;
+using Content.Shared.Tag;
+
+namespace Content.Shared.Shadowkin;
+
+public abstract class SharedEtherealSystem : EntitySystem
+{
+ [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+ [Dependency] private readonly TagSystem _tag = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnStartup);
+ SubscribeLocalEvent(OnShutdown);
+ SubscribeLocalEvent(OnInteractionAttempt);
+ SubscribeLocalEvent(OnBeforeThrow);
+ SubscribeLocalEvent(OnAttemptPowerUse);
+ SubscribeLocalEvent(OnAttackAttempt);
+ SubscribeLocalEvent(OnShootAttempt);
+ SubscribeLocalEvent(OnMindbreak);
+ SubscribeLocalEvent(OnMobStateChanged);
+ }
+
+ public virtual void OnStartup(EntityUid uid, EtherealComponent component, MapInitEvent args)
+ {
+ if (!TryComp(uid, out var fixtures))
+ return;
+
+ var fixture = fixtures.Fixtures.First();
+
+ component.OldMobMask = fixture.Value.CollisionMask;
+ component.OldMobLayer = fixture.Value.CollisionLayer;
+
+ if (_cfg.GetCVar(CCVars.EtherealPassThrough))
+ {
+ _physics.SetCollisionMask(uid, fixture.Key, fixture.Value, (int) CollisionGroup.GhostImpassable, fixtures);
+ _physics.SetCollisionLayer(uid, fixture.Key, fixture.Value, 0, fixtures);
+
+ if (_tag.RemoveTag(uid, "DoorBumpOpener"))
+ component.HasDoorBumpTag = true;
+
+ return;
+ }
+
+ _physics.SetCollisionMask(uid, fixture.Key, fixture.Value, (int) CollisionGroup.FlyingMobMask, fixtures);
+ _physics.SetCollisionLayer(uid, fixture.Key, fixture.Value, (int) CollisionGroup.FlyingMobLayer, fixtures);
+ }
+
+ public virtual void OnShutdown(EntityUid uid, EtherealComponent component, ComponentShutdown args)
+ {
+ if (!TryComp(uid, out var fixtures))
+ return;
+
+ var fixture = fixtures.Fixtures.First();
+
+ _physics.SetCollisionMask(uid, fixture.Key, fixture.Value, component.OldMobMask, fixtures);
+ _physics.SetCollisionLayer(uid, fixture.Key, fixture.Value, component.OldMobLayer, fixtures);
+
+ if (component.HasDoorBumpTag)
+ _tag.AddTag(uid, "DoorBumpOpener");
+ }
+
+ private void OnMindbreak(EntityUid uid, EtherealComponent component, ref OnMindbreakEvent args)
+ {
+ RemComp(uid, component);
+ }
+
+ private void OnMobStateChanged(EntityUid uid, EtherealComponent component, MobStateChangedEvent args)
+ {
+ if (args.NewMobState == MobState.Critical
+ || args.NewMobState == MobState.Dead)
+ RemComp(uid, component);
+ }
+
+ private void OnShootAttempt(Entity ent, ref ShotAttemptedEvent args)
+ {
+ args.Cancel();
+ }
+
+ private void OnAttackAttempt(EntityUid uid, EtherealComponent component, AttackAttemptEvent args)
+ {
+ if (HasComp(args.Target))
+ return;
+
+ args.Cancel();
+ }
+
+ private void OnBeforeThrow(Entity ent, ref BeforeThrowEvent args)
+ {
+ var thrownItem = args.ItemUid;
+
+ // Raise an AttemptPacifiedThrow event and rely on other systems to check
+ // whether the candidate item is OK to throw:
+ var ev = new AttemptPacifiedThrowEvent(thrownItem, ent);
+ RaiseLocalEvent(thrownItem, ref ev);
+ if (!ev.Cancelled)
+ return;
+
+ args.Cancelled = true;
+ }
+
+ private void OnInteractionAttempt(EntityUid uid, EtherealComponent component, InteractionAttemptEvent args)
+ {
+ if (!HasComp(args.Target)
+ || HasComp(args.Target))
+ return;
+
+ args.Cancel();
+ if (_gameTiming.InPrediction)
+ return;
+
+ _popup.PopupEntity(Loc.GetString("ethereal-pickup-fail"), args.Target.Value, uid);
+ }
+
+ private void OnAttemptPowerUse(EntityUid uid, EtherealComponent component, OnAttemptPowerUseEvent args)
+ {
+ if (args.Power == "DarkSwap")
+ return;
+
+ args.Cancel();
+ }
+}
diff --git a/Content.Shared/Shadowkin/ShowEtherealComponent.cs b/Content.Shared/Shadowkin/ShowEtherealComponent.cs
new file mode 100644
index 00000000000..45fa78fa0c4
--- /dev/null
+++ b/Content.Shared/Shadowkin/ShowEtherealComponent.cs
@@ -0,0 +1,6 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Shadowkin;
+
+[RegisterComponent]
+public sealed partial class ShowEtherealComponent : Component { }
\ No newline at end of file
diff --git a/Resources/Audio/Effects/Shadowkin/Powers/darkswapoff.ogg b/Resources/Audio/Effects/Shadowkin/darkswapoff.ogg
similarity index 100%
rename from Resources/Audio/Effects/Shadowkin/Powers/darkswapoff.ogg
rename to Resources/Audio/Effects/Shadowkin/darkswapoff.ogg
diff --git a/Resources/Audio/Effects/Shadowkin/Powers/darkswapon.ogg b/Resources/Audio/Effects/Shadowkin/darkswapon.ogg
similarity index 100%
rename from Resources/Audio/Effects/Shadowkin/Powers/darkswapon.ogg
rename to Resources/Audio/Effects/Shadowkin/darkswapon.ogg
diff --git a/Resources/Audio/Effects/Shadowkin/Powers/futuristic-teleport.ogg b/Resources/Audio/Effects/Shadowkin/futuristic-teleport.ogg
similarity index 100%
rename from Resources/Audio/Effects/Shadowkin/Powers/futuristic-teleport.ogg
rename to Resources/Audio/Effects/Shadowkin/futuristic-teleport.ogg
diff --git a/Resources/Audio/Effects/Shadowkin/Powers/license.txt b/Resources/Audio/Effects/Shadowkin/license.txt
similarity index 75%
rename from Resources/Audio/Effects/Shadowkin/Powers/license.txt
rename to Resources/Audio/Effects/Shadowkin/license.txt
index c77ea8eb098..d87bd10983a 100644
--- a/Resources/Audio/Effects/Shadowkin/Powers/license.txt
+++ b/Resources/Audio/Effects/Shadowkin/license.txt
@@ -1,4 +1,4 @@
darkswapon.ogg licensed under Pixabay Licence taken from https://pixabay.com/users/cristian_changing-30278997/
darkswapoff.ogg licensed under Pixabay Licence taken from https://pixabay.com/users/cristian_changing-30278997/
futuristic-teleport.ogg licensed under Pixabay Licence taken from https://pixabay.com/users/cristian_changing-30278997/
-teleport.ogg licensed under Pixabay Licence taken from https://pixabay.com/users/cristian_changing-30278997/
+shadeskip.ogg licensed under Pixabay Licence taken from https://pixabay.com/users/cristian_changing-30278997/
diff --git a/Resources/Audio/Effects/Shadowkin/Powers/teleport.ogg b/Resources/Audio/Effects/Shadowkin/shadeskip.ogg
similarity index 100%
rename from Resources/Audio/Effects/Shadowkin/Powers/teleport.ogg
rename to Resources/Audio/Effects/Shadowkin/shadeskip.ogg
diff --git a/Resources/Audio/Voice/Shadowkin/attributions.yml b/Resources/Audio/Voice/Shadowkin/attributions.yml
new file mode 100644
index 00000000000..4ab746f4659
--- /dev/null
+++ b/Resources/Audio/Voice/Shadowkin/attributions.yml
@@ -0,0 +1,4 @@
+- files: ["wurble.ogg", "mar.ogg"]
+ license: "CC-BY-SA-3.0"
+ copyright: "Taken from CHOMPStation"
+ source: "https://github.com/CHOMPStation2/CHOMPStation2"
\ No newline at end of file
diff --git a/Resources/Audio/Voice/Shadowkin/mar.ogg b/Resources/Audio/Voice/Shadowkin/mar.ogg
new file mode 100644
index 00000000000..b13d2df8377
Binary files /dev/null and b/Resources/Audio/Voice/Shadowkin/mar.ogg differ
diff --git a/Resources/Audio/Voice/Shadowkin/wurble.ogg b/Resources/Audio/Voice/Shadowkin/wurble.ogg
new file mode 100644
index 00000000000..859c9df3530
Binary files /dev/null and b/Resources/Audio/Voice/Shadowkin/wurble.ogg differ
diff --git a/Resources/Fonts/Lymphatic.ttf b/Resources/Fonts/Lymphatic.ttf
new file mode 100644
index 00000000000..36beef04b1e
Binary files /dev/null and b/Resources/Fonts/Lymphatic.ttf differ
diff --git a/Resources/Locale/en-US/actions/actions/shadowkin.ftl b/Resources/Locale/en-US/actions/actions/shadowkin.ftl
new file mode 100644
index 00000000000..063e1eafb74
--- /dev/null
+++ b/Resources/Locale/en-US/actions/actions/shadowkin.ftl
@@ -0,0 +1,2 @@
+action-name-shadowkin-rest = Rest
+action-description-shadowkin-rest = Rama
diff --git a/Resources/Locale/en-US/alerts/shadowkin.ftl b/Resources/Locale/en-US/alerts/shadowkin.ftl
new file mode 100644
index 00000000000..10f8438b76c
--- /dev/null
+++ b/Resources/Locale/en-US/alerts/shadowkin.ftl
@@ -0,0 +1,2 @@
+alerts-shadowkin-power-name = Power Level
+alerts-shadowkin-power-desc = How much energy is available to spend on Shadowkin powers.
\ No newline at end of file
diff --git a/Resources/Locale/en-US/chat/emotes.ftl b/Resources/Locale/en-US/chat/emotes.ftl
index e95cb2795db..4e26752c4b4 100644
--- a/Resources/Locale/en-US/chat/emotes.ftl
+++ b/Resources/Locale/en-US/chat/emotes.ftl
@@ -28,6 +28,8 @@ chat-emote-name-monkeyscreeches = Monkey Screeches
chat-emote-name-robotbeep = Robot
chat-emote-name-yawn = Yawn
chat-emote-name-snore = Snore
+chat-emote-name-mars = Mars
+chat-emote-name-wurble = Wurble
# Message
chat-emote-msg-scream = screams!
diff --git a/Resources/Locale/en-US/chat/managers/chat-manager.ftl b/Resources/Locale/en-US/chat/managers/chat-manager.ftl
index 034f38f854c..e25522ac1df 100644
--- a/Resources/Locale/en-US/chat/managers/chat-manager.ftl
+++ b/Resources/Locale/en-US/chat/managers/chat-manager.ftl
@@ -50,6 +50,8 @@ chat-manager-admin-channel-name = ADMIN
chat-manager-rate-limited = You are sending messages too quickly!
chat-manager-rate-limit-admin-announcement = Player { $player } breached chat rate limits. Watch them if this is a regular occurence.
+chat-manager-send-empathy-chat-wrap-message = {$source}: {$message}
+
## Speech verbs for chat
chat-speech-verb-suffix-exclamation = !
@@ -156,3 +158,5 @@ chat-speech-verb-name-electricity = Electricity
chat-speech-verb-electricity-1 = crackles
chat-speech-verb-electricity-2 = buzzes
chat-speech-verb-electricity-3 = screeches
+
+chat-speech-verb-marish = Mars
\ No newline at end of file
diff --git a/Resources/Locale/en-US/chat/ui/chat-box.ftl b/Resources/Locale/en-US/chat/ui/chat-box.ftl
index 720f0d15ab4..0dbfc0a27b1 100644
--- a/Resources/Locale/en-US/chat/ui/chat-box.ftl
+++ b/Resources/Locale/en-US/chat/ui/chat-box.ftl
@@ -30,4 +30,4 @@ hud-chatbox-channel-Notifications = Notifications
hud-chatbox-channel-Server = Server
hud-chatbox-channel-Visual = Actions
hud-chatbox-channel-Damage = Damage
-hud-chatbox-channel-Unspecified = Unspecified
+hud-chatbox-channel-Unspecified = Unspecified
\ No newline at end of file
diff --git a/Resources/Locale/en-US/language/languages.ftl b/Resources/Locale/en-US/language/languages.ftl
index 02527498dbd..76d172658b1 100644
--- a/Resources/Locale/en-US/language/languages.ftl
+++ b/Resources/Locale/en-US/language/languages.ftl
@@ -61,6 +61,9 @@ language-RobotTalk-description = A language consisting of harsh binary chirps, w
language-Sign-name = Tau-Ceti Basic Sign Language
language-Sign-description = TCB-SL for short, this sign language is prevalent among mute and deaf people.
+language-Marish-name = Marish
+language-Marish-description = An Language that can be used to speak in Empathy, Sharing eachother emotions with only one word, Shadowkins speaks this language with ease, tho its is nearly impossible to replicate it or learn it.
+
language-ValyrianStandard-name = Valyrian Standard
language-ValyrianStandard-description =
A language descended from eastern european languages of old earth - Valyrian Standard is the commonly spoken tongue of Harpies brought up on their homeworld of Valyrian 4b
diff --git a/Resources/Locale/en-US/markings/shadowkin.ftl b/Resources/Locale/en-US/markings/shadowkin.ftl
new file mode 100644
index 00000000000..5ad1f09963c
--- /dev/null
+++ b/Resources/Locale/en-US/markings/shadowkin.ftl
@@ -0,0 +1,7 @@
+marking-EyesShadowkin = Shadowkin
+
+marking-TailShadowkin = Shadowkin
+marking-TailShadowkinBig = Shadowkin (Big)
+marking-TailShadowkinShorter = Shadowkin (Short)
+marking-TailShadowkinMedium = Shadowkin (Medium)
+marking-TailShadowkinBigFluff = Shadowkin (Big and Fluffy)
\ No newline at end of file
diff --git a/Resources/Locale/en-US/metabolism/metabolizer-types.ftl b/Resources/Locale/en-US/metabolism/metabolizer-types.ftl
index 30ab6c050e2..d0f57e2bc0b 100644
--- a/Resources/Locale/en-US/metabolism/metabolizer-types.ftl
+++ b/Resources/Locale/en-US/metabolism/metabolizer-types.ftl
@@ -11,3 +11,4 @@ metabolizer-type-moth = Moth
metabolizer-type-arachnid = Arachnid
metabolizer-type-vampiric = Vampiric
metabolizer-type-liquorlifeline = Liquor Lifeline
+metabolizer-type-shadowkin = Shadowkin
diff --git a/Resources/Locale/en-US/psionics/psionic-powers.ftl b/Resources/Locale/en-US/psionics/psionic-powers.ftl
index 37482a41b65..f9954d7b001 100644
--- a/Resources/Locale/en-US/psionics/psionic-powers.ftl
+++ b/Resources/Locale/en-US/psionics/psionic-powers.ftl
@@ -1,5 +1,6 @@
generic-power-initialization-feedback = I Awaken.
already-casting = I cannot channel more than one power at a time.
+no-mana = I cannot channel enough power.
# Dispel
dispel-power-description = Dispel summoned entities such as familiars or forcewalls.
@@ -139,3 +140,12 @@ examine-mindbroken-message =
psionic-roll-failed = For a moment, my consciousness expands, yet I feel that it is not enough.
entity-anomaly-no-grid = There is nowhere for me to conjure beings.
power-overwhelming-power-feedback = {CAPITALIZE($entity)} wields a vast connection to the noösphere
+
+# Shadowkin ShadeSkip
+action-description-shadowkin-shadeskip = Aaramrra!
+
+# DarkSwap
+action-name-darkswap = DarkSwap
+action-description-darkswap = Mmra Mamm!
+
+ethereal-pickup-fail = My hand sizzles as it passes through...
\ No newline at end of file
diff --git a/Resources/Locale/en-US/research/technologies.ftl b/Resources/Locale/en-US/research/technologies.ftl
index 684a08dd9ae..fe7293d8481 100644
--- a/Resources/Locale/en-US/research/technologies.ftl
+++ b/Resources/Locale/en-US/research/technologies.ftl
@@ -43,6 +43,7 @@ research-technology-basic-xenoarcheology = Basic XenoArcheology
research-technology-alternative-research = Alternative Research
research-technology-magnets-tech = Localized Magnetism
research-technology-advanced-parts = Advanced Parts
+research-technology-advanced-bluespace = Advanced Bluespace Research
research-technology-anomaly-harnessing = Anomaly Core Harnessing
research-technology-grappling = Grappling
research-technology-abnormal-artifact-manipulation = Artifact Recycling
diff --git a/Resources/Locale/en-US/species/shadowkin.ftl b/Resources/Locale/en-US/species/shadowkin.ftl
new file mode 100644
index 00000000000..ebc56487b7a
--- /dev/null
+++ b/Resources/Locale/en-US/species/shadowkin.ftl
@@ -0,0 +1,15 @@
+shadowkin-power-examined-other = {CAPITALIZE(SUBJECT($target))} seems to be {$powerType}.
+shadowkin-power-examined-self = I have {$power}/{$powerMax} energy, I am {$powerType}.
+
+shadowkin-power-5 = energetic
+shadowkin-power-4 = great
+shadowkin-power-3 = good
+shadowkin-power-2 = okay
+shadowkin-power-1 = exhausted
+shadowkin-power-0 = drained
+
+examine-mindbroken-shadowkin-message = {CAPITALIZE($entity)} seems to be a blackeye.
+
+identity-eye-shadowkin = {$color}-eye
+
+shadowkin-blackeye = I feel my power draining away...
\ No newline at end of file
diff --git a/Resources/Locale/en-US/species/species.ftl b/Resources/Locale/en-US/species/species.ftl
index 6c40c454040..9278267cc44 100644
--- a/Resources/Locale/en-US/species/species.ftl
+++ b/Resources/Locale/en-US/species/species.ftl
@@ -11,3 +11,4 @@ species-name-moth = Moth Person
species-name-skeleton = Skeleton
species-name-vox = Vox
species-name-ipc = IPC
+species-name-shadowkin = Shadowkin
\ No newline at end of file
diff --git a/Resources/Locale/en-US/traits/traits.ftl b/Resources/Locale/en-US/traits/traits.ftl
index cfd53bbde33..d5c993e2994 100644
--- a/Resources/Locale/en-US/traits/traits.ftl
+++ b/Resources/Locale/en-US/traits/traits.ftl
@@ -400,3 +400,6 @@ trait-description-CyberEyesOmni =
This expensive implant provides the combined benefits of a SecHud, MedHud, and a DiagHud.
Note that this augmentation is considered Contraband for anyone not under the employ of station Security personel,
and may be disabled by your employer before dispatch to the station.
+
+trait-name-ShadowkinBlackeye = Blackeye
+trait-description-ShadowkinBlackeye = You lose your special Shadowkin powers, in return for some points.
diff --git a/Resources/Prototypes/Actions/psionics.yml b/Resources/Prototypes/Actions/psionics.yml
index 138823313aa..70f78460885 100644
--- a/Resources/Prototypes/Actions/psionics.yml
+++ b/Resources/Prototypes/Actions/psionics.yml
@@ -4,18 +4,18 @@
description: action-description-dispel
noSpawn: true
components:
- - type: EntityTargetAction
- icon: Interface/VerbIcons/dispel.png
- useDelay: 45
- checkCanAccess: false
- range: 6
- itemIconStyle: BigAction
- canTargetSelf: false
- blacklist:
- components:
- - PsionicInsulation
- - Mindbroken
- event: !type:DispelPowerActionEvent
+ - type: EntityTargetAction
+ icon: Interface/VerbIcons/dispel.png
+ useDelay: 45
+ checkCanAccess: false
+ range: 6
+ itemIconStyle: BigAction
+ canTargetSelf: false
+ blacklist:
+ components:
+ - PsionicInsulation
+ - Mindbroken
+ event: !type:DispelPowerActionEvent
- type: entity
id: ActionMassSleep
@@ -23,13 +23,13 @@
description: action-description-mass-sleep
noSpawn: true
components:
- - type: WorldTargetAction
- icon: Interface/VerbIcons/mass_sleep.png
- useDelay: 60
- checkCanAccess: false
- range: 8
- itemIconStyle: BigAction
- event: !type:MassSleepPowerActionEvent
+ - type: WorldTargetAction
+ icon: Interface/VerbIcons/mass_sleep.png
+ useDelay: 60
+ checkCanAccess: false
+ range: 8
+ itemIconStyle: BigAction
+ event: !type:MassSleepPowerActionEvent
- type: entity
id: ActionMindSwap
@@ -37,17 +37,17 @@
description: action-description-mind-swap
noSpawn: true
components:
- - type: EntityTargetAction
- icon: Interface/VerbIcons/mind_swap.png
- useDelay: 240
- checkCanAccess: false
- range: 8
- itemIconStyle: BigAction
- blacklist:
- components:
- - PsionicInsulation
- - Mindbroken
- event: !type:MindSwapPowerActionEvent
+ - type: EntityTargetAction
+ icon: Interface/VerbIcons/mind_swap.png
+ useDelay: 240
+ checkCanAccess: false
+ range: 8
+ itemIconStyle: BigAction
+ blacklist:
+ components:
+ - PsionicInsulation
+ - Mindbroken
+ event: !type:MindSwapPowerActionEvent
- type: entity
id: ActionMindSwapReturn
@@ -55,11 +55,11 @@
description: action-description-mind-swap-return
noSpawn: true
components:
- - type: InstantAction
- icon: Interface/VerbIcons/mind_swap_return.png
- useDelay: 20
- checkCanInteract: false
- event: !type:MindSwapPowerReturnActionEvent
+ - type: InstantAction
+ icon: Interface/VerbIcons/mind_swap_return.png
+ useDelay: 20
+ checkCanInteract: false
+ event: !type:MindSwapPowerReturnActionEvent
- type: entity
id: ActionNoosphericZap
@@ -67,16 +67,16 @@
description: action-description-noospheric-zap
noSpawn: true
components:
- - type: EntityTargetAction
- icon: Interface/VerbIcons/noospheric_zap.png
- useDelay: 100
- range: 5
- itemIconStyle: BigAction
- blacklist:
- components:
- - PsionicInsulation
- - Mindbroken
- event: !type:NoosphericZapPowerActionEvent
+ - type: EntityTargetAction
+ icon: Interface/VerbIcons/noospheric_zap.png
+ useDelay: 100
+ range: 5
+ itemIconStyle: BigAction
+ blacklist:
+ components:
+ - PsionicInsulation
+ - Mindbroken
+ event: !type:NoosphericZapPowerActionEvent
- type: entity
id: ActionPyrokinesis
@@ -84,13 +84,13 @@
description: action-description-pyrokinesis
noSpawn: true
components:
- - type: EntityTargetAction
- icon: Interface/VerbIcons/pyrokinesis.png
- useDelay: 50
- range: 6
- checkCanAccess: false
- itemIconStyle: BigAction
- event: !type:PyrokinesisPowerActionEvent
+ - type: EntityTargetAction
+ icon: Interface/VerbIcons/pyrokinesis.png
+ useDelay: 50
+ range: 6
+ checkCanAccess: false
+ itemIconStyle: BigAction
+ event: !type:PyrokinesisPowerActionEvent
- type: entity
id: ActionMetapsionic
@@ -98,10 +98,10 @@
description: action-description-metapsionic
noSpawn: true
components:
- - type: InstantAction
- icon: Interface/VerbIcons/metapsionic.png
- useDelay: 45
- event: !type:MetapsionicPowerActionEvent
+ - type: InstantAction
+ icon: Interface/VerbIcons/metapsionic.png
+ useDelay: 45
+ event: !type:MetapsionicPowerActionEvent
- type: entity
id: ActionPsionicRegeneration
@@ -109,10 +109,10 @@
description: action-description-psionic-regeneration
noSpawn: true
components:
- - type: InstantAction
- icon: Interface/VerbIcons/psionic_regeneration.png
- useDelay: 120
- event: !type:PsionicRegenerationPowerActionEvent
+ - type: InstantAction
+ icon: Interface/VerbIcons/psionic_regeneration.png
+ useDelay: 120
+ event: !type:PsionicRegenerationPowerActionEvent
- type: entity
id: ActionTelegnosis
@@ -120,10 +120,10 @@
description: action-description-telegnosis
noSpawn: true
components:
- - type: InstantAction
- icon: Interface/VerbIcons/telegnosis.png
- useDelay: 150
- event: !type:TelegnosisPowerActionEvent
+ - type: InstantAction
+ icon: Interface/VerbIcons/telegnosis.png
+ useDelay: 150
+ event: !type:TelegnosisPowerActionEvent
- type: entity
id: ActionPsionicInvisibility
@@ -131,10 +131,10 @@
description: action-description-psionic-invisibility
noSpawn: true
components:
- - type: InstantAction
- icon: Interface/VerbIcons/psionic_invisibility.png
- useDelay: 120
- event: !type:PsionicInvisibilityPowerActionEvent
+ - type: InstantAction
+ icon: Interface/VerbIcons/psionic_invisibility.png
+ useDelay: 120
+ event: !type:PsionicInvisibilityPowerActionEvent
- type: entity
id: ActionPsionicInvisibilityUsed
@@ -142,9 +142,9 @@
description: action-description-psionic-invisibility-off
noSpawn: true
components:
- - type: InstantAction
- icon: Interface/VerbIcons/psionic_invisibility_off.png
- event: !type:RemovePsionicInvisibilityOffPowerActionEvent
+ - type: InstantAction
+ icon: Interface/VerbIcons/psionic_invisibility_off.png
+ event: !type:RemovePsionicInvisibilityOffPowerActionEvent
- type: entity
id: ActionHealingWord
@@ -152,36 +152,36 @@
description: action-description-healing-word
noSpawn: true
components:
- - type: EntityTargetAction
- icon: { sprite : Interface/Actions/psionics.rsi, state: healing_word }
- useDelay: 10
- checkCanAccess: false
- range: 6
- itemIconStyle: BigAction
- canTargetSelf: true
- blacklist:
- components:
- - PsionicInsulation
- - Mindbroken
- event: !type:PsionicHealOtherPowerActionEvent
- healingAmount:
- groups: # These all get divided by the number of damage types in the group. So they're all -2.5.
- Genetic: -2.5
- Toxin: -5
- Airloss: -5
- Brute: -7.5
- Burn: -10
- rotReduction: 10
- useDelay: 1
- doRevive: true
- powerName: Healing Word
- popupText: healing-word-begin
- playSound: true
- minGlimmer: 2
- maxGlimmer: 4
- glimmerSoundThreshold: 100
- glimmerPopupThreshold: 200
- glimmerDoAfterVisibilityThreshold: 70
+ - type: EntityTargetAction
+ icon: { sprite: Interface/Actions/psionics.rsi, state: healing_word }
+ useDelay: 10
+ checkCanAccess: false
+ range: 6
+ itemIconStyle: BigAction
+ canTargetSelf: true
+ blacklist:
+ components:
+ - PsionicInsulation
+ - Mindbroken
+ event: !type:PsionicHealOtherPowerActionEvent
+ healingAmount:
+ groups: # These all get divided by the number of damage types in the group. So they're all -2.5.
+ Genetic: -2.5
+ Toxin: -5
+ Airloss: -5
+ Brute: -7.5
+ Burn: -10
+ rotReduction: 10
+ useDelay: 1
+ doRevive: true
+ powerName: Healing Word
+ popupText: healing-word-begin
+ playSound: true
+ minGlimmer: 2
+ maxGlimmer: 4
+ glimmerSoundThreshold: 100
+ glimmerPopupThreshold: 200
+ glimmerDoAfterVisibilityThreshold: 70
- type: entity
id: ActionRevivify
@@ -189,38 +189,38 @@
description: action-description-revivify
noSpawn: true
components:
- - type: EntityTargetAction
- icon: { sprite : Interface/Actions/psionics.rsi, state: revivify }
- useDelay: 120
- checkCanAccess: false
- range: 2
- itemIconStyle: BigAction
- canTargetSelf: false
- blacklist:
- components:
- - PsionicInsulation
- - Mindbroken
- event: !type:PsionicHealOtherPowerActionEvent
- healingAmount:
- # These all get divided by the number of damage types in the group. So they're all -15
- # Additionally, they're multiplied by the caster's Amplification, which,
- # assuming this is the only power they have, the multiplier is between 2.9-3.9
- groups:
- Genetic: -15
- Toxin: -30
- Airloss: -60 # Except airloss, which heals 30 per type
- Brute: -45
- Burn: -60
- rotReduction: 60
- doRevive: true
- powerName: Revivify
- popupText: revivify-begin
- playSound: true
- minGlimmer: 10 # These also get multiplied by caster stats. So,
- maxGlimmer: 15 # keeping in mind the ~3.5x multiplier, this spikes glimmer by as much as 60 points.
- glimmerSoundThreshold: 50
- glimmerPopupThreshold: 100
- glimmerDoAfterVisibilityThreshold: 35
+ - type: EntityTargetAction
+ icon: { sprite: Interface/Actions/psionics.rsi, state: revivify }
+ useDelay: 120
+ checkCanAccess: false
+ range: 2
+ itemIconStyle: BigAction
+ canTargetSelf: false
+ blacklist:
+ components:
+ - PsionicInsulation
+ - Mindbroken
+ event: !type:PsionicHealOtherPowerActionEvent
+ healingAmount:
+ # These all get divided by the number of damage types in the group. So they're all -15
+ # Additionally, they're multiplied by the caster's Amplification, which,
+ # assuming this is the only power they have, the multiplier is between 2.9-3.9
+ groups:
+ Genetic: -15
+ Toxin: -30
+ Airloss: -60 # Except airloss, which heals 30 per type
+ Brute: -45
+ Burn: -60
+ rotReduction: 60
+ doRevive: true
+ powerName: Revivify
+ popupText: revivify-begin
+ playSound: true
+ minGlimmer: 10 # These also get multiplied by caster stats. So,
+ maxGlimmer: 15 # keeping in mind the ~3.5x multiplier, this spikes glimmer by as much as 60 points.
+ glimmerSoundThreshold: 50
+ glimmerPopupThreshold: 100
+ glimmerDoAfterVisibilityThreshold: 35
- type: entity
id: ActionShadeskip
@@ -263,30 +263,78 @@
description: action-description-telekinetic-pulse
noSpawn: true
components:
- - type: InstantAction
- icon: { sprite : Interface/Actions/psionics.rsi, state: telekinetic_pulse }
- useDelay: 45
- checkCanInteract: false
- event: !type:AnomalyPowerActionEvent
- settings:
- powerName: "Telekinetic Pulse"
- overchargeFeedback: "shadeskip-overcharge-feedback" # The text behind this is fine.
- overchargeCooldown: 120
- overchargeRecoil:
- groups:
- Burn: -100 #This will be divided by the caster's Dampening.
- minGlimmer: 6
- maxGlimmer: 8
- doSupercritical: false
- entitySpawnEntries:
- - settings:
- spawnOnPulse: true
- minAmount: 1
- maxAmount: 1
- maxRange: 0.5
- spawns:
- - EffectFlashTelekineticPulse
- gravity:
- maxThrowRange: 3
- maxThrowStrength: 5
- spaceRange: 3
+ - type: InstantAction
+ icon: { sprite: Interface/Actions/psionics.rsi, state: telekinetic_pulse }
+ useDelay: 45
+ checkCanInteract: false
+ event: !type:AnomalyPowerActionEvent
+ settings:
+ powerName: "Telekinetic Pulse"
+ overchargeFeedback: "shadeskip-overcharge-feedback" # The text behind this is fine.
+ overchargeCooldown: 120
+ overchargeRecoil:
+ groups:
+ Burn: -100 #This will be divided by the caster's Dampening.
+ minGlimmer: 6
+ maxGlimmer: 8
+ doSupercritical: false
+ entitySpawnEntries:
+ - settings:
+ spawnOnPulse: true
+ minAmount: 1
+ maxAmount: 1
+ maxRange: 0.5
+ spawns:
+ - EffectFlashTelekineticPulse
+ gravity:
+ maxThrowRange: 3
+ maxThrowStrength: 5
+ spaceRange: 3
+
+- type: entity
+ id: ActionShadowkinShadeskip
+ name: action-name-shadeskip
+ description: action-description-shadowkin-shadeskip
+ noSpawn: true
+ components:
+ - type: InstantAction
+ icon: { sprite: Interface/Actions/shadowkin_icons.rsi, state: shadeskip }
+ useDelay: 10
+ checkCanInteract: false
+ event: !type:AnomalyPowerActionEvent
+ settings:
+ powerName: "Shadowkin-Shadeskip"
+ manaCost: 25
+ checkInsulation: false
+ minGlimmer: 0
+ maxGlimmer: 0
+ doSupercritical: false
+ entitySpawnEntries:
+ - settings:
+ spawnOnPulse: true
+ minAmount: 5
+ maxAmount: 10
+ maxRange: 2.5
+ spawns:
+ - ShadowkinShadow
+ - settings:
+ spawnOnPulse: true
+ minAmount: 1
+ maxAmount: 1
+ maxRange: 0.5
+ spawns:
+ - EffectFlashShadowkinShadeskip
+
+- type: entity
+ id: ActionDarkSwap
+ name: action-name-darkswap
+ description: action-description-darkswap
+ noSpawn: true
+ components:
+ - type: InstantAction
+ icon: { sprite: Interface/Actions/shadowkin_icons.rsi, state: darkswap }
+ useDelay: 10
+ checkCanInteract: false
+ event: !type:DarkSwapActionEvent
+ manaCost: 100
+ checkInsulation: false
diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml
index f81def7f7c3..a7144cdda57 100644
--- a/Resources/Prototypes/Actions/types.yml
+++ b/Resources/Prototypes/Actions/types.yml
@@ -304,6 +304,18 @@
icon: { sprite: Clothing/Head/Hats/pyjamasyndicatered.rsi, state: icon }
event: !type:SleepActionEvent
+- type: entity
+ id: ShadowkinActionSleep
+ name: action-name-shadowkin-rest
+ description: action-description-shadowkin-rest
+ noSpawn: true
+ components:
+ - type: InstantAction
+ checkCanInteract: false
+ checkConsciousness: false
+ icon: { sprite: Interface/Actions/shadowkin_icons.rsi, state: rest }
+ event: !type:SleepActionEvent
+
- type: entity
id: ActionWake
name: Wake up
diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml
index 1e0a8a8c626..bb0c47f48fb 100644
--- a/Resources/Prototypes/Alerts/alerts.yml
+++ b/Resources/Prototypes/Alerts/alerts.yml
@@ -7,6 +7,7 @@
- category: Health
- category: Mood
- category: Stamina
+ - alertType: ShadowkinPower
- alertType: SuitPower
- category: Internals
- alertType: Fire
diff --git a/Resources/Prototypes/Alerts/shadowkin.yml b/Resources/Prototypes/Alerts/shadowkin.yml
new file mode 100644
index 00000000000..66d41351bab
--- /dev/null
+++ b/Resources/Prototypes/Alerts/shadowkin.yml
@@ -0,0 +1,23 @@
+- type: alert
+ id: ShadowkinPower
+ icons:
+ - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi
+ state: power0
+ - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi
+ state: power1
+ - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi
+ state: power2
+ - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi
+ state: power3
+ - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi
+ state: power4
+ - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi
+ state: power5
+ - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi
+ state: power6
+ - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi
+ state: power7
+ name: alerts-shadowkin-power-name
+ description: alerts-shadowkin-power-desc
+ minSeverity: 0
+ maxSeverity: 7
\ No newline at end of file
diff --git a/Resources/Prototypes/Body/Organs/shadowkin.yml b/Resources/Prototypes/Body/Organs/shadowkin.yml
new file mode 100644
index 00000000000..695ddec1ab2
--- /dev/null
+++ b/Resources/Prototypes/Body/Organs/shadowkin.yml
@@ -0,0 +1,113 @@
+- type: entity
+ id: OrganShadowkinBrain
+ parent: OrganHumanBrain
+ components:
+ - type: Sprite
+ sprite: Mobs/Species/Shadowkin/organs.rsi
+ state: brain
+
+- type: entity
+ id: OrganShadowkinEyes
+ parent: OrganHumanEyes
+ description: I see beyond anything you ever will!
+ components:
+ - type: Sprite
+ sprite: Mobs/Species/Shadowkin/organs.rsi
+ layers:
+ - state: eyes
+
+- type: entity
+ id: OrganShadowkinEars
+ parent: OrganHumanEars
+ description: Hey, listen!
+ components:
+ - type: Sprite
+ sprite: Mobs/Species/Shadowkin/organs.rsi
+ state: ears
+
+- type: entity
+ id: OrganShadowkinTongue
+ parent: OrganHumanTongue
+ components:
+ - type: Sprite
+ sprite: Mobs/Species/Shadowkin/organs.rsi
+ state: tongue
+
+
+- type: entity
+ id: OrganShadowkinAppendix
+ parent: OrganHumanAppendix
+ components:
+ - type: Sprite
+ sprite: Mobs/Species/Shadowkin/organs.rsi
+ layers:
+ - state: appendix
+
+
+- type: entity
+ id: OrganShadowkinHeart
+ parent: OrganHumanHeart
+ components:
+ - type: Sprite
+ sprite: Mobs/Species/Shadowkin/organs.rsi
+ state: heart
+ - type: Metabolizer
+ maxReagents: 2
+ metabolizerTypes: [Shadowkin]
+ groups:
+ - id: Medicine
+ - id: Poison
+ - id: Narcotic
+
+- type: entity
+ id: OrganShadowkinStomach
+ parent: OrganHumanStomach
+ description: '"Yummy!", says the stomach, although you are unable to hear it.'
+ components:
+ - type: Sprite
+ sprite: Mobs/Species/Shadowkin/organs.rsi
+ state: stomach
+ - type: SolutionContainerManager
+ solutions:
+ stomach:
+ maxVol: 40
+ food:
+ maxVol: 5
+ reagents:
+ - ReagentId: UncookedAnimalProteins
+ Quantity: 5
+ - type: Metabolizer
+ maxReagents: 3
+ metabolizerTypes: [Shadowkin]
+ groups:
+ - id: Food
+ - id: Drink
+
+- type: entity
+ id: OrganShadowkinLiver
+ parent: OrganHumanLiver
+ description: "Live 'er? I hardly know 'er!"
+ components:
+ - type: Sprite
+ sprite: Mobs/Species/Shadowkin/organs.rsi
+ state: liver
+ - type: Metabolizer
+ maxReagents: 1
+ metabolizerTypes: [Shadowkin]
+ groups:
+ - id: Alcohol
+ rateModifier: 0.1
+
+- type: entity
+ id: OrganShadowkinKidneys
+ parent: OrganHumanKidneys
+ description: Give the kid their knees back, please, this is the third time this week.
+ components:
+ - type: Sprite
+ sprite: Mobs/Species/Shadowkin/organs.rsi
+ layers:
+ - state: kidneys
+ - type: Metabolizer
+ maxReagents: 5
+ metabolizerTypes: [Shadowkin]
+ removeEmpty: true
\ No newline at end of file
diff --git a/Resources/Prototypes/Body/Parts/shadowkin.yml b/Resources/Prototypes/Body/Parts/shadowkin.yml
new file mode 100644
index 00000000000..0ddff93443c
--- /dev/null
+++ b/Resources/Prototypes/Body/Parts/shadowkin.yml
@@ -0,0 +1,155 @@
+- type: entity
+ id: PartShadowkin
+ parent: BaseItem
+ name: "Shadowkin body part"
+ abstract: true
+ components:
+ - type: Sprite
+ netsync: false
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ - type: Icon
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ - type: Damageable
+ damageContainer: Biological
+ - type: BodyPart
+ - type: ContainerContainer
+ containers:
+ bodypart: !type:Container
+ ents: []
+
+- type: entity
+ id: TorsoShadowkin
+ name: "Shadowkin torso"
+ parent: PartShadowkin
+ components:
+ - type: Sprite
+ state: "torso_m"
+ - type: Icon
+ state: "torso_m"
+ - type: BodyPart
+ partType: Torso
+
+- type: entity
+ id: HeadShadowkin
+ name: "Shadowkin head"
+ parent: PartShadowkin
+ components:
+ - type: Sprite
+ state: "head_m"
+ - type: Icon
+ state: "head_m"
+ - type: BodyPart
+ partType: Head
+ - type: Input
+ context: "ghost"
+ - type: MovementSpeedModifier
+ baseWalkSpeed: 0
+ baseSprintSpeed: 0
+ - type: InputMover
+ - type: GhostOnMove
+
+- type: entity
+ id: LeftArmShadowkin
+ name: "left Shadowkin arm"
+ parent: PartShadowkin
+ components:
+ - type: Sprite
+ state: "l_arm"
+ - type: Icon
+ state: "l_arm"
+ - type: BodyPart
+ partType: Arm
+ symmetry: Left
+
+- type: entity
+ id: RightArmShadowkin
+ name: "right Shadowkin arm"
+ parent: PartShadowkin
+ components:
+ - type: Sprite
+ state: "r_arm"
+ - type: Icon
+ state: "r_arm"
+ - type: BodyPart
+ partType: Arm
+ symmetry: Right
+
+- type: entity
+ id: LeftHandShadowkin
+ name: "left Shadowkin hand"
+ parent: PartShadowkin
+ components:
+ - type: Sprite
+ state: "l_hand"
+ - type: Icon
+ state: "l_hand"
+ - type: BodyPart
+ partType: Hand
+ symmetry: Left
+
+- type: entity
+ id: RightHandShadowkin
+ name: "right Shadowkin hand"
+ parent: PartShadowkin
+ components:
+ - type: Sprite
+ state: "r_hand"
+ - type: Icon
+ state: "r_hand"
+ - type: BodyPart
+ partType: Hand
+ symmetry: Right
+
+- type: entity
+ id: LeftLegShadowkin
+ name: "left Shadowkin leg"
+ parent: PartShadowkin
+ components:
+ - type: Sprite
+ state: "l_leg"
+ - type: Icon
+ state: "l_leg"
+ - type: BodyPart
+ partType: Leg
+ symmetry: Left
+ - type: MovementBodyPart
+
+- type: entity
+ id: RightLegShadowkin
+ name: "right Shadowkin leg"
+ parent: PartShadowkin
+ components:
+ - type: Sprite
+ state: "r_leg"
+ - type: Icon
+ state: "r_leg"
+ - type: BodyPart
+ partType: Leg
+ symmetry: Right
+ - type: MovementBodyPart
+
+- type: entity
+ id: LeftFootShadowkin
+ name: "left Shadowkin foot"
+ parent: PartShadowkin
+ components:
+ - type: Sprite
+ state: "l_foot"
+ - type: Icon
+ state: "l_foot"
+ - type: BodyPart
+ partType: Foot
+ symmetry: Left
+
+- type: entity
+ id: RightFootShadowkin
+ name: "right Shadowkin foot"
+ parent: PartShadowkin
+ components:
+ - type: Sprite
+ state: "r_foot"
+ - type: Icon
+ state: "r_foot"
+ - type: BodyPart
+ partType: Foot
+ symmetry: Right
diff --git a/Resources/Prototypes/Body/Prototypes/shadowkin.yml b/Resources/Prototypes/Body/Prototypes/shadowkin.yml
new file mode 100644
index 00000000000..dddad7bdb5d
--- /dev/null
+++ b/Resources/Prototypes/Body/Prototypes/shadowkin.yml
@@ -0,0 +1,48 @@
+- type: body
+ id: Shadowkin
+ name: "Shadowkin"
+ root: torso
+ slots:
+ head:
+ part: HeadShadowkin
+ connections:
+ - torso
+ organs:
+ brain: OrganShadowkinBrain
+ eyes: OrganShadowkinEyes
+ torso:
+ part: TorsoShadowkin
+ connections:
+ - left arm
+ - right arm
+ - left leg
+ - right leg
+ organs:
+ heart: OrganShadowkinHeart
+ stomach: OrganShadowkinStomach
+ liver: OrganShadowkinLiver
+ kidneys: OrganShadowkinKidneys
+ right arm:
+ part: RightArmShadowkin
+ connections:
+ - right hand
+ left arm:
+ part: LeftArmShadowkin
+ connections:
+ - left hand
+ right hand:
+ part: RightHandShadowkin
+ left hand:
+ part: LeftHandShadowkin
+ right leg:
+ part: RightLegShadowkin
+ connections:
+ - right foot
+ left leg:
+ part: LeftLegShadowkin
+ connections:
+ - left foot
+ right foot:
+ part: RightFootShadowkin
+ left foot:
+ part: LeftFootShadowkin
\ No newline at end of file
diff --git a/Resources/Prototypes/Chemistry/metabolizer_types.yml b/Resources/Prototypes/Chemistry/metabolizer_types.yml
index b49fad6c277..80f69893c6e 100644
--- a/Resources/Prototypes/Chemistry/metabolizer_types.yml
+++ b/Resources/Prototypes/Chemistry/metabolizer_types.yml
@@ -52,3 +52,7 @@
- type: metabolizerType
id: LiquorLifeline
name: metabolizer-type-liquorlifeline
+
+- type: metabolizerType
+ id: Shadowkin
+ name: metabolizer-type-shadowkin
diff --git a/Resources/Prototypes/Damage/modifier_sets.yml b/Resources/Prototypes/Damage/modifier_sets.yml
index 39c956df624..8cca467f69b 100644
--- a/Resources/Prototypes/Damage/modifier_sets.yml
+++ b/Resources/Prototypes/Damage/modifier_sets.yml
@@ -358,6 +358,20 @@
Piercing: 0.6
Holy: 1.5
+- type: damageModifierSet
+ id: Shadowkin
+ coefficients:
+ Blunt: 0.95
+ Slash: 1.2
+ Piercing: 1.1
+ Asphyxiation: 0
+ Cold: 0.75
+ Heat: 1.2
+ Cellular: 0.25
+ Bloodloss: 1.35
+ Shock: 1.25
+ Radiation: 1.3
+
- type: damageModifierSet
id: DermalArmor
coefficients:
diff --git a/Resources/Prototypes/Datasets/Names/shadowkin.yml b/Resources/Prototypes/Datasets/Names/shadowkin.yml
new file mode 100644
index 00000000000..4dbf4c5dc54
--- /dev/null
+++ b/Resources/Prototypes/Datasets/Names/shadowkin.yml
@@ -0,0 +1,70 @@
+# Names for the shadowkin,
+# Shadowkin names are descriptive of
+# Their Primary Emotion,
+# A State of Being,
+# Or past Memories.
+
+- type: dataset
+ id: names_shadowkin
+ values:
+ # Mar
+ # - Mar
+
+ # Sad
+ - Fragile
+ - Heartbreak
+ - Inferior
+ - Lone
+ - Lonesome
+ - Loss
+ - Solitary
+ - Solitude
+ - Sorrow
+ - Shade
+
+ # Angry
+ - Fear
+ - Fearful
+ - Fury
+ - Pain
+ - Rage
+ - Rush
+ - Wrath
+
+ # Happy
+ - Calm
+ - Content
+ - Contented
+ - Happy
+ - Hope
+ - Joyous
+ - Lovely
+ - Peace
+ - Peaceful
+ - Quiet
+ - Serene
+ - Serenity
+ - Tranquil
+ - Tranquility
+
+ # Memory
+ - Dillusioned
+ - Forgotten
+ - Focusless
+ - Lost
+ - Memory
+ - Recollection
+ - Remembrance
+ - Reminisce
+ - Reminiscence
+
+ # Other
+ - Apathy
+ - Collected
+ - Curiosity
+ - Free
+ - Interest
+ - Jax # White eye (jack of all trades) :)
+ - Still
+ - Unbound
+ - Shadows
\ No newline at end of file
diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/humanoid.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/humanoid.yml
index 2c8b01553a7..4db83e9ab13 100644
--- a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/humanoid.yml
+++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/humanoid.yml
@@ -10,6 +10,8 @@
- type: randomHumanoidSettings
id: SyndicateListener
+ speciesBlacklist:
+ - Shadowkin
components:
- type: Loadout
prototypes: [SyndicateListenerGear]
@@ -34,6 +36,8 @@
- type: randomHumanoidSettings
id: Mobster
randomizeName: false
+ speciesBlacklist:
+ - Shadowkin
components:
- type: GhostRole
name: Mobster
@@ -65,6 +69,8 @@
- type: randomHumanoidSettings
id: MobsterAlt
randomizeName: false
+ speciesBlacklist:
+ - Shadowkin
components:
- type: GhostRole
name: Mobster
diff --git a/Resources/Prototypes/DeltaV/Traits/altvision.yml b/Resources/Prototypes/DeltaV/Traits/altvision.yml
index 390e14d4ad1..54fec7df8b9 100644
--- a/Resources/Prototypes/DeltaV/Traits/altvision.yml
+++ b/Resources/Prototypes/DeltaV/Traits/altvision.yml
@@ -7,6 +7,7 @@
species:
- Vulpkanin
- Harpy
+ - Shadowkin
- !type:CharacterTraitRequirement
inverted: true
traits:
@@ -23,6 +24,7 @@
species:
- Vulpkanin
- Harpy
+ - Shadowkin
- !type:CharacterTraitRequirement
inverted: true
traits:
diff --git a/Resources/Prototypes/DeltaV/Voice/speech_emote_sounds.yml b/Resources/Prototypes/DeltaV/Voice/speech_emote_sounds.yml
index 119134dcf18..ad91d9c2d2e 100644
--- a/Resources/Prototypes/DeltaV/Voice/speech_emote_sounds.yml
+++ b/Resources/Prototypes/DeltaV/Voice/speech_emote_sounds.yml
@@ -60,6 +60,10 @@
collection: VulpkaninHowls
Weh:
collection: Weh
+ Mars:
+ collection: Mars
+ Wurble:
+ collection: Wurble
- type: emoteSounds
id: MaleVulpkanin
diff --git a/Resources/Prototypes/Entities/Clothing/Back/specific.yml b/Resources/Prototypes/Entities/Clothing/Back/specific.yml
index fcdecffc8f1..e2932537e90 100644
--- a/Resources/Prototypes/Entities/Clothing/Back/specific.yml
+++ b/Resources/Prototypes/Entities/Clothing/Back/specific.yml
@@ -61,3 +61,25 @@
solution: tank
- type: ExaminableSolution
solution: tank
+
+- type: entity
+ parent: Clothing
+ id: ClothingBackpackEtherealTeleporter
+ name: ethereal teleporter
+ description: Originally created while several research facilities were experimenting on Shadowkin, this backpack allows the wearer to jump the gap between the "normal" dimension and The Dark.
+ components:
+ - type: Tag
+ tags:
+ - WhitelistChameleon
+ - type: Sprite
+ sprite: Clothing/Back/etherealteleporter.rsi
+ state: icon
+ - type: Item
+ size: Ginormous
+ - type: Clothing
+ slots: BACK
+ sprite: Clothing/Back/etherealteleporter.rsi
+ # TODO: Uncomment this when ClothingGrantPsionicPower is fixed and back working.
+ # - type: ClothingGrantPsionicPower
+ # power: DarkSwapPower
+ # - type: Psionic
diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml
index 9e47a685f0b..f50b0dbca54 100644
--- a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml
+++ b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml
@@ -265,3 +265,15 @@
sprite: Clothing/Eyes/Glasses/meson.rsi
- type: Clothing
sprite: Clothing/Eyes/Glasses/meson.rsi
+
+- type: entity
+ parent: ClothingEyesBase
+ id: ClothingEyesGlassesEthereal
+ name: ethereal goggles
+ description: An unusual pair of goggles developed during a time of inhumane experimentation involving Shadowkin. They are designed to allow the wearer to peer into The Dark.
+ components:
+ - type: Sprite
+ sprite: Clothing/Eyes/Glasses/etherealgoogles.rsi
+ - type: Clothing
+ sprite: Clothing/Eyes/Glasses/etherealgoogles.rsi
+ - type: ShowEthereal
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/misc.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/misc.yml
index f908465f7a5..b5169a9cf6b 100644
--- a/Resources/Prototypes/Entities/Clothing/OuterClothing/misc.yml
+++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/misc.yml
@@ -428,3 +428,20 @@
sprite: Clothing/OuterClothing/Misc/unathirobe.rsi
- type: Clothing
sprite: Clothing/OuterClothing/Misc/unathirobe.rsi
+
+- type: entity
+ parent: ClothingOuterBase
+ id: ClothingOuterShadowkinRestraints
+ name: shadowkin restraints
+ description: One of the first creations after finding Shadowkin, these were used to contain the Shadowkin during research so they didn't teleport away.
+ components:
+ - type: Sprite
+ sprite: Clothing/OuterClothing/Misc/shadowkinrestraints.rsi
+ - type: Clothing
+ sprite: Clothing/OuterClothing/Misc/shadowkinrestraints.rsi
+ equipDelay: 0.5
+ unequipDelay: 10
+ - type: ShadowkinCuff
+ - type: GuideHelp
+ guides:
+ - Shadowkin
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Effects/bluespace_flash.yml b/Resources/Prototypes/Entities/Effects/bluespace_flash.yml
index a0b3f81abce..e9ad08f0156 100644
--- a/Resources/Prototypes/Entities/Effects/bluespace_flash.yml
+++ b/Resources/Prototypes/Entities/Effects/bluespace_flash.yml
@@ -38,4 +38,46 @@
lifetime: 1
- type: EmitSoundOnSpawn
sound:
- path: /Audio/Effects/Lightning/lightningbolt.ogg
\ No newline at end of file
+ path: /Audio/Effects/Lightning/lightningbolt.ogg
+
+- type: entity
+ id: EffectFlashShadowkinShadeskip
+ noSpawn: true
+ components:
+ - type: PointLight
+ radius: 5
+ energy: 3.5
+ color: "#7100bd"
+ - type: TimedDespawn
+ lifetime: 3
+ - type: EmitSoundOnSpawn
+ sound:
+ path: /Audio/Effects/Shadowkin/shadeskip.ogg
+
+- type: entity
+ id: EffectFlashShadowkinDarkSwapOn
+ noSpawn: true
+ components:
+ - type: PointLight
+ radius: 5
+ energy: 3.5
+ color: "#7100bd"
+ - type: TimedDespawn
+ lifetime: 3
+ - type: EmitSoundOnSpawn
+ sound:
+ path: /Audio/Effects/Shadowkin/darkswapon.ogg
+
+- type: entity
+ id: EffectFlashShadowkinDarkSwapOff
+ noSpawn: true
+ components:
+ - type: PointLight
+ radius: 5
+ energy: 3.5
+ color: "#7100bd"
+ - type: TimedDespawn
+ lifetime: 3
+ - type: EmitSoundOnSpawn
+ sound:
+ path: /Audio/Effects/Shadowkin/darkswapoff.ogg
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/reptilian.yml
index cad3e779621..6197f820308 100644
--- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/reptilian.yml
+++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/reptilian.yml
@@ -208,7 +208,7 @@
id: LizardChestTiger
bodyPart: Chest
markingCategory: Chest
- speciesRestriction: [Reptilian]
+ speciesRestriction: [Reptilian, Shadowkin]
sprites:
- sprite: Mobs/Customization/reptilian_parts.rsi
state: body_tiger
diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/shadowkin.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/shadowkin.yml
new file mode 100644
index 00000000000..f86852a9879
--- /dev/null
+++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/shadowkin.yml
@@ -0,0 +1,83 @@
+# Ears
+
+- type: marking
+ id: EarsShadowkin
+ bodyPart: HeadTop
+ markingCategory: HeadTop
+ speciesRestriction: [Shadowkin]
+ forcedColoring: true
+ sprites:
+ - sprite: Mobs/Customization/Shadowkin/ears.rsi
+ state: shadowkin
+
+- type: marking
+ id: EarsShadowkinStriped
+ bodyPart: HeadTop
+ markingCategory: HeadTop
+ speciesRestriction: [Shadowkin]
+ coloring:
+ default:
+ type:
+ !type:SkinColoring
+ layers:
+ shadowkin_stripes:
+ type:
+ !type:SimpleColoring
+ color: "#FFFFFF"
+ sprites:
+ - sprite: Mobs/Customization/Shadowkin/ears.rsi
+ state: shadowkin
+ - sprite: Mobs/Customization/Shadowkin/ears.rsi
+ state: shadowkin_stripes
+
+# Tails
+
+- type: marking
+ id: TailShadowkin
+ bodyPart: Tail
+ markingCategory: Tail
+ speciesRestriction: [Shadowkin]
+ forcedColoring: true
+ sprites:
+ - sprite: Mobs/Customization/Shadowkin/tails64x32.rsi
+ state: shadowkin
+
+- type: marking
+ id: TailShadowkinBig
+ bodyPart: Tail
+ markingCategory: Tail
+ speciesRestriction: [Shadowkin]
+ forcedColoring: true
+ sprites:
+ - sprite: Mobs/Customization/Shadowkin/tails64x32.rsi
+ state: shadowkin_big
+
+- type: marking
+ id: TailShadowkinBigFluff
+ bodyPart: Tail
+ markingCategory: Tail
+ speciesRestriction: [Shadowkin]
+ forcedColoring: true
+ sprites:
+ - sprite: Mobs/Customization/Shadowkin/tails64x32.rsi
+ state: shadowkin_big_fluff
+
+- type: marking
+ id: TailShadowkinShorter
+ bodyPart: Tail
+ markingCategory: Tail
+ speciesRestriction: [Shadowkin]
+ forcedColoring: true
+ sprites:
+ - sprite: Mobs/Customization/Shadowkin/tails32x32.rsi
+ state: shadowkin_shorter
+
+- type: marking
+ id: TailShadowkinMedium
+ bodyPart: Tail
+ markingCategory: Tail
+ speciesRestriction: [Shadowkin]
+ forcedColoring: true
+ sprites:
+ - sprite: Mobs/Customization/Shadowkin/tails32x32.rsi
+ state: shadowkin_medium
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/slime.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/slime.yml
index 57b25798e74..0bff2347471 100644
--- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/slime.yml
+++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/slime.yml
@@ -2,7 +2,7 @@
id: SlimeGradientLeftArm
bodyPart: LArm
markingCategory: LeftArm
- speciesRestriction: [SlimePerson]
+ speciesRestriction: [SlimePerson, Shadowkin]
sprites:
- sprite: Mobs/Customization/slime_parts.rsi
state: gradient_l_arm
@@ -11,7 +11,7 @@
id: SlimeGradientRightArm
bodyPart: RArm
markingCategory: RightArm
- speciesRestriction: [SlimePerson]
+ speciesRestriction: [SlimePerson, Shadowkin]
sprites:
- sprite: Mobs/Customization/slime_parts.rsi
state: gradient_r_arm
@@ -20,7 +20,7 @@
id: SlimeGradientLeftLeg
bodyPart: LLeg
markingCategory: LeftLeg
- speciesRestriction: [SlimePerson]
+ speciesRestriction: [SlimePerson, Shadowkin]
sprites:
- sprite: Mobs/Customization/slime_parts.rsi
state: gradient_l_leg
@@ -29,7 +29,7 @@
id: SlimeGradientRightLeg
bodyPart: RLeg
markingCategory: RightLeg
- speciesRestriction: [SlimePerson]
+ speciesRestriction: [SlimePerson, Shadowkin]
sprites:
- sprite: Mobs/Customization/slime_parts.rsi
state: gradient_r_leg
@@ -38,7 +38,7 @@
id: SlimeGradientLeftFoot
bodyPart: LFoot
markingCategory: LeftFoot
- speciesRestriction: [SlimePerson]
+ speciesRestriction: [SlimePerson, Shadowkin]
sprites:
- sprite: Mobs/Customization/slime_parts.rsi
state: gradient_l_foot
@@ -47,7 +47,7 @@
id: SlimeGradientRightFoot
bodyPart: RFoot
markingCategory: RightFoot
- speciesRestriction: [SlimePerson]
+ speciesRestriction: [SlimePerson, Shadowkin]
sprites:
- sprite: Mobs/Customization/slime_parts.rsi
state: gradient_r_foot
@@ -56,7 +56,7 @@
id: SlimeGradientLeftHand
bodyPart: LHand
markingCategory: LeftHand
- speciesRestriction: [SlimePerson]
+ speciesRestriction: [SlimePerson, Shadowkin]
sprites:
- sprite: Mobs/Customization/slime_parts.rsi
state: gradient_l_hand
@@ -65,7 +65,7 @@
id: SlimeGradientRightHand
bodyPart: RHand
markingCategory: RightHand
- speciesRestriction: [SlimePerson]
+ speciesRestriction: [SlimePerson, Shadowkin]
sprites:
- sprite: Mobs/Customization/slime_parts.rsi
state: gradient_r_hand
diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/tattoos.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/tattoos.yml
index 88307085397..93a16fcfd3c 100644
--- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/tattoos.yml
+++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/tattoos.yml
@@ -2,7 +2,7 @@
id: TattooHiveChest
bodyPart: Chest
markingCategory: Chest
- speciesRestriction: [Human, Dwarf, Felinid, Oni] # Delta V - Felinid, Oni
+ speciesRestriction: [Human, Dwarf, Felinid, Oni, Shadowkin] # Delta V - Felinid, Oni
coloring:
default:
type:
@@ -16,7 +16,7 @@
id: TattooNightlingChest
bodyPart: Chest
markingCategory: Chest
- speciesRestriction: [Human, Dwarf, Felinid, Oni] # Delta V - Felinid, Oni
+ speciesRestriction: [Human, Dwarf, Felinid, Oni, Shadowkin] # Delta V - Felinid, Oni
coloring:
default:
type:
@@ -30,7 +30,7 @@
id: TattooSilverburghLeftLeg
bodyPart: LLeg
markingCategory: LeftLeg
- speciesRestriction: [Human, Dwarf, Felinid, Oni] # Delta V - Felinid, Oni
+ speciesRestriction: [Human, Dwarf, Felinid, Oni, Shadowkin] # Delta V - Felinid, Oni
coloring:
default:
type:
@@ -44,7 +44,7 @@
id: TattooSilverburghRightLeg
bodyPart: RLeg
markingCategory: RightLeg
- speciesRestriction: [Human, Dwarf, Felinid, Oni] # Delta V - Felinid, Oni
+ speciesRestriction: [Human, Dwarf, Felinid, Oni, Shadowkin] # Delta V - Felinid, Oni
coloring:
default:
type:
@@ -58,7 +58,7 @@
id: TattooCampbellLeftArm
bodyPart: LArm
markingCategory: LeftArm
- speciesRestriction: [Human, Dwarf, Felinid, Oni] # Delta V - Felinid, Oni
+ speciesRestriction: [Human, Dwarf, Felinid, Oni, Shadowkin] # Delta V - Felinid, Oni
coloring:
default:
type:
@@ -72,7 +72,7 @@
id: TattooCampbellRightArm
bodyPart: RArm
markingCategory: RightArm
- speciesRestriction: [Human, Dwarf, Felinid, Oni] # Delta V - Felinid, Oni
+ speciesRestriction: [Human, Dwarf, Felinid, Oni, Shadowkin] # Delta V - Felinid, Oni
coloring:
default:
type:
@@ -86,7 +86,7 @@
id: TattooCampbellLeftLeg
bodyPart: LLeg
markingCategory: LeftLeg
- speciesRestriction: [Human, Dwarf, Felinid, Oni] # Delta V - Felinid, Oni
+ speciesRestriction: [Human, Dwarf, Felinid, Oni, Shadowkin] # Delta V - Felinid, Oni
coloring:
default:
type:
@@ -100,7 +100,7 @@
id: TattooCampbellRightLeg
bodyPart: RLeg
markingCategory: RightLeg
- speciesRestriction: [Human, Dwarf, Felinid, Oni] # Delta V - Felinid, Oni
+ speciesRestriction: [Human, Dwarf, Felinid, Oni, Shadowkin] # Delta V - Felinid, Oni
coloring:
default:
type:
diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
index c690bbb3117..dce408ed827 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
@@ -10,6 +10,7 @@
- PsionicInvisibility
- Ghost
- Normal
+ - Ethereal
- type: ContentEye
maxZoom: 8.916104, 8.916104
- type: Tag
diff --git a/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml b/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml
index e96633dde8b..a4bfe2289bd 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml
@@ -21,6 +21,8 @@
- type: randomHumanoidSettings
id: DeathSquad
randomizeName: false
+ speciesBlacklist:
+ - Shadowkin
components:
- type: MindShield
- type: GhostRole
@@ -59,6 +61,8 @@
- type: randomHumanoidSettings
id: ERTLeader
randomizeName: false
+ speciesBlacklist:
+ - Shadowkin
components:
- type: MindShield
- type: GhostRole
@@ -486,6 +490,8 @@
- type: randomHumanoidSettings
id: CBURNAgent
+ speciesBlacklist:
+ - Shadowkin
components:
- type: MindShield
- type: Loadout
@@ -516,6 +522,8 @@
- type: randomHumanoidSettings
id: CentcomOfficial
+ speciesBlacklist:
+ - Shadowkin
components:
- type: MindShield
- type: GhostRole
@@ -544,6 +552,8 @@
- type: randomHumanoidSettings
id: SyndicateAgent
+ speciesBlacklist:
+ - Shadowkin
components:
- type: Loadout
prototypes: [SyndicateOperativeGearExtremelyBasic]
@@ -560,6 +570,8 @@
- type: randomHumanoidSettings
id: NukeOp
+ speciesBlacklist:
+ - Shadowkin
components:
- type: NukeOperative
- type: Psionic
@@ -582,6 +594,8 @@
- type: randomHumanoidSettings
id: Cluwne
+ speciesBlacklist:
+ - Shadowkin
randomizeName: false
components:
- type: GhostRole
diff --git a/Resources/Prototypes/Entities/Mobs/Player/observer.yml b/Resources/Prototypes/Entities/Mobs/Player/observer.yml
index 0086be81d9a..c92595ffc9d 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/observer.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/observer.yml
@@ -32,6 +32,7 @@
- PsionicInvisibility
- Ghost
- Normal
+ - Ethereal
- type: Input
context: "ghost"
- type: Examiner
diff --git a/Resources/Prototypes/Entities/Mobs/Player/shadowkin.yml b/Resources/Prototypes/Entities/Mobs/Player/shadowkin.yml
new file mode 100644
index 00000000000..2a58fe5c1fe
--- /dev/null
+++ b/Resources/Prototypes/Entities/Mobs/Player/shadowkin.yml
@@ -0,0 +1,5 @@
+- type: entity
+ save: false
+ name: Urist McShadow
+ parent: MobShadowkinBase
+ id: MobShadowkin
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml
index c0d23c489c0..7294506bc88 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/base.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml
@@ -152,7 +152,6 @@
- type: Identity
- type: IdExaminable
- type: Hands
- - type: Internals
- type: Inventory
- type: InventorySlots
- type: FloatingVisuals
diff --git a/Resources/Prototypes/Entities/Mobs/Species/shadowkin.yml b/Resources/Prototypes/Entities/Mobs/Species/shadowkin.yml
new file mode 100644
index 00000000000..393cb0b8716
--- /dev/null
+++ b/Resources/Prototypes/Entities/Mobs/Species/shadowkin.yml
@@ -0,0 +1,304 @@
+- type: entity
+ save: false
+ parent:
+ - MobBloodstream
+ - MobAtmosStandard
+ - MobFlammable
+ - BaseMobSpecies
+ id: MobShadowkinBase
+ name: Urist McShadow
+ abstract: true
+ components:
+ - type: Destructible
+ thresholds:
+ - trigger:
+ !type:DamageTypeTrigger
+ damageType: Blunt
+ damage: 400
+ behaviors:
+ - !type:GibBehavior {}
+ - !type:SpawnEntitiesBehavior
+ spawn:
+ ShadowkinShadow:
+ min: 1
+ max: 1
+ EffectFlashShadowkinShadeskip:
+ min: 1
+ max: 1
+ - trigger:
+ !type:DamageTypeTrigger
+ damageType: Heat
+ damage: 1500
+ behaviors:
+ - !type:SpawnEntitiesBehavior
+ spawnInContainer: true
+ spawn:
+ Ash:
+ min: 1
+ max: 1
+ - !type:BurnBodyBehavior {}
+ - !type:PlaySoundBehavior
+ sound:
+ collection: MeatLaserImpact
+ - type: PassiveDamage # Slight passive regen. Assuming one damage type, comes out to about 4 damage a minute.
+ allowedStates:
+ - Alive
+ damageCap: 20
+ damage:
+ types:
+ Heat: -0.07
+ groups:
+ Brute: -0.07
+ - type: StatusEffects
+ allowed:
+ - Stun
+ - KnockedDown
+ - SlowedDown
+ - Stutter
+ - SeeingRainbows
+ - Electrocution
+ - ForcedSleep
+ - TemporaryBlindness
+ - Drunk
+ - SlurredSpeech
+ - RatvarianLanguage
+ - PressureImmunity
+ - Muted
+ - Pacified
+ - StaminaModifier
+ - type: Blindable
+ - type: ThermalRegulator
+ metabolismHeat: 800
+ radiatedHeat: 100
+ implicitHeatRegulation: 500
+ sweatHeatRegulation: 2000
+ shiveringHeatRegulation: 2000
+ normalBodyTemperature: 310.15
+ thermalRegulationTemperatureThreshold: 25
+ - type: Perishable
+ - type: FireVisuals
+ alternateState: Standing
+ - type: OfferItem
+ - type: LayingDown
+ - type: Shoving
+ - type: BloodstreamAffectedByMass
+ power: 0.6
+ - type: Hunger
+ - type: Thirst
+ - type: Carriable
+ - type: HumanoidAppearance
+ species: Shadowkin
+ - type: Icon
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: full
+ - type: Body
+ prototype: Shadowkin
+ - type: Flammable
+ damage:
+ types:
+ Heat: 1.5 # burn more
+ - type: MobThresholds
+ thresholds: # Weak
+ 0: Alive
+ 80: Critical
+ 180: Dead
+ - type: SlowOnDamage
+ speedModifierThresholds:
+ 48: 0.85
+ 64: 0.65
+ - type: Damageable
+ damageContainer: Biological # Shadowkin
+ damageModifierSet: Shadowkin
+ - type: Barotrauma
+ damage:
+ types:
+ Blunt: 0.35 # per second, scales with pressure and other constants.
+ - type: Bloodstream
+ bloodlossDamage:
+ types:
+ Bloodloss: 1
+ bloodlossHealDamage:
+ types:
+ Bloodloss: -0.25
+ - type: Temperature
+ heatDamageThreshold: 330
+ coldDamageThreshold: 195
+ currentTemperature: 310.15
+ specificHeat: 46
+ coldDamage:
+ types:
+ Cold: 0.05 #per second, scales with temperature & other constants
+ heatDamage:
+ types:
+ Heat: 0.25 #per second, scales with temperature & other constants
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape: !type:PhysShapeCircle
+ radius: 0.35
+ density: 130 #lower density
+ restitution: 0.0
+ mask:
+ - MobMask
+ layer:
+ - MobLayer
+ - type: Sprite
+ netsync: false
+ noRot: true
+ drawdepth: Mobs
+ scale: 0.85, 0.85
+ layers:
+ - map: ["enum.HumanoidVisualLayers.Chest"]
+ - map: ["enum.HumanoidVisualLayers.Head"]
+ - map: ["enum.HumanoidVisualLayers.Snout"]
+ - map: ["enum.HumanoidVisualLayers.Eyes"]
+ shader: unshaded
+ - map: ["enum.HumanoidVisualLayers.RArm"]
+ - map: ["enum.HumanoidVisualLayers.LArm"]
+ - map: ["enum.HumanoidVisualLayers.RLeg"]
+ - map: ["enum.HumanoidVisualLayers.LLeg"]
+ - shader: StencilClear
+ sprite: Mobs/Species/Human/parts.rsi
+ state: l_leg
+ - shader: StencilMask
+ map: ["enum.HumanoidVisualLayers.StencilMask"]
+ sprite: Mobs/Customization/masking_helpers.rsi
+ state: full
+ visible: false
+ - map: ["enum.HumanoidVisualLayers.LFoot"]
+ - map: ["enum.HumanoidVisualLayers.RFoot"]
+ - map: ["socks"]
+ - map: ["underpants"]
+ - map: ["undershirt"]
+ - map: ["jumpsuit"]
+ - map: ["enum.HumanoidVisualLayers.LHand"]
+ - map: ["enum.HumanoidVisualLayers.RHand"]
+ - map: ["enum.HumanoidVisualLayers.Handcuffs"]
+ color: "#ffffff"
+ sprite: Objects/Misc/handcuffs.rsi
+ state: body-overlay-2
+ visible: false
+ - map: ["id"]
+ - map: ["gloves"]
+ - map: ["shoes"]
+ - map: ["ears"]
+ - map: ["outerClothing"]
+ - map: ["eyes"]
+ - map: ["belt"]
+ - map: ["neck"]
+ - map: ["back"]
+ - map: ["enum.HumanoidVisualLayers.FacialHair"]
+ - map: ["enum.HumanoidVisualLayers.Hair"]
+ - map: ["enum.HumanoidVisualLayers.HeadSide"]
+ - map: ["enum.HumanoidVisualLayers.HeadTop"]
+ - map: ["mask"]
+ - map: ["head"]
+ - map: ["pocket1"]
+ - map: ["pocket2"]
+ - map: ["enum.HumanoidVisualLayers.Tail"]
+ - type: MeleeWeapon
+ soundHit:
+ collection: AlienClaw
+ angle: 30
+ animation: WeaponArcClaw
+ damage:
+ types:
+ Slash: 5
+ - type: Vocal
+ sounds:
+ Male: MaleShadowkin
+ Female: FemaleShadowkin
+ Unsexed: MaleShadowkin
+ - type: TypingIndicator
+ proto: alien
+ - type: MovementSpeedModifier
+ baseWalkSpeed: 2.7
+ baseSprintSpeed: 4.5
+ - type: Flashable
+ eyeDamageChance: 0.3
+ eyeDamage: 1
+ durationMultiplier: 1.5
+ - type: Speech
+ allowedEmotes: ['Mars', 'Wurble']
+ - type: Shadowkin
+ - type: Psionic
+ mindbreakingFeedback: shadowkin-blackeye
+ manaGain: 0.25
+ mana: 150
+ maxMana: 250
+ bypassManaCheck: true
+ removable: false
+ - type: InnatePsionicPowers
+ powersToAdd:
+ - ShadowkinPowers
+ - type: LanguageKnowledge
+ speaks:
+ - TauCetiBasic
+ - Marish
+ understands:
+ - TauCetiBasic
+ - Marish
+
+- type: entity
+ save: false
+ parent: MobHumanDummy
+ id: MobShadowkinDummy
+ noSpawn: true
+ description: A dummy shadowkin meant to be used in character setup.
+ components:
+ - type: HumanoidAppearance
+ species: Shadowkin
+ - type: Sprite
+ netsync: false
+ noRot: true
+ drawdepth: Mobs
+ scale: 0.85, 0.85 # Small
+ layers:
+ - map: ["enum.HumanoidVisualLayers.Chest"]
+ - map: ["enum.HumanoidVisualLayers.Head"]
+ - map: ["enum.HumanoidVisualLayers.Snout"]
+ - map: ["enum.HumanoidVisualLayers.Eyes"]
+ shader: unshaded
+ - map: ["enum.HumanoidVisualLayers.RArm"]
+ - map: ["enum.HumanoidVisualLayers.LArm"]
+ - map: ["enum.HumanoidVisualLayers.RLeg"]
+ - map: ["enum.HumanoidVisualLayers.LLeg"]
+ - shader: StencilClear
+ sprite: Mobs/Species/Human/parts.rsi
+ state: l_leg
+ - shader: StencilMask
+ map: ["enum.HumanoidVisualLayers.StencilMask"]
+ sprite: Mobs/Customization/masking_helpers.rsi
+ state: full
+ visible: false
+ - map: ["enum.HumanoidVisualLayers.LFoot"]
+ - map: ["enum.HumanoidVisualLayers.RFoot"]
+ - map: ["socks"]
+ - map: ["underpants"]
+ - map: ["undershirt"]
+ - map: ["jumpsuit"]
+ - map: ["enum.HumanoidVisualLayers.LHand"]
+ - map: ["enum.HumanoidVisualLayers.RHand"]
+ - map: ["enum.HumanoidVisualLayers.Handcuffs"]
+ color: "#ffffff"
+ sprite: Objects/Misc/handcuffs.rsi
+ state: body-overlay-2
+ visible: false
+ - map: ["id"]
+ - map: ["gloves"]
+ - map: ["shoes"]
+ - map: ["ears"]
+ - map: ["outerClothing"]
+ - map: ["eyes"]
+ - map: ["belt"]
+ - map: ["neck"]
+ - map: ["back"]
+ - map: ["enum.HumanoidVisualLayers.FacialHair"]
+ - map: ["enum.HumanoidVisualLayers.Hair"]
+ - map: ["enum.HumanoidVisualLayers.HeadSide"]
+ - map: ["enum.HumanoidVisualLayers.HeadTop"]
+ - map: ["mask"]
+ - map: ["head"]
+ - map: ["pocket1"]
+ - map: ["pocket2"]
+ - map: ["enum.HumanoidVisualLayers.Tail"]
diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml
index ad8f3d5c719..3ff5a3955a9 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml
@@ -57,6 +57,7 @@
- idcard
- Belt
- type: UnpoweredFlashlight
+ - type: EtherealLight
- type: PointLight
enabled: false
radius: 1.5
diff --git a/Resources/Prototypes/Entities/Objects/Fun/prizeticket.yml b/Resources/Prototypes/Entities/Objects/Fun/prizeticket.yml
index d6d86c08b23..19aa6a3015a 100644
--- a/Resources/Prototypes/Entities/Objects/Fun/prizeticket.yml
+++ b/Resources/Prototypes/Entities/Objects/Fun/prizeticket.yml
@@ -285,6 +285,9 @@
- id: PetRock
prob: 0.80
orGroup: Prize
+ - id: PlushieShadowkin
+ prob: 0.80
+ orGroup: Prize
# Uncommon
- id: PrizeTicket60
prob: 0.50
diff --git a/Resources/Prototypes/Entities/Objects/Fun/toys.yml b/Resources/Prototypes/Entities/Objects/Fun/toys.yml
index d7a5f3542dc..fc771414b4a 100644
--- a/Resources/Prototypes/Entities/Objects/Fun/toys.yml
+++ b/Resources/Prototypes/Entities/Objects/Fun/toys.yml
@@ -1954,4 +1954,14 @@
size: Ginormous
sprite: Objects/Weapons/Melee/Throngler-in-hand.rsi
- type: DisarmMalus
- malus: 0
\ No newline at end of file
+ malus: 0
+
+- type: entity
+ parent: BasePlushie
+ id: PlushieShadowkin
+ name: shadowkin plushie
+ description: A plushie of a Shadowkin. It's very soft.
+ components:
+ - type: Sprite
+ sprite: Objects/Fun/toys.rsi
+ state: shadowkin
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Objects/Materials/bluespace.yml b/Resources/Prototypes/Entities/Objects/Materials/bluespace.yml
index bc0ed8f03cd..7af87ca40d6 100644
--- a/Resources/Prototypes/Entities/Objects/Materials/bluespace.yml
+++ b/Resources/Prototypes/Entities/Objects/Materials/bluespace.yml
@@ -14,6 +14,10 @@
- type: PhysicalComposition
materialComposition:
Bluespace: 100
+ - type: EmitSoundOnUse
+ sound:
+ collection: RadiationPulse
+ - type: EtherealStunItem
- type: Tag
tags:
- BluespaceCrystal
diff --git a/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml b/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml
index a02f94215b8..8fc465f4fb2 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml
@@ -40,6 +40,7 @@
sprite: Objects/Misc/Lights/lights.rsi
size: Normal
heldPrefix: off
+ - type: EtherealLight
- type: PointLight
enabled: false
radius: 3
diff --git a/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml
index a1005004946..838715f1da5 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml
@@ -25,8 +25,7 @@
- type: MeleeSound
soundGroups:
Brute:
- path:
- "/Audio/Weapons/slash.ogg"
+ path: "/Audio/Weapons/slash.ogg"
- type: Sprite
sprite: Objects/Misc/kudzu.rsi
state: kudzu_11
@@ -38,21 +37,19 @@
fix1:
hard: false
density: 7
- shape:
- !type:PhysShapeAabb
+ shape: !type:PhysShapeAabb
bounds: "-0.5,-0.5,0.5,0.5"
layer:
- - MidImpassable
+ - MidImpassable
- type: Damageable
damageModifierSet: Wood
- type: Destructible
thresholds:
- - trigger:
- !type:DamageTrigger
- damage: 10
- behaviors:
- - !type:DoActsBehavior
- acts: [ "Destruction" ]
+ - trigger: !type:DamageTrigger
+ damage: 10
+ behaviors:
+ - !type:DoActsBehavior
+ acts: ["Destruction"]
- type: Temperature
heatDamage:
types:
@@ -69,14 +66,14 @@
Flammable: [Touch]
Extinguish: [Touch]
reactions:
- - reagents: [WeedKiller, PlantBGone]
- methods: [Touch]
- effects:
- - !type:HealthChange
- scaleByQuantity: true
- damage:
- types:
- Heat: 10
+ - reagents: [WeedKiller, PlantBGone]
+ methods: [Touch]
+ effects:
+ - !type:HealthChange
+ scaleByQuantity: true
+ damage:
+ types:
+ Heat: 10
- type: AtmosExposed
- type: Kudzu
growthTickChance: 0.3
@@ -86,19 +83,19 @@
sprintSpeedModifier: 0.2
ignoreWhitelist:
components:
- - IgnoreKudzu
+ - IgnoreKudzu
- type: Food
requiredStomachs: 2 # ruminants have 4 stomachs but i dont care to give them literally 4 stomachs. 2 is good
delay: 0.5
- type: FlavorProfile
flavors:
- - fiber
+ - fiber
- type: SolutionContainerManager
solutions:
food:
reagents:
- - ReagentId: Nutriment
- Quantity: 2
+ - ReagentId: Nutriment
+ Quantity: 2
- type: entity
id: WeakKudzu
@@ -127,22 +124,22 @@
sprintSpeedModifier: 0.8
ignoreWhitelist:
components:
- - IgnoreKudzu
+ - IgnoreKudzu
- type: RandomSpawner
deleteSpawnerAfterSpawn: false
rareChance: 0.15
offset: 0.2
chance: 0.05
prototypes:
- - LightTree01
- - LightTree02
- - LightTree03
- - LightTree04
- - LightTree05
- - LightTree06
- - CrystalCyan
+ - LightTree01
+ - LightTree02
+ - LightTree03
+ - LightTree04
+ - LightTree05
+ - LightTree06
+ - CrystalCyan
rarePrototypes:
- - AnomalyFloraBulb
+ - AnomalyFloraBulb
- type: entity
id: KudzuFlowerAngry
@@ -154,11 +151,11 @@
- type: RandomSpawner
chance: 0.05
rarePrototypes:
- - AnomalyFloraBulb
- - AnomalyFloraBulb
- - MobLuminousEntity
- - MobLuminousObject
- - MobLuminousPerson
+ - AnomalyFloraBulb
+ - AnomalyFloraBulb
+ - MobLuminousEntity
+ - MobLuminousObject
+ - MobLuminousPerson
- type: entity
id: FleshKudzu
@@ -173,8 +170,7 @@
- type: MeleeSound
soundGroups:
Brute:
- path:
- "/Audio/Weapons/slash.ogg"
+ path: "/Audio/Weapons/slash.ogg"
- type: Sprite
sprite: Objects/Misc/fleshkudzu.rsi
state: kudzu_11
@@ -186,20 +182,18 @@
fix1:
hard: false
density: 7
- shape:
- !type:PhysShapeAabb
+ shape: !type:PhysShapeAabb
bounds: "-0.5,-0.5,0.5,0.5"
layer:
- - MidImpassable
+ - MidImpassable
- type: Damageable
- type: Destructible
thresholds:
- - trigger:
- !type:DamageTrigger
- damage: 40
- behaviors:
- - !type:DoActsBehavior
- acts: [ "Destruction" ]
+ - trigger: !type:DamageTrigger
+ damage: 40
+ behaviors:
+ - !type:DoActsBehavior
+ acts: ["Destruction"]
- type: DamageContacts
damage:
types:
@@ -207,7 +201,7 @@
Piercing: 1.5
ignoreWhitelist:
tags:
- - Flesh
+ - Flesh
- type: Kudzu
growthTickChance: 0.1
spreadChance: 0.4
@@ -232,23 +226,23 @@
- type: Flammable
fireSpread: true
damage:
- types:
- Heat: 3
+ types:
+ Heat: 3
- type: AtmosExposed
- type: SpeedModifierContacts
walkSpeedModifier: 0.3
sprintSpeedModifier: 0.3
ignoreWhitelist:
tags:
- - Flesh
+ - Flesh
- type: Food # delightfully devilish !
delay: 0.5
- type: SolutionContainerManager
solutions:
food:
reagents:
- - ReagentId: Protein
- Quantity: 2
+ - ReagentId: Protein
+ Quantity: 2
- type: Respirator
damage:
types:
@@ -260,7 +254,7 @@
- type: entity
name: dark haze
id: ShadowKudzu
- parent: [ BaseKudzu, BaseShadow ]
+ parent: [BaseKudzu, BaseShadow]
components:
- type: Physics
canCollide: false
@@ -269,9 +263,9 @@
drawdepth: Effects
sprite: Effects/spookysmoke.rsi
layers:
- - state: spookysmoke
- color: "#793a80dd"
- map: [base]
+ - state: spookysmoke
+ color: "#793a80dd"
+ map: [base]
- type: Kudzu
growthTickChance: 0.2
spreadChance: 0.99
@@ -281,10 +275,10 @@
offset: 0.2
chance: 0.45
prototypes:
- - ShadowBasaltRandom
+ - ShadowBasaltRandom
rarePrototypes:
- - ShadowPortal
- - ShadowKudzuLootSpawner
+ - ShadowPortal
+ - ShadowKudzuLootSpawner
- type: Tag
tags:
- HideContextMenu
@@ -292,10 +286,10 @@
- type: OptionsVisualizer
visuals:
base:
- - options: Default
- data: { state: spookysmoke }
- - options: ReducedMotion
- data: { state: spookysmoke_static }
+ - options: Default
+ data: { state: spookysmoke }
+ - options: ReducedMotion
+ data: { state: spookysmoke_static }
- type: entity
name: Haze
@@ -304,3 +298,18 @@
components:
- type: Kudzu
spreadChance: 0 #appears during pulsation. It shouldnt spreading.
+
+- type: entity
+ name: Shadowkin Haze
+ id: ShadowkinShadow
+ parent: ShadowKudzuWeak
+ components:
+ - type: RandomSpawner
+ deleteSpawnerAfterSpawn: false
+ rareChance: 0
+ offset: 0.2
+ chance: 0.45
+ prototypes:
+ - ShadowBasaltRandom
+ - type: TimedDespawn
+ lifetime: 30
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml b/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml
index 2a982ed6f79..cbb5dfee0a1 100644
--- a/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml
+++ b/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml
@@ -65,6 +65,7 @@
- type: Item
sprite: Objects/Tools/flashlight.rsi
storedRotation: -90
+ - type: EtherealLight
- type: PointLight
enabled: false
mask: /Textures/Effects/LightMasks/cone.png
diff --git a/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml b/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml
index 6b301a50eed..c6c55a916c7 100644
--- a/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml
+++ b/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml
@@ -31,6 +31,7 @@
state: glow
shader: unshaded
state: base
+ - type: EtherealLight
- type: PointLight
color: "#FFE4CE" # 5000K color temp
energy: 0.8
diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
index 35434003d29..0088fc4306b 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
@@ -806,6 +806,8 @@
- WeaponLaserCarbinePractice
- Zipties
- ShockCollar
+ - ShadowkinRestraints
+ # DeltaV - .38 special ammo - Add various .38 special ammo to security techfab
- MagazineBoxSpecial
- MagazineBoxSpecialPractice
- SpeedLoaderSpecial
@@ -1398,6 +1400,7 @@
staticRecipes:
- PrizeBall
- PlushieMothRandom
+ - PlushieShadowkin
- PlushieMothMusician
- PlushieMothBartender
- PlushieBee
diff --git a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml
index e98b40b9a0c..c79cfa2641c 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml
@@ -86,6 +86,7 @@
- type: Electrified
enabled: false
usesApcPower: true
+ - type: EtherealLight
- type: PointLight
enabled: false
castShadows: false
diff --git a/Resources/Prototypes/Guidebook/species.yml b/Resources/Prototypes/Guidebook/species.yml
index d9d039ea436..f7b77b7ec6f 100644
--- a/Resources/Prototypes/Guidebook/species.yml
+++ b/Resources/Prototypes/Guidebook/species.yml
@@ -12,6 +12,7 @@
- SlimePerson
- IPCs
- Harpy
+ - Shadowkin
- type: guideEntry
id: Arachnid
@@ -56,4 +57,9 @@
- type: guideEntry
id: Harpy
name: species-name-harpy
- text: "/ServerInfo/Guidebook/Mobs/Harpy.xml"
\ No newline at end of file
+ text: "/ServerInfo/Guidebook/Mobs/Harpy.xml"
+
+- type: guideEntry
+ id: Shadowkin
+ name: species-name-shadowkin
+ text: "/ServerInfo/Guidebook/Mobs/Shadowkin.xml"
diff --git a/Resources/Prototypes/Language/Species-Specific/marish.yml b/Resources/Prototypes/Language/Species-Specific/marish.yml
new file mode 100644
index 00000000000..872d67373d2
--- /dev/null
+++ b/Resources/Prototypes/Language/Species-Specific/marish.yml
@@ -0,0 +1,20 @@
+# Spoken by shadowkins.
+- type: language
+ id: Marish
+ speech:
+ color: "#be3cc5"
+ fontId: Lymphatic
+ empathySpeech: true
+ speechVerbOverrides:
+ - chat-speech-verb-marish
+ obfuscation:
+ !type:SyllableObfuscation
+ minSyllables: 1 # Replacements are really short
+ maxSyllables: 2
+ replacement:
+ - mar
+ - mwrrr
+ - maaAr
+ - aarrr
+ - wrurrl
+ - mmar
\ No newline at end of file
diff --git a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml
index f7a31205706..3834c3b95c4 100644
--- a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml
+++ b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml
@@ -18,6 +18,12 @@
- !type:CharacterTraitRequirement
traits:
- AnomalousPositronics
+ - !type:CharacterLogicOrRequirement
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ inverted: true
+ species:
+ - Shadowkin
startingGear: ForensicMantisGear
icon: "JobIconForensicMantis"
supervisors: job-supervisors-rd
diff --git a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/prisoner.yml b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/prisoner.yml
index 00ffdde666f..0ca17947425 100644
--- a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/prisoner.yml
+++ b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/prisoner.yml
@@ -13,6 +13,15 @@
- !type:DepartmentTimeRequirement
department: Security
min: 21600
+ - !type:CharacterLogicOrRequirement
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ inverted: true
+ species:
+ - Shadowkin
+ - !type:CharacterTraitRequirement
+ traits:
+ - ShadowkinBlackeye
- type: startingGear
id: PrisonerGear
diff --git a/Resources/Prototypes/Psionics/psionics.yml b/Resources/Prototypes/Psionics/psionics.yml
index 378d48b3fad..54275fc9c51 100644
--- a/Resources/Prototypes/Psionics/psionics.yml
+++ b/Resources/Prototypes/Psionics/psionics.yml
@@ -52,7 +52,7 @@
name: Pyrokinesis
description: pyrokinesis-power-description
actions:
- - ActionPyrokinesis
+ - ActionPyrokinesis
components:
- type: PyrokinesisPower
initializationFeedback: pyrokinesis-power-initialization-feedback
@@ -64,7 +64,7 @@
name: Metapsionic Pulse
description: metapsionic-power-description
actions:
- - ActionMetapsionic
+ - ActionMetapsionic
components:
- type: MetapsionicPower
initializationFeedback: metapsionic-power-initialization-feedback
@@ -77,7 +77,7 @@
name: Psionic Regeneration
description: psionic-regeneration-power-description
actions:
- - ActionPsionicRegeneration
+ - ActionPsionicRegeneration
components:
- type: PsionicRegenerationPower
initializationFeedback: psionic-regeneration-power-initialization-feedback
@@ -90,7 +90,7 @@
name: Telegnosis
description: telegnosis-power-description
actions:
- - ActionTelegnosis
+ - ActionTelegnosis
components:
- type: TelegnosisPower
initializationFeedback: telegnosis-power-initialization-feedback
@@ -103,7 +103,7 @@
name: Psionic Invisibility
description: psionic-invisibility-power-description
actions:
- - ActionDispel
+ - ActionDispel
components:
- type: PsionicInvisibilityPower
initializationFeedback: psionic-invisibility-power-initialization-feedback
@@ -204,7 +204,7 @@
name: Shadeskip
description: shadeskip-power-description
actions:
- - ActionShadeskip
+ - ActionShadeskip
initializationFeedback: shadeskip-power-initialization-feedback
metapsionicFeedback: shadeskip-power-metapsionic-feedback
amplificationModifier: 1
@@ -214,7 +214,32 @@
name: Telekinetic Pulse
description: telekinetic-pulse-power-description
actions:
- - ActionTelekineticPulse
+ - ActionTelekineticPulse
initializationFeedback: telekinetic-pulse-power-initialization-feedback
metapsionicFeedback: telekinetic-pulse-power-metapsionic-feedback
- amplificationModifier: 1
\ No newline at end of file
+ amplificationModifier: 1
+
+- type: psionicPower
+ id: ShadowkinPowers
+ name: Shadowkin Powers
+ description: shadowkin-powers-description
+ actions:
+ - ActionShadowkinShadeskip
+ - ActionDarkSwap
+ powerSlotCost: 1
+
+- type: psionicPower
+ id: EtherealVisionPower
+ name: Ethereal Vision
+ description: ethereal-vision-powers-description
+ components:
+ - type: ShowEthereal
+ powerSlotCost: 0
+
+- type: psionicPower
+ id: DarkSwapPower
+ name: DarkSwap
+ description: darkswap-power-description
+ actions:
+ - ActionDarkSwap
+ powerSlotCost: 1
diff --git a/Resources/Prototypes/Reagents/toxins.yml b/Resources/Prototypes/Reagents/toxins.yml
index 5d29f024ce8..abb33f832a4 100644
--- a/Resources/Prototypes/Reagents/toxins.yml
+++ b/Resources/Prototypes/Reagents/toxins.yml
@@ -511,6 +511,12 @@
shouldHave: true
reagent: Protein
amount: 0.5
+ - !type:AdjustReagent
+ conditions:
+ - !type:OrganType
+ type: Shadowkin
+ reagent: Protein
+ amount: 0.5
- type: reagent
id: Allicin
diff --git a/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/makeshifthandcuffs.yml b/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/makeshifthandcuffs.yml
index 08d6d57fb94..47eaf30e5cc 100644
--- a/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/makeshifthandcuffs.yml
+++ b/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/makeshifthandcuffs.yml
@@ -11,4 +11,3 @@
doAfter: 5
- node: cuffscable
entity: Cablecuffs
-
diff --git a/Resources/Prototypes/Recipes/Lathes/prizecounter.yml b/Resources/Prototypes/Recipes/Lathes/prizecounter.yml
index c600702b206..9e5e239c6f5 100644
--- a/Resources/Prototypes/Recipes/Lathes/prizecounter.yml
+++ b/Resources/Prototypes/Recipes/Lathes/prizecounter.yml
@@ -6,6 +6,14 @@
materials:
PrizeTicket: 30
+- type: latheRecipe
+ id: PlushieShadowkin
+ result: PlushieShadowkin
+ applyMaterialDiscount: false
+ completetime: 0.1
+ materials:
+ PrizeTicket: 50
+
- type: latheRecipe
id: PlushieMothRandom
result: PlushieMothRandom
diff --git a/Resources/Prototypes/Recipes/Lathes/security.yml b/Resources/Prototypes/Recipes/Lathes/security.yml
index 04c2ad1ec1f..98e8c530b87 100644
--- a/Resources/Prototypes/Recipes/Lathes/security.yml
+++ b/Resources/Prototypes/Recipes/Lathes/security.yml
@@ -758,9 +758,10 @@
result: GrenadeBlast
completetime: 3
materials:
- Steel: 450
- Plastic: 300
- Gold: 150
+ Steel: 150
+ Plastic: 100
+ Gold: 50
+
- type: latheRecipe
id: GrenadeFlash
result: GrenadeFlash
@@ -872,3 +873,10 @@
materials:
Plastic: 15
Uranium: 10
+
+- type: latheRecipe
+ id: ShadowkinRestraints
+ result: ClothingOuterShadowkinRestraints
+ completetime: 6
+ materials:
+ Steel: 300
diff --git a/Resources/Prototypes/Roles/Antags/Thief.yml b/Resources/Prototypes/Roles/Antags/Thief.yml
index 131db8cf1da..2715e6d96d7 100644
--- a/Resources/Prototypes/Roles/Antags/Thief.yml
+++ b/Resources/Prototypes/Roles/Antags/Thief.yml
@@ -3,4 +3,4 @@
name: roles-antag-thief-name
antagonist: true
setPreference: true
- objective: roles-antag-thief-objective
+ objective: roles-antag-thief-objective
\ No newline at end of file
diff --git a/Resources/Prototypes/Roles/Antags/traitor.yml b/Resources/Prototypes/Roles/Antags/traitor.yml
index fec2280ddc8..05b0553c78f 100644
--- a/Resources/Prototypes/Roles/Antags/traitor.yml
+++ b/Resources/Prototypes/Roles/Antags/traitor.yml
@@ -6,4 +6,4 @@
objective: roles-antag-syndicate-agent-objective
requirements:
- !type:CharacterOverallTimeRequirement # DeltaV - Playtime requirement
- min: 86400 # DeltaV - 24 hours
+ min: 86400 # DeltaV - 24 hours
\ No newline at end of file
diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml b/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml
index d4f8bdb0671..dcb2b6438a3 100644
--- a/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml
+++ b/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml
@@ -16,6 +16,12 @@
- !type:CharacterTraitRequirement
traits:
- AnomalousPositronics
+ - !type:CharacterLogicOrRequirement
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ inverted: true
+ species:
+ - Shadowkin
startingGear: ChaplainGear
icon: "JobIconChaplain"
supervisors: job-supervisors-rd
diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml b/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml
index 8552073dcc6..3468d10f278 100644
--- a/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml
+++ b/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml
@@ -16,6 +16,12 @@
- !type:CharacterTraitRequirement
traits:
- AnomalousPositronics
+ - !type:CharacterLogicOrRequirement
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ inverted: true
+ species:
+ - Shadowkin
startingGear: LibrarianGear
icon: "JobIconLibrarian"
supervisors: job-supervisors-rd
diff --git a/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml b/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml
index b132729432c..6b61fe856e7 100644
--- a/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml
+++ b/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml
@@ -18,6 +18,15 @@
min: 43200 # DeltaV - 12 hours
- !type:CharacterOverallTimeRequirement
min: 72000 # DeltaV - 20 hours
+ - !type:CharacterLogicOrRequirement
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ inverted: true
+ species:
+ - Shadowkin
+ - !type:CharacterTraitRequirement
+ traits:
+ - ShadowkinBlackeye
weight: 10
startingGear: CMOGear
icon: "JobIconChiefMedicalOfficer"
diff --git a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml
index 46d91ee00ee..4fc9325f39d 100644
--- a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml
+++ b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml
@@ -19,6 +19,12 @@
- !type:CharacterTraitRequirement
traits:
- AnomalousPositronics
+ - !type:CharacterLogicOrRequirement
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ inverted: true
+ species:
+ - Shadowkin
weight: 10
startingGear: ResearchDirectorGear
icon: "JobIconResearchDirector"
diff --git a/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml b/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml
index 01f14f80994..18f6d04022c 100644
--- a/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml
+++ b/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml
@@ -6,7 +6,6 @@
requirements:
- !type:CharacterDepartmentTimeRequirement
department: Security
- min: 14400 # DeltaV - 4 hours
startingGear: SecurityOfficerGear
icon: "JobIconSecurityOfficer"
supervisors: job-supervisors-hos
diff --git a/Resources/Prototypes/Shaders/shaders.yml b/Resources/Prototypes/Shaders/shaders.yml
index 3f0cb5ae1fb..41d04137bbe 100644
--- a/Resources/Prototypes/Shaders/shaders.yml
+++ b/Resources/Prototypes/Shaders/shaders.yml
@@ -110,4 +110,16 @@
- type: shader
id: Flap
kind: source
- path: "/Textures/Shaders/flap.swsl"
\ No newline at end of file
+ path: "/Textures/Shaders/flap.swsl"
+
+ # Shadowkin shaders
+
+- type: shader
+ id: ColorTint
+ kind: source
+ path: "/Textures/Shaders/color_tint.swsl"
+
+- type: shader
+ id: Ethereal
+ kind: source
+ path: "/Textures/Shaders/ethereal.swsl"
\ No newline at end of file
diff --git a/Resources/Prototypes/SoundCollections/emotes.yml b/Resources/Prototypes/SoundCollections/emotes.yml
index 35693a70a4d..f655a8cf6b4 100644
--- a/Resources/Prototypes/SoundCollections/emotes.yml
+++ b/Resources/Prototypes/SoundCollections/emotes.yml
@@ -86,3 +86,13 @@
- /Audio/Voice/IPC/whirr1.ogg
- /Audio/Voice/IPC/whirr2.ogg
- /Audio/Voice/IPC/whirr3.ogg
+
+- type: soundCollection
+ id: Mars
+ files:
+ - /Audio/Voice/Shadowkin/mar.ogg
+
+- type: soundCollection
+ id: Wurble
+ files:
+ - /Audio/Voice/Shadowkin/wurble.ogg
\ No newline at end of file
diff --git a/Resources/Prototypes/Species/shadowkin.yml b/Resources/Prototypes/Species/shadowkin.yml
new file mode 100644
index 00000000000..7043397d8e7
--- /dev/null
+++ b/Resources/Prototypes/Species/shadowkin.yml
@@ -0,0 +1,179 @@
+- type: species
+ id: Shadowkin
+ name: species-name-shadowkin
+ roundStart: true
+ prototype: MobShadowkin
+ sprites: MobShadowkinSprites
+ defaultSkinTone: "#FFFFFF"
+ markingLimits: MobShadowkinMarkingLimits
+ dollPrototype: MobShadowkinDummy
+ skinColoration: Hues
+ naming: First
+ maleFirstNames: names_shadowkin
+ femaleFirstNames: names_shadowkin
+ minAge: 18
+ maxAge: 300
+ youngAge: 20
+ oldAge: 250
+ sexes:
+ - Male
+ - Female
+ - Unsexed
+ minHeight: 0.65
+ defaultHeight: 0.85
+ maxHeight: 1.15
+ minWidth: 0.6
+ defaultWidth: 0.85
+ maxWidth: 1.2
+
+- type: speciesBaseSprites
+ id: MobShadowkinSprites
+ sprites:
+ Head: MobShadowkinHead
+ Snout: MobShadowkinAnyMarkingFollowSkin
+ HeadTop: MobShadowkinAnyMarkingFollowSkin
+ HeadSide: MobShadowkinAnyMarkingFollowSkin
+ Tail: MobShadowkinAnyMarkingFollowSkin
+ Chest: MobShadowkinTorso
+ Eyes: MobShadowkinEyes
+ LArm: MobShadowkinLArm
+ RArm: MobShadowkinRArm
+ LHand: MobShadowkinLHand
+ RHand: MobShadowkinRHand
+ LLeg: MobShadowkinLLeg
+ RLeg: MobShadowkinRLeg
+ LFoot: MobShadowkinLFoot
+ RFoot: MobShadowkinRFoot
+
+- type: markingPoints
+ id: MobShadowkinMarkingLimits
+ points:
+ Tail:
+ points: 1
+ required: true
+ defaultMarkings: [TailShadowkin]
+ HeadTop:
+ points: 1
+ required: true
+ defaultMarkings: [EarsShadowkin]
+ Chest:
+ points: 1
+ required: false
+ RightLeg:
+ points: 2
+ required: false
+ RightFoot:
+ points: 2
+ required: false
+ LeftLeg:
+ points: 2
+ required: false
+ LeftFoot:
+ points: 2
+ required: false
+ RightArm:
+ points: 2
+ required: false
+ RightHand:
+ points: 2
+ required: false
+ LeftArm:
+ points: 2
+ required: false
+ LeftHand:
+ points: 2
+ required: false
+
+- type: humanoidBaseSprite
+ id: MobShadowkinAnyMarkingFollowSkin
+ markingsMatchSkin: true
+
+- type: humanoidBaseSprite
+ id: MobShadowkinHead
+ baseSprite:
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: head_m
+
+- type: humanoidBaseSprite
+ id: MobShadowkinHeadMale
+ baseSprite:
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: head_m
+
+- type: humanoidBaseSprite
+ id: MobShadowkinHeadFemale
+ baseSprite:
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: head_f
+
+- type: humanoidBaseSprite
+ id: MobShadowkinTorso
+ baseSprite:
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: torso_m
+
+- type: humanoidBaseSprite
+ id: MobShadowkinTorsoMale
+ baseSprite:
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: torso_m
+
+- type: humanoidBaseSprite
+ id: MobShadowkinTorsoFemale
+ baseSprite:
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: torso_f
+
+- type: humanoidBaseSprite
+ id: MobShadowkinLLeg
+ baseSprite:
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: l_leg
+
+- type: humanoidBaseSprite
+ id: MobShadowkinLHand
+ baseSprite:
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: l_hand
+
+- type: humanoidBaseSprite
+ id: MobShadowkinEyes
+ baseSprite:
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: eyes
+
+- type: humanoidBaseSprite
+ id: MobShadowkinLArm
+ baseSprite:
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: l_arm
+
+- type: humanoidBaseSprite
+ id: MobShadowkinLFoot
+ baseSprite:
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: l_foot
+
+- type: humanoidBaseSprite
+ id: MobShadowkinRLeg
+ baseSprite:
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: r_leg
+
+- type: humanoidBaseSprite
+ id: MobShadowkinRHand
+ baseSprite:
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: r_hand
+
+- type: humanoidBaseSprite
+ id: MobShadowkinRArm
+ baseSprite:
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: r_arm
+
+- type: humanoidBaseSprite
+ id: MobShadowkinRFoot
+ baseSprite:
+ sprite: Mobs/Species/Shadowkin/parts.rsi
+ state: r_foot
\ No newline at end of file
diff --git a/Resources/Prototypes/Species/species_weights.yml b/Resources/Prototypes/Species/species_weights.yml
index 990a8ccecfa..d158862d38b 100644
--- a/Resources/Prototypes/Species/species_weights.yml
+++ b/Resources/Prototypes/Species/species_weights.yml
@@ -4,8 +4,9 @@
weights:
Human: 5
Reptilian: 4
- SlimePerson: 4
+ SlimePerson: 3
Oni: 3 #Nyanotrasen Oni, see Prototypes/Nyanotrasen/Entities/Mobs/Species/Oni.yml
Felinid: 4 # Nyanotrasen - Felinid, see Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml
- Vulpkanin: 3 # DeltaV - Vulpkanin, see Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml
+ Vulpkanin: 4 # DeltaV - Vulpkanin, see Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml
Diona: 2
+ Shadowkin: 0
diff --git a/Resources/Prototypes/Traits/disabilities.yml b/Resources/Prototypes/Traits/disabilities.yml
index 9961c849487..5e5028035f5 100644
--- a/Resources/Prototypes/Traits/disabilities.yml
+++ b/Resources/Prototypes/Traits/disabilities.yml
@@ -217,6 +217,7 @@
inverted: true
species:
- Vulpkanin # This trait functions exactly as-is for the Vulpkanin trait.
+ - Shadowkin
components:
- type: Flashable
eyeDamageChance: 0.3
diff --git a/Resources/Prototypes/Traits/mental.yml b/Resources/Prototypes/Traits/mental.yml
index 4a50c8c3972..8fe672c2212 100644
--- a/Resources/Prototypes/Traits/mental.yml
+++ b/Resources/Prototypes/Traits/mental.yml
@@ -27,6 +27,7 @@
inverted: true
species:
- Oni
+ - Shadowkin
- !type:CharacterTraitRequirement
inverted: true
traits:
@@ -64,6 +65,7 @@
inverted: true
species:
- Oni
+ - Shadowkin
- !type:CharacterTraitRequirement
inverted: true
traits:
@@ -97,6 +99,12 @@
- !type:CharacterTraitRequirement
traits:
- AnomalousPositronics
+ - !type:CharacterLogicOrRequirement
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ inverted: true
+ species:
+ - Shadowkin
- !type:CharacterTraitRequirement
inverted: true
traits:
@@ -130,6 +138,12 @@
- !type:CharacterTraitRequirement
traits:
- AnomalousPositronics
+ - !type:CharacterLogicOrRequirement
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ inverted: true
+ species:
+ - Shadowkin
- !type:CharacterTraitRequirement
inverted: true
traits:
@@ -163,6 +177,12 @@
- !type:CharacterTraitRequirement
traits:
- AnomalousPositronics
+ - !type:CharacterLogicOrRequirement
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ inverted: true
+ species:
+ - Shadowkin
- !type:CharacterTraitRequirement
inverted: true
traits:
@@ -196,6 +216,12 @@
- !type:CharacterTraitRequirement
traits:
- AnomalousPositronics
+ - !type:CharacterLogicOrRequirement
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ inverted: true
+ species:
+ - Shadowkin
- !type:CharacterTraitRequirement
inverted: true
traits:
@@ -228,6 +254,12 @@
- !type:CharacterTraitRequirement
traits:
- AnomalousPositronics
+ - !type:CharacterLogicOrRequirement
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ inverted: true
+ species:
+ - Shadowkin
- !type:CharacterTraitRequirement
inverted: true
traits:
diff --git a/Resources/Prototypes/Traits/physical.yml b/Resources/Prototypes/Traits/physical.yml
index 288386b8f4e..afaaf0b2cb4 100644
--- a/Resources/Prototypes/Traits/physical.yml
+++ b/Resources/Prototypes/Traits/physical.yml
@@ -305,6 +305,7 @@
species:
- Felinid # Felinids already have cat claws.
- Reptilian # Reptilians also have cat claws.
+ - Shadowkin # Shadowkins also have claws.
# - Vulpkanin # Vulpkanin have "Blunt" claws. One could argue this trait "Sharpens" their claws.
- !type:CharacterTraitRequirement
inverted: true
@@ -403,6 +404,7 @@
species:
- Arachnid
- Arachne
+ - Shadowkin
- IPC
components:
- type: Sericulture
diff --git a/Resources/Prototypes/Traits/skills.yml b/Resources/Prototypes/Traits/skills.yml
index 0e4868f19bc..0d6eb045ac6 100644
--- a/Resources/Prototypes/Traits/skills.yml
+++ b/Resources/Prototypes/Traits/skills.yml
@@ -22,16 +22,16 @@
components:
- type: SelfAware
analyzableTypes:
- - Blunt
- - Slash
- - Piercing
- - Heat
- - Shock
- - Cold
- - Caustic
+ - Blunt
+ - Slash
+ - Piercing
+ - Heat
+ - Shock
+ - Cold
+ - Caustic
detectableGroups:
- - Airloss
- - Toxin
+ - Airloss
+ - Toxin
- type: trait
id: HeavyweightDrunk
@@ -186,6 +186,12 @@
- !type:CharacterTraitRequirement
traits:
- AnomalousPositronics
+ - !type:CharacterLogicOrRequirement
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ inverted: true
+ species:
+ - Shadowkin
- !type:CharacterTraitRequirement
inverted: true
traits:
@@ -221,6 +227,12 @@
inverted: true
traits:
- LatentPsychic
+ - !type:CharacterLogicOrRequirement
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ inverted: true
+ species:
+ - Shadowkin
- type: trait
id: NaturalTelepath
@@ -252,6 +264,12 @@
- !type:CharacterTraitRequirement
traits:
- AnomalousPositronics
+ - !type:CharacterLogicOrRequirement
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ inverted: true
+ species:
+ - Shadowkin
- type: trait
id: TrapAvoider
@@ -261,10 +279,10 @@
- type: StepTriggerImmune
whitelist:
types:
- - Shard
- - Landmine
- - Mousetrap
- - SlipEntity
+ - Shard
+ - Landmine
+ - Mousetrap
+ - SlipEntity
requirements:
- !type:CharacterSpeciesRequirement
inverted: true
@@ -279,6 +297,8 @@
componentRemovals:
- PsionicInsulation
requirements:
- - !type:CharacterSpeciesRequirement
- species:
- - IPC
+ - !type:CharacterLogicOrRequirement
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ species:
+ - IPC
diff --git a/Resources/Prototypes/Traits/species.yml b/Resources/Prototypes/Traits/species.yml
index 504cf469d46..17be3489fe1 100644
--- a/Resources/Prototypes/Traits/species.yml
+++ b/Resources/Prototypes/Traits/species.yml
@@ -60,3 +60,17 @@
traits:
- Swashbuckler
- Spearmaster
+
+- type: trait
+ id: ShadowkinBlackeye
+ category: Mental
+ points: 4
+ componentRemovals:
+ - Shadowkin
+ components:
+ - type: Shadowkin
+ blackeyeSpawn: true
+ requirements:
+ - !type:CharacterSpeciesRequirement
+ species:
+ - Shadowkin
\ No newline at end of file
diff --git a/Resources/Prototypes/Voice/speech_emote_sounds.yml b/Resources/Prototypes/Voice/speech_emote_sounds.yml
index a5d05e24b71..5c3f8ec6976 100644
--- a/Resources/Prototypes/Voice/speech_emote_sounds.yml
+++ b/Resources/Prototypes/Voice/speech_emote_sounds.yml
@@ -183,6 +183,90 @@
params:
variation: 0.125
+- type: emoteSounds
+ id: MaleShadowkin
+ params:
+ variation: 0.125
+ sounds:
+ Scream:
+ collection: SlimeMaleScreams
+ Laugh:
+ collection: MaleLaugh
+ Sneeze:
+ collection: MaleSneezes
+ Cough:
+ collection: MaleCoughs
+ Yawn:
+ collection: MaleYawn
+ Snore:
+ collection: Snores
+ Honk:
+ collection: BikeHorn
+ Sigh:
+ collection: MaleSigh
+ Crying:
+ collection: MaleCry
+ Whistle:
+ collection: Whistles
+ Weh:
+ collection: Weh
+ Hiss:
+ collection: FelinidHisses
+ Meow:
+ collection: FelinidMeows
+ Mew:
+ collection: FelinidMews
+ Growl:
+ collection: FelinidGrowls
+ Purr:
+ collection: FelinidPurrs
+ Mars:
+ collection: Mars
+ Wurble:
+ collection: Wurble
+
+- type: emoteSounds
+ id: FemaleShadowkin
+ params:
+ variation: 0.125
+ sounds:
+ Scream:
+ collection: SlimeFemaleScreams
+ Laugh:
+ collection: MaleLaugh
+ Sneeze:
+ collection: FemaleSneezes
+ Cough:
+ collection: FemaleCoughs
+ Yawn:
+ collection: FemaleYawn
+ Snore:
+ collection: Snores
+ Honk:
+ collection: BikeHorn
+ Sigh:
+ collection: FemaleSigh
+ Crying:
+ collection: FemaleCry
+ Whistle:
+ collection: Whistles
+ Weh:
+ collection: Weh
+ Hiss:
+ collection: FelinidHisses
+ Meow:
+ collection: FelinidMeows
+ Mew:
+ collection: FelinidMews
+ Growl:
+ collection: FelinidGrowls
+ Purr:
+ collection: FelinidPurrs
+ Mars:
+ collection: Mars
+ Wurble:
+ collection: Wurble
+
- type: emoteSounds
id: UnisexVox
sounds:
diff --git a/Resources/Prototypes/Voice/speech_emotes.yml b/Resources/Prototypes/Voice/speech_emotes.yml
index ca4aa17229e..309794c1dcd 100644
--- a/Resources/Prototypes/Voice/speech_emotes.yml
+++ b/Resources/Prototypes/Voice/speech_emotes.yml
@@ -340,3 +340,19 @@
chatMessages: [ whirrs ]
chatTriggers:
- whirrs
+
+- type: emote
+ id: Mars
+ name: chat-emote-name-mars
+ category: Vocal
+ chatMessages: [ mars ]
+ chatTriggers:
+ - mars
+
+- type: emote
+ id: Wurble
+ name: chat-emote-name-wurble
+ category: Vocal
+ chatMessages: [ wurble ]
+ chatTriggers:
+ - wurble
diff --git a/Resources/Prototypes/fonts.yml b/Resources/Prototypes/fonts.yml
index 8e49fe8c355..c61a5fb8041 100644
--- a/Resources/Prototypes/fonts.yml
+++ b/Resources/Prototypes/fonts.yml
@@ -62,6 +62,10 @@
id: Noganas
path: /Fonts/Noganas.ttf
+- type: font
+ id: Lymphatic
+ path: /Fonts/Lymphatic.ttf
+
- type: font
id: Cambria
path: /Fonts/Cambria.ttf
diff --git a/Resources/ServerInfo/Guidebook/Mobs/Shadowkin.xml b/Resources/ServerInfo/Guidebook/Mobs/Shadowkin.xml
new file mode 100644
index 00000000000..f2f73ee6663
--- /dev/null
+++ b/Resources/ServerInfo/Guidebook/Mobs/Shadowkin.xml
@@ -0,0 +1,36 @@
+
+# Shadowkin
+
+
+
+
+
+Fluffy lil' guys.
+
+## Ability Differences
+
+- Need no air to survive
+- Can Shadeskip
+- Can travel to and from The Dark at will
+- Dims nearby lights when in the The Dark
+- When too low on energy, they may fall into a powerful sleep
+- Can "speak" in the Empathy, which only other Shadowkin can understand
+- Slightly less blunt damage
+- A bit more slash damage
+- Slightly more piercing damage
+- Less cold damage
+- Slightly more heat damage
+- Near no cellular damage
+- More bloodloss damage
+- Slightly more shock damage
+- More radiation damage
+
+## Physical Differences
+
+- Very large and brightly colored eyes with no pupils
+- Sees the world through their eyes' tint
+- Very large ears
+- Very fluffy
+- Can be Male, Female, or Unisex
+- Can be 18-300 years old
+
\ No newline at end of file
diff --git a/Resources/ServerInfo/Guidebook/Mobs/shadowkin.Lore.txt b/Resources/ServerInfo/Guidebook/Mobs/shadowkin.Lore.txt
new file mode 100644
index 00000000000..e3cda6d77e2
--- /dev/null
+++ b/Resources/ServerInfo/Guidebook/Mobs/shadowkin.Lore.txt
@@ -0,0 +1,178 @@
+# Lore
+
+## Biology
+
+[color=#a88b5e]Physicality[/color]
+
+Shadowkin seem very similar to canine species when looked at.
+While mammalian in nature, it is nearly impossible to find any sexual dimorphism between genders, if they even have any.
+They seem to all share the same body shape and often voices.
+
+The head consists of a snout with a nose, sharp teeth in the mouth, and two big ears on top of the head.
+In addition to the two big ears, Shadowkin have two smaller ears lower down, giving them an exceptionally good ability to detect where exactly sounds come from.
+
+Shadowkin eyes are usually very large and fully mono-colored; they have no discernible pupils, irises, or similar.
+The eye of a Shadowkin is a solid orb of color and, perhaps, their most defining trait.
+
+All observations show Shadowkin do not seem to age after a certain point.
+Some may look older or younger than others, but the aging process seems to stop when the Shadowkin is chronologically in their 20s.
+
+Unusually, compiled medical data on Shadowkin shows that they have no lungs, thus not needing to breathe.
+The compiled medical data also shows that they have a large, more circular shape where their lungs would be.
+
+[color=#a88b5e]Mentality[/color]
+
+Shadowkin core personalities are mirrored by their eye color.
+Some common colors and their meanings are:
+
+- Red: Determined to reach a goal, even if it means sacrificing others.
+- Green: Eager to learn and fit in with others.
+- Blue: Very calm and collected, or shy.
+
+Other colors also exist in addition to these but are not as common.
+The other colors are just mixes of the three main colors.
+For example, here are a couple of mixes:
+
+- Purple: Determined, yet probably won't harm, in some cases shy.
+- Yellow: Quite eager to be number one, especially if related to knowledge.
+- Orange: Shy and calm, yet will fight if provoked.
+- White: Very robust, exceeds expectations in most things they do, but is quite boring to interact with generally.
+
+There are rumors other colors exist as well.
+Shadowkin that lose their ability to travel between dimensions have [color=#000000]black eyes[/color] no matter their core personality.
+These black-eyed Shadowkin make up the largest number of them living in Realspace.
+
+## History
+
+Shadowkin are creatures native to their so-called Pocket Dimension, a part of space that is unable to be accurately pinpointed and/or visited by most people.
+From there, Shadowkin visit our worlds, and sometimes a large number of them are forced into our world in mass migrations and then live among us.
+What exactly causes these mass migrations is unknown, but may be related to them losing their ability to travel between dimensions.
+
+[color=#a88b5e]Homeworld[/color]
+
+While no home system or world has been found, some Shadowkin talk about dreaming of their homeworld, describing a lush and green land with abundant resources.
+
+[color=#a88b5e]Racial/Government Status[/color]
+
+There currently exists no Shadowkin government, and with Shadowkin being as rare as they are, they tend to fit into currently established societies and civilizations, with varying degrees of success in the past.
+In recent years, however, many cultures have taken to accepting Shadowkin as another galactic constant, going as far as allowing them lives similar to those that any of the other species would lead.
+
+Some systems early on took and experimented on Shadowkin, in an attempt to utilize their powers in various technology, and even recreating them with very experimental technology.
+The tests failed and this was met with a lot of resistance from Shadowkin, many of them being killed or becoming Blackeyes in the process.
+After this, those systems realized that Shadowkin were a sentient species, and deeply apologized for their actions.
+
+## Culture
+
+[color=#a88b5e]The Typical Experience[/color]
+
+Most times, life is pretty normal. A Shadowkin can have a job, go to the job to work, then spend time off with friends.
+The fact is most times of the day, the small abilities one might have are nice, but not overly useful. Sure, a Shadowkin could dim the nearby lights, but at the same time, the Shadowkin could also just flick the light switch.
+However, it can sometimes happen that when a Shadowkin slept in and needs to get to work quickly, they might just teleport to work.
+Some people might be overly wary of Shadowkin, some might be drawn towards Shadowkin.
+Many Shadowkin are used to not wearing clothes, which can lead to some awkward situations, but those are generally the same situations any other furred species would have.
+
+[color=#a88b5e]Language[/color]
+
+The only recorded spoken language of the Shadowkin is an unusual language named "Mar", named after the fact that every single word in this language is the word "mar".
+Spoken in different tones, with more or less emphasis on the various parts, or by drawing out parts of this word, a multitude of different "mar"s can be created, but they follow no apparent conventions.
+Shadowkin can perfectly understand each other though and discuss complex topics using only this one word.
+The Shadowkin can hear the Mar language from anywhere and understand it via Empathy, yet do not know who it is from unless they are close enough to see the sender.
+Other humanoid species are incapable of understanding or learning Mar, as they are unable to accentuate their speech in the same way or hear some of the silent, Empathic tones they use.
+Shadowkin tend to learn one or more languages of the Realspace beings.
+The capability in such learned languages depends fully on each single Shadowkin, where some have only very broken capabilities, while others are fluent in several languages.
+
+[color=#a88b5e]Naming Conventions[/color]
+
+Shadowkin tend to name themselves with a singular word, ranging from states of being, such as Lone and Collected, to words that describe their memories connected to their supposed homeworld, like Dreamer.
+Name schemes of the humans are usually frowned upon, as they do not reflect upon the Shadowkin, resulting in reactions ranging anywhere from an actual frown to being entirely ignored.
+Shadowkin following another species naming scheme are often Blackeyes, not fitting in with other Shadowkin as well.
+Names hold a great deal for Shadowkin because of how closely they are often tied to describing who they are.
+Following life-changing events, Shadowkin often change their names to reflect the new person they have become.
+
+[color=#a88b5e]Rumors and Speculation[/color]
+
+Shadowkin are beings from another dimension, capable of performing feats that others could only describe as "magic", with no scientific explanation.
+It is speculated that Shadowkin actually have a whole nation in their dimension, and those that come to Realspace just simply refuse to talk about it.
+Sometimes Shadowkin mention a "Hub", acting as evidence for this theory.
+
+Some believe Shadowkin are the result of experimentation, though them being taken and experimented on is a large piece of evidence denying this theory.
+
+Some believe Shadowkin are the result of an expedition crew that disappeared hundreds of years ago, even though the first Shadowkin sightings were long before that.
+Obviously, this expedition ship performed time travel.
+
+## OOC Notes
+
+Extra information, should be read if playing Shadowkin, if not you should try to learn this in gameplay.
+
+[color=#a88b5e]History[/color]
+
+Shadowkin used to live on a very lush forest planet, using primitive technology to work with metal, create tools, and build stone structures.
+According to the dreams, Shadowkin back then had forged tools and blades, but had no electricity, presumably putting them at a medieval technology level.
+In a sudden event, everyone and everything organic suddenly found themselves in an alternate dimension, sucked straight out of their previous home.
+The Shadowkin being ripped from their homes happened in three total "dimensional ripples".
+
+Now stuck in this alternate dimension, Shadowkin changed a lot.
+They stopped aging and with time developed the ability to use the energy of this dimension to power their unusual "magic".
+And even later, they learned how to leave the dimension and travel to Realspace and back again.
+However, as the eons passes, memories became shady of their home.
+Many Shadowkin forgot more and more about the thousands of years they had lived, reducing memory to focus on the more important closer time.
+However in dreams, Shadowkin, even those born in Realspace, often have visions of their home planet.
+Some events can trigger certain memories to return like meeting someone they knew from the past makes them remember in great detail who they are.
+As such, it can happen that two Shadowkin can randomly meet and remember that they met hundreds of years ago, often confusing nearby unknowing humanoids.
+
+[color=#a88b5e]Biology[/color]
+
+If a Shadowkin personality changes drastically, their eye color will change as an effect, reflecting upon their new personality.
+Shadowkin, being ageless entities, can look older or younger, but that is merely a visual representation of how they feel about their age.
+22 years after birth a Shadowkin stops aging, and in all of their history no instance of them dying of old age has been recorded.
+
+[color=#a88b5e]Shadowkin energy[/color]
+
+Shadowkin have an odd organ in them, their "Core".
+This organ is the source and storage place for their power.
+Without it, they would be unable to use their abilities.
+The Core is very sensitive to physical trauma, yet is very resilient to electrical or magical damage.
+The Core can get irreversibly destroyed from overloading it, using too much power too quickly, which is what happens to the Blackeyes.
+The Core can not be replaced, using a new one would require a new body, and trying to use another Shadowkin Core will result in death.
+
+The Shadowkin's ability to fall into a deep sleep is a method to recharge their energy at a significantly higher rate than idling.
+Though while in this deep sleep, it is difficult to wake up, and cannot be woken up by anything other than themself.
+
+Shadowkin can know exactly how much energy they have, and feel differently based on how much they have.
+They can also Empathically sense the energy of others nearby and can estimate how much energy they have based on their feelings.
+
+[color=#a88b5e]Shadowkin Abilities[/color]
+
+Shadowkin have a few abilities.
+They can teleport, requiring a small amount of energy, but they can do it quickly.
+They can teleport to and from The Dark, requiring much more energy than teleportation.
+They can immediately fall into a deep sleep at any time.
+
+[color=#a88b5e]The Ritual[/color]
+
+When a Shadowkin reaches adulthood, they undergo a "Ritual".
+During this Ritual they intentionally overload their energy reserves, forcing their capacity to expand rapidly in order to gain enough energy to travel between dimensions.
+Most Shadowkin pass the Ritual and that's the end of it, but some Shadowkin perish or become Blackeyes during the ritual.
+
+[color=#a88b5e]Important Terminology[/color]
+
+[color=#a88b5e]Shadowkin:[/color]
+The name given for your race.
+While your race doesn't have a name for itself, this is how most of the Galaxy refers to you, so it's how you refer to yourself too.
+
+[color=#a88b5e]The Dark:[/color]
+The other dimension where you are from.
+There are theories that The Dark and Bluespace are connected, but you don't know the details about that.
+The Dark is a dimension where strange rules apply.
+For outsiders who somehow enter it, whatever they imagine being in there, they see there, if not too complex.
+If an outsider comes with a willing Shadowkin they may see what the Shadowkin sees, being anything they imagine, or a reflection of the station.
+
+[color=#a88b5e]Blackeyes:[/color]
+A Shadowkin who has undergone and failed the Ritual, or lost their ability via other methods.
+The Blackeyes have completely black eyes, no matter their actual personality.
+They choose a new name, which follows a favorite species' naming scheme.
+They are often ignored or left alone more by other Shadowkin.
+[color=#a88b5e]Note:[/color] Blackeyes correlation with black eyes is done entirely via roleplay, the game will not handle this for you.
+
+[color=#a88b5e]Mar:[/color]
+A word used to verbally communicate feelings, emotions, wishes, hopes, ideas, and whatever, in addition to your Empathy.
\ No newline at end of file
diff --git a/Resources/Textures/Clothing/Back/etherealteleporter.rsi/equipped-BACKPACK.png b/Resources/Textures/Clothing/Back/etherealteleporter.rsi/equipped-BACKPACK.png
new file mode 100644
index 00000000000..7496c91c550
Binary files /dev/null and b/Resources/Textures/Clothing/Back/etherealteleporter.rsi/equipped-BACKPACK.png differ
diff --git a/Resources/Textures/Clothing/Back/etherealteleporter.rsi/icon.png b/Resources/Textures/Clothing/Back/etherealteleporter.rsi/icon.png
new file mode 100644
index 00000000000..265318a96f5
Binary files /dev/null and b/Resources/Textures/Clothing/Back/etherealteleporter.rsi/icon.png differ
diff --git a/Resources/Textures/Clothing/Back/etherealteleporter.rsi/meta.json b/Resources/Textures/Clothing/Back/etherealteleporter.rsi/meta.json
new file mode 100644
index 00000000000..e4f5aa0f920
--- /dev/null
+++ b/Resources/Textures/Clothing/Back/etherealteleporter.rsi/meta.json
@@ -0,0 +1,56 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from https://github.com/ParadiseSS13/Paradise/blob/master/icons/mob/clothing/back.dmi",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ },
+ {
+ "name": "equipped-BACKPACK",
+ "directions": 4,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/equipped-EYES.png b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/equipped-EYES.png
new file mode 100644
index 00000000000..39fafeb1449
Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/equipped-EYES.png differ
diff --git a/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/icon.png b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/icon.png
new file mode 100644
index 00000000000..8ef5aa68d09
Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/icon.png differ
diff --git a/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/inhand-left.png b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/inhand-left.png
new file mode 100644
index 00000000000..7ecbd93e8ae
Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/inhand-left.png differ
diff --git a/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/inhand-right.png b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/inhand-right.png
new file mode 100644
index 00000000000..26fd8e57a42
Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/inhand-right.png differ
diff --git a/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/meta.json b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/meta.json
new file mode 100644
index 00000000000..555efbc7daa
--- /dev/null
+++ b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/meta.json
@@ -0,0 +1,26 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "JustAnOrange",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ },
+ {
+ "name": "equipped-EYES",
+ "directions": 4
+ },
+ {
+ "name": "inhand-left",
+ "directions": 4
+ },
+ {
+ "name": "inhand-right",
+ "directions": 4
+ }
+ ]
+}
diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/equipped-OUTERCLOTHING.png
new file mode 100644
index 00000000000..e8618fc6b7d
Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/equipped-OUTERCLOTHING.png differ
diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/icon.png
new file mode 100644
index 00000000000..def3161bb0a
Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/icon.png differ
diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/inhand-left.png b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/inhand-left.png
new file mode 100644
index 00000000000..5c15def7666
Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/inhand-left.png differ
diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/inhand-right.png b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/inhand-right.png
new file mode 100644
index 00000000000..99c96d368ad
Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/inhand-right.png differ
diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/meta.json
new file mode 100644
index 00000000000..57a0b994f9b
--- /dev/null
+++ b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/meta.json
@@ -0,0 +1,26 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "JustAnOrange",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "equipped-OUTERCLOTHING",
+ "directions": 4
+ },
+ {
+ "name": "icon"
+ },
+ {
+ "name": "inhand-left",
+ "directions": 4
+ },
+ {
+ "name": "inhand-right",
+ "directions": 4
+ }
+ ]
+}
diff --git a/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/darkswap.png b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/darkswap.png
new file mode 100644
index 00000000000..3c2815e8c24
Binary files /dev/null and b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/darkswap.png differ
diff --git a/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/meta.json b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/meta.json
new file mode 100644
index 00000000000..62b03fecafc
--- /dev/null
+++ b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/meta.json
@@ -0,0 +1,23 @@
+{
+ "version": 1,
+ "size": {
+ "x": 64,
+ "y": 64
+ },
+ "license": "CC-BY-SA-3.0",
+ "copyright": "DEATHB4DEFEAT#4404 (801294818839756811)",
+ "states": [
+ {
+ "name": "darkswap",
+ "directions": 1
+ },
+ {
+ "name": "rest",
+ "directions": 1
+ },
+ {
+ "name": "shadeskip",
+ "directions": 1
+ }
+ ]
+}
diff --git a/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/rest.png b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/rest.png
new file mode 100644
index 00000000000..188b4ec5911
Binary files /dev/null and b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/rest.png differ
diff --git a/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/shadeskip.png b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/shadeskip.png
new file mode 100644
index 00000000000..b0f1bd8dc8c
Binary files /dev/null and b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/shadeskip.png differ
diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/meta.json b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/meta.json
new file mode 100644
index 00000000000..f642565f9ab
--- /dev/null
+++ b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/meta.json
@@ -0,0 +1,43 @@
+{
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA-3.0",
+ "copyright": "DEATHB4DEFEAT#4404 (801294818839756811)",
+ "states": [
+ {
+ "name": "power0",
+ "delays": [[0.8, 0.8, 0.8, 0.8, 0.8, 0.8]]
+ },
+ {
+ "name": "power1",
+ "delays": [[0.7, 0.7, 0.7, 0.7, 0.7, 0.7]]
+ },
+ {
+ "name": "power2",
+ "delays": [[0.6, 0.6, 0.6, 0.6, 0.6, 0.6]]
+ },
+ {
+ "name": "power3",
+ "delays": [[0.5, 0.5, 0.5, 0.5, 0.5, 0.5]]
+ },
+ {
+ "name": "power4",
+ "delays": [[0.4, 0.4, 0.4, 0.4, 0.4, 0.4]]
+ },
+ {
+ "name": "power5",
+ "delays": [[0.3, 0.3, 0.3, 0.3, 0.3, 0.3]]
+ },
+ {
+ "name": "power6",
+ "delays": [[0.2, 0.2, 0.2, 0.2, 0.2, 0.2]]
+ },
+ {
+ "name": "power7",
+ "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]
+ }
+ ]
+}
diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power0.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power0.png
new file mode 100644
index 00000000000..ab370f753ed
Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power0.png differ
diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power1.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power1.png
new file mode 100644
index 00000000000..d72965eeec8
Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power1.png differ
diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power2.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power2.png
new file mode 100644
index 00000000000..1b2c51575c7
Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power2.png differ
diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power3.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power3.png
new file mode 100644
index 00000000000..0f93f925ac2
Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power3.png differ
diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power4.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power4.png
new file mode 100644
index 00000000000..3f3035da0df
Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power4.png differ
diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power5.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power5.png
new file mode 100644
index 00000000000..af3f7168618
Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power5.png differ
diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power6.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power6.png
new file mode 100644
index 00000000000..9f7957c44f2
Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power6.png differ
diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power7.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power7.png
new file mode 100644
index 00000000000..ac9f17f55bf
Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power7.png differ
diff --git a/Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/meta.json b/Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/meta.json
new file mode 100644
index 00000000000..b67f4f34899
--- /dev/null
+++ b/Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/meta.json
@@ -0,0 +1,19 @@
+{
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from S.P.L.U.R.T ears.dmi at commit https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/8b4abffbe538fbded19b44b989ebc6808748f31f",
+ "states": [
+ {
+ "name": "shadowkin",
+ "directions": 4
+ },
+ {
+ "name": "shadowkin_stripes",
+ "directions": 4
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/shadowkin.png b/Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/shadowkin.png
new file mode 100644
index 00000000000..3aec926c5e2
Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/shadowkin.png differ
diff --git a/Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/shadowkin_stripes.png b/Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/shadowkin_stripes.png
new file mode 100644
index 00000000000..ce1f04ce602
Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/shadowkin_stripes.png differ
diff --git a/Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/meta.json b/Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/meta.json
new file mode 100644
index 00000000000..4731b9de3dd
--- /dev/null
+++ b/Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/meta.json
@@ -0,0 +1,19 @@
+{
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA-3.0",
+ "copyright": "https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/96703f76bccd8fe6a96b78524efb97a9af661767 Shadowkin big fluff made by DSC@Cabbage#9633 (561159087765848084)",
+ "states": [
+ {
+ "name": "shadowkin_shorter",
+ "directions": 4
+ },
+ {
+ "name": "shadowkin_medium",
+ "directions": 4
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/shadowkin_medium.png b/Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/shadowkin_medium.png
new file mode 100644
index 00000000000..ca162bb1b05
Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/shadowkin_medium.png differ
diff --git a/Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/shadowkin_shorter.png b/Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/shadowkin_shorter.png
new file mode 100644
index 00000000000..979e4e6b26c
Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/shadowkin_shorter.png differ
diff --git a/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/meta.json b/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/meta.json
new file mode 100644
index 00000000000..535c01bbaf1
--- /dev/null
+++ b/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/meta.json
@@ -0,0 +1,23 @@
+{
+ "version": 1,
+ "size": {
+ "x": 64,
+ "y": 32
+ },
+ "license": "CC-BY-SA-3.0",
+ "copyright": "https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/96703f76bccd8fe6a96b78524efb97a9af661767 Shadowkin big fluff made by DSC@Cabbage#9633 (561159087765848084)",
+ "states": [
+ {
+ "name": "shadowkin",
+ "directions": 4
+ },
+ {
+ "name": "shadowkin_big",
+ "directions": 4
+ },
+ {
+ "name": "shadowkin_big_fluff",
+ "directions": 4
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/shadowkin.png b/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/shadowkin.png
new file mode 100644
index 00000000000..94ff21d9cbb
Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/shadowkin.png differ
diff --git a/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/shadowkin_big.png b/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/shadowkin_big.png
new file mode 100644
index 00000000000..988aa6e589f
Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/shadowkin_big.png differ
diff --git a/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/shadowkin_big_fluff.png b/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/shadowkin_big_fluff.png
new file mode 100644
index 00000000000..20ad696ccee
Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/shadowkin_big_fluff.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/appendix.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/appendix.png
new file mode 100644
index 00000000000..0d2ad309c74
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/appendix.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/brain.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/brain.png
new file mode 100644
index 00000000000..ac2806b79ce
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/brain.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/core.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/core.png
new file mode 100644
index 00000000000..ac2d7893fdb
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/core.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/ears.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/ears.png
new file mode 100644
index 00000000000..6ff3ac86b76
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/ears.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/eyes.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/eyes.png
new file mode 100644
index 00000000000..f7c0a306aaa
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/eyes.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/heart.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/heart.png
new file mode 100644
index 00000000000..1b79b529aee
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/heart.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/kidneys.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/kidneys.png
new file mode 100644
index 00000000000..482bb241022
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/kidneys.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/liver.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/liver.png
new file mode 100644
index 00000000000..0a2e6ab25ae
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/liver.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/lungs.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/lungs.png
new file mode 100644
index 00000000000..a76c9fc1eb4
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/lungs.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/meta.json b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/meta.json
new file mode 100644
index 00000000000..1c9aebfb6d6
--- /dev/null
+++ b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/meta.json
@@ -0,0 +1,44 @@
+{
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA-3.0",
+ "copyright": "https://github.com/tgstation/tgstation/commit/f309886bf3e29808206693e9142304260df134e9",
+ "states": [
+ {
+ "name": "appendix"
+ },
+ {
+ "name": "brain"
+ },
+ {
+ "name": "core"
+ },
+ {
+ "name": "ears"
+ },
+ {
+ "name": "eyes"
+ },
+ {
+ "name": "heart"
+ },
+ {
+ "name": "kidneys"
+ },
+ {
+ "name": "liver"
+ },
+ {
+ "name": "lungs"
+ },
+ {
+ "name": "stomach"
+ },
+ {
+ "name": "tongue"
+ }
+ ]
+}
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/stomach.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/stomach.png
new file mode 100644
index 00000000000..a0341750d32
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/stomach.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/tongue.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/tongue.png
new file mode 100644
index 00000000000..64306900f57
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/tongue.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/eyes.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/eyes.png
new file mode 100644
index 00000000000..20fd326f17f
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/eyes.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/full-nomarkings.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/full-nomarkings.png
new file mode 100644
index 00000000000..b62d81fec79
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/full-nomarkings.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/full.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/full.png
new file mode 100644
index 00000000000..253eb0c3c9b
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/full.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/head_f.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/head_f.png
new file mode 100644
index 00000000000..9d99bd18903
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/head_f.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/head_m.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/head_m.png
new file mode 100644
index 00000000000..9d99bd18903
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/head_m.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_arm.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_arm.png
new file mode 100644
index 00000000000..078ca333f6d
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_arm.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_foot.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_foot.png
new file mode 100644
index 00000000000..30ad69dc3f0
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_foot.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_hand.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_hand.png
new file mode 100644
index 00000000000..0cbc7371d14
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_hand.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_leg.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_leg.png
new file mode 100644
index 00000000000..b961dfc842a
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_leg.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/meta.json b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/meta.json
new file mode 100644
index 00000000000..a259ab696b8
--- /dev/null
+++ b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/meta.json
@@ -0,0 +1,71 @@
+{
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA-3.0",
+ "copyright": "https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/5bc3ea02ce03a551c85017f1ddd411315a19a5ca#diff-519788fa2ca74299d1686a44d3ab2098b49ed5ab65d293ec742bead7d49f0b8d",
+ "states": [
+ {
+ "name": "full",
+ "directions": 4
+ },
+ {
+ "name": "full-nomarkings",
+ "directions": 4
+ },
+ {
+ "name": "head_m",
+ "directions": 4
+ },
+ {
+ "name": "head_f",
+ "directions": 4
+ },
+ {
+ "name": "torso_m",
+ "directions": 4
+ },
+ {
+ "name": "torso_f",
+ "directions": 4
+ },
+ {
+ "name": "r_arm",
+ "directions": 4
+ },
+ {
+ "name": "l_arm",
+ "directions": 4
+ },
+ {
+ "name": "r_hand",
+ "directions": 4
+ },
+ {
+ "name": "l_hand",
+ "directions": 4
+ },
+ {
+ "name": "r_leg",
+ "directions": 4
+ },
+ {
+ "name": "r_foot",
+ "directions": 4
+ },
+ {
+ "name": "l_leg",
+ "directions": 4
+ },
+ {
+ "name": "l_foot",
+ "directions": 4
+ },
+ {
+ "name": "eyes",
+ "directions": 4
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_arm.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_arm.png
new file mode 100644
index 00000000000..c294120942c
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_arm.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_foot.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_foot.png
new file mode 100644
index 00000000000..390e0a27ee3
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_foot.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_hand.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_hand.png
new file mode 100644
index 00000000000..331e33a587e
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_hand.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_leg.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_leg.png
new file mode 100644
index 00000000000..6b0270f6343
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_leg.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/torso_f.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/torso_f.png
new file mode 100644
index 00000000000..83cc63cdd29
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/torso_f.png differ
diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/torso_m.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/torso_m.png
new file mode 100644
index 00000000000..dafc83b65eb
Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/torso_m.png differ
diff --git a/Resources/Textures/Objects/Fun/toys.rsi/meta.json b/Resources/Textures/Objects/Fun/toys.rsi/meta.json
index 24345aadf38..cc03557e0b4 100644
--- a/Resources/Textures/Objects/Fun/toys.rsi/meta.json
+++ b/Resources/Textures/Objects/Fun/toys.rsi/meta.json
@@ -386,6 +386,9 @@
{
"name": "beachb-inhand-right",
"directions": 4
+ },
+ {
+ "name": "shadowkin"
}
]
}
\ No newline at end of file
diff --git a/Resources/Textures/Objects/Fun/toys.rsi/shadowkin.png b/Resources/Textures/Objects/Fun/toys.rsi/shadowkin.png
new file mode 100644
index 00000000000..df4118ecd45
Binary files /dev/null and b/Resources/Textures/Objects/Fun/toys.rsi/shadowkin.png differ
diff --git a/Resources/Textures/Shaders/color_tint.swsl b/Resources/Textures/Shaders/color_tint.swsl
new file mode 100644
index 00000000000..a5449b2d4d6
--- /dev/null
+++ b/Resources/Textures/Shaders/color_tint.swsl
@@ -0,0 +1,56 @@
+light_mode unshaded;
+
+uniform sampler2D SCREEN_TEXTURE;
+uniform lowp vec3 tint_color; // RGB color between 0 and 1
+uniform lowp float tint_amount; // Number between 0 and 1
+
+// Function to convert RGB to HSV.
+highp vec3 rgb2hsv(highp vec3 c)
+{
+ highp vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
+ highp vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
+ highp vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
+
+ highp float d = q.x - min(q.w, q.y);
+ /* float e = 1.0e-10; */
+ highp float e = 0.0000000001;
+ return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
+}
+
+// Function to convert HSV to RGB.
+highp vec3 hsv2rgb(highp vec3 c)
+{
+ highp vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+ highp vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
+
+void fragment() {
+ highp vec4 color = zTextureSpec(SCREEN_TEXTURE, FRAGCOORD.xy * SCREEN_PIXEL_SIZE);
+
+ // Convert color to HSV.
+ highp vec3 hsvTint = rgb2hsv(tint_color);
+ highp vec3 hsvColor = rgb2hsv(color.rgb);
+
+ // Set the original hue to the tint hue as long as it's not greyscale.
+ if (hsvTint.y > 0.05 && hsvTint.z != 0.0)
+ {
+ hsvColor.x = hsvTint.x;
+ }
+ // Modify saturation based on tint color saturation,
+ // Halving it if it's higher and capping it at the original.
+ hsvColor.y = (hsvColor.y < hsvTint.y) ?
+ mix(hsvColor.y, hsvTint.y, 0.75) : mix(hsvColor.y, hsvTint.y, 0.35);
+
+ // Modify value based on tint color value, but only if it's darker.
+ hsvColor.z = (mix(hsvColor.z, hsvTint.z, 0.85) <= hsvColor.z) ?
+ mix(hsvColor.z, hsvTint.z, 0.85) : hsvColor.z;
+
+ // Convert back to RGB.
+ highp vec3 rgbColorMod = hsv2rgb(hsvColor);
+
+ // Mix the final RGB product with the original color to the intensity of the tint.
+ color.rgb = mix(color.rgb, rgbColorMod, tint_amount);
+
+ COLOR = color;
+}
\ No newline at end of file
diff --git a/Resources/Textures/Shaders/ethereal.swsl b/Resources/Textures/Shaders/ethereal.swsl
new file mode 100644
index 00000000000..dc9d971e1cd
--- /dev/null
+++ b/Resources/Textures/Shaders/ethereal.swsl
@@ -0,0 +1,75 @@
+light_mode unshaded;
+
+uniform sampler2D SCREEN_TEXTURE;
+
+// Function to convert RGB to HSV.
+highp vec3 rgb2hsv(highp vec3 c)
+{
+ highp vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
+ highp vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
+ highp vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
+
+ highp float d = q.x - min(q.w, q.y);
+ /* float e = 1.0e-10; */
+ highp float e = 0.0000000001;
+ return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
+}
+
+// Function to convert HSV to RGB.
+highp vec3 hsv2rgb(highp vec3 c)
+{
+ highp vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+ highp vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
+
+// Random number function with potential negative values.
+highp float rand(highp vec2 n) {
+ highp float r = 2.0 * (0.5 + 0.5 * fract (sin (dot (n.xy, vec2(12.9898, 78.233)))* 43758.5453)) - 1.0;
+ return r * (r < 0.0 ? 0.8 : 1.3);
+}
+
+void fragment() {
+ highp vec4 color = zTextureSpec(SCREEN_TEXTURE, FRAGCOORD.xy * SCREEN_PIXEL_SIZE);
+
+ // Increase the contrast of the image if the luminance is low enough.
+ highp float luminance = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
+ if (luminance < 0.06) {
+ color.rgb *= 0.5;
+ }
+
+ // Convert to HSV.
+ highp vec3 hsvColor = rgb2hsv(color.rgb);
+
+ // Apply a breathing effect to the value of the image.
+ hsvColor.z *= mix(0.35, 0.7, (sin(TIME) * 0.65));
+
+ // Increase the saturation of the color, incorperating a random value, as long as the value is above 0.1.
+ if (hsvColor.z > 0.065) {
+ hsvColor.y *= (rand(FRAGCOORD.xy * (TIME * 0.15)) * 1.5) + 1.0;
+ }
+
+ // Convert back to RGB.
+ color.rgb = hsv2rgb(hsvColor);
+
+
+
+ // get distortion magnitude. hand crafted from a random jumble of trig functions
+ highp float w = sin(TIME + (FRAGCOORD.x + FRAGCOORD.y + 2.0*sin(TIME*0.3) * sin(TIME*0.3 + FRAGCOORD.x - FRAGCOORD.y)) );
+
+ // visualize distortion via:
+ // COLOR = vec4(w,w,w,1.0);
+
+ w *= 5.0;
+
+ highp vec4 background = zTextureSpec(SCREEN_TEXTURE, ( FRAGCOORD.xy + vec2(w) ) * SCREEN_PIXEL_SIZE );
+ highp vec3 hsvBg = rgb2hsv(background.rgb);
+ hsvBg.x *= -1.0;
+ background.rgb = hsv2rgb(hsvBg);
+
+ color.xyz = mix(background.xyz, color.xyz, 0.75);
+
+
+
+ COLOR = color;
+}