diff --git a/Content.Client/Chat/Managers/ChatManager.cs b/Content.Client/Chat/Managers/ChatManager.cs index 18f03cd7db..009579df98 100644 --- a/Content.Client/Chat/Managers/ChatManager.cs +++ b/Content.Client/Chat/Managers/ChatManager.cs @@ -72,6 +72,11 @@ public void SendMessage(string text, ChatSelectChannel channel) _consoleHost.ExecuteCommand($"tsay \"{CommandParsing.Escape(str)}\""); break; + // Parkstation-EmpathyChat + case ChatSelectChannel.Empathy: + _consoleHost.ExecuteCommand($"esay \"{CommandParsing.Escape(str)}\""); + break; + default: throw new ArgumentOutOfRangeException(nameof(channel), channel, null); } diff --git a/Content.Client/Parkstation/Chat/ShadowkinChatUpdateSystem.cs b/Content.Client/Parkstation/Chat/ShadowkinChatUpdateSystem.cs new file mode 100644 index 0000000000..c6359c5035 --- /dev/null +++ b/Content.Client/Parkstation/Chat/ShadowkinChatUpdateSystem.cs @@ -0,0 +1,35 @@ +using Content.Client.Chat.Managers; +using Content.Shared.Parkstation.Species.Shadowkin.Components; +using Robust.Client.Player; + +namespace Content.Client.Parkstation.Chat +{ + public sealed class ShadowkinChatUpdateSystem : EntitySystem + { + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnRemove); + } + + + public EmpathyChatComponent? Player => CompOrNull(_playerManager.LocalEntity); + public bool IsShadowkin => Player != null; + + private void OnInit(EntityUid uid, EmpathyChatComponent component, ComponentInit args) + { + _chatManager.UpdatePermissions(); + } + + private void OnRemove(EntityUid uid, EmpathyChatComponent component, ComponentRemove args) + { + _chatManager.UpdatePermissions(); + } + } +} diff --git a/Content.Client/Parkstation/Overlays/Shaders/ColorTintOverlay.cs b/Content.Client/Parkstation/Overlays/Shaders/ColorTintOverlay.cs new file mode 100644 index 0000000000..b34291d827 --- /dev/null +++ b/Content.Client/Parkstation/Overlays/Shaders/ColorTintOverlay.cs @@ -0,0 +1,63 @@ +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; + +namespace Content.Client.Parkstation.Overlays.Shaders; + +/// +/// 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] private readonly IEntityManager _entity = 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; + /// + /// Component required to be on the entity to tint the screen. + /// + public Component? Comp = null; + + + public ColorTintOverlay() + { + IoCManager.InjectDependencies(this); + + _shader = _prototype.Index("ColorTint").InstanceUnique(); + } + + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture == null || + _player.LocalEntity is not { Valid: true } player || + Comp != null && !_entity.HasComponent(player, Comp.GetType())) + 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); + } +} diff --git a/Content.Client/Parkstation/Overlays/Shaders/EtherealOverlay.cs b/Content.Client/Parkstation/Overlays/Shaders/EtherealOverlay.cs new file mode 100644 index 0000000000..40ae3108c0 --- /dev/null +++ b/Content.Client/Parkstation/Overlays/Shaders/EtherealOverlay.cs @@ -0,0 +1,39 @@ +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; + +namespace Content.Client.Parkstation.Overlays.Shaders; + +public sealed class EtherealOverlay : Overlay +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IPlayerManager _player = 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 void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture == null) return; + if (_player.LocalEntity is not { Valid: true } player) 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); + } +} diff --git a/Content.Client/Parkstation/Overlays/Shaders/IgnoreHumanoidWithComponentOverlay.cs b/Content.Client/Parkstation/Overlays/Shaders/IgnoreHumanoidWithComponentOverlay.cs new file mode 100644 index 0000000000..91c249cf73 --- /dev/null +++ b/Content.Client/Parkstation/Overlays/Shaders/IgnoreHumanoidWithComponentOverlay.cs @@ -0,0 +1,107 @@ +using Content.Shared.Humanoid; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; + +namespace Content.Client.Parkstation.Overlays.Shaders; + +/// +/// Not really an overlay to be honest +/// +public sealed class IgnoreHumanoidWithComponentOverlay : Overlay +{ + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + + public List IgnoredComponents = new(); + public List AllowAnywayComponents = new(); + private readonly List _nonVisibleList = new(); + + + public IgnoreHumanoidWithComponentOverlay() + { + IoCManager.InjectDependencies(this); + } + + + protected override void Draw(in OverlayDrawArgs args) + { + var spriteQuery = _entityManager.GetEntityQuery(); + + foreach (var humanoid in _entityManager.EntityQuery(true)) + { + if (_playerManager.LocalEntity == humanoid.Owner) + continue; + + var cont = true; + foreach (var comp in IgnoredComponents) + { + if (!_entityManager.HasComponent(humanoid.Owner, comp.GetType())) + continue; + + cont = false; + break; + } + foreach (var comp in AllowAnywayComponents) + { + if (!_entityManager.HasComponent(humanoid.Owner, comp.GetType())) + continue; + + cont = true; + break; + } + if (cont) + { + Reset(humanoid.Owner); + continue; + } + + + if (!spriteQuery.TryGetComponent(humanoid.Owner, out var sprite)) + continue; + + if (!sprite.Visible || _nonVisibleList.Contains(humanoid.Owner)) + continue; + + sprite.Visible = false; + _nonVisibleList.Add(humanoid.Owner); + } + + foreach (var humanoid in _nonVisibleList.ToArray()) + { + if (!_entityManager.Deleted(humanoid)) + continue; + + _nonVisibleList.Remove(humanoid); + } + } + + + /// + /// Resets the overlay, making all entities visible again + /// + public void Reset() + { + foreach (var humanoid in _nonVisibleList.ToArray()) + { + _nonVisibleList.Remove(humanoid); + + if (_entityManager.TryGetComponent(humanoid, out var sprite)) + sprite.Visible = true; + } + } + + /// + /// Resets the overlay for a specific entity, making it visible again + /// + public void Reset(EntityUid entity) + { + if (!_nonVisibleList.Contains(entity)) + return; + + _nonVisibleList.Remove(entity); + + if (_entityManager.TryGetComponent(entity, out var sprite)) + sprite.Visible = true; + } +} diff --git a/Content.Client/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.DarkSwapped.cs b/Content.Client/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.DarkSwapped.cs new file mode 100644 index 0000000000..cbda937ac5 --- /dev/null +++ b/Content.Client/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.DarkSwapped.cs @@ -0,0 +1,74 @@ +using Content.Client.Parkstation.Overlays.Shaders; +using Content.Shared.Humanoid; +using Content.Shared.Parkstation.Species.Shadowkin.Components; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Player; + +namespace Content.Client.Parkstation.Species.Shadowkin.Systems; + +public sealed class ShadowkinDarkSwappedSystem : EntitySystem +{ + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IOverlayManager _overlay = default!; + + private IgnoreHumanoidWithComponentOverlay _ignoreOverlay = default!; + private EtherealOverlay _etherealOverlay = default!; + + + public override void Initialize() + { + base.Initialize(); + + _ignoreOverlay = new IgnoreHumanoidWithComponentOverlay(); + _ignoreOverlay.IgnoredComponents.Add(new HumanoidAppearanceComponent()); + _ignoreOverlay.AllowAnywayComponents.Add(new ShadowkinDarkSwappedComponent()); + _etherealOverlay = new EtherealOverlay(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + } + + + private void OnStartup(EntityUid uid, ShadowkinDarkSwappedComponent component, ComponentStartup args) + { + if (_player.LocalEntity != uid) + return; + + AddOverlay(); + } + + private void OnShutdown(EntityUid uid, ShadowkinDarkSwappedComponent component, ComponentShutdown args) + { + if (_player.LocalEntity != uid) + return; + + RemoveOverlay(); + } + + private void OnPlayerAttached(EntityUid uid, ShadowkinDarkSwappedComponent component, LocalPlayerAttachedEvent args) + { + AddOverlay(); + } + + private void OnPlayerDetached(EntityUid uid, ShadowkinDarkSwappedComponent component, LocalPlayerDetachedEvent args) + { + RemoveOverlay(); + } + + + private void AddOverlay() + { + _overlay.AddOverlay(_ignoreOverlay); + _overlay.AddOverlay(_etherealOverlay); + } + + private void RemoveOverlay() + { + _ignoreOverlay.Reset(); + _overlay.RemoveOverlay(_ignoreOverlay); + _overlay.RemoveOverlay(_etherealOverlay); + } +} diff --git a/Content.Client/Parkstation/Species/Shadowkin/Systems/ShadowkinSystem.Blackeye.cs b/Content.Client/Parkstation/Species/Shadowkin/Systems/ShadowkinSystem.Blackeye.cs new file mode 100644 index 0000000000..37583553e6 --- /dev/null +++ b/Content.Client/Parkstation/Species/Shadowkin/Systems/ShadowkinSystem.Blackeye.cs @@ -0,0 +1,56 @@ +using Content.Shared.Humanoid; +using Content.Shared.Parkstation.Species.Shadowkin.Events; +using Content.Shared.Parkstation.Species.Shadowkin.Components; +using Robust.Client.GameObjects; + +namespace Content.Client.Parkstation.Species.Shadowkin.Systems; + +public sealed class ShadowkinBlackeyeSystem : EntitySystem +{ + [Dependency] private readonly IEntityManager _entity = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeNetworkEvent(OnBlackeye); + + SubscribeLocalEvent(OnInit); + } + + + private void OnBlackeye(ShadowkinBlackeyeEvent ev) + { + var uid = _entity.GetEntity(ev.Ent); + SetColor(uid, Color.Black); + } + + + private void OnInit(EntityUid uid, ShadowkinComponent component, ComponentInit args) + { + if (!_entity.TryGetComponent(uid, out var sprite) || + !sprite.LayerMapTryGet(HumanoidVisualLayers.Eyes, out var index) || + !sprite.TryGetLayer(index, out var layer)) + return; + + // Blackeye if none of the RGB values are greater than 75 + if (layer.Color.R * 255 < 75 && layer.Color.G * 255 < 75 && layer.Color.B * 255 < 75) + { + // TODO Need to move this to server somehow, can't trust the client with this + var ent = _entity.GetNetEntity(uid); + RaiseNetworkEvent(new ShadowkinBlackeyeEvent(ent, false)); + } + } + + + private void SetColor(EntityUid uid, Color color) + { + if (!_entity.TryGetComponent(uid, out var sprite) || + !sprite.LayerMapTryGet(HumanoidVisualLayers.Eyes, out var index) || + !sprite.TryGetLayer(index, out var layer)) + return; + + sprite.LayerSetColor(index, color); + } +} diff --git a/Content.Client/Parkstation/Species/Shadowkin/Systems/ShadowkinSystem.Tint.cs b/Content.Client/Parkstation/Species/Shadowkin/Systems/ShadowkinSystem.Tint.cs new file mode 100644 index 0000000000..e6cabf01c5 --- /dev/null +++ b/Content.Client/Parkstation/Species/Shadowkin/Systems/ShadowkinSystem.Tint.cs @@ -0,0 +1,107 @@ +using Content.Client.Parkstation.Overlays.Shaders; +using Content.Shared.GameTicking; +using Content.Shared.Humanoid; +using Content.Shared.Parkstation.Species.Shadowkin.Components; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Player; + +namespace Content.Client.Parkstation.Species.Shadowkin.Systems; + +public sealed class ShadowkinTintSystem : EntitySystem +{ + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IOverlayManager _overlay = default!; + [Dependency] private readonly IEntityManager _entity = default!; + + private ColorTintOverlay _tintOverlay = default!; + + + public override void Initialize() + { + base.Initialize(); + + _tintOverlay = new ColorTintOverlay + { + TintColor = new Vector3(0.5f, 0f, 0.5f), + TintAmount = 0.25f, + Comp = new ShadowkinComponent() + }; + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + SubscribeLocalEvent(OnRoundRestart); + } + + + private void OnStartup(EntityUid uid, ShadowkinComponent component, ComponentStartup args) + { + if (_player.LocalEntity != uid) + return; + + _overlay.AddOverlay(_tintOverlay); + } + + private void OnShutdown(EntityUid uid, ShadowkinComponent component, ComponentShutdown args) + { + if (_player.LocalEntity != uid) + return; + + _overlay.RemoveOverlay(_tintOverlay); + } + + private void OnPlayerAttached(EntityUid uid, ShadowkinComponent component, LocalPlayerAttachedEvent args) + { + _overlay.AddOverlay(_tintOverlay); + } + + private void OnPlayerDetached(EntityUid uid, ShadowkinComponent component, LocalPlayerDetachedEvent args) + { + _overlay.RemoveOverlay(_tintOverlay); + } + + private void OnRoundRestart(RoundRestartCleanupEvent args) + { + _overlay.RemoveOverlay(_tintOverlay); + } + + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var uid = _player.LocalEntity; + if (uid == null || + !_entity.TryGetComponent(uid, out ShadowkinComponent? comp) || + !_entity.TryGetComponent(uid, out SpriteComponent? sprite) || + !sprite.LayerMapTryGet(HumanoidVisualLayers.Eyes, out var index) || + !sprite.TryGetLayer(index, out var layer)) + return; + + // Eye color + comp.TintColor = new Vector3(layer.Color.R, layer.Color.G, layer.Color.B); + + const float min = 0.45f; + const float max = 0.75f; + comp.TintIntensity = Math.Clamp(min + (comp.PowerLevel / comp.PowerLevelMax) / 3, min, max); + + UpdateShader(comp.TintColor, comp.TintIntensity); + } + + + private void UpdateShader(Vector3? color, float? intensity) + { + while (_overlay.HasOverlay()) + _overlay.RemoveOverlay(_tintOverlay); + + if (color != null) + _tintOverlay.TintColor = color; + if (intensity != null) + _tintOverlay.TintAmount = intensity; + + _overlay.AddOverlay(_tintOverlay); + } +} diff --git a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs index bc79283d76..ca8582d67a 100644 --- a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs +++ b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs @@ -38,6 +38,7 @@ using Robust.Shared.Replays; using Robust.Shared.Timing; using Robust.Shared.Utility; +using Content.Client.Parkstation.Chat; //Nyano - Summary: chat namespace. namespace Content.Client.UserInterface.Systems.Chat; @@ -61,6 +62,7 @@ public sealed class ChatUIController : UIController [UISystemDependency] private readonly TypingIndicatorSystem? _typingIndicator = default; [UISystemDependency] private readonly ChatSystem? _chatSys = default; [UISystemDependency] private readonly PsionicChatUpdateSystem? _psionic = default!; //Nyano - Summary: makes the psionic chat available. + [UISystemDependency] private readonly ShadowkinChatUpdateSystem? _shadowkin = default!; // Parkstation-EmpathyChat [ValidatePrototypeId] private const string ChatNamePalette = "ChatNames"; @@ -81,7 +83,8 @@ public sealed class ChatUIController : UIController {SharedChatSystem.AdminPrefix, ChatSelectChannel.Admin}, {SharedChatSystem.RadioCommonPrefix, ChatSelectChannel.Radio}, {SharedChatSystem.DeadPrefix, ChatSelectChannel.Dead}, - {SharedChatSystem.TelepathicPrefix, ChatSelectChannel.Telepathic} //Nyano - Summary: adds the telepathic prefix =. + {SharedChatSystem.TelepathicPrefix, ChatSelectChannel.Telepathic}, //Nyano - Summary: adds the telepathic prefix =. + {SharedChatSystem.EmpathyPrefix, ChatSelectChannel.Empathy}, // Parkstation-EmpathyChat }; public static readonly Dictionary ChannelPrefixes = new() @@ -95,7 +98,8 @@ public sealed class ChatUIController : UIController {ChatSelectChannel.Admin, SharedChatSystem.AdminPrefix}, {ChatSelectChannel.Radio, SharedChatSystem.RadioCommonPrefix}, {ChatSelectChannel.Dead, SharedChatSystem.DeadPrefix}, - {ChatSelectChannel.Telepathic, SharedChatSystem.TelepathicPrefix } //Nyano - Summary: associates telepathic with =. + {ChatSelectChannel.Telepathic, SharedChatSystem.TelepathicPrefix }, //Nyano - Summary: associates telepathic with =. + {ChatSelectChannel.Empathy, SharedChatSystem.EmpathyPrefix}, // Parkstation-EmpathyChat }; /// @@ -557,6 +561,7 @@ private void UpdateChannelPermissions() FilterableChannels |= ChatChannel.AdminChat; CanSendChannels |= ChatSelectChannel.Admin; FilterableChannels |= ChatChannel.Telepathic; //Nyano - Summary: makes admins able to see psionic chat. + FilterableChannels |= ChatChannel.Empathy; // Parkstation-EmpathyChat } // Nyano - Summary: - Begin modified code block to add telepathic as a channel for a psionic user. @@ -567,6 +572,14 @@ private void UpdateChannelPermissions() } // /Nyano - End modified code block + // Parkstation-EmpathyChat-Start + if (_shadowkin != null && _shadowkin.IsShadowkin) + { + FilterableChannels |= ChatChannel.Empathy; + CanSendChannels |= ChatSelectChannel.Empathy; + } + // Parkstation-EmpathyChat-End + SelectableChannels = CanSendChannels; // Necessary so that we always have a channel to fall back to. diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs index 1d2a431446..08a9d5a3e2 100644 --- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs +++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs @@ -17,6 +17,7 @@ public sealed partial class ChannelFilterPopup : Popup ChatChannel.Emotes, ChatChannel.Radio, ChatChannel.Telepathic, //Nyano - Summary: adds telepathic chat to where it belongs in order in the chat. + ChatChannel.Empathy, // Parkstation-EmpathyChat ChatChannel.Notifications, ChatChannel.LOOC, ChatChannel.OOC, diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorButton.cs b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorButton.cs index 041d8075e3..3d0d29ec6d 100644 --- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorButton.cs +++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorButton.cs @@ -64,7 +64,8 @@ public Color ChannelSelectColor(ChatSelectChannel channel) ChatSelectChannel.OOC => Color.LightSkyBlue, ChatSelectChannel.Dead => Color.MediumPurple, ChatSelectChannel.Admin => Color.HotPink, - ChatSelectChannel.Telepathic => Color.PaleVioletRed, //Nyano - Summary: determines the color for the chat. + ChatSelectChannel.Telepathic => Color.PaleVioletRed, //Nyano - Summary: determines the color for the chat. + ChatSelectChannel.Empathy => Color.PaleVioletRed, // Parkstation-EmpathyChat _ => Color.DarkGray }; } diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs index c1f3559d79..cb8d5e45ab 100644 --- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs +++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs @@ -13,7 +13,8 @@ public sealed class ChannelSelectorPopup : Popup ChatSelectChannel.Whisper, ChatSelectChannel.Emotes, ChatSelectChannel.Radio, - ChatSelectChannel.Telepathic, //Nyano - Summary: determines the order in which telepathic shows. + ChatSelectChannel.Telepathic, //Nyano - Summary: determines the order in which telepathic shows. + ChatSelectChannel.Empathy, // Parkstation-EmpathyChat ChatSelectChannel.LOOC, ChatSelectChannel.OOC, ChatSelectChannel.Dead, diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index b4641928e4..ce22e74c7c 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -6,6 +6,7 @@ using Content.Server.Chat.Managers; using Content.Server.GameTicking; using Content.Server.Language; +using Content.Server.Parkstation.Chat; using Content.Server.Speech.Components; using Content.Server.Speech.EntitySystems; using Content.Server.Chat; @@ -65,6 +66,7 @@ public sealed partial class ChatSystem : SharedChatSystem [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly StationSystem _stationSystem = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly ParkstationChatSystem _parkstationChatSystem = default!; // Parkstation-EmpathyChat [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly ReplacementAccentSystem _wordreplacement = default!; @@ -277,6 +279,10 @@ public void TrySendInGameICMessage( case InGameICChatType.Telepathic: _telepath.SendTelepathicChat(source, message, range == ChatTransmitRange.HideChat); break; + // Parkstation-EmpathyChat + case InGameICChatType.Empathy: + _parkstationChatSystem.SendEmpathyChat(source, message, range == ChatTransmitRange.HideChat); + break; } } diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 92e658591a..4eeba58093 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -46,6 +46,7 @@ using Content.Shared.Humanoid.Prototypes; using Robust.Shared.GameObjects.Components.Localization; //DeltaV End Metem Usings using Content.Server.EntityList; +using Content.Server.Parkstation.Cloning; using Content.Shared.SSDIndicator; using Content.Shared.Damage.ForceSay; using Content.Server.Polymorph.Components; @@ -249,6 +250,12 @@ public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity(uid); + // For other systems adding components to the mob + var bce = new BeenClonedEvent(pref, mind, mob, bodyToClone, clonePod.Owner); + RaiseLocalEvent(bce); + // TODO: Ideally, components like this should be components on the mind entity so this isn't necessary. // Add on special job components to the mob. if (_jobs.MindTryGetJob(mindEnt, out _, out var prototype)) diff --git a/Content.Server/Ghost/GhostSystem.cs b/Content.Server/Ghost/GhostSystem.cs index c0e753c4c5..d7b40efc59 100644 --- a/Content.Server/Ghost/GhostSystem.cs +++ b/Content.Server/Ghost/GhostSystem.cs @@ -149,6 +149,9 @@ private void OnGhostStartup(EntityUid uid, GhostComponent component, ComponentSt _visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility); } + if (TryComp(uid, out EyeComponent? eye)) + _eye.SetVisibilityMask(uid, eye.VisibilityMask | (int) VisibilityFlags.DarkSwapInvisibility, eye); + SetCanSeeGhosts(uid, true); var time = _gameTiming.CurTime; diff --git a/Content.Server/Magic/MagicSystem.cs b/Content.Server/Magic/MagicSystem.cs index 18602c3de8..82581d0e6e 100644 --- a/Content.Server/Magic/MagicSystem.cs +++ b/Content.Server/Magic/MagicSystem.cs @@ -398,12 +398,12 @@ private void SpawnSpellHelper(List entityEntries, EntityCoordi #endregion - private void Speak(BaseActionEvent args) + public void Speak(BaseActionEvent args, bool showInChat = true) // Parkstation-Shadowkin // Make Spell Speak function public and add showInChat { if (args is not ISpeakSpell speak || string.IsNullOrWhiteSpace(speak.Speech)) return; _chat.TrySendInGameICMessage(args.Performer, Loc.GetString(speak.Speech), - InGameICChatType.Speak, false); + InGameICChatType.Speak, !showInChat); // Parkstation-Shadowkin } } diff --git a/Content.Server/Parkstation/Chat/ESayCommand.cs b/Content.Server/Parkstation/Chat/ESayCommand.cs new file mode 100644 index 0000000000..ab5129129a --- /dev/null +++ b/Content.Server/Parkstation/Chat/ESayCommand.cs @@ -0,0 +1,43 @@ +using Content.Server.Chat.Systems; +using Content.Shared.Administration; +using Content.Shared.Chat; +using Robust.Shared.Console; +using Robust.Shared.Enums; +using Robust.Shared.Player; + +namespace Content.Server.Parkstation.Chat; + +[AnyCommand] +internal sealed class ESayCommand : IConsoleCommand +{ + public string Command => "esay"; + public string Description => "Send chat messages to Shadowkin."; + public string Help => $"{Command} "; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (shell.Player is not ICommonSession player) + { + shell.WriteError("This command cannot be run from the server."); + return; + } + + if (player.Status != SessionStatus.InGame) + return; + + if (player.AttachedEntity is not {} playerEntity) + { + shell.WriteError("You don't have an entity!"); + return; + } + + if (args.Length < 1) + return; + + var message = string.Join(" ", args).Trim(); + if (string.IsNullOrEmpty(message)) + return; + + EntitySystem.Get().TrySendInGameICMessage(playerEntity, message, InGameICChatType.Empathy, false, false, shell, player, checkRadioPrefix: false); + } +} diff --git a/Content.Server/Parkstation/Chat/ParkstationChatSystem.cs b/Content.Server/Parkstation/Chat/ParkstationChatSystem.cs new file mode 100644 index 0000000000..9872d9b69e --- /dev/null +++ b/Content.Server/Parkstation/Chat/ParkstationChatSystem.cs @@ -0,0 +1,61 @@ +using System.Linq; +using Content.Server.Administration.Logs; +using Content.Server.Administration.Managers; +using Content.Server.Chat.Managers; +using Content.Server.Chat.Systems; +using Content.Server.Parkstation.Speech.EntitySystems; +using Content.Shared.Chat; +using Content.Shared.Database; +using Content.Shared.Parkstation.Species.Shadowkin.Components; +using Robust.Shared.Network; +using Robust.Shared.Player; + +namespace Content.Server.Parkstation.Chat +{ + /// + /// Extensions for Parkstation's chat stuff + /// + public sealed class ParkstationChatSystem : EntitySystem + { + [Dependency] private readonly IAdminManager _adminManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; + + private IEnumerable GetShadowkinChatClients() + { + return Filter.Empty() + .AddWhereAttachedEntity(entity => HasComp(entity)) + .Recipients + .Select(p => p.ConnectedClient); + } + + private IEnumerable GetAdminClients() + { + return _adminManager.ActiveAdmins + .Select(p => p.ConnectedClient); + } + + public void SendEmpathyChat(EntityUid source, string message, bool hideChat) + { + if (!HasComp(source)) return; + + var clients = GetShadowkinChatClients(); + var admins = GetAdminClients(); + var localMessage = EntitySystem.Get().Accentuate(message); + var messageWrap = Loc.GetString("chat-manager-send-empathy-chat-wrap-message", + ("empathyChannelName", Loc.GetString("chat-manager-empathy-channel-name")), + ("message", message)); + var adminMessageWrap = Loc.GetString("chat-manager-send-empathy-chat-wrap-message-admin", + ("empathyChannelName", Loc.GetString("chat-manager-empathy-channel-name")), + ("source", source), + ("message", message)); + + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Empathy chat from {ToPrettyString(source):Player}: {message}"); + + _chatSystem.TrySendInGameICMessage(source, localMessage, InGameICChatType.Speak, hideChat); + _chatManager.ChatMessageToMany(ChatChannel.Empathy, message, messageWrap, source, hideChat, true, clients.ToList(), Color.PaleVioletRed); + _chatManager.ChatMessageToMany(ChatChannel.Empathy, message, adminMessageWrap, source, hideChat, true, admins, Color.PaleVioletRed); + } + } +} diff --git a/Content.Server/Parkstation/Cloning/CloningEvents.cs b/Content.Server/Parkstation/Cloning/CloningEvents.cs new file mode 100644 index 0000000000..f1ab620f3d --- /dev/null +++ b/Content.Server/Parkstation/Cloning/CloningEvents.cs @@ -0,0 +1,21 @@ +using Content.Shared.Mind; +using Content.Shared.Preferences; + +namespace Content.Server.Parkstation.Cloning; + +[ByRefEvent] +public sealed class BeingClonedEvent(HumanoidCharacterProfile profile, MindComponent mind, EntityUid cloner) : CancellableEntityEventArgs +{ + public HumanoidCharacterProfile Profile = profile; + public MindComponent Mind = mind; + public EntityUid Cloner = cloner; +} + +public sealed class BeenClonedEvent(HumanoidCharacterProfile profile, MindComponent mind, EntityUid mob, EntityUid OriginalMob, EntityUid cloner) : EntityEventArgs +{ + public HumanoidCharacterProfile Profile = profile; + public MindComponent Mind = mind; + public EntityUid Mob = mob; + public EntityUid OriginalMob = OriginalMob; + public EntityUid Cloner = cloner; +} diff --git a/Content.Server/Parkstation/Species/Shadowkin/Components/ShadowkinBlackeyeTraitComponent.cs b/Content.Server/Parkstation/Species/Shadowkin/Components/ShadowkinBlackeyeTraitComponent.cs new file mode 100644 index 0000000000..fe4c81a0e4 --- /dev/null +++ b/Content.Server/Parkstation/Species/Shadowkin/Components/ShadowkinBlackeyeTraitComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server.Parkstation.Species.Shadowkin.Components; + +[RegisterComponent] +public sealed partial class ShadowkinBlackeyeTraitComponent : Component +{ + +} diff --git a/Content.Server/Parkstation/Species/Shadowkin/Components/ShadowkinDarkSwapPowerComponent.cs b/Content.Server/Parkstation/Species/Shadowkin/Components/ShadowkinDarkSwapPowerComponent.cs new file mode 100644 index 0000000000..500a487c20 --- /dev/null +++ b/Content.Server/Parkstation/Species/Shadowkin/Components/ShadowkinDarkSwapPowerComponent.cs @@ -0,0 +1,54 @@ +using Content.Server.NPC.Components; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Server.Parkstation.Species.Shadowkin.Components; + +[RegisterComponent] +public sealed partial class ShadowkinDarkSwapPowerComponent : Component +{ + /// + /// Factions temporarily removed from the entity while swapped + /// + public List SuppressedFactions = new(); + + /// + /// Factions temporarily added to the entity while swapped + /// + [DataField("factions", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List AddedFactions = new() { "ShadowkinDarkFriendly" }; + + [DataField("shadowkinDarkSwapActionEntity")] + public EntityUid? ShadowkinDarkSwapActionEntity; + + + /// + /// If the entity should be sent to the dark + /// + [DataField("invisible")] + public bool Invisible = true; + + /// + /// If it should be pacified + /// + [DataField("pacify")] + public bool Pacify = true; + + /// + /// If the entity should dim nearby lights when swapped + /// + [DataField("darken"), ViewVariables(VVAccess.ReadWrite)] + public bool Darken = true; + + /// + /// How far to dim nearby lights + /// + [DataField("range"), ViewVariables(VVAccess.ReadWrite)] + public float DarkenRange = 5f; + + /// + /// How fast to refresh nearby light dimming in seconds + /// Without this performance would be significantly worse + /// + [ViewVariables(VVAccess.ReadWrite)] + public float DarkenRate = 0.084f; // 1/12th of a second +} diff --git a/Content.Server/Parkstation/Species/Shadowkin/Components/ShadowkinRestPowerComponent.cs b/Content.Server/Parkstation/Species/Shadowkin/Components/ShadowkinRestPowerComponent.cs new file mode 100644 index 0000000000..2cb321468a --- /dev/null +++ b/Content.Server/Parkstation/Species/Shadowkin/Components/ShadowkinRestPowerComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Server.Parkstation.Species.Shadowkin.Components; + +[RegisterComponent] +public sealed partial class ShadowkinRestPowerComponent : Component +{ + [ViewVariables(VVAccess.ReadOnly)] + public bool IsResting = false; + + [DataField("shadowkinRestActionEntity")] + public EntityUid? ShadowkinRestActionEntity; +} diff --git a/Content.Server/Parkstation/Species/Shadowkin/Components/ShadowkinSightComponent.cs b/Content.Server/Parkstation/Species/Shadowkin/Components/ShadowkinSightComponent.cs new file mode 100644 index 0000000000..b9b097a149 --- /dev/null +++ b/Content.Server/Parkstation/Species/Shadowkin/Components/ShadowkinSightComponent.cs @@ -0,0 +1,4 @@ +namespace Content.Server.Parkstation.Species.Shadowkin.Components; + +[RegisterComponent] +public sealed partial class ShadowkinSightComponent : Component; diff --git a/Content.Server/Parkstation/Species/Shadowkin/Components/ShadowkinTeleportPowerComponent.cs b/Content.Server/Parkstation/Species/Shadowkin/Components/ShadowkinTeleportPowerComponent.cs new file mode 100644 index 0000000000..274e7eae80 --- /dev/null +++ b/Content.Server/Parkstation/Species/Shadowkin/Components/ShadowkinTeleportPowerComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Server.Parkstation.Species.Shadowkin.Components; + +[RegisterComponent] +public sealed partial class ShadowkinTeleportPowerComponent : Component +{ + [DataField("shadowkinTeleportActionEntity")] + public EntityUid? ShadowkinTeleportActionEntity; +} diff --git a/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.DarkSwap.cs b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.DarkSwap.cs new file mode 100644 index 0000000000..610e25a37f --- /dev/null +++ b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.DarkSwap.cs @@ -0,0 +1,309 @@ +using System.Linq; +using Content.Server.Magic; +using Content.Server.NPC.Components; +using Content.Server.NPC.Systems; +using Content.Server.Parkstation.Species.Shadowkin.Components; +using Content.Shared.Actions; +using Content.Shared.CombatMode.Pacification; +using Content.Shared.Cuffs.Components; +using Content.Shared.Damage.Systems; +using Content.Shared.Eye; +using Content.Shared.Ghost; +using Content.Shared.Parkstation.Species.Shadowkin.Events; +using Content.Shared.Parkstation.Species.Shadowkin.Components; +using Content.Shared.Stealth; +using Content.Shared.Stealth.Components; +using Robust.Server.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Prototypes; + +namespace Content.Server.Parkstation.Species.Shadowkin.Systems; + +public sealed class ShadowkinDarkSwapSystem : EntitySystem +{ + [Dependency] private readonly ShadowkinPowerSystem _power = default!; + [Dependency] private readonly VisibilitySystem _visibility = default!; + [Dependency] private readonly IEntityManager _entity = default!; + [Dependency] private readonly SharedEyeSystem _eye = default!; + [Dependency] private readonly ShadowkinDarkenSystem _darken = default!; + [Dependency] private readonly StaminaSystem _stamina = default!; + [Dependency] private readonly SharedStealthSystem _stealth = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly MagicSystem _magic = default!; + [Dependency] private readonly NpcFactionSystem _factions = default!; + + [ValidatePrototypeId] + private const string ShadowkinDarkSwapActionId = "ShadowkinDarkSwapAction"; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(Startup); + SubscribeLocalEvent(Shutdown); + + SubscribeLocalEvent(DarkSwap); + + SubscribeLocalEvent(OnInvisStartup); + SubscribeLocalEvent(OnInvisShutdown); + } + + + private void Startup(EntityUid uid, ShadowkinDarkSwapPowerComponent component, ComponentStartup args) + { + _actions.AddAction(uid, ref component.ShadowkinDarkSwapActionEntity, ShadowkinDarkSwapActionId, uid); + } + + private void Shutdown(EntityUid uid, ShadowkinDarkSwapPowerComponent component, ComponentShutdown args) + { + _actions.RemoveAction(uid, component.ShadowkinDarkSwapActionEntity); + } + + private void OnInvisStartup(EntityUid uid, ShadowkinDarkSwappedComponent component, ComponentStartup args) + { + if (component.Pacify) + { + var pax = EnsureComp(uid); + pax.DisallowAllCombat = true; + pax.DisallowDisarm = true; + } + + if (component.Invisible) + { + SetVisibility(uid, true, true, true); + SuppressFactions(uid, true); + } + } + + private void OnInvisShutdown(EntityUid uid, ShadowkinDarkSwappedComponent component, ComponentShutdown args) + { + RemComp(uid); + + if (component.Invisible) + { + SetVisibility(uid, false, true, true); + SuppressFactions(uid, false); + } + + // Prevent more updates while cleaning up + component.Darken = false; + + // In case more updates occur for some reason. create a copy of the list to prevent error + foreach (var light in component.DarkenedLights.ToList()) + { + if (!_entity.TryGetComponent(light, out var pointLight) || + !_entity.TryGetComponent(light, out var shadowkinLight)) + continue; + + _darken.ResetLight(pointLight, shadowkinLight); + } + + // Clear the original list + component.DarkenedLights.Clear(); + } + + + private void DarkSwap(EntityUid uid, ShadowkinDarkSwapPowerComponent component, ShadowkinDarkSwapEvent args) + { + var performer = _entity.GetNetEntity(args.Performer); + + // Need power to drain power + if (!_entity.HasComponent(args.Performer) + || _entity.TryGetComponent(args.Performer, out var cuff) + && cuff.AntiShadowkin) + return; + + // Don't activate abilities if handcuffed + // TODO: Something like the Psionic Headcage to disable powers for Shadowkin + if (_entity.HasComponent(args.Performer)) + return; + + SetDarkened(performer, !_entity.HasComponent(args.Performer), args.SoundOn, + args.VolumeOn, args.SoundOff, args.VolumeOff, args, args.StaminaCostOn, args.PowerCostOn, + args.StaminaCostOff, args.PowerCostOff); + + _magic.Speak(args, false); + } + + + /// + /// Handles the effects of darkswapping + /// + /// The entity being modified + /// Is the entity swapping in to or out of The Dark? + /// Sound for the darkswapping + /// Volume for the on sound + /// Sound for the un swapping + /// Volume for the off sound + /// Stamina cost for darkswapping + /// Power cost for darkswapping + /// Stamina cost for un swapping + /// Power cost for un swapping + /// If from an event, handle it + public void SetDarkened( + NetEntity performer, + bool addComp, + SoundSpecifier? soundOn, + float? volumeOn, + SoundSpecifier? soundOff, + float? volumeOff, + ShadowkinDarkSwapEvent? args, + float staminaCostOn = 0, + float powerCostOn = 0, + float staminaCostOff = 0, + float powerCostOff = 0) + { + var ent = _entity.GetEntity(performer); + + // We require the power component to DarkSwap + if (!_entity.TryGetComponent(ent, out var power)) + return; + + // Ask other systems if we can DarkSwap + var ev = new ShadowkinDarkSwapAttemptEvent(ent); + RaiseLocalEvent(ev); + if (ev.Cancelled) + return; + + if (addComp) // Into The Dark + { + // Add the DarkSwapped component and set variables to match the power component + var comp = _entity.EnsureComponent(ent); + comp.Invisible = power.Invisible; + comp.Pacify = power.Pacify; + comp.Darken = power.Darken; + comp.DarkenRange = power.DarkenRange; + comp.DarkenRate = power.DarkenRate; + + // Tell other systems we've DarkSwapped + RaiseNetworkEvent(new ShadowkinDarkSwappedEvent(performer, true)); + + // Play a sound if there is one + if (soundOn != null) + _audio.PlayPvs(soundOn, ent, AudioParams.Default.WithVolume(volumeOn ?? 5f)); + + // Drain power and stamina if we have a cost + _power.TryAddPowerLevel(ent, -powerCostOn); + _stamina.TakeStaminaDamage(ent, staminaCostOn); + } + else // Out on The Dark + { + // Remove the DarkSwapped component, the rest is handled in the shutdown event + _entity.RemoveComponent(ent); + + // Tell other systems we've un DarkSwapped + RaiseNetworkEvent(new ShadowkinDarkSwappedEvent(performer, false)); + + // Play a sound if there is one + if (soundOff != null) + _audio.PlayPvs(soundOff, ent, AudioParams.Default.WithVolume(volumeOff ?? 5f)); + + // Drain power and stamina if we have a cost + _power.TryAddPowerLevel(ent, -powerCostOff); + _stamina.TakeStaminaDamage(ent, staminaCostOff); + } + + // If we have an event, handle it + if (args != null) + args.Handled = true; + } + + public void SetVisibility(EntityUid uid, bool set, bool invisibility, bool stealth) + { + // We require the visibility component for this to work + EnsureComp(uid); + + if (set) // Invisible + { + // Allow the entity to see DarkSwapped entities + if (_entity.TryGetComponent(uid, out EyeComponent? eye)) + _eye.SetVisibilityMask(uid, eye.VisibilityMask | (int) VisibilityFlags.DarkSwapInvisibility, eye); + + if (invisibility) + { + // Make other entities unable to see the entity unless also DarkSwapped + _visibility.AddLayer(uid, (ushort) VisibilityFlags.DarkSwapInvisibility, false); + _visibility.RemoveLayer(uid, (ushort) VisibilityFlags.Normal, false); + } + _visibility.RefreshVisibility(uid); + + // Add a stealth shader to the entity + if (!_entity.HasComponent(uid) && stealth) + { + _stealth.SetVisibility(uid, 0.8f, _entity.EnsureComponent(uid)); + _stealth.SetEnabled(uid, true); + } + } + else // Visible + { + // Remove the ability to see DarkSwapped entities + if (_entity.TryGetComponent(uid, out EyeComponent? eye)) + _eye.SetVisibilityMask(uid, eye.VisibilityMask & ~(int) VisibilityFlags.DarkSwapInvisibility, eye); + + if (invisibility) + { + // Make other entities able to see the entity again + _visibility.RemoveLayer(uid, (ushort) VisibilityFlags.DarkSwapInvisibility, false); + _visibility.AddLayer(uid, (ushort) VisibilityFlags.Normal, false); + } + _visibility.RefreshVisibility(uid); + + // Remove the stealth shader from the entity + if (!_entity.HasComponent(uid)) + _stealth.SetEnabled(uid, false); + } + } + + /// + /// Remove existing factions on the entity and move them to the power component to add back when removed from The Dark + /// + /// Entity to modify factions for + /// Add or remove the factions + public void SuppressFactions(EntityUid uid, bool set) + { + // We require the power component to keep track of the factions + if (!_entity.TryGetComponent(uid, out var component)) + return; + + if (set) + { + if (!_entity.TryGetComponent(uid, out var factions)) + return; + + // Copy the suppressed factions to the power component + component.SuppressedFactions = factions.Factions.ToList(); + + // Remove the factions from the entity + foreach (var faction in factions.Factions) + _factions.RemoveFaction(uid, faction); + + // Add status factions for The Dark to the entity + foreach (var faction in component.AddedFactions) + _factions.AddFaction(uid, faction); + } + else + { + // Remove the status factions from the entity + foreach (var faction in component.AddedFactions) + _factions.RemoveFaction(uid, faction); + + // Add the factions back to the entity + foreach (var faction in component.SuppressedFactions) + _factions.AddFaction(uid, faction); + + component.SuppressedFactions.Clear(); + } + } + + public void ForceDarkSwap(EntityUid uid, ShadowkinComponent component) + { + // Add/Remove the component, which should handle the rest + if (_entity.HasComponent(uid)) + _entity.RemoveComponent(uid); + else + _entity.AddComponent(uid); + } +} diff --git a/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.Darken.cs b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.Darken.cs new file mode 100644 index 0000000000..6405d883ef --- /dev/null +++ b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.Darken.cs @@ -0,0 +1,145 @@ +using System.Linq; +using Content.Server.Light.Components; +using Content.Server.Parkstation.Species.Shadowkin.Components; +using Content.Shared.Parkstation.Species.Shadowkin.Components; +using Robust.Server.GameObjects; +using Robust.Shared.Random; + +namespace Content.Server.Parkstation.Species.Shadowkin.Systems; + +public sealed class ShadowkinDarkenSystem : EntitySystem +{ + [Dependency] private readonly IEntityManager _entity = default!; + [Dependency] private readonly SharedPointLightSystem _light = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + + + public void ResetLight(PointLightComponent light, ShadowkinLightComponent sLight) + { + sLight.AttachedEntity = EntityUid.Invalid; + + if (sLight.OldRadiusEdited) + _light.SetRadius(light.Owner, sLight.OldRadius); + sLight.OldRadiusEdited = false; + + if (sLight.OldEnergyEdited) + _light.SetEnergy(light.Owner, sLight.OldEnergy); + sLight.OldEnergyEdited = false; + } + + + public override void Update(float frameTime) + { + base.Update(frameTime); + + // TODO Use a cached query, and update it on ShadowkinDarkSwappedComponent.Initialize + var shadowkins = _entity.EntityQueryEnumerator(); + + while (shadowkins.MoveNext(out var uid, out var shadowkin)) + { + // Don't do anything if this Shadowkin isn't darkening + if (!shadowkin.Darken) + continue; + + // Cooldown + shadowkin.DarkenAccumulator -= frameTime; + if (shadowkin.DarkenAccumulator > 0f) + continue; + shadowkin.DarkenAccumulator += shadowkin.DarkenRate; + + var transform = Transform(uid); + + var darkened = new List(); + // Get all lights in range + var lightQuery = _lookup.GetEntitiesInRange(transform.MapID, transform.WorldPosition, shadowkin.DarkenRange, flags: LookupFlags.StaticSundries) + .Where(x => _entity.HasComponent(x) && _entity.HasComponent(x)); + + // Add all lights in range to the list if not already there + foreach (var entity in lightQuery) + { + if (!darkened.Contains(entity)) + darkened.Add(entity); + } + + // Randomize the list to avoid bias + _random.Shuffle(darkened); + shadowkin.DarkenedLights = darkened; + + var playerPos = Transform(uid).WorldPosition; + + foreach (var light in shadowkin.DarkenedLights.ToArray()) + { + var lightPos = Transform(light).WorldPosition; + var pointLight = _entity.GetComponent(light); + + + // Not a light we should affect + if (!_entity.TryGetComponent(light, out ShadowkinLightComponent? shadowkinLight)) + continue; + + // Not powered, undo changes + if (_entity.TryGetComponent(light, out PoweredLightComponent? powered) && !powered.On) + { + ResetLight(pointLight, shadowkinLight); + continue; + } + + + // If the light isn't attached to an entity, attach it to this Shadowkin + if (shadowkinLight.AttachedEntity == EntityUid.Invalid) + { + shadowkinLight.AttachedEntity = uid; + } + // Check if the light is being updated by the correct Shadowkin + // Prevents horrible flickering when the light is in range of multiple Shadowkin + if (shadowkinLight.AttachedEntity != EntityUid.Invalid && + shadowkinLight.AttachedEntity != uid) + { + shadowkin.DarkenedLights.Remove(light); + continue; + } + + // 3% chance to remove the attached entity so it can become another Shadowkin's light + if (shadowkinLight.AttachedEntity == uid) + { + if (_random.Prob(0.03f)) + shadowkinLight.AttachedEntity = EntityUid.Invalid; + } + + + // If we haven't edited the radius yet, save the old radius + if (!shadowkinLight.OldRadiusEdited) + { + shadowkinLight.OldRadius = pointLight.Radius; + shadowkinLight.OldRadiusEdited = true; + } + if (!shadowkinLight.OldEnergyEdited) + { + shadowkinLight.OldEnergy = pointLight.Energy; + shadowkinLight.OldEnergyEdited = true; + } + + var distance = (lightPos - playerPos).Length(); + var radius = distance * 2f; + var energy = distance * 0.8f; + + // Set new radius based on distance + if (shadowkinLight.OldRadiusEdited && radius > shadowkinLight.OldRadius) + radius = shadowkinLight.OldRadius; + if (shadowkinLight.OldRadiusEdited && radius < shadowkinLight.OldRadius * 0.20f) + radius = shadowkinLight.OldRadius * 0.20f; + + // Set new energy based on distance + if (shadowkinLight.OldEnergyEdited && energy > shadowkinLight.OldEnergy) + energy = shadowkinLight.OldEnergy; + if (shadowkinLight.OldEnergyEdited && energy < shadowkinLight.OldEnergy * 0.20f) + energy = shadowkinLight.OldEnergy * 0.20f; + + // Put changes into effect + _light.SetRadius(pointLight.Owner, radius); + _light.SetEnergy(pointLight.Owner, energy); + } + } + } +} diff --git a/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.Rest.cs b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.Rest.cs new file mode 100644 index 0000000000..a574ac50a3 --- /dev/null +++ b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.Rest.cs @@ -0,0 +1,81 @@ +using Content.Server.Parkstation.Species.Shadowkin.Components; +using Content.Shared.Actions; +using Content.Shared.Bed.Sleep; +using Content.Shared.Parkstation.Species.Shadowkin.Events; +using Content.Shared.Parkstation.Species.Shadowkin.Components; +using Robust.Shared.Prototypes; + +namespace Content.Server.Parkstation.Species.Shadowkin.Systems; + +public sealed class ShadowkinRestSystem : EntitySystem +{ + [Dependency] private readonly IEntityManager _entity = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly ShadowkinPowerSystem _power = default!; + + [ValidatePrototypeId] + private const string ShadowkinRestActionId = "ShadowkinRestAction"; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnShutdown); + + SubscribeLocalEvent(Rest); + } + + + private void OnStartup(EntityUid uid, ShadowkinRestPowerComponent component, ComponentStartup args) + { + _actions.AddAction(uid, ref component.ShadowkinRestActionEntity, ShadowkinRestActionId); + } + + private void OnShutdown(EntityUid uid, ShadowkinRestPowerComponent component, ComponentShutdown args) + { + _actions.RemoveAction(uid, component.ShadowkinRestActionEntity); + } + + private void Rest(EntityUid uid, ShadowkinRestPowerComponent component, ShadowkinRestEvent args) + { + // Need power to modify power + if (!_entity.HasComponent(args.Performer)) + return; + + // Rest is a funny ability, keep it :) + // // Don't activate abilities if handcuffed + // if (_entity.HasComponent(args.Performer)) + // return; + + // Now doing what you weren't before + component.IsResting = !component.IsResting; + + // Resting + if (component.IsResting) + { + // Sleepy time + _entity.EnsureComponent(args.Performer); + // No waking up normally (it would do nothing) + if (_entity.TryGetComponent(uid, out var sleepingComponent)) + _actions.RemoveAction(uid, sleepingComponent.WakeAction); + + _power.TryAddMultiplier(uid, 2f); + + // No action cooldown + _actions.ClearCooldown(sleepingComponent?.WakeAction); + } + // Waking + else + { + // Wake up + _entity.RemoveComponent(uid); + _entity.RemoveComponent(uid); + _power.TryAddMultiplier(uid, -2f); + + // Action cooldown + args.Handled = true; + } + } +} diff --git a/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.Teleport.cs b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.Teleport.cs new file mode 100644 index 0000000000..3d81c0254a --- /dev/null +++ b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.Teleport.cs @@ -0,0 +1,151 @@ +using System.Numerics; +using Content.Server.Magic; +using Content.Server.Parkstation.Species.Shadowkin.Components; +using Content.Shared.Actions; +using Content.Shared.Cuffs.Components; +using Content.Shared.Damage.Systems; +using Content.Shared.Interaction; +using Content.Shared.Movement.Pulling.Components; +using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Parkstation.Species.Shadowkin.Events; +using Content.Shared.Parkstation.Species.Shadowkin.Components; +using Content.Shared.Physics; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.Parkstation.Species.Shadowkin.Systems; + +public sealed class ShadowkinTeleportSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly ShadowkinPowerSystem _power = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly IEntityManager _entity = default!; + [Dependency] private readonly StaminaSystem _stamina = default!; + [Dependency] private readonly PullingSystem _pulling = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly MagicSystem _magic = default!; + [Dependency] private readonly SharedInteractionSystem _interaction = default!; + + [ValidatePrototypeId] + private const string ShadowkinTeleportActionId = "ShadowkinTeleportAction"; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(Startup); + SubscribeLocalEvent(Shutdown); + + SubscribeLocalEvent(Teleport); + } + + + private void Startup(EntityUid uid, ShadowkinTeleportPowerComponent component, ComponentStartup args) + { + _actions.AddAction(uid, ref component.ShadowkinTeleportActionEntity, ShadowkinTeleportActionId); + } + + private void Shutdown(EntityUid uid, ShadowkinTeleportPowerComponent component, ComponentShutdown args) + { + _actions.RemoveAction(uid, component.ShadowkinTeleportActionEntity); + } + + + private void Teleport(EntityUid uid, ShadowkinTeleportPowerComponent component, ShadowkinTeleportEvent args) + { + // Need power to drain power + if (!_entity.TryGetComponent(args.Performer, out var comp) + // Don't activate abilities if handcuffed + || _entity.TryGetComponent(args.Performer, out var cuff) + && cuff.AntiShadowkin) + return; + + + var transform = Transform(args.Performer); + // Must be on the same map + if (transform.MapID != args.Target.GetMapId(EntityManager)) + return; + + PullableComponent? pullable = null; // To avoid "might not be initialized when accessed" warning + if (_entity.TryGetComponent(args.Performer, out var puller) && + puller.Pulling != null && + _entity.TryGetComponent(puller.Pulling, out pullable) && + pullable.BeingPulled) + { + // Temporarily stop pulling to avoid not teleporting to the target + _pulling.TryStopPull(puller.Pulling.Value, pullable); + } + + // Teleport the performer to the target + _transform.SetCoordinates(args.Performer, args.Target); + _transform.AttachToGridOrMap(args.Performer); + + if (pullable != null && puller != null) + { + // Get transform of the pulled entity + var pulledTransform = Transform(pullable.Owner); + + // Teleport the pulled entity to the target + // TODO: Relative position to the performer + _transform.SetCoordinates(pullable.Owner, args.Target); + _transform.AttachToGridOrMap(pullable.Owner); + + // Resume pulling + _pulling.TryStartPull(args.Performer, pullable.Owner); + } + + + // Play the teleport sound + _audio.PlayPvs(args.Sound, args.Performer, AudioParams.Default.WithVolume(args.Volume)); + + // Take power and deal stamina damage + _power.TryAddPowerLevel(comp.Owner, -args.PowerCost); + _stamina.TakeStaminaDamage(args.Performer, args.StaminaCost); + + // Speak + _magic.Speak(args, false); + + args.Handled = true; + } + + + public void ForceTeleport(EntityUid uid, ShadowkinComponent component) + { + // Create the event we'll later raise, and set it to our Shadowkin. + var args = new ShadowkinTeleportEvent { Performer = uid }; + + // Pick a random location on the map until we find one that can be reached. + var coords = Transform(uid).Coordinates; + EntityCoordinates? target = null; + + // It'll iterate up to 8 times, shrinking in distance each time, and if it doesn't find a valid location, it'll return. + for (var i = 8; i != 0; i--) + { + var angle = Angle.FromDegrees(_random.Next(360)); + var offset = new Vector2((float) (i * Math.Cos(angle)), (float) (i * Math.Sin(angle))); + + target = coords.Offset(offset); + + if (_interaction.InRangeUnobstructed(uid, target.Value, 0, + CollisionGroup.MobMask | CollisionGroup.MobLayer)) + break; + + target = null; + } + + // If we didn't find a valid location, return. + if (target == null) + return; + + args.Target = target.Value; + + // Raise the event to teleport the Shadowkin. + RaiseLocalEvent(uid, args); + } +} diff --git a/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.cs b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.cs new file mode 100644 index 0000000000..5a9fc9a58a --- /dev/null +++ b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.cs @@ -0,0 +1,259 @@ +using System.Threading.Tasks; +using Content.Shared.Alert; +using Content.Shared.Parkstation.Species.Shadowkin.Components; +using Content.Shared.Parkstation.Species.Shadowkin.Events; +using Robust.Shared.Utility; + +namespace Content.Server.Parkstation.Species.Shadowkin.Systems; + +public sealed class ShadowkinPowerSystem : EntitySystem +{ + [Dependency] private readonly IEntityManager _entity = default!; + [Dependency] private readonly AlertsSystem _alerts = default!; + [Dependency] private readonly ShadowkinBlackeyeSystem _blackeye = default!; + + private readonly Dictionary _powerDictionary; + + + public ShadowkinPowerSystem() + { + var Locale = IoCManager.Resolve(); // Whyyyy // Dependencies aren't resolved yet, must resolve manually to get locale for the _powerDictionary + + _powerDictionary = new Dictionary + { + { ShadowkinPowerThreshold.Max, Locale.GetString("shadowkin-power-max") }, + { ShadowkinPowerThreshold.Great, Locale.GetString("shadowkin-power-great") }, + { ShadowkinPowerThreshold.Good, Locale.GetString("shadowkin-power-good") }, + { ShadowkinPowerThreshold.Okay, Locale.GetString("shadowkin-power-okay") }, + { ShadowkinPowerThreshold.Tired, Locale.GetString("shadowkin-power-tired") }, + { ShadowkinPowerThreshold.Min, Locale.GetString("shadowkin-power-min") }, + }; + } + + + /// The current power level + /// The name of the power level + public string GetLevelName(float powerLevel) + { + // Placeholders + var result = ShadowkinPowerThreshold.Min; + var value = ShadowkinComponent.PowerThresholds[ShadowkinPowerThreshold.Max]; + + // Find the highest threshold that is lower than the current power level + foreach (var threshold in ShadowkinComponent.PowerThresholds) + { + if (threshold.Value <= value && + threshold.Value >= powerLevel) + { + result = threshold.Key; + value = threshold.Value; + } + } + + // Return the name of the threshold + _powerDictionary.TryGetValue(result, out var powerType); + powerType ??= Loc.GetString("shadowkin-power-okay"); + return powerType; + } + + /// + /// Sets the alert level of a shadowkin + /// + /// The entity uid + /// Enable the alert or not + /// The current power level + public void UpdateAlert(EntityUid uid, bool enabled, float? powerLevel = null) + { + if (!enabled || powerLevel == null) + { + _alerts.ClearAlert(uid, AlertType.ShadowkinPower); + return; + } + + // Get shadowkin component + if (!_entity.TryGetComponent(uid, out var component)) + { + DebugTools.Assert("Tried to update alert of entity without shadowkin component."); + return; + } + + // 250 / 7 ~= 35 + // Pwr / 35 ~= (0-7) + // Round to ensure (0-7) + var power = Math.Clamp(Math.Round(component.PowerLevel / 35), 0, 7); + + // Set the alert level + _alerts.ShowAlert(uid, AlertType.ShadowkinPower, (short) power); + } + + + /// + /// Tries to update the power level of a shadowkin based on an amount of seconds + /// + /// The entity uid + /// The time since the last update in seconds + public bool TryUpdatePowerLevel(EntityUid uid, float frameTime) + { + // Check if the entity has a shadowkin component + if (!_entity.TryGetComponent(uid, out var component)) + return false; + + // Check if power gain is enabled + if (!component.PowerLevelGainEnabled) + return false; + + // Set the new power level + UpdatePowerLevel(uid, frameTime); + + return true; + } + + /// + /// Updates the power level of a shadowkin based on an amount of seconds + /// + /// The entity uid + /// The time since the last update in seconds + public void UpdatePowerLevel(EntityUid uid, float frameTime) + { + // Get shadowkin component + if (!_entity.TryGetComponent(uid, out var component)) + { + DebugTools.Assert("Tried to update power level of entity without shadowkin component."); + return; + } + + // Calculate new power level (P = P + t * G * M) + var newPowerLevel = component.PowerLevel + frameTime * component.PowerLevelGain * component.PowerLevelGainMultiplier; + + newPowerLevel = Math.Clamp(newPowerLevel, component.PowerLevelMin, component.PowerLevelMax); + + // Set the new power level + SetPowerLevel(uid, newPowerLevel); + } + + + /// + /// Tries to add to the power level of a shadowkin + /// + /// The entity uid + /// The amount to add to the power level + public bool TryAddPowerLevel(EntityUid uid, float amount) + { + // Check if the entity has a shadowkin component + if (!_entity.TryGetComponent(uid, out _)) + return false; + + // Set the new power level + AddPowerLevel(uid, amount); + + return true; + } + + /// + /// Adds to the power level of a shadowkin + /// + /// The entity uid + /// The amount to add to the power level + public void AddPowerLevel(EntityUid uid, float amount) + { + // Get shadowkin component + if (!_entity.TryGetComponent(uid, out var component)) + { + DebugTools.Assert("Tried to add to power level of entity without shadowkin component."); + return; + } + + // Get new power level + var newPowerLevel = component.PowerLevel + amount; + + newPowerLevel = Math.Clamp(newPowerLevel, component.PowerLevelMin, component.PowerLevelMax); + + // Set the new power level + SetPowerLevel(uid, newPowerLevel); + } + + + /// + /// Sets the power level of a shadowkin + /// + /// The entity uid + /// The new power level + public void SetPowerLevel(EntityUid uid, float newPowerLevel) + { + // Get shadowkin component + if (!_entity.TryGetComponent(uid, out var component)) + { + DebugTools.Assert("Tried to set power level of entity without shadowkin component."); + return; + } + + newPowerLevel = Math.Clamp(newPowerLevel, component.PowerLevelMin, component.PowerLevelMax); + + // Set the new power level + component._powerLevel = newPowerLevel; + } + + + /// + /// Tries to blackeye a shadowkin + /// + public bool TryBlackeye(EntityUid uid, bool damage = true, bool checkPower = true) + { + return _blackeye.TryBlackeye(uid, damage, checkPower); + } + + /// + /// Blackeyes a shadowkin + /// + public void Blackeye(EntityUid uid, bool damage = true) + { + _blackeye.Blackeye(uid, damage); + } + + + /// + /// Tries to add a power multiplier + /// + /// The entity uid + /// The multiplier to add + /// The time in seconds to wait before removing the multiplier + public bool TryAddMultiplier(EntityUid uid, float multiplier, float? time = null) + { + if (!_entity.HasComponent(uid) || + float.IsNaN(multiplier)) + return false; + + AddMultiplier(uid, multiplier, time); + + return true; + } + + /// + /// Adds a power multiplier + /// + /// The entity uid + /// The multiplier to add + /// The time in seconds to wait before removing the multiplier + public void AddMultiplier(EntityUid uid, float multiplier, float? time = null) + { + // Get shadowkin component + if (!_entity.TryGetComponent(uid, out var component)) + { + DebugTools.Assert("Tried to add multiplier to entity without shadowkin component."); + return; + } + + // Add the multiplier + component.PowerLevelGainMultiplier += multiplier; + + // Remove the multiplier after a certain amount of time + if (time != null) + { + Task.Run(async () => + { + await Task.Delay((int) time * 1000); + component.PowerLevelGainMultiplier -= multiplier; + }); + } + } +} diff --git a/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinSightSystem.cs b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinSightSystem.cs new file mode 100644 index 0000000000..24ab53914b --- /dev/null +++ b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinSightSystem.cs @@ -0,0 +1,29 @@ +using Content.Server.Parkstation.Species.Shadowkin.Components; +using Content.Shared.Inventory.Events; + +namespace Content.Server.Parkstation.Species.Shadowkin.Systems; + +public sealed class ShadowkinSightSystem : EntitySystem +{ + [Dependency] private readonly ShadowkinDarkSwapSystem _darkSwap = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnEquipped); + SubscribeLocalEvent(OnUnEquipped); + } + + + private void OnEquipped(EntityUid uid, ShadowkinSightComponent component, GotEquippedEvent args) + { + _darkSwap.SetVisibility(args.Equipee, true, false, false); + } + + private void OnUnEquipped(EntityUid uid, ShadowkinSightComponent component, GotUnequippedEvent args) + { + _darkSwap.SetVisibility(args.Equipee, false, false, false); + } +} diff --git a/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinSystem.Blackeye.Trait.cs b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinSystem.Blackeye.Trait.cs new file mode 100644 index 0000000000..35849df2ba --- /dev/null +++ b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinSystem.Blackeye.Trait.cs @@ -0,0 +1,24 @@ +using Content.Server.Parkstation.Species.Shadowkin.Components; +using Content.Shared.Parkstation.Species.Shadowkin.Events; + +namespace Content.Server.Parkstation.Species.Shadowkin.Systems; + +public sealed class ShadowkinBlackeyeTraitSystem : EntitySystem +{ + [Dependency] private readonly IEntityManager _entity = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + } + + + private void OnStartup(EntityUid uid, ShadowkinBlackeyeTraitComponent _, ComponentStartup args) + { + var ent = _entity.GetNetEntity(uid); + RaiseLocalEvent(uid, new ShadowkinBlackeyeEvent(ent, false)); + RaiseNetworkEvent(new ShadowkinBlackeyeEvent(ent, false)); + } +} diff --git a/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinSystem.Blackeye.cs b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinSystem.Blackeye.cs new file mode 100644 index 0000000000..86f652e0e0 --- /dev/null +++ b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinSystem.Blackeye.cs @@ -0,0 +1,148 @@ +using Content.Server.Nyanotrasen.Cloning; +using Content.Server.Parkstation.Cloning; +using Content.Server.Parkstation.Species.Shadowkin.Components; +using Content.Shared.Damage; +using Content.Shared.Damage.Components; +using Content.Shared.Damage.Prototypes; +using Content.Shared.Damage.Systems; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Systems; +using Content.Shared.Parkstation.Species.Shadowkin.Components; +using Content.Shared.Parkstation.Species.Shadowkin.Events; +using Content.Shared.Popups; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Server.Parkstation.Species.Shadowkin.Systems; + +public sealed class ShadowkinBlackeyeSystem : EntitySystem +{ + [Dependency] private readonly ShadowkinPowerSystem _power = default!; + [Dependency] private readonly IEntityManager _entity = default!; + [Dependency] private readonly StaminaSystem _stamina = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnBlackeyeAttempt); + SubscribeAllEvent(OnBlackeye); + + SubscribeLocalEvent(OnCloned); + } + + + private void OnBlackeyeAttempt(ShadowkinBlackeyeAttemptEvent ev) + { + var uid = _entity.GetEntity(ev.Ent); + if (!_entity.TryGetComponent(uid, out var component) + || component.Blackeye + || ev.CheckPower + && component.PowerLevel > ShadowkinComponent.PowerThresholds[ShadowkinPowerThreshold.Min] + 5) + ev.Cancel(); + } + + private void OnBlackeye(ShadowkinBlackeyeEvent ev) + { + var uid = _entity.GetEntity(ev.Ent); + + // Check if the entity is a shadowkin + if (!_entity.TryGetComponent(uid, out var component)) + return; + + // Stop gaining power + component.Blackeye = true; + component.PowerLevelGainEnabled = false; + _power.SetPowerLevel(uid, ShadowkinComponent.PowerThresholds[ShadowkinPowerThreshold.Min]); + + // Update client state + Dirty(uid, component); + + // Remove powers + _entity.RemoveComponent(uid); + _entity.RemoveComponent(uid); + _entity.RemoveComponent(uid); + _entity.RemoveComponent(uid); + _entity.RemoveComponent(uid); + + + if (!ev.Damage) + return; + + // Popup + _popup.PopupEntity(Loc.GetString("shadowkin-blackeye"), uid, uid, PopupType.Large); + + // Stamina crit + if (_entity.TryGetComponent(uid, out var stamina)) + _stamina.TakeStaminaDamage(uid, stamina.CritThreshold, null, uid); + + // Nearly crit with cellular damage + // If already 5 damage off of crit, don't do anything + if (!_entity.TryGetComponent(uid, out var damageable) || + !_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var key)) + return; + + var minus = damageable.TotalDamage; + + _damageable.TryChangeDamage(uid, + new DamageSpecifier(_prototype.Index("Cellular"), + Math.Max((double) (key.Value - minus - 5), 0)), true); + } + + private void OnCloned(BeenClonedEvent ev) + { + // Don't give blackeyed Shadowkin their abilities back when they're cloned. + if (_entity.TryGetComponent(ev.OriginalMob, out var shadowkin) && + shadowkin.Blackeye) + _power.TryBlackeye(ev.Mob, false, false); + + // Blackeye the Shadowkin that come from the metempsychosis machine + if (_entity.HasComponent(ev.Cloner) && + _entity.HasComponent(ev.Mob)) + _power.TryBlackeye(ev.Mob, false, false); + } + + + /// + /// Tries to blackeye a shadowkin + /// + public bool TryBlackeye(EntityUid uid, bool damage = true, bool checkPower = true) + { + if (!_entity.HasComponent(uid)) + return false; + + var ent = _entity.GetNetEntity(uid); + // Raise an attempted blackeye event + var ev = new ShadowkinBlackeyeAttemptEvent(ent, checkPower); + RaiseLocalEvent(ev); + if (ev.Cancelled) + return false; + + Blackeye(uid, damage); + return true; + } + + /// + /// Blackeyes a shadowkin + /// + public void Blackeye(EntityUid uid, bool damage = true) + { + var ent = _entity.GetNetEntity(uid); + + // Get shadowkin component + if (!_entity.TryGetComponent(uid, out var component)) + { + DebugTools.Assert("Tried to blackeye entity without shadowkin component."); + return; + } + + component.Blackeye = true; + RaiseNetworkEvent(new ShadowkinBlackeyeEvent(ent, damage)); + RaiseLocalEvent(new ShadowkinBlackeyeEvent(ent, damage)); + } +} diff --git a/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinSystem.cs b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinSystem.cs new file mode 100644 index 0000000000..882920c21a --- /dev/null +++ b/Content.Server/Parkstation/Species/Shadowkin/Systems/ShadowkinSystem.cs @@ -0,0 +1,191 @@ +using Content.Server.Mind; +using Content.Shared.Bed.Sleep; +using Content.Shared.Cuffs.Components; +using Content.Shared.Examine; +using Content.Shared.IdentityManagement; +using Content.Shared.Mobs.Systems; +using Content.Shared.Parkstation.Species.Shadowkin.Components; +using Content.Shared.Parkstation.Species.Shadowkin.Events; +using Robust.Shared.Random; + +namespace Content.Server.Parkstation.Species.Shadowkin.Systems; + +public sealed class ShadowkinSystem : EntitySystem +{ + [Dependency] private readonly ShadowkinPowerSystem _power = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IEntityManager _entity = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + + [Dependency] private readonly ShadowkinDarkSwapSystem _darkSwap = default!; + [Dependency] private readonly ShadowkinTeleportSystem _teleport = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + } + + + private void OnExamine(EntityUid uid, ShadowkinComponent component, ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + var powerType = _power.GetLevelName(component.PowerLevel); + + // Show exact values for yourself + if (args.Examined == args.Examiner) + { + args.PushMarkup(Loc.GetString("shadowkin-power-examined-self", + ("power", (int) component.PowerLevel), + ("powerMax", component.PowerLevelMax), + ("powerType", powerType) + )); + } + // Show general values for others + else + { + args.PushMarkup(Loc.GetString("shadowkin-power-examined-other", + ("target", Identity.Entity(uid, _entity)), + ("powerType", powerType) + )); + } + } + + private void OnInit(EntityUid uid, ShadowkinComponent component, ComponentInit args) + { + if (component.PowerLevel <= ShadowkinComponent.PowerThresholds[ShadowkinPowerThreshold.Min] + 1f) + _power.SetPowerLevel(uid, ShadowkinComponent.PowerThresholds[ShadowkinPowerThreshold.Good]); + + var max = _random.NextFloat(component.MaxedPowerRateMin, component.MaxedPowerRateMax); + component.MaxedPowerAccumulator = max; + component.MaxedPowerRoof = max; + + var min = _random.NextFloat(component.MinPowerMin, component.MinPowerMax); + component.MinPowerAccumulator = min; + component.MinPowerRoof = min; + + _power.UpdateAlert(uid, true, component.PowerLevel); + } + + private void OnShutdown(EntityUid uid, ShadowkinComponent component, ComponentShutdown args) + { + _power.UpdateAlert(uid, false); + } + + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = _entity.EntityQueryEnumerator(); + + // Update power level for all shadowkin + while (query.MoveNext(out var uid, out var shadowkin)) + { + // Ensure dead or critical shadowkin aren't swapped, skip them + if (_mobState.IsDead(uid) || + _mobState.IsCritical(uid)) + { + _entity.RemoveComponent(uid); + continue; + } + + // Don't update things for ssd shadowkin + if (!_entity.System().TryGetMind(uid, out var mindId, out var mind) || + mind.Session == null) + continue; + + var oldPowerLevel = _power.GetLevelName(shadowkin.PowerLevel); + _power.TryUpdatePowerLevel(uid, frameTime); + + if (oldPowerLevel != _power.GetLevelName(shadowkin.PowerLevel)) + { + _power.TryBlackeye(uid); + Dirty(shadowkin); + } + // I can't figure out how to get this to go to the 100% filled state in the above if statement 😢 + _power.UpdateAlert(uid, true, shadowkin.PowerLevel); + + // Don't randomly activate abilities if handcuffed + // TODO: Something like the Psionic Headcage to disable powers for Shadowkin + if (_entity.HasComponent(uid)) + continue; + + #region MaxPower + // Check if they're at max power + if (shadowkin.PowerLevel >= ShadowkinComponent.PowerThresholds[ShadowkinPowerThreshold.Max]) + { + // If so, start the timer + shadowkin.MaxedPowerAccumulator -= frameTime; + + // If the time's up, do things + if (shadowkin.MaxedPowerAccumulator <= 0f) + { + // Randomize the timer + var next = _random.NextFloat(shadowkin.MaxedPowerRateMin, shadowkin.MaxedPowerRateMax); + shadowkin.MaxedPowerRoof = next; + shadowkin.MaxedPowerAccumulator = next; + + var chance = _random.Next(7); + + if (chance <= 2) + { + _darkSwap.ForceDarkSwap(uid, shadowkin); + } + else if (chance <= 7) + { + _teleport.ForceTeleport(uid, shadowkin); + } + } + } + else + { + // Slowly regenerate if not maxed + shadowkin.MaxedPowerAccumulator += frameTime / 5f; + shadowkin.MaxedPowerAccumulator = Math.Clamp(shadowkin.MaxedPowerAccumulator, 0f, shadowkin.MaxedPowerRoof); + } + #endregion + + #region MinPower + // Check if they're at the average of the Tired and Okay thresholds + // Just Tired is too little, and Okay is too much, get the average + if (shadowkin.PowerLevel <= + ( + ShadowkinComponent.PowerThresholds[ShadowkinPowerThreshold.Tired] + + ShadowkinComponent.PowerThresholds[ShadowkinPowerThreshold.Okay] + ) / 2f && + // Don't sleep if asleep + !_entity.HasComponent(uid) + ) + { + // If so, start the timer + shadowkin.MinPowerAccumulator -= frameTime; + + // If the timer is up, force rest + if (shadowkin.MinPowerAccumulator <= 0f) + { + // Random new timer + var next = _random.NextFloat(shadowkin.MinPowerMin, shadowkin.MinPowerMax); + shadowkin.MinPowerRoof = next; + shadowkin.MinPowerAccumulator = next; + + // Send event to rest + RaiseLocalEvent(uid, new ShadowkinRestEvent { Performer = uid }); + } + } + else + { + // Slowly regenerate if not tired + shadowkin.MinPowerAccumulator += frameTime / 5f; + shadowkin.MinPowerAccumulator = Math.Clamp(shadowkin.MinPowerAccumulator, 0f, shadowkin.MinPowerRoof); + } + #endregion + } + } +} diff --git a/Content.Server/Parkstation/Speech/Components/ShadowkinAccentComponent.cs b/Content.Server/Parkstation/Speech/Components/ShadowkinAccentComponent.cs new file mode 100644 index 0000000000..6d6f8dbc40 --- /dev/null +++ b/Content.Server/Parkstation/Speech/Components/ShadowkinAccentComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server.Parkstation.Speech.Components; + +[RegisterComponent] +public sealed partial class ShadowkinAccentComponent : Component +{ + +} diff --git a/Content.Server/Parkstation/Speech/EntitySystems/ShadowkinAccentSystem.cs b/Content.Server/Parkstation/Speech/EntitySystems/ShadowkinAccentSystem.cs new file mode 100644 index 0000000000..67096fe6ab --- /dev/null +++ b/Content.Server/Parkstation/Speech/EntitySystems/ShadowkinAccentSystem.cs @@ -0,0 +1,46 @@ +using System.Text.RegularExpressions; +using Content.Server.Parkstation.Speech.Components; +using Content.Server.Speech; +using Robust.Shared.Random; + +namespace Content.Server.Parkstation.Speech.EntitySystems +{ + public sealed class ShadowkinAccentSystem : EntitySystem + { + [Dependency] private readonly IRobustRandom _random = default!; + + private static readonly Regex mRegex = new(@"[adgjmpsv]", RegexOptions.Compiled); + private static readonly Regex aRegex = new(@"[behknqtwy]", RegexOptions.Compiled); + private static readonly Regex rRegex = new(@"[cfiloruxz]", RegexOptions.Compiled); + private static readonly Regex MRegex = new(@"[ADGJMPSV]", RegexOptions.Compiled); + private static readonly Regex ARegex = new(@"[BEHKNQTWY]", RegexOptions.Compiled); + private static readonly Regex RRegex = new(@"[CFILORUXZ]", RegexOptions.Compiled); + + + public override void Initialize() + { + SubscribeLocalEvent(OnAccent); + } + + + public string Accentuate(string message) + { + message = message.Trim(); + + // Replace letters with other letters + message = mRegex.Replace(message, "m"); + message = aRegex.Replace(message, "a"); + message = rRegex.Replace(message, "r"); + message = MRegex.Replace(message, "M"); + message = ARegex.Replace(message, "A"); + message = RRegex.Replace(message, "R"); + + return message; + } + + private void OnAccent(EntityUid uid, ShadowkinAccentComponent component, AccentGetEvent args) + { + args.Message = Accentuate(args.Message); + } + } +} diff --git a/Content.Shared/Alert/AlertCategory.cs b/Content.Shared/Alert/AlertCategory.cs index 7450f585a4..af7ded643d 100644 --- a/Content.Shared/Alert/AlertCategory.cs +++ b/Content.Shared/Alert/AlertCategory.cs @@ -12,6 +12,7 @@ public enum AlertCategory Health, Internals, Stamina, + ShadowkinPower, // Parkstation-Shadowkin Piloting, Hunger, Thirst, diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs index dc323dc64a..15b26aa400 100644 --- a/Content.Shared/Alert/AlertType.cs +++ b/Content.Shared/Alert/AlertType.cs @@ -31,6 +31,7 @@ public enum AlertType : byte Thirsty, Parched, Stamina, + ShadowkinPower, // Parkstation-Shadowkin Pulled, Pulling, Magboots, diff --git a/Content.Shared/Chat/ChatChannel.cs b/Content.Shared/Chat/ChatChannel.cs index d87fcbc075..31a0b686e4 100644 --- a/Content.Shared/Chat/ChatChannel.cs +++ b/Content.Shared/Chat/ChatChannel.cs @@ -4,7 +4,7 @@ namespace Content.Shared.Chat /// Represents chat channels that the player can filter chat tabs by. /// [Flags] - public enum ChatChannel : ushort + public enum ChatChannel { None = 0, @@ -90,10 +90,17 @@ public enum ChatChannel : ushort /// Telepathic = 1 << 15, + /// + /// Empathy channel for Shadowkin. + /// + Empathy = 1 << 16, // Parkstation-EmpathyChat + /// /// Channels considered to be IC. /// - IC = Local | Whisper | Radio | Dead | Emotes | Damage | Visual | Telepathic | Notifications, //Nyano - Summary: Adds telepathic as an 'IC' labelled chat.. + //Nyano - Summary: Adds telepathic as an 'IC' labelled chat.. + // Parkstation-EmpathyChat + IC = Local | Whisper | Radio | Dead | Emotes | Damage | Visual | Telepathic | Empathy | Notifications, AdminRelated = Admin | AdminAlert | AdminChat, } diff --git a/Content.Shared/Chat/ChatSelectChannel.cs b/Content.Shared/Chat/ChatSelectChannel.cs index 5104bbc306..3ee7a71c4f 100644 --- a/Content.Shared/Chat/ChatSelectChannel.cs +++ b/Content.Shared/Chat/ChatSelectChannel.cs @@ -7,7 +7,7 @@ /// Maps to , giving better names. /// [Flags] - public enum ChatSelectChannel : ushort + public enum ChatSelectChannel { None = 0, @@ -52,10 +52,15 @@ public enum ChatSelectChannel : ushort Admin = ChatChannel.AdminChat, /// - /// Nyano - Summary:. Telepathic channel for all psionic entities. + /// Nyano - Summary:. Telepathic channel for all psionic entities. /// Telepathic = ChatChannel.Telepathic, + /// + /// Shadowkin empathy channel + /// + Empathy = ChatChannel.Empathy, // Parkstation-EmpathyChat + Console = ChatChannel.Unspecified } } diff --git a/Content.Shared/Chat/SharedChatSystem.cs b/Content.Shared/Chat/SharedChatSystem.cs index c48d70405f..cd5feceaba 100644 --- a/Content.Shared/Chat/SharedChatSystem.cs +++ b/Content.Shared/Chat/SharedChatSystem.cs @@ -23,6 +23,7 @@ public abstract class SharedChatSystem : EntitySystem public const char AdminPrefix = ']'; public const char WhisperPrefix = ','; public const char TelepathicPrefix = '='; //Nyano - Summary: Adds the telepathic channel's prefix. + public const char EmpathyPrefix = '~'; // Parkstation-EmpathyChat public const char DefaultChannelKey = 'h'; [ValidatePrototypeId] @@ -255,7 +256,8 @@ public enum InGameICChatType : byte Speak, Emote, Whisper, - Telepathic + Telepathic, + Empathy, // Parkstation-EmpathyChat } /// diff --git a/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs b/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs index 464ef77885..e8c4f34732 100644 --- a/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs +++ b/Content.Shared/CombatMode/Pacification/PacifiedComponent.cs @@ -12,7 +12,6 @@ namespace Content.Shared.CombatMode.Pacification; /// If you want full-pacifism (no combat mode at all), you can simply set before adding. /// [RegisterComponent, NetworkedComponent, AutoGenerateComponentPause] -[Access(typeof(PacificationSystem))] public sealed partial class PacifiedComponent : Component { [DataField] diff --git a/Content.Shared/Cuffs/Components/HandcuffComponent.cs b/Content.Shared/Cuffs/Components/HandcuffComponent.cs index 07468a4dab..ffc5a1f423 100644 --- a/Content.Shared/Cuffs/Components/HandcuffComponent.cs +++ b/Content.Shared/Cuffs/Components/HandcuffComponent.cs @@ -99,6 +99,12 @@ public sealed partial class HandcuffComponent : Component /// [DataField] public bool UncuffEasierWhenLarge = false; + + + // Parkstation-Shadowkin Start + [DataField] + public bool AntiShadowkin; + // Parkstation-Shadowkin End } /// diff --git a/Content.Shared/Eye/VisibilityFlags.cs b/Content.Shared/Eye/VisibilityFlags.cs index 7e2dd33d7d..d1b2d13c7e 100644 --- a/Content.Shared/Eye/VisibilityFlags.cs +++ b/Content.Shared/Eye/VisibilityFlags.cs @@ -10,6 +10,8 @@ public enum VisibilityFlags : int Normal = 1 << 0, Ghost = 1 << 1, PsionicInvisibility = 1 << 2, //Nyano - Summary: adds Psionic Invisibility as a visibility layer. Currently does nothing. + DarkSwapInvisibility = 1 << 3, // Parkstation-Shadowkin + AIEye = 1 << 4, // Parkstation-SAI TelegnosticProjection = 5, } } diff --git a/Content.Shared/Parkstation/Clothing/Components/ClothingGrantComponentComponent.cs b/Content.Shared/Parkstation/Clothing/Components/ClothingGrantComponentComponent.cs index 749ed07366..a3ffb93754 100644 --- a/Content.Shared/Parkstation/Clothing/Components/ClothingGrantComponentComponent.cs +++ b/Content.Shared/Parkstation/Clothing/Components/ClothingGrantComponentComponent.cs @@ -1,15 +1,14 @@ using Robust.Shared.Prototypes; -namespace Content.Shared.Parkstation.Clothing.Components +namespace Content.Shared.Parkstation.Clothing.Components; + +[RegisterComponent] +public sealed partial class ClothingGrantComponentComponent : Component { - [RegisterComponent] - public sealed partial class ClothingGrantComponentComponent : Component - { - [DataField("component", required: true)] - [AlwaysPushInheritance] - public ComponentRegistry Components { get; private set; } = new(); + [DataField("component", required: true)] + [AlwaysPushInheritance] + public ComponentRegistry Components { get; private set; } = new(); - [ViewVariables(VVAccess.ReadWrite)] - public bool IsActive = false; - } + [ViewVariables(VVAccess.ReadWrite)] + public bool IsActive = false; } diff --git a/Content.Shared/Parkstation/Species/Shadowkin/Components/EmpathyChatComponent.cs b/Content.Shared/Parkstation/Species/Shadowkin/Components/EmpathyChatComponent.cs new file mode 100644 index 0000000000..da3274e432 --- /dev/null +++ b/Content.Shared/Parkstation/Species/Shadowkin/Components/EmpathyChatComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Parkstation.Species.Shadowkin.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class EmpathyChatComponent : Component +{ + +} diff --git a/Content.Shared/Parkstation/Species/Shadowkin/Components/ShadowkinComponent.cs b/Content.Shared/Parkstation/Species/Shadowkin/Components/ShadowkinComponent.cs new file mode 100644 index 0000000000..41186eae39 --- /dev/null +++ b/Content.Shared/Parkstation/Species/Shadowkin/Components/ShadowkinComponent.cs @@ -0,0 +1,121 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Parkstation.Species.Shadowkin.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ShadowkinComponent : Component +{ + #region Random occurrences + [ViewVariables(VVAccess.ReadWrite)] + public float MaxedPowerAccumulator = 0f; + + [ViewVariables(VVAccess.ReadWrite)] + public float MaxedPowerRoof = 0f; + + [ViewVariables(VVAccess.ReadWrite)] + public float MaxedPowerRateMin = 90f; + + [ViewVariables(VVAccess.ReadWrite)] + public float MaxedPowerRateMax = 240f; + + + [ViewVariables(VVAccess.ReadWrite)] + public float MinPowerAccumulator = 0f; + + [ViewVariables(VVAccess.ReadWrite)] + public float MinPowerRoof = 0f; + + [ViewVariables(VVAccess.ReadWrite)] + public float MinPowerMin = 15f; + + [ViewVariables(VVAccess.ReadWrite)] + public float MinPowerMax = 40f; + #endregion + + + #region Shader + /// + /// Automatically set to eye color. + /// + [ViewVariables(VVAccess.ReadOnly)] + public Vector3 TintColor = new(0.5f, 0f, 0.5f); + + /// + /// Based on PowerLevel. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float TintIntensity = 0.65f; + #endregion + + + #region Power level + /// + /// Current amount of energy. + /// + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public float PowerLevel + { + get => _powerLevel; + set => _powerLevel = Math.Clamp(value, PowerLevelMin, PowerLevelMax); + } + public float _powerLevel = 150f; + + /// + /// Don't let PowerLevel go above this value. + /// + [ViewVariables(VVAccess.ReadOnly), AutoNetworkedField] + public float PowerLevelMax = PowerThresholds[ShadowkinPowerThreshold.Max]; + + /// + /// Blackeyes if PowerLevel is this value. + /// + [ViewVariables(VVAccess.ReadOnly), AutoNetworkedField] + public float PowerLevelMin = PowerThresholds[ShadowkinPowerThreshold.Min]; + + /// + /// How much energy is gained per second. + /// + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public float PowerLevelGain = 0.5f; + + /// + /// Power gain multiplier + /// + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public float PowerLevelGainMultiplier = 1f; + + /// + /// Whether to gain power or not. + /// + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public bool PowerLevelGainEnabled = true; + + /// + /// Whether they are a blackeye. + /// + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public bool Blackeye = false; + + + // TODO This sucks, make this not hardcoded so we can do some fun things with it + public static readonly Dictionary PowerThresholds = new() + { + { ShadowkinPowerThreshold.Max, 250.0f }, + { ShadowkinPowerThreshold.Great, 200.0f }, + { ShadowkinPowerThreshold.Good, 150.0f }, + { ShadowkinPowerThreshold.Okay, 100.0f }, + { ShadowkinPowerThreshold.Tired, 50.0f }, + { ShadowkinPowerThreshold.Min, 0.0f }, + }; + #endregion +} + +public enum ShadowkinPowerThreshold : byte +{ + Max = 1 << 4, + Great = 1 << 3, + Good = 1 << 2, + Okay = 1 << 1, + Tired = 1 << 0, + Min = 0, +} diff --git a/Content.Shared/Parkstation/Species/Shadowkin/Components/ShadowkinDarkSwappedComponent.cs b/Content.Shared/Parkstation/Species/Shadowkin/Components/ShadowkinDarkSwappedComponent.cs new file mode 100644 index 0000000000..7eabadd869 --- /dev/null +++ b/Content.Shared/Parkstation/Species/Shadowkin/Components/ShadowkinDarkSwappedComponent.cs @@ -0,0 +1,51 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Parkstation.Species.Shadowkin.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ShadowkinDarkSwappedComponent : Component +{ + /// + /// If the entity should be sent to the dark + /// + /// + /// This is also defined in the power component, this is if you want to use only some effects of the swap without a toggle + /// + [DataField("invisible")] + public bool Invisible = true; + + /// + /// If the entity should be pacified + /// + /// + [DataField("pacify")] + public bool Pacify = true; + + /// + /// If the entity should dim nearby lights when swapped + /// + [DataField("darken"), ViewVariables(VVAccess.ReadWrite)] + public bool Darken = true; + + /// + /// How far to dim nearby lights + /// + /// + [DataField("range"), ViewVariables(VVAccess.ReadWrite)] + public float DarkenRange = 5f; + + [ViewVariables(VVAccess.ReadOnly)] + public List DarkenedLights = new(); + + /// + /// How fast to refresh nearby light dimming in seconds + ///
+ /// Without this, performance would be significantly worse + ///
+ /// + [ViewVariables(VVAccess.ReadWrite)] + public float DarkenRate = 0.084f; // 1/12th of a second + + [ViewVariables(VVAccess.ReadWrite)] + public float DarkenAccumulator = 0f; +} diff --git a/Content.Shared/Parkstation/Species/Shadowkin/Components/ShadowkinLightComponent.cs b/Content.Shared/Parkstation/Species/Shadowkin/Components/ShadowkinLightComponent.cs new file mode 100644 index 0000000000..a287989a53 --- /dev/null +++ b/Content.Shared/Parkstation/Species/Shadowkin/Components/ShadowkinLightComponent.cs @@ -0,0 +1,24 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Parkstation.Species.Shadowkin.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ShadowkinLightComponent : Component +{ + [ViewVariables(VVAccess.ReadOnly)] + public EntityUid AttachedEntity = EntityUid.Invalid; + + + [ViewVariables(VVAccess.ReadOnly)] + public float OldRadius = 0f; + + [ViewVariables(VVAccess.ReadOnly)] + public bool OldRadiusEdited = false; + + + [ViewVariables(VVAccess.ReadOnly)] + public float OldEnergy = 0f; + + [ViewVariables(VVAccess.ReadOnly)] + public bool OldEnergyEdited = false; +} diff --git a/Content.Shared/Parkstation/Species/Shadowkin/Events/ShadowkinEvents.Blackeye.cs b/Content.Shared/Parkstation/Species/Shadowkin/Events/ShadowkinEvents.Blackeye.cs new file mode 100644 index 0000000000..70613f4b22 --- /dev/null +++ b/Content.Shared/Parkstation/Species/Shadowkin/Events/ShadowkinEvents.Blackeye.cs @@ -0,0 +1,22 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Parkstation.Species.Shadowkin.Events; + +/// +/// Raised to notify other systems of an attempt to blackeye a shadowkin. +/// +public sealed class ShadowkinBlackeyeAttemptEvent(NetEntity ent, bool checkPower = true) : CancellableEntityEventArgs +{ + public readonly NetEntity Ent = ent; + public readonly bool CheckPower = checkPower; +} + +/// +/// Raised when a shadowkin becomes a blackeye. +/// +[Serializable, NetSerializable] +public sealed class ShadowkinBlackeyeEvent(NetEntity ent, bool damage = true) : EntityEventArgs +{ + public readonly NetEntity Ent = ent; + public readonly bool Damage = damage; +} diff --git a/Content.Shared/Parkstation/Species/Shadowkin/Events/ShadowkinEvents.Powers.cs b/Content.Shared/Parkstation/Species/Shadowkin/Events/ShadowkinEvents.Powers.cs new file mode 100644 index 0000000000..d30413cfa4 --- /dev/null +++ b/Content.Shared/Parkstation/Species/Shadowkin/Events/ShadowkinEvents.Powers.cs @@ -0,0 +1,96 @@ +using Content.Shared.Actions; +using Content.Shared.Magic; +using Robust.Shared.Audio; +using Robust.Shared.Serialization; + +namespace Content.Shared.Parkstation.Species.Shadowkin.Events; + + +/// +/// Raised when the shadowkin teleport action is used. +/// +public sealed partial class ShadowkinTeleportEvent : WorldTargetActionEvent, ISpeakSpell +{ + [DataField("sound")] + public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Parkstation/Effects/Shadowkin/Powers/teleport.ogg"); + + [DataField("volume")] + public float Volume = 5f; + + + [DataField("powerCost")] + public float PowerCost = 40f; + + [DataField("staminaCost")] + public float StaminaCost = 20f; + + + [DataField("speech")] + public string? Speech { get; set; } +} + +/// +/// Raised when the shadowkin darkSwap action is used. +/// +public sealed partial class ShadowkinDarkSwapEvent : InstantActionEvent, ISpeakSpell +{ + [DataField("soundOn")] + public SoundSpecifier SoundOn = new SoundPathSpecifier("/Audio/Parkstation/Effects/Shadowkin/Powers/darkswapon.ogg"); + + [DataField("volumeOn")] + public float VolumeOn = 5f; + + [DataField("soundOff")] + public SoundSpecifier SoundOff = new SoundPathSpecifier("/Audio/Parkstation/Effects/Shadowkin/Powers/darkswapoff.ogg"); + + [DataField("volumeOff")] + public float VolumeOff = 5f; + + + /// + /// How much stamina to drain when darkening. + /// + [DataField("powerCostOn")] + public float PowerCostOn = 60f; + + /// + /// How much stamina to drain when lightening. + /// + [DataField("powerCostOff")] + public float PowerCostOff = 45f; + + /// + /// How much stamina to drain when darkening. + /// + [DataField("staminaCostOn")] + public float StaminaCostOn = 25f; + + /// + /// How much stamina to drain when lightening. + /// + [DataField("staminaCostOff")] + public float StaminaCostOff = 25f; + + + [DataField("speech")] + public string? Speech { get; set; } +} + +public sealed class ShadowkinDarkSwapAttemptEvent(EntityUid performer) : CancellableEntityEventArgs +{ + public EntityUid Performer = performer; +} + + +public sealed partial class ShadowkinRestEvent: InstantActionEvent; + + +/// +/// Raised over network to notify the client that they're going in/out of The Dark. +/// +[Serializable, NetSerializable] +public sealed class ShadowkinDarkSwappedEvent(NetEntity performer, bool darkSwapped) : EntityEventArgs +{ + public NetEntity Performer = performer; + public bool DarkSwapped = darkSwapped; +} diff --git a/Content.Shared/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.DarkSwap.cs b/Content.Shared/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.DarkSwap.cs new file mode 100644 index 0000000000..48068385d9 --- /dev/null +++ b/Content.Shared/Parkstation/Species/Shadowkin/Systems/ShadowkinPowerSystem.DarkSwap.cs @@ -0,0 +1,36 @@ +using Content.Shared.Interaction.Events; +using Content.Shared.Popups; +using Robust.Shared.Timing; +using Content.Shared.Parkstation.Species.Shadowkin.Components; + +namespace Content.Shared.Parkstation.Species.Shadowkin.Systems; + +public sealed class ShadowkinDarken : EntitySystem +{ + [Dependency] private readonly IEntityManager _entity = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteractionAttempt); + } + + + private void OnInteractionAttempt(EntityUid uid, ShadowkinDarkSwappedComponent component, InteractionAttemptEvent args) + { + if (args.Target == null + || !_entity.HasComponent(args.Target) + || _entity.HasComponent(args.Target)) + return; + + args.Cancel(); + + if (_gameTiming is { InPrediction: true, IsFirstTimePredicted: true }) + //TODO: This appears on way too many things + _popup.PopupEntity(Loc.GetString("ethereal-pickup-fail"), args.Target.Value, uid); + } +} diff --git a/Resources/Audio/Parkstation/Effects/Shadowkin/Powers/darkswapoff.ogg b/Resources/Audio/Parkstation/Effects/Shadowkin/Powers/darkswapoff.ogg new file mode 100644 index 0000000000..61f331a68a Binary files /dev/null and b/Resources/Audio/Parkstation/Effects/Shadowkin/Powers/darkswapoff.ogg differ diff --git a/Resources/Audio/Parkstation/Effects/Shadowkin/Powers/darkswapon.ogg b/Resources/Audio/Parkstation/Effects/Shadowkin/Powers/darkswapon.ogg new file mode 100644 index 0000000000..deb6139934 Binary files /dev/null and b/Resources/Audio/Parkstation/Effects/Shadowkin/Powers/darkswapon.ogg differ diff --git a/Resources/Audio/Parkstation/Effects/Shadowkin/Powers/futuristic-teleport.ogg b/Resources/Audio/Parkstation/Effects/Shadowkin/Powers/futuristic-teleport.ogg new file mode 100644 index 0000000000..87aeb33fdd Binary files /dev/null and b/Resources/Audio/Parkstation/Effects/Shadowkin/Powers/futuristic-teleport.ogg differ diff --git a/Resources/Audio/Parkstation/Effects/Shadowkin/Powers/license.txt b/Resources/Audio/Parkstation/Effects/Shadowkin/Powers/license.txt new file mode 100644 index 0000000000..c77ea8eb09 --- /dev/null +++ b/Resources/Audio/Parkstation/Effects/Shadowkin/Powers/license.txt @@ -0,0 +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/ diff --git a/Resources/Audio/Parkstation/Effects/Shadowkin/Powers/teleport.ogg b/Resources/Audio/Parkstation/Effects/Shadowkin/Powers/teleport.ogg new file mode 100644 index 0000000000..3cca66b47c Binary files /dev/null and b/Resources/Audio/Parkstation/Effects/Shadowkin/Powers/teleport.ogg differ diff --git a/Resources/Locale/en-US/chat/managers/chat-manager.ftl b/Resources/Locale/en-US/chat/managers/chat-manager.ftl index 589d79e6ea..8b71b896aa 100644 --- a/Resources/Locale/en-US/chat/managers/chat-manager.ftl +++ b/Resources/Locale/en-US/chat/managers/chat-manager.ftl @@ -47,6 +47,12 @@ chat-manager-send-hook-ooc-wrap-message = OOC: (D){$senderName}: {$message} chat-manager-dead-channel-name = DEAD chat-manager-admin-channel-name = ADMIN +## Parkstation-EmpathyChat-Start +chat-manager-send-empathy-chat-wrap-message = {$empathyChannelName}: {$message} +chat-manager-send-empathy-chat-wrap-message-admin = {$empathyChannelName} - {$source}: {$message} +chat-manager-empathy-channel-name = EMPATHY +## Parkstation-EmpathyChat-End + 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. diff --git a/Resources/Locale/en-US/chat/ui/chat-box.ftl b/Resources/Locale/en-US/chat/ui/chat-box.ftl index 720f0d15ab..aeb03de816 100644 --- a/Resources/Locale/en-US/chat/ui/chat-box.ftl +++ b/Resources/Locale/en-US/chat/ui/chat-box.ftl @@ -15,6 +15,8 @@ hud-chatbox-select-channel-OOC = OOC hud-chatbox-select-channel-Damage = Damage hud-chatbox-select-channel-Visual = Actions hud-chatbox-select-channel-Radio = Radio +## Parkstation-EmpathyChat +hud-chatbox-select-channel-Empathy = Empathy hud-chatbox-channel-Admin = Admin Misc hud-chatbox-channel-AdminAlert = Admin Alert @@ -28,6 +30,8 @@ hud-chatbox-channel-OOC = OOC hud-chatbox-channel-Radio = Radio hud-chatbox-channel-Notifications = Notifications hud-chatbox-channel-Server = Server +## Parkstation-EmpathyChat +hud-chatbox-channel-Empathy = Empathy hud-chatbox-channel-Visual = Actions hud-chatbox-channel-Damage = Damage hud-chatbox-channel-Unspecified = Unspecified diff --git a/Resources/Locale/en-US/parkstation/Content/Interaction/interaction-popup.ftl b/Resources/Locale/en-US/parkstation/Content/Interaction/interaction-popup.ftl new file mode 100644 index 0000000000..55c0b95785 --- /dev/null +++ b/Resources/Locale/en-US/parkstation/Content/Interaction/interaction-popup.ftl @@ -0,0 +1,6 @@ +petting-success-generic-others = { CAPITALIZE(THE($user)) } pets {THE($target)}. +petting-success-soft-floofy-others = { CAPITALIZE(THE($user)) } pets {THE($target)} on {POSS-ADJ($target)} soft floofy head. + +hugging-failure-generic = You try to hug {THE($target)}, but they don't seem to like it. +hugging-failure-generic-others = {CAPITALIZE(THE($user))} tries to hug {THE($target)}, they don't seem to like it. +hugging-failure-generic-target = {CAPITALIZE(THE($user))} hugs you, you frown back. diff --git a/Resources/Locale/en-US/parkstation/Content/Species/Shadowkin/shadowkin.ftl b/Resources/Locale/en-US/parkstation/Content/Species/Shadowkin/shadowkin.ftl new file mode 100644 index 0000000000..65b1c2caab --- /dev/null +++ b/Resources/Locale/en-US/parkstation/Content/Species/Shadowkin/shadowkin.ftl @@ -0,0 +1,11 @@ +shadowkin-power-examined-other = {CAPITALIZE(SUBJECT($target))} seems to be {$powerType}. +shadowkin-power-examined-self = You have {$power}/{$powerMax} energy, you are {$powerType}. + +shadowkin-power-max = energetic +shadowkin-power-great = great +shadowkin-power-good = good +shadowkin-power-okay = okay +shadowkin-power-tired = exhausted +shadowkin-power-min = a blackeye + +shadowkin-blackeye = You feel your power draining away, you are exhausted! diff --git a/Resources/Locale/en-US/parkstation/Prototypes/Alerts/shadowkin.ftl b/Resources/Locale/en-US/parkstation/Prototypes/Alerts/shadowkin.ftl new file mode 100644 index 0000000000..32653a424e --- /dev/null +++ b/Resources/Locale/en-US/parkstation/Prototypes/Alerts/shadowkin.ftl @@ -0,0 +1,2 @@ +alerts-shadowkin-power-name = Power Level +alerts-shadowkin-power-desc = How much energy you can expend via your abilities. diff --git a/Resources/Locale/en-US/parkstation/Prototypes/Magic/shadowkin.ftl b/Resources/Locale/en-US/parkstation/Prototypes/Magic/shadowkin.ftl new file mode 100644 index 0000000000..83d316b15e --- /dev/null +++ b/Resources/Locale/en-US/parkstation/Prototypes/Magic/shadowkin.ftl @@ -0,0 +1,10 @@ +action-name-shadowkin-teleport=Teleport +action-description-shadowkin-teleport=Aaramrra! + +action-name-shadowkin-darkswap=Dark Swap +action-description-shadowkin-darkswap=Mmra Mamm! + +action-name-shadowkin-rest=Rest +action-description-shadowkin-rest=Rama + +ethereal-pickup-fail=Your hand sizzles as it passes through... diff --git a/Resources/Locale/en-US/parkstation/Prototypes/Species/shadowkin.ftl b/Resources/Locale/en-US/parkstation/Prototypes/Species/shadowkin.ftl new file mode 100644 index 0000000000..038f4fc29b --- /dev/null +++ b/Resources/Locale/en-US/parkstation/Prototypes/Species/shadowkin.ftl @@ -0,0 +1 @@ +species-name-shadowkin = Shadowkin diff --git a/Resources/Locale/en-US/parkstation/Traits/disabilities.ftl b/Resources/Locale/en-US/parkstation/Traits/disabilities.ftl index 8360aaeb9d..ea2a7f347b 100644 --- a/Resources/Locale/en-US/parkstation/Traits/disabilities.ftl +++ b/Resources/Locale/en-US/parkstation/Traits/disabilities.ftl @@ -1,2 +1,5 @@ trait-name-Nearsighted = Nearsighted trait-description-Nearsighted = You require glasses to see properly. + +trait-name-ShadowkinBlackeye = Blackeye +trait-description-ShadowkinBlackeye = You have lost your Shadowkin powers. diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index 00b799670f..8026cca6fa 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -6,6 +6,7 @@ order: - category: Health - category: Stamina + - category: ShadowkinPower # Parkstation-Shadowkin - alertType: SuitPower - category: Internals - alertType: Fire diff --git a/Resources/Prototypes/Catalog/Fills/Crates/fun.yml b/Resources/Prototypes/Catalog/Fills/Crates/fun.yml index b55bdd4832..cf50a99b04 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/fun.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/fun.yml @@ -303,4 +303,3 @@ - id: BoxDarts amount: 1 prob: 0.05 - diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml index 07152d74ef..20460d5a30 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml @@ -69,6 +69,7 @@ - map: [ "enum.HumanoidVisualLayers.HeadSide" ] - map: [ "enum.HumanoidVisualLayers.HeadTop" ] - map: [ "enum.HumanoidVisualLayers.Tail" ] + - map: [ "enum.HumanoidVisualLayers.Wings" ] # Parkstation-Wings - map: [ "mask" ] - map: [ "head" ] - map: [ "pocket1" ] diff --git a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml index c135ac2b82..7f1c017887 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml @@ -104,6 +104,7 @@ - map: [ "belt" ] - map: [ "id" ] - map: [ "enum.HumanoidVisualLayers.Tail" ] # Mentioned in moth code: This needs renaming lol. + - map: [ "enum.HumanoidVisualLayers.Wings" ] # Parkstation-Wings - map: [ "neck" ] - map: [ "back" ] - map: [ "enum.HumanoidVisualLayers.FacialHair" ] diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index d812433b44..adf13b84a6 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -27,9 +27,14 @@ sprite: Mobs/Customization/masking_helpers.rsi state: unisex_full visible: false - - map: ["jumpsuit"] - map: ["enum.HumanoidVisualLayers.LFoot"] - map: ["enum.HumanoidVisualLayers.RFoot"] + # Parkstation-Underwear Start + # - map: ["socks"] + # - map: ["underpants"] + # - map: ["undershirt"] + - map: ["jumpsuit"] + # Parkstation-Underwear End - map: ["enum.HumanoidVisualLayers.LHand"] - map: ["enum.HumanoidVisualLayers.RHand"] - map: [ "gloves" ] @@ -46,6 +51,7 @@ - map: [ "enum.HumanoidVisualLayers.HeadSide" ] - map: [ "enum.HumanoidVisualLayers.HeadTop" ] - map: [ "enum.HumanoidVisualLayers.Tail" ] + - map: [ "enum.HumanoidVisualLayers.Wings" ] # Parkstation-Wings - map: [ "mask" ] - map: [ "head" ] - map: [ "pocket1" ] @@ -341,9 +347,14 @@ sprite: Mobs/Customization/masking_helpers.rsi state: unisex_full visible: false - - map: ["jumpsuit"] - map: ["enum.HumanoidVisualLayers.LFoot"] - map: ["enum.HumanoidVisualLayers.RFoot"] + # Parkstation-Underwear Start + # - map: ["socks"] + # - map: ["underpants"] + # - map: ["undershirt"] + - map: ["jumpsuit"] + # Parkstation-Underwear End - map: ["enum.HumanoidVisualLayers.LHand"] - map: ["enum.HumanoidVisualLayers.RHand"] - map: ["enum.HumanoidVisualLayers.Handcuffs"] @@ -365,6 +376,7 @@ - map: [ "enum.HumanoidVisualLayers.HeadSide" ] - map: [ "enum.HumanoidVisualLayers.HeadTop" ] - map: [ "enum.HumanoidVisualLayers.Tail" ] + - map: [ "enum.HumanoidVisualLayers.Wings" ] # Parkstation-Wings - map: [ "mask" ] - map: [ "head" ] - map: [ "pocket1" ] diff --git a/Resources/Prototypes/Entities/Mobs/Species/harpy.yml b/Resources/Prototypes/Entities/Mobs/Species/harpy.yml index f4055170b4..45ec60b049 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/harpy.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/harpy.yml @@ -41,6 +41,7 @@ - map: [ "enum.HumanoidVisualLayers.FacialHair" ] - map: [ "enum.HumanoidVisualLayers.HeadSide" ] - map: [ "enum.HumanoidVisualLayers.Tail" ] + - map: [ "enum.HumanoidVisualLayers.Wings" ] # Parkstation-Wings - map: [ "pocket1" ] - map: [ "pocket2" ] - map: [ "clownedon" ] # Dynamically generated @@ -162,6 +163,7 @@ - map: [ "enum.HumanoidVisualLayers.HeadSide" ] - map: [ "enum.HumanoidVisualLayers.HeadTop" ] - map: [ "enum.HumanoidVisualLayers.Tail" ] + - map: [ "enum.HumanoidVisualLayers.Wings" ] # Parkstation-Wings - map: [ "pocket1" ] - map: [ "pocket2" ] - map: [ "clownedon" ] # Dynamically generated diff --git a/Resources/Prototypes/Entities/Mobs/Species/moth.yml b/Resources/Prototypes/Entities/Mobs/Species/moth.yml index 1c55dcf0df..4437475840 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/moth.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/moth.yml @@ -106,7 +106,8 @@ - map: [ "eyes" ] - map: [ "belt" ] - map: [ "id" ] - - map: [ "enum.HumanoidVisualLayers.Tail" ] #in the utopian future we should probably have a wings enum inserted here so everyhting doesn't break + - map: [ "enum.HumanoidVisualLayers.Tail" ] + - map: [ "enum.HumanoidVisualLayers.Wings" ] # Parkstation-Wings - map: [ "neck" ] - map: [ "back" ] - map: [ "enum.HumanoidVisualLayers.FacialHair" ] diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index 2c28f60da5..8be6b315e5 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: ShadowkinLight # Parkstation-Shadowkin - type: PointLight enabled: false radius: 1.5 diff --git a/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml b/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml index a42b2fa113..1193aaf009 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: ShadowkinLight # Parkstation-Shadowkin - type: PointLight enabled: false radius: 3 diff --git a/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml b/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml index 2b75a7e3dd..fb1562a0a3 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: ShadowkinLight # Parkstation-Shadowkin - 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 22bbdb7b9f..1ec006e8ec 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: ShadowkinLight # Parkstation-Shadowkin - type: PointLight color: "#FFE4CE" # 5000K color temp energy: 0.8 diff --git a/Resources/Prototypes/Nyanotrasen/metempsychoticHumanoids.yml b/Resources/Prototypes/Nyanotrasen/metempsychoticHumanoids.yml index 891067b1c1..87101c7139 100644 --- a/Resources/Prototypes/Nyanotrasen/metempsychoticHumanoids.yml +++ b/Resources/Prototypes/Nyanotrasen/metempsychoticHumanoids.yml @@ -11,3 +11,4 @@ Reptilian: 0.5 SlimePerson: 0.5 Vulpkanin: 0.5 + Shadowkin: 0.4 diff --git a/Resources/Prototypes/Parkstation/Alerts/shadowkin.yml b/Resources/Prototypes/Parkstation/Alerts/shadowkin.yml new file mode 100644 index 0000000000..52bdb7c4ab --- /dev/null +++ b/Resources/Prototypes/Parkstation/Alerts/shadowkin.yml @@ -0,0 +1,24 @@ +- type: alert + id: ShadowkinPower + category: ShadowkinPower + icons: + - sprite: /Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi + state: power0 + - sprite: /Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi + state: power1 + - sprite: /Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi + state: power2 + - sprite: /Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi + state: power3 + - sprite: /Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi + state: power4 + - sprite: /Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi + state: power5 + - sprite: /Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi + state: power6 + - sprite: /Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi + state: power7 + name: alerts-shadowkin-power-name + description: alerts-shadowkin-power-desc + minSeverity: 0 + maxSeverity: 7 diff --git a/Resources/Prototypes/Parkstation/Body/Organs/shadowkin.yml b/Resources/Prototypes/Parkstation/Body/Organs/shadowkin.yml new file mode 100644 index 0000000000..3d51544e08 --- /dev/null +++ b/Resources/Prototypes/Parkstation/Body/Organs/shadowkin.yml @@ -0,0 +1,136 @@ +- type: entity + id: BaseShadowkinOrgan + parent: BaseItem + abstract: true + components: + - type: Organ + - type: Sprite + sprite: Parkstation/Mobs/Species/Shadowkin/organs.rsi + + +- type: entity + id: OrganShadowkinBrain + parent: BaseShadowkinOrgan + name: brain + description: The source of incredible, unending intelligence. + components: + - type: Sprite + state: brain + - type: Organ + - type: Input + context: ghost + - type: InputMover + - type: MovementSpeedModifier # How the hell does it walk? + baseWalkSpeed: 0 + baseSprintSpeed: 0 + - type: Brain + +- type: entity + id: OrganShadowkinEyes + parent: BaseShadowkinOrgan + name: eyes + description: I see beyond anything you ever will! + components: + - type: Sprite + state: eyes + - type: Organ + +- type: entity + id: OrganShadowkinEars + parent: BaseShadowkinOrgan + name: ears + description: Hey, listen! + components: + - type: Sprite + state: ears + - type: Organ + +- type: entity + id: OrganShadowkinTongue + parent: BaseShadowkinOrgan + name: tongue + description: A fleshy muscle mostly used for lying. + components: + - type: Sprite + state: tongue + - type: Organ + + +- type: entity + id: OrganShadowkinAppendix + parent: BaseShadowkinOrgan + name: appendix + description: What does this do? + components: + - type: Sprite + state: appendix + - type: Organ + + +- type: entity + id: OrganShadowkinHeart + parent: BaseShadowkinOrgan + name: heart + description: I feel bad for the heartless bastard who lost this. + components: + - type: Sprite + state: heart + - type: Organ + - type: Metabolizer + maxReagents: 2 + metabolizerTypes: [Shadowkin] + groups: + - id: Medicine + - id: Poison + - id: Narcotic + +- type: entity + id: OrganShadowkinStomach + parent: BaseShadowkinOrgan + name: stomach + description: '"Yummy!", says the stomach, although you are unable to hear it.' + components: + - type: Sprite + state: stomach + - type: Organ + - type: SolutionContainerManager + solutions: + stomach: + maxVol: 40 + - type: Stomach + - type: Metabolizer + maxReagents: 3 + metabolizerTypes: [Shadowkin] + groups: + - id: Food + - id: Drink + +- type: entity + id: OrganShadowkinLiver + parent: BaseShadowkinOrgan + name: liver + description: "Live 'er? I hardly know 'er!" + components: + - type: Sprite + state: liver + - type: Organ + - type: Metabolizer + maxReagents: 1 + metabolizerTypes: [Shadowkin] + groups: + - id: Alcohol + rateModifier: 0.1 + +- type: entity + id: OrganShadowkinKidneys + parent: BaseShadowkinOrgan + name: kidneys + description: Give the kid their knees back, please, this is the third time this week. + components: + - type: Sprite + state: kidneys + - type: Organ + - type: Metabolizer + maxReagents: 5 + metabolizerTypes: [Shadowkin] + removeEmpty: true \ No newline at end of file diff --git a/Resources/Prototypes/Parkstation/Chemistry/metabolizer_types.yml b/Resources/Prototypes/Parkstation/Chemistry/metabolizer_types.yml new file mode 100644 index 0000000000..66ce009b66 --- /dev/null +++ b/Resources/Prototypes/Parkstation/Chemistry/metabolizer_types.yml @@ -0,0 +1,3 @@ +- type: metabolizerType + id: Shadowkin + name: shadowkin diff --git a/Resources/Prototypes/Parkstation/Damage/containers.yml b/Resources/Prototypes/Parkstation/Damage/containers.yml new file mode 100644 index 0000000000..40a3284e7c --- /dev/null +++ b/Resources/Prototypes/Parkstation/Damage/containers.yml @@ -0,0 +1,10 @@ +# Use whenever this isn't BS +# - type: damageContainer +# id: Shadowkin +# supportedGroups: +# - Brute +# - Burn +# - Toxin +# - Genetic +# supportedTypes: +# - Bloodloss diff --git a/Resources/Prototypes/Parkstation/Damage/modifier_sets.yml b/Resources/Prototypes/Parkstation/Damage/modifier_sets.yml new file mode 100644 index 0000000000..f1a86d203c --- /dev/null +++ b/Resources/Prototypes/Parkstation/Damage/modifier_sets.yml @@ -0,0 +1,13 @@ +- type: damageModifierSet + id: Shadowkin + coefficients: + Blunt: 0.95 + Slash: 1.2 + Piercing: 1.1 + Asphyxiation: 0 + Cold: 0.75 + Heat: 1.5 + Cellular: 0.25 + Bloodloss: 1.35 + Shock: 1.25 + Radiation: 1.3 diff --git a/Resources/Prototypes/Parkstation/Datasets/Names/shadowkin.yml b/Resources/Prototypes/Parkstation/Datasets/Names/shadowkin.yml new file mode 100644 index 0000000000..f656581482 --- /dev/null +++ b/Resources/Prototypes/Parkstation/Datasets/Names/shadowkin.yml @@ -0,0 +1,69 @@ +# 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 diff --git a/Resources/Prototypes/Parkstation/Entities/Body/Parts/shadowkin.yml b/Resources/Prototypes/Parkstation/Entities/Body/Parts/shadowkin.yml new file mode 100644 index 0000000000..da5fba8dd2 --- /dev/null +++ b/Resources/Prototypes/Parkstation/Entities/Body/Parts/shadowkin.yml @@ -0,0 +1,156 @@ +- type: entity + id: PartShadowkin + parent: BaseItem + name: Shadowkin body part + abstract: true + components: + - type: Sprite + netsync: false + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + - type: Icon + sprite: Parkstation/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/Parkstation/Entities/Body/Prototypes/shadowkin.yml b/Resources/Prototypes/Parkstation/Entities/Body/Prototypes/shadowkin.yml new file mode 100644 index 0000000000..88e30ad0b2 --- /dev/null +++ b/Resources/Prototypes/Parkstation/Entities/Body/Prototypes/shadowkin.yml @@ -0,0 +1,49 @@ +- 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 + # lungs: OrganShadowkinLungs + 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 diff --git a/Resources/Prototypes/Parkstation/Entities/Clothing/Eyes/glasses.yml b/Resources/Prototypes/Parkstation/Entities/Clothing/Eyes/glasses.yml new file mode 100644 index 0000000000..3693a440ee --- /dev/null +++ b/Resources/Prototypes/Parkstation/Entities/Clothing/Eyes/glasses.yml @@ -0,0 +1,14 @@ +- type: entity + parent: ClothingEyesBase + id: ClothingEyesGlassesShadowkinDarkWindow + name: darkened goggles + description: An unusual pair of goggles designed to allow the wearer to peer into The Dark. + components: + - type: Sprite + sprite: Parkstation/Clothing/Eyes/Glasses/darkened.rsi + - type: Clothing + sprite: Parkstation/Clothing/Eyes/Glasses/darkened.rsi + - type: EyeProtection + - type: DropOnSlip + chance: 20 + - type: ShadowkinSight diff --git a/Resources/Prototypes/Parkstation/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Parkstation/Entities/Clothing/Head/hardsuit-helmets.yml index fc6b369310..f636057462 100644 --- a/Resources/Prototypes/Parkstation/Entities/Clothing/Head/hardsuit-helmets.yml +++ b/Resources/Prototypes/Parkstation/Entities/Clothing/Head/hardsuit-helmets.yml @@ -39,3 +39,30 @@ Piercing: 0.95 Heat: 0.9 Radiation: 0.6 + +- type: entity + parent: ClothingHeadHardsuitBase + id: ClothingHeadHelmetHardsuitShadowkinDarkswap + noSpawn: true + name: darkened softsuit helmet + components: + - type: Sprite + sprite: Parkstation/Clothing/Head/Hardsuits/darkened.rsi + - type: Clothing + sprite: Parkstation/Clothing/Head/Hardsuits/darkened.rsi + - type: PointLight + color: "#d6adff" + - type: Armor + modifiers: + coefficients: + Blunt: 1.4 + Slash: 1.3 + Piercing: 1.2 + Radiation: 0.4 + - type: ClothingSpeedModifier + walkModifier: 0.5 + sprintModifier: 0.5 + - type: ClothingGrantComponent + component: + - type: ShadowkinDarkSwapped + darken: false diff --git a/Resources/Prototypes/Parkstation/Entities/Clothing/Head/hardsuits.yml b/Resources/Prototypes/Parkstation/Entities/Clothing/Head/hardsuits.yml index 43f01db5ec..aaf97e3bb0 100644 --- a/Resources/Prototypes/Parkstation/Entities/Clothing/Head/hardsuits.yml +++ b/Resources/Prototypes/Parkstation/Entities/Clothing/Head/hardsuits.yml @@ -26,3 +26,31 @@ damageCoefficient: 0.8 - type: ToggleableClothing clothingPrototype: ClothingHeadHelmetHardsuitHoP + +- type: entity + parent: ClothingOuterHardsuitBase + id: ClothingOuterHardsuitShadowkinDarkswap + name: darkened softsuit + description: This hardsuit allows the wearer to jump the gap between the "normal" dimension and The Dark. + components: + - type: Sprite + sprite: Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi + - type: Clothing + sprite: Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi + - type: Armor + modifiers: + coefficients: + Blunt: 0.9 + Slash: 0.8 + Piercing: 0.9 + Radiation: 0.3 + - type: ClothingSpeedModifier + walkModifier: 0.9 + sprintModifier: 0.9 + - type: Tag + tags: + - FullBodyOuter + - Hardsuit + - type: ToggleableClothing + clothingPrototype: ClothingHeadHelmetHardsuitShadowkinDarkswap + action: ShadowkinDarkSwapHardsuitToggle diff --git a/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/tails.yml b/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/tails.yml index a665f455fb..c16c3954ba 100644 --- a/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/tails.yml +++ b/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/tails.yml @@ -181,4 +181,3 @@ sprites: - sprite: Parkstation/Mobs/Customization/tails32x32.rsi state: shadowkin_medium - diff --git a/Resources/Prototypes/Parkstation/Entities/Mobs/Player/shadowkin.yml b/Resources/Prototypes/Parkstation/Entities/Mobs/Player/shadowkin.yml new file mode 100644 index 0000000000..916bf83767 --- /dev/null +++ b/Resources/Prototypes/Parkstation/Entities/Mobs/Player/shadowkin.yml @@ -0,0 +1,194 @@ +- type: entity + save: false + parent: BaseMobHuman + id: MobShadowkin + name: Urist McFloof + components: + - type: HumanoidAppearance + species: Shadowkin + initial: Shadowkin + - type: Hunger + - type: Thirst + - type: Icon + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: full + - type: Body + prototype: Shadowkin + requiredLegs: 2 + # - type: DiseaseCarrier + # diseaseResist: 0.1 + - type: Flammable + # damage: + # types: + # Heat: 1.5 # burn more + - type: MobState + - 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: Respirator # this is a temporary fix, shadowkin should not be BaseMobHuman nor MobRespirator + maxSaturation: 5.0 + minSaturation: 5.0 # shadowkin never lose saturation, so they basically don't breath + cycleDelay: 0.0 # this should set all multipliers in the respirator system to zero + damage: + types: + Asphyxiation: 0 # just in case, no airloss damage from respirator component, recovery rate is unchanged + - type: Bloodstream + bloodlossDamage: + types: + Bloodloss: + 1 + bloodlossHealDamage: + types: + Bloodloss: + -0.25 + - type: Tag + tags: + - CanPilot + - FootstepSound + - DoorBumpOpener + - 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 + 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"] + - 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"] + - map: ["enum.HumanoidVisualLayers.Wings"] + - type: Eye + zoom: "0.85, 0.85" + - type: MeleeWeapon + soundHit: + collection: Punch + animation: WeaponArcClaw + damage: + types: + Blunt: 2 + Slash: 3 + Piercing: 1 + - type: EmpathyChat + - type: Shadowkin + - type: ShadowkinDarkSwapPower + - type: ShadowkinRestPower + - type: ShadowkinTeleportPower + - type: Vocal + sounds: + Male: MaleSlime + Female: FemaleSlime + Unsexed: MaleSlime + - type: CombatMode + canDisarm: true + - type: MindContainer + showExamineInfo: true + - type: Input + context: "human" + - type: MobMover + - type: InputMover + - type: Alerts + - type: Actions + - type: CameraRecoil + - type: Examiner + - type: CanHostGuardian + - type: NpcFactionMember + factions: + - NanoTrasen + - type: InteractionPopup + successChance: 0.75 + interactFailureString: petting-failure-generic + interactSuccessString: petting-success-soft-floofy + interactSuccessSound: /Audio/Effects/thudswoosh.ogg + messagePerceivedByOthers: petting-success-soft-floofy-others + - type: PotentialPsionic + chance: -2 # They have their own abilities. + - type: MovementSpeedModifier + + +- 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 diff --git a/Resources/Prototypes/Parkstation/Entities/Objects/Fun/toys.yml b/Resources/Prototypes/Parkstation/Entities/Objects/Fun/toys.yml new file mode 100644 index 0000000000..b0bc44e90a --- /dev/null +++ b/Resources/Prototypes/Parkstation/Entities/Objects/Fun/toys.yml @@ -0,0 +1,14 @@ +- type: entity + parent: BasePlushie + id: PlushieShadowkin + name: shadowkin plushie + description: A plushie of a Shadowkin. It's very soft. + components: + - type: ShadowkinDarkSwapped + invisible: false + pacify: false + darken: true + range: 4 + - type: Sprite + sprite: Parkstation/Objects/Fun/Plushies/shadowkin.rsi + state: shadowkin diff --git a/Resources/Prototypes/Parkstation/Entities/Objects/Misc/handcuffs.yml b/Resources/Prototypes/Parkstation/Entities/Objects/Misc/handcuffs.yml new file mode 100644 index 0000000000..792a745205 --- /dev/null +++ b/Resources/Prototypes/Parkstation/Entities/Objects/Misc/handcuffs.yml @@ -0,0 +1,16 @@ +- type: entity + parent: Handcuffs + id: HandcuffsShadowkin + 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: Handcuff + cuffedRSI: Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi + breakoutTime: 40 + cuffTime: 4 + uncuffTime: 5 + stunBonus: 4 + antiShadowkin: true + - type: Sprite + sprite: Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi + state: icon diff --git a/Resources/Prototypes/Parkstation/HumanoidProfiles/shadowkin.yml b/Resources/Prototypes/Parkstation/HumanoidProfiles/shadowkin.yml new file mode 100644 index 0000000000..1093b4f2c7 --- /dev/null +++ b/Resources/Prototypes/Parkstation/HumanoidProfiles/shadowkin.yml @@ -0,0 +1,6 @@ +- type: humanoidProfile + id: Shadowkin + profile: + species: Shadowkin + height: 0.85 + width: 0.85 diff --git a/Resources/Prototypes/Parkstation/Magic/shadowkin.yml b/Resources/Prototypes/Parkstation/Magic/shadowkin.yml new file mode 100644 index 0000000000..f33d94d59b --- /dev/null +++ b/Resources/Prototypes/Parkstation/Magic/shadowkin.yml @@ -0,0 +1,71 @@ +- type: entity + id: ShadowkinTeleportAction + noSpawn: true + name: action-name-shadowkin-teleport + description: action-description-shadowkin-teleport + components: + - type: WorldTargetAction + useDelay: 5 + range: 32 + itemIconStyle: NoItem + icon: + sprite: Parkstation/Interface/Actions/shadowkin_icons.rsi + state: teleport + checkCanAccess: true + repeat: true + priority: -20 + event: !type:ShadowkinTeleportEvent + powerCost: 40 + staminaCost: 20 + speech: action-description-shadowkin-teleport + +- type: entity + id: ShadowkinDarkSwapAction + noSpawn: true + name: action-name-shadowkin-darkswap + description: action-description-shadowkin-darkswap + components: + - type: InstantAction + useDelay: 15 + itemIconStyle: NoItem + priority: -21 + icon: + sprite: Parkstation/Interface/Actions/shadowkin_icons.rsi + state: darkswap + event: !type:ShadowkinDarkSwapEvent + powerCostOn: 60 + powerCostOff: 45 + staminaCostOn: 25 + staminaCostOff: 25 + speech: action-description-shadowkin-darkswap + +- type: entity + id: ShadowkinRestAction + noSpawn: true + name: action-name-shadowkin-rest + description: action-description-shadowkin-rest + components: + - type: InstantAction + useDelay: 60 + itemIconStyle: NoItem + priority: -22 + icon: + sprite: Parkstation/Interface/Actions/shadowkin_icons.rsi + state: rest + checkCanInteract: false + checkConsciousness: false + event: !type:ShadowkinRestEvent + +- type: entity + id: ShadowkinDarkSwapHardsuitToggle + noSpawn: true + name: action-name-shadowkin-darkswap + components: + - type: InstantAction + useDelay: 15 + itemIconStyle: NoItem + priority: -21 + icon: + sprite: Parkstation/Clothing/Head/Hardsuits/darkened.rsi + state: icon + event: !type:ToggleClothingEvent diff --git a/Resources/Prototypes/Parkstation/Shaders/shaders.yml b/Resources/Prototypes/Parkstation/Shaders/shaders.yml new file mode 100644 index 0000000000..79e8ae2f8e --- /dev/null +++ b/Resources/Prototypes/Parkstation/Shaders/shaders.yml @@ -0,0 +1,9 @@ +- type: shader + id: ColorTint + kind: source + path: "/Textures/Parkstation/Shaders/color_tint.swsl" + +- type: shader + id: Ethereal + kind: source + path: "/Textures/Parkstation/Shaders/ethereal.swsl" diff --git a/Resources/Prototypes/Parkstation/Species/shadowkin.yml b/Resources/Prototypes/Parkstation/Species/shadowkin.yml new file mode 100644 index 0000000000..fd44829b3e --- /dev/null +++ b/Resources/Prototypes/Parkstation/Species/shadowkin.yml @@ -0,0 +1,161 @@ +- 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 + +- type: speciesBaseSprites + id: MobShadowkinSprites + sprites: + Head: MobShadowkinHead + Hair: MobShadowkinAnyMarking + FacialHair: MobShadowkinAnyMarking + Snout: MobShadowkinAnyMarking + HeadTop: MobShadowkinAnyMarking + HeadSide: MobShadowkinAnyMarking + Wings: MobShadowkinAnyMarking + Tail: MobShadowkinAnyMarking + 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 + Legs: + points: 2 + required: false + Arms: + points: 2 + required: false + +- type: humanoidBaseSprite + id: MobShadowkinAnyMarking + +- type: humanoidBaseSprite + id: MobShadowkinAnyMarkingFollowSkin + markingsMatchSkin: true + +- type: humanoidBaseSprite + id: MobShadowkinHead + baseSprite: + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: head_m + +- type: humanoidBaseSprite + id: MobShadowkinHeadMale + baseSprite: + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: head_m + +- type: humanoidBaseSprite + id: MobShadowkinHeadFemale + baseSprite: + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: head_f + +- type: humanoidBaseSprite + id: MobShadowkinTorso + baseSprite: + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: torso_m + +- type: humanoidBaseSprite + id: MobShadowkinTorsoMale + baseSprite: + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: torso_m + +- type: humanoidBaseSprite + id: MobShadowkinTorsoFemale + baseSprite: + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: torso_f + +- type: humanoidBaseSprite + id: MobShadowkinLLeg + baseSprite: + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: l_leg + +- type: humanoidBaseSprite + id: MobShadowkinLHand + baseSprite: + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: l_hand + +- type: humanoidBaseSprite + id: MobShadowkinEyes + baseSprite: + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: eyes + +- type: humanoidBaseSprite + id: MobShadowkinLArm + baseSprite: + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: l_arm + +- type: humanoidBaseSprite + id: MobShadowkinLFoot + baseSprite: + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: l_foot + +- type: humanoidBaseSprite + id: MobShadowkinRLeg + baseSprite: + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: r_leg + +- type: humanoidBaseSprite + id: MobShadowkinRHand + baseSprite: + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: r_hand + +- type: humanoidBaseSprite + id: MobShadowkinRArm + baseSprite: + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: r_arm + +- type: humanoidBaseSprite + id: MobShadowkinRFoot + baseSprite: + sprite: Parkstation/Mobs/Species/Shadowkin/parts.rsi + state: r_foot diff --git a/Resources/Prototypes/Parkstation/Traits/disabilities.yml b/Resources/Prototypes/Parkstation/Traits/disabilities.yml index b7aa00353c..2ae402e5c7 100644 --- a/Resources/Prototypes/Parkstation/Traits/disabilities.yml +++ b/Resources/Prototypes/Parkstation/Traits/disabilities.yml @@ -10,3 +10,14 @@ - MedicalBorg components: - type: Nearsighted + +- type: trait + id: ShadowkinBlackeye + points: 5 + category: Negative + requirements: + - !type:CharacterSpeciesRequirement + species: + - Shadowkin + components: + - type: ShadowkinBlackeyeTrait diff --git a/Resources/Prototypes/Parkstation/factions.yml b/Resources/Prototypes/Parkstation/factions.yml new file mode 100644 index 0000000000..340a8ed8da --- /dev/null +++ b/Resources/Prototypes/Parkstation/factions.yml @@ -0,0 +1,9 @@ +- type: npcFaction + id: ShadowkinDarkHostile + hostile: + - ShadowkinDarkFriendly + +- type: npcFaction + id: ShadowkinDarkFriendly + hostile: + - ShadowkinDarkHostile diff --git a/Resources/Prototypes/Reagents/toxins.yml b/Resources/Prototypes/Reagents/toxins.yml index 661e1b7dd1..30cb3893a1 100644 --- a/Resources/Prototypes/Reagents/toxins.yml +++ b/Resources/Prototypes/Reagents/toxins.yml @@ -512,6 +512,14 @@ shouldHave: true reagent: Protein amount: 0.5 + # Parkstation-Shadowkin Start + - !type:AdjustReagent + conditions: + - !type:OrganType + type: Shadowkin + reagent: Protein + amount: 0.5 + # Parkstation-Shadowkin End - type: reagent id: Allicin diff --git a/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/meta.json b/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/meta.json index 89a44c1cfd..1cc52acb4d 100644 --- a/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/meta.json +++ b/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/meta.json @@ -83,6 +83,16 @@ "name": "tail_ltiger", "directions": 4 }, + { + "name": "tail_smooth_wagging", + "directions": 4, + "delays": [ + [0.125, 0.1, 0.075, 0.075, 0.075, 0.075, 0.075, 0.1, 0.125, 0.1, 0.075, 0.075, 0.075, 0.075, 0.075, 0.1], + [0.125, 0.1, 0.075, 0.075, 0.075, 0.075, 0.075, 0.1, 0.125, 0.1, 0.075, 0.075, 0.075, 0.075, 0.075, 0.1], + [0.125, 0.1, 0.075, 0.075, 0.075, 0.075, 0.075, 0.1, 0.125, 0.1, 0.075, 0.075, 0.075, 0.075, 0.075, 0.1], + [0.125, 0.1, 0.075, 0.075, 0.075, 0.075, 0.075, 0.1, 0.125, 0.1, 0.075, 0.075, 0.075, 0.075, 0.075, 0.1] + ] + }, { "name": "tail_smooth_wagging_primary", "directions": 4, diff --git a/Resources/Textures/Parkstation/Clothing/Eyes/Glasses/darkened.rsi/equipped-EYES.png b/Resources/Textures/Parkstation/Clothing/Eyes/Glasses/darkened.rsi/equipped-EYES.png new file mode 100644 index 0000000000..39fafeb144 Binary files /dev/null and b/Resources/Textures/Parkstation/Clothing/Eyes/Glasses/darkened.rsi/equipped-EYES.png differ diff --git a/Resources/Textures/Parkstation/Clothing/Eyes/Glasses/darkened.rsi/icon.png b/Resources/Textures/Parkstation/Clothing/Eyes/Glasses/darkened.rsi/icon.png new file mode 100644 index 0000000000..8ef5aa68d0 Binary files /dev/null and b/Resources/Textures/Parkstation/Clothing/Eyes/Glasses/darkened.rsi/icon.png differ diff --git a/Resources/Textures/Parkstation/Clothing/Eyes/Glasses/darkened.rsi/inhand-left.png b/Resources/Textures/Parkstation/Clothing/Eyes/Glasses/darkened.rsi/inhand-left.png new file mode 100644 index 0000000000..7ecbd93e8a Binary files /dev/null and b/Resources/Textures/Parkstation/Clothing/Eyes/Glasses/darkened.rsi/inhand-left.png differ diff --git a/Resources/Textures/Parkstation/Clothing/Eyes/Glasses/darkened.rsi/inhand-right.png b/Resources/Textures/Parkstation/Clothing/Eyes/Glasses/darkened.rsi/inhand-right.png new file mode 100644 index 0000000000..26fd8e57a4 Binary files /dev/null and b/Resources/Textures/Parkstation/Clothing/Eyes/Glasses/darkened.rsi/inhand-right.png differ diff --git a/Resources/Textures/Parkstation/Clothing/Eyes/Glasses/darkened.rsi/meta.json b/Resources/Textures/Parkstation/Clothing/Eyes/Glasses/darkened.rsi/meta.json new file mode 100644 index 0000000000..555efbc7da --- /dev/null +++ b/Resources/Textures/Parkstation/Clothing/Eyes/Glasses/darkened.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/Parkstation/Clothing/Head/Hardsuits/darkened.rsi/equipped-HELMET.png b/Resources/Textures/Parkstation/Clothing/Head/Hardsuits/darkened.rsi/equipped-HELMET.png new file mode 100644 index 0000000000..f6599f7bd4 Binary files /dev/null and b/Resources/Textures/Parkstation/Clothing/Head/Hardsuits/darkened.rsi/equipped-HELMET.png differ diff --git a/Resources/Textures/Parkstation/Clothing/Head/Hardsuits/darkened.rsi/icon.png b/Resources/Textures/Parkstation/Clothing/Head/Hardsuits/darkened.rsi/icon.png new file mode 100644 index 0000000000..94d9e03b82 Binary files /dev/null and b/Resources/Textures/Parkstation/Clothing/Head/Hardsuits/darkened.rsi/icon.png differ diff --git a/Resources/Textures/Parkstation/Clothing/Head/Hardsuits/darkened.rsi/meta.json b/Resources/Textures/Parkstation/Clothing/Head/Hardsuits/darkened.rsi/meta.json new file mode 100644 index 0000000000..66589a849a --- /dev/null +++ b/Resources/Textures/Parkstation/Clothing/Head/Hardsuits/darkened.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "JustAnOrange", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000..c93eb1bc51 Binary files /dev/null and b/Resources/Textures/Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi/equipped-OUTERCLOTHING.png differ diff --git a/Resources/Textures/Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi/icon.png b/Resources/Textures/Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi/icon.png new file mode 100644 index 0000000000..4aa2067759 Binary files /dev/null and b/Resources/Textures/Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi/icon.png differ diff --git a/Resources/Textures/Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi/inhand-left.png b/Resources/Textures/Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi/inhand-left.png new file mode 100644 index 0000000000..6872735699 Binary files /dev/null and b/Resources/Textures/Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi/inhand-left.png differ diff --git a/Resources/Textures/Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi/inhand-right.png b/Resources/Textures/Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi/inhand-right.png new file mode 100644 index 0000000000..37b71b2cd3 Binary files /dev/null and b/Resources/Textures/Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi/inhand-right.png differ diff --git a/Resources/Textures/Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi/meta.json b/Resources/Textures/Parkstation/Clothing/OuterClothing/Hardsuits/darkened.rsi/meta.json new file mode 100644 index 0000000000..57a0b994f9 --- /dev/null +++ b/Resources/Textures/Parkstation/Clothing/OuterClothing/Hardsuits/darkened.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/Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi/body-overlay-2.png b/Resources/Textures/Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi/body-overlay-2.png new file mode 100644 index 0000000000..e8618fc6b7 Binary files /dev/null and b/Resources/Textures/Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi/body-overlay-2.png differ diff --git a/Resources/Textures/Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi/icon.png b/Resources/Textures/Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi/icon.png new file mode 100644 index 0000000000..def3161bb0 Binary files /dev/null and b/Resources/Textures/Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi/icon.png differ diff --git a/Resources/Textures/Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi/inhand-left.png b/Resources/Textures/Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi/inhand-left.png new file mode 100644 index 0000000000..5c15def766 Binary files /dev/null and b/Resources/Textures/Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi/inhand-left.png differ diff --git a/Resources/Textures/Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi/inhand-right.png b/Resources/Textures/Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi/inhand-right.png new file mode 100644 index 0000000000..99c96d368a Binary files /dev/null and b/Resources/Textures/Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi/inhand-right.png differ diff --git a/Resources/Textures/Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi/meta.json b/Resources/Textures/Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi/meta.json new file mode 100644 index 0000000000..365122e186 --- /dev/null +++ b/Resources/Textures/Parkstation/Clothing/OuterClothing/Misc/shadowkin_restraints.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "JustAnOrange", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "body-overlay-2", + "directions": 4 + }, + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Parkstation/Interface/Actions/shadowkin_icons.rsi/darkswap.png b/Resources/Textures/Parkstation/Interface/Actions/shadowkin_icons.rsi/darkswap.png new file mode 100644 index 0000000000..3c2815e8c2 Binary files /dev/null and b/Resources/Textures/Parkstation/Interface/Actions/shadowkin_icons.rsi/darkswap.png differ diff --git a/Resources/Textures/Parkstation/Interface/Actions/shadowkin_icons.rsi/meta.json b/Resources/Textures/Parkstation/Interface/Actions/shadowkin_icons.rsi/meta.json new file mode 100644 index 0000000000..0cc0b55cd1 --- /dev/null +++ b/Resources/Textures/Parkstation/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": "DSC@DEATHB4DEFEAT#4404 (801294818839756811)", + "states": [ + { + "name": "darkswap", + "directions": 1 + }, + { + "name": "rest", + "directions": 1 + }, + { + "name": "teleport", + "directions": 1 + } + ] +} diff --git a/Resources/Textures/Parkstation/Interface/Actions/shadowkin_icons.rsi/rest.png b/Resources/Textures/Parkstation/Interface/Actions/shadowkin_icons.rsi/rest.png new file mode 100644 index 0000000000..188b4ec591 Binary files /dev/null and b/Resources/Textures/Parkstation/Interface/Actions/shadowkin_icons.rsi/rest.png differ diff --git a/Resources/Textures/Parkstation/Interface/Actions/shadowkin_icons.rsi/teleport.png b/Resources/Textures/Parkstation/Interface/Actions/shadowkin_icons.rsi/teleport.png new file mode 100644 index 0000000000..b0f1bd8dc8 Binary files /dev/null and b/Resources/Textures/Parkstation/Interface/Actions/shadowkin_icons.rsi/teleport.png differ diff --git a/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/meta.json b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/meta.json new file mode 100644 index 0000000000..7dcfe2a004 --- /dev/null +++ b/Resources/Textures/Parkstation/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": "DSC@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/Parkstation/Interface/Alerts/shadowkin_power.rsi/power0.png b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power0.png new file mode 100644 index 0000000000..ab370f753e Binary files /dev/null and b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power0.png differ diff --git a/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power1.png b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power1.png new file mode 100644 index 0000000000..d72965eeec Binary files /dev/null and b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power1.png differ diff --git a/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power2.png b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power2.png new file mode 100644 index 0000000000..1b2c51575c Binary files /dev/null and b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power2.png differ diff --git a/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power3.png b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power3.png new file mode 100644 index 0000000000..0f93f925ac Binary files /dev/null and b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power3.png differ diff --git a/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power4.png b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power4.png new file mode 100644 index 0000000000..3f3035da0d Binary files /dev/null and b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power4.png differ diff --git a/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power5.png b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power5.png new file mode 100644 index 0000000000..af3f716861 Binary files /dev/null and b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power5.png differ diff --git a/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power6.png b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power6.png new file mode 100644 index 0000000000..9f7957c44f Binary files /dev/null and b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power6.png differ diff --git a/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power7.png b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power7.png new file mode 100644 index 0000000000..ac9f17f55b Binary files /dev/null and b/Resources/Textures/Parkstation/Interface/Alerts/shadowkin_power.rsi/power7.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/appendix.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/appendix.png new file mode 100644 index 0000000000..0d2ad309c7 Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/appendix.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/brain.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/brain.png new file mode 100644 index 0000000000..ac2806b79c Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/brain.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/core.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/core.png new file mode 100644 index 0000000000..ac2d7893fd Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/core.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/ears.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/ears.png new file mode 100644 index 0000000000..6ff3ac86b7 Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/ears.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/eyes.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/eyes.png new file mode 100644 index 0000000000..f7c0a306aa Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/eyes.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/heart.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/heart.png new file mode 100644 index 0000000000..1b79b529ae Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/heart.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/kidneys.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/kidneys.png new file mode 100644 index 0000000000..482bb24102 Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/kidneys.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/liver.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/liver.png new file mode 100644 index 0000000000..0a2e6ab25a Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/liver.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/lungs.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/lungs.png new file mode 100644 index 0000000000..a76c9fc1eb Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/lungs.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/meta.json b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/meta.json new file mode 100644 index 0000000000..1c9aebfb6d --- /dev/null +++ b/Resources/Textures/Parkstation/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/Parkstation/Mobs/Species/Shadowkin/organs.rsi/stomach.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/stomach.png new file mode 100644 index 0000000000..a0341750d3 Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/stomach.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/tongue.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/tongue.png new file mode 100644 index 0000000000..64306900f5 Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/organs.rsi/tongue.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/eyes.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/eyes.png new file mode 100644 index 0000000000..20fd326f17 Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/eyes.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/full.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/full.png new file mode 100644 index 0000000000..8ebd75032e Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/full.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/head_f.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/head_f.png new file mode 100644 index 0000000000..9d99bd1890 Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/head_f.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/head_m.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/head_m.png new file mode 100644 index 0000000000..9d99bd1890 Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/head_m.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/l_arm.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/l_arm.png new file mode 100644 index 0000000000..078ca333f6 Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/l_arm.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/l_foot.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/l_foot.png new file mode 100644 index 0000000000..30ad69dc3f Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/l_foot.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/l_hand.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/l_hand.png new file mode 100644 index 0000000000..0cbc7371d1 Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/l_hand.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/l_leg.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/l_leg.png new file mode 100644 index 0000000000..b961dfc842 Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/l_leg.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/meta.json b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/meta.json new file mode 100644 index 0000000000..c4f9c9c10f --- /dev/null +++ b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/meta.json @@ -0,0 +1,67 @@ +{ + "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": "eyes", + "directions": 4 + }, + { + "name": "full", + "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 + } + ] +} diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/r_arm.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/r_arm.png new file mode 100644 index 0000000000..c294120942 Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/r_arm.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/r_foot.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/r_foot.png new file mode 100644 index 0000000000..390e0a27ee Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/r_foot.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/r_hand.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/r_hand.png new file mode 100644 index 0000000000..331e33a587 Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/r_hand.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/r_leg.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/r_leg.png new file mode 100644 index 0000000000..6b0270f634 Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/r_leg.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/torso_f.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/torso_f.png new file mode 100644 index 0000000000..83cc63cdd2 Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/torso_f.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/torso_m.png b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/torso_m.png new file mode 100644 index 0000000000..dafc83b65e Binary files /dev/null and b/Resources/Textures/Parkstation/Mobs/Species/Shadowkin/parts.rsi/torso_m.png differ diff --git a/Resources/Textures/Parkstation/Objects/Fun/Plushies/shadowkin.rsi/meta.json b/Resources/Textures/Parkstation/Objects/Fun/Plushies/shadowkin.rsi/meta.json new file mode 100644 index 0000000000..1c02ef69e9 --- /dev/null +++ b/Resources/Textures/Parkstation/Objects/Fun/Plushies/shadowkin.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by DSC@Cabbage#9633 (561159087765848084)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "shadowkin" + } + ] +} diff --git a/Resources/Textures/Parkstation/Objects/Fun/Plushies/shadowkin.rsi/shadowkin.png b/Resources/Textures/Parkstation/Objects/Fun/Plushies/shadowkin.rsi/shadowkin.png new file mode 100644 index 0000000000..df4118ecd4 Binary files /dev/null and b/Resources/Textures/Parkstation/Objects/Fun/Plushies/shadowkin.rsi/shadowkin.png differ diff --git a/Resources/Textures/Parkstation/Shaders/color_tint.swsl b/Resources/Textures/Parkstation/Shaders/color_tint.swsl new file mode 100644 index 0000000000..f95011cefa --- /dev/null +++ b/Resources/Textures/Parkstation/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; +} diff --git a/Resources/Textures/Parkstation/Shaders/ethereal.swsl b/Resources/Textures/Parkstation/Shaders/ethereal.swsl new file mode 100644 index 0000000000..45dcb9b6ac --- /dev/null +++ b/Resources/Textures/Parkstation/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 *= (3.0 + 1 * 2.0); + + highp vec4 background = zTextureSpec(SCREEN_TEXTURE, ( FRAGCOORD.xy + vec2(w) ) * SCREEN_PIXEL_SIZE ); + highp vec3 hsvBg = rgb2hsv(background.rgb); + hsvBg.x *= -1; + background.rgb = hsv2rgb(hsvBg); + + color.xyz = mix(background.xyz, color.xyz, 0.75); + + + + COLOR = color; +}