diff --git a/Content.Client/Backmen/Chat/PsionicChatUpdateSystem.cs b/Content.Client/Backmen/Chat/PsionicChatUpdateSystem.cs new file mode 100644 index 00000000000..cc74a1c6fd5 --- /dev/null +++ b/Content.Client/Backmen/Chat/PsionicChatUpdateSystem.cs @@ -0,0 +1,31 @@ +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Client.Chat.Managers; +using Robust.Client.Player; + +namespace Content.Client.Backmen.Chat; + +public sealed class PsionicChatUpdateSystem : EntitySystem +{ + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnRemove); + } + + public PsionicComponent? Player => CompOrNull(_playerManager.LocalPlayer?.ControlledEntity); + public bool IsPsionic => Player != null; + + private void OnInit(EntityUid uid, PsionicComponent component, ComponentInit args) + { + _chatManager.UpdatePermissions(); + } + + private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove args) + { + _chatManager.UpdatePermissions(); + } +} diff --git a/Content.Client/Backmen/EntityHealthBar/EntityHealthBarOverlay.cs b/Content.Client/Backmen/EntityHealthBar/EntityHealthBarOverlay.cs new file mode 100644 index 00000000000..3e75b44535a --- /dev/null +++ b/Content.Client/Backmen/EntityHealthBar/EntityHealthBarOverlay.cs @@ -0,0 +1,158 @@ +using System.Numerics; +using Content.Shared.Damage; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.FixedPoint; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Client.Backmen.EntityHealthBar; + +/// +/// Yeah a lot of this is duplicated from doafters. +/// Not much to be done until there's a generic HUD system +/// +public sealed class EntityHealthBarOverlay : Overlay +{ + private readonly IEntityManager _entManager; + private readonly SharedTransformSystem _transform; + private readonly MobStateSystem _mobStateSystem; + private readonly MobThresholdSystem _mobThresholdSystem; + private readonly Texture _barTexture; + private readonly ShaderInstance _shader; + public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; + public List DamageContainers = new(); + + public EntityHealthBarOverlay(IEntityManager entManager, IPrototypeManager protoManager) + { + _entManager = entManager; + _transform = _entManager.EntitySysManager.GetEntitySystem(); + _mobStateSystem = _entManager.EntitySysManager.GetEntitySystem(); + _mobThresholdSystem = _entManager.EntitySysManager.GetEntitySystem(); + + var sprite = new SpriteSpecifier.Rsi(new ("/Textures/Interface/Misc/health_bar.rsi"), "icon"); + _barTexture = _entManager.EntitySysManager.GetEntitySystem().Frame0(sprite); + + _shader = protoManager.Index("unshaded").Instance(); + } + + protected override void Draw(in OverlayDrawArgs args) + { + var handle = args.WorldHandle; + var rotation = args.Viewport.Eye?.Rotation ?? Angle.Zero; + var spriteQuery = _entManager.GetEntityQuery(); + var xformQuery = _entManager.GetEntityQuery(); + + const float scale = 1f; + var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale)); + var rotationMatrix = Matrix3.CreateRotation(-rotation); + handle.UseShader(_shader); + + foreach (var (thresholds, mob, dmg) in _entManager.EntityQuery(true)) + { + if (!xformQuery.TryGetComponent(mob.Owner, out var xform) || + xform.MapID != args.MapId) + { + continue; + } + + if (dmg.DamageContainerID == null || !DamageContainers.Contains(dmg.DamageContainerID)) + continue; + + var worldPosition = _transform.GetWorldPosition(xform); + var worldMatrix = Matrix3.CreateTranslation(worldPosition); + + Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld); + Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty); + + handle.SetTransform(matty); + + float yOffset; + if (spriteQuery.TryGetComponent(mob.Owner, out var sprite)) + { + yOffset = sprite.Bounds.Height + 15f; + } + else + { + yOffset = 1f; + } + + var position = new Vector2(-_barTexture.Width / 2f / EyeManager.PixelsPerMeter, + yOffset / EyeManager.PixelsPerMeter); + + // Draw the underlying bar texture + handle.DrawTexture(_barTexture, position); + + // we are all progressing towards death every day + (float ratio, bool inCrit) deathProgress = CalcProgress(mob.Owner, mob, dmg, thresholds); + + var color = GetProgressColor(deathProgress.ratio, deathProgress.inCrit); + + // Hardcoded width of the progress bar because it doesn't match the texture. + const float startX = 2f; + const float endX = 22f; + + var xProgress = (endX - startX) * deathProgress.ratio + startX; + + var box = new Box2(new Vector2(startX, 3f) / EyeManager.PixelsPerMeter, new Vector2(xProgress, 4f) / EyeManager.PixelsPerMeter); + box = box.Translated(position); + handle.DrawRect(box, color); + } + + handle.UseShader(null); + handle.SetTransform(Matrix3.Identity); + } + + /// + /// Returns a ratio between 0 and 1, and whether the entity is in crit. + /// + private (float, bool) CalcProgress(EntityUid uid, MobStateComponent component, DamageableComponent dmg, MobThresholdsComponent thresholds) + { + if (_mobStateSystem.IsAlive(uid, component)) + { + if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var threshold, thresholds)) + return (1, false); + + var ratio = 1 - ((FixedPoint2)(dmg.TotalDamage / threshold)).Float(); + return (ratio, false); + } + + if (_mobStateSystem.IsCritical(uid, component)) + { + if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var critThreshold, thresholds) || + !_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Dead, out var deadThreshold, thresholds)) + { + return (1, true); + } + + var ratio = 1 - + ((dmg.TotalDamage - critThreshold) / + (deadThreshold - critThreshold)).Value.Float(); + + return (ratio, true); + } + + return (0, true); + } + + public static Color GetProgressColor(float progress, bool crit) + { + if (progress >= 1.0f) + { + return new Color(0f, 1f, 0f); + } + // lerp + if (!crit) + { + var hue = (5f / 18f) * progress; + return Color.FromHsv((hue, 1f, 0.75f, 1f)); + } else + { + return Color.Red; + } + } +} diff --git a/Content.Client/Backmen/EntityHealthBar/ShowHealthBarsSystem.cs b/Content.Client/Backmen/EntityHealthBar/ShowHealthBarsSystem.cs new file mode 100644 index 00000000000..0bdffe259cd --- /dev/null +++ b/Content.Client/Backmen/EntityHealthBar/ShowHealthBarsSystem.cs @@ -0,0 +1,68 @@ +using Content.Shared.Backmen.EntityHealthBar; +using Content.Shared.Backmen.EntityHealthBar; +using Content.Shared.GameTicking; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Prototypes; + +namespace Content.Client.Backmen.EntityHealthBar; + +public sealed class ShowHealthBarsSystem : EntitySystem +{ + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; + [Dependency] private readonly IOverlayManager _overlayMan = default!; + + private EntityHealthBarOverlay _overlay = default!; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + SubscribeLocalEvent(OnRoundRestart); + + _overlay = new(EntityManager, _protoMan); + } + + private void OnInit(EntityUid uid, ShowHealthBarsComponent component, ComponentInit args) + { + if (_player.LocalPlayer?.ControlledEntity == uid) + { + ApplyOverlays(component); + } + } + + private void OnRemove(EntityUid uid, ShowHealthBarsComponent component, ComponentRemove args) + { + if (_player.LocalPlayer?.ControlledEntity == uid) + { + _overlayMan.RemoveOverlay(_overlay); + } + } + + private void OnPlayerAttached(EntityUid uid, ShowHealthBarsComponent component, PlayerAttachedEvent args) + { + ApplyOverlays(component); + } + + private void ApplyOverlays(ShowHealthBarsComponent component) + { + _overlayMan.AddOverlay(_overlay); + _overlay.DamageContainers.Clear(); + _overlay.DamageContainers.AddRange(component.DamageContainers); + } + + private void OnPlayerDetached(EntityUid uid, ShowHealthBarsComponent component, PlayerDetachedEvent args) + { + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnRoundRestart(RoundRestartCleanupEvent args) + { + _overlayMan.RemoveOverlay(_overlay); + } +} diff --git a/Content.Client/Backmen/Psionics/Glimmer/GlimmerReactiveVisuals.cs b/Content.Client/Backmen/Psionics/Glimmer/GlimmerReactiveVisuals.cs new file mode 100644 index 00000000000..9d1951ab3cc --- /dev/null +++ b/Content.Client/Backmen/Psionics/Glimmer/GlimmerReactiveVisuals.cs @@ -0,0 +1,6 @@ +namespace Content.Client.Psionics.Glimmer; + +public enum GlimmerReactiveVisualLayers : byte +{ + GlimmerEffect, +} diff --git a/Content.Client/Backmen/Psionics/UI/AcceptPsionicsEUI.cs b/Content.Client/Backmen/Psionics/UI/AcceptPsionicsEUI.cs new file mode 100644 index 00000000000..69eb94972bd --- /dev/null +++ b/Content.Client/Backmen/Psionics/UI/AcceptPsionicsEUI.cs @@ -0,0 +1,42 @@ +using Content.Client.Eui; +using Content.Shared.Backmen.Psionics; +using JetBrains.Annotations; +using Robust.Client.Graphics; + +namespace Content.Client.Psionics.UI +{ + [UsedImplicitly] + public sealed class AcceptPsionicsEui : BaseEui + { + private readonly AcceptPsionicsWindow _window; + + public AcceptPsionicsEui() + { + _window = new AcceptPsionicsWindow(); + + _window.DenyButton.OnPressed += _ => + { + SendMessage(new AcceptPsionicsChoiceMessage(AcceptPsionicsUiButton.Deny)); + _window.Close(); + }; + + _window.AcceptButton.OnPressed += _ => + { + SendMessage(new AcceptPsionicsChoiceMessage(AcceptPsionicsUiButton.Accept)); + _window.Close(); + }; + } + + public override void Opened() + { + IoCManager.Resolve().RequestWindowAttention(); + _window.OpenCentered(); + } + + public override void Closed() + { + _window.Close(); + } + + } +} diff --git a/Content.Client/Backmen/Psionics/UI/AcceptPsionicsWindow.cs b/Content.Client/Backmen/Psionics/UI/AcceptPsionicsWindow.cs new file mode 100644 index 00000000000..883d9f07972 --- /dev/null +++ b/Content.Client/Backmen/Psionics/UI/AcceptPsionicsWindow.cs @@ -0,0 +1,62 @@ +using System.Numerics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.Localization; +using static Robust.Client.UserInterface.Controls.BoxContainer; + +namespace Content.Client.Psionics.UI +{ + public sealed class AcceptPsionicsWindow : DefaultWindow + { + public readonly Button DenyButton; + public readonly Button AcceptButton; + + public AcceptPsionicsWindow() + { + + Title = Loc.GetString("accept-psionics-window-title"); + + Contents.AddChild(new BoxContainer + { + Orientation = LayoutOrientation.Vertical, + Children = + { + new BoxContainer + { + Orientation = LayoutOrientation.Vertical, + Children = + { + (new Label() + { + Text = Loc.GetString("accept-psionics-window-prompt-text-part") + }), + new BoxContainer + { + Orientation = LayoutOrientation.Horizontal, + Align = AlignMode.Center, + Children = + { + (AcceptButton = new Button + { + Text = Loc.GetString("accept-cloning-window-accept-button"), + }), + + (new Control() + { + MinSize = new Vector2(20, 0) + }), + + (DenyButton = new Button + { + Text = Loc.GetString("accept-cloning-window-deny-button"), + }) + } + }, + } + }, + } + }); + } + } +} diff --git a/Content.Client/Chat/Managers/ChatManager.cs b/Content.Client/Chat/Managers/ChatManager.cs index 67b5f5202f9..9bc86c0e950 100644 --- a/Content.Client/Chat/Managers/ChatManager.cs +++ b/Content.Client/Chat/Managers/ChatManager.cs @@ -15,6 +15,8 @@ internal sealed class ChatManager : IChatManager private ISawmill _sawmill = default!; + public event Action? PermissionsUpdated; + public void Initialize() { _sawmill = Logger.GetSawmill("chat"); @@ -67,9 +69,17 @@ public void SendMessage(string text, ChatSelectChannel channel) _consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(str)}\""); break; + case ChatSelectChannel.Telepathic: + _consoleHost.ExecuteCommand($"tsay \"{CommandParsing.Escape(str)}\""); + break; + default: throw new ArgumentOutOfRangeException(nameof(channel), channel, null); } } + public void UpdatePermissions() + { + PermissionsUpdated?.Invoke(); + } } } diff --git a/Content.Client/Chat/Managers/IChatManager.cs b/Content.Client/Chat/Managers/IChatManager.cs index 6464ca10196..bc590602b43 100644 --- a/Content.Client/Chat/Managers/IChatManager.cs +++ b/Content.Client/Chat/Managers/IChatManager.cs @@ -6,6 +6,8 @@ public interface IChatManager { void Initialize(); + event Action PermissionsUpdated; public void SendMessage(string text, ChatSelectChannel channel); + public void UpdatePermissions(); } } diff --git a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs index 19a3efdb921..365bdca4b41 100644 --- a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs +++ b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Numerics; using Content.Client.Administration.Managers; +using Content.Client.Backmen.Chat; // backmen: psionic using Content.Client.Chat; using Content.Client.Chat.Managers; using Content.Client.Chat.TypingIndicator; @@ -55,6 +56,7 @@ public sealed class ChatUIController : UIController [UISystemDependency] private readonly GhostSystem? _ghost = default; [UISystemDependency] private readonly TypingIndicatorSystem? _typingIndicator = default; [UISystemDependency] private readonly ChatSystem? _chatSys = default; + [UISystemDependency] private readonly PsionicChatUpdateSystem? _psionic = default!; // backmen: psionic private ISawmill _sawmill = default!; @@ -69,7 +71,8 @@ public sealed class ChatUIController : UIController {SharedChatSystem.EmotesAltPrefix, ChatSelectChannel.Emotes}, {SharedChatSystem.AdminPrefix, ChatSelectChannel.Admin}, {SharedChatSystem.RadioCommonPrefix, ChatSelectChannel.Radio}, - {SharedChatSystem.DeadPrefix, ChatSelectChannel.Dead} + {SharedChatSystem.DeadPrefix, ChatSelectChannel.Dead}, + {SharedChatSystem.TelepathicPrefix, ChatSelectChannel.Telepathic} // backmen: Psionic }; public static readonly Dictionary ChannelPrefixes = new() @@ -82,7 +85,8 @@ public sealed class ChatUIController : UIController {ChatSelectChannel.Emotes, SharedChatSystem.EmotesPrefix}, {ChatSelectChannel.Admin, SharedChatSystem.AdminPrefix}, {ChatSelectChannel.Radio, SharedChatSystem.RadioCommonPrefix}, - {ChatSelectChannel.Dead, SharedChatSystem.DeadPrefix} + {ChatSelectChannel.Dead, SharedChatSystem.DeadPrefix}, + {ChatSelectChannel.Telepathic, SharedChatSystem.TelepathicPrefix} // backmen: Psionic }; /// @@ -162,6 +166,7 @@ public override void Initialize() _sawmill = Logger.GetSawmill("chat"); _sawmill.Level = LogLevel.Info; _admin.AdminStatusUpdated += UpdateChannelPermissions; + _manager.PermissionsUpdated += UpdateChannelPermissions; //Backmen:Psionics _player.LocalPlayerChanged += OnLocalPlayerChanged; _state.OnStateChanged += StateChanged; _net.RegisterNetMessage(OnChatMessage); @@ -520,6 +525,14 @@ private void UpdateChannelPermissions() FilterableChannels |= ChatChannel.AdminAlert; FilterableChannels |= ChatChannel.AdminChat; CanSendChannels |= ChatSelectChannel.Admin; + FilterableChannels |= ChatChannel.Telepathic; + } + + // psionics + if (_psionic != null && _psionic.IsPsionic) + { + FilterableChannels |= ChatChannel.Telepathic; + CanSendChannels |= ChatSelectChannel.Telepathic; } SelectableChannels = CanSendChannels; diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs index 4a3b9aa568e..6c7706a57cf 100644 --- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs +++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs @@ -16,6 +16,7 @@ public sealed partial class ChannelFilterPopup : Popup ChatChannel.Whisper, ChatChannel.Emotes, ChatChannel.Radio, + ChatChannel.Telepathic, // backmen: Psionic ChatChannel.LOOC, ChatChannel.OOC, ChatChannel.Dead, diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorButton.cs b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorButton.cs index 96a7594ff6f..e3945d05826 100644 --- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorButton.cs +++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorButton.cs @@ -82,6 +82,7 @@ public Color ChannelSelectColor(ChatSelectChannel channel) ChatSelectChannel.OOC => Color.LightSkyBlue, ChatSelectChannel.Dead => Color.MediumPurple, ChatSelectChannel.Admin => Color.HotPink, + ChatSelectChannel.Telepathic => Color.PaleVioletRed, // backmen: Psionic _ => Color.DarkGray }; } diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs index 0852c10bb91..d92d37833a5 100644 --- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs +++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs @@ -13,6 +13,7 @@ public sealed class ChannelSelectorPopup : Popup ChatSelectChannel.Whisper, ChatSelectChannel.Emotes, ChatSelectChannel.Radio, + ChatSelectChannel.Telepathic, // backmen: Psionic ChatSelectChannel.LOOC, ChatSelectChannel.OOC, ChatSelectChannel.Dead, diff --git a/Content.Server/Backmen/Abilities/Psionics/Abilities/DispelPowerSystem.cs b/Content.Server/Backmen/Abilities/Psionics/Abilities/DispelPowerSystem.cs new file mode 100644 index 00000000000..9baf483aac5 --- /dev/null +++ b/Content.Server/Backmen/Abilities/Psionics/Abilities/DispelPowerSystem.cs @@ -0,0 +1,143 @@ +using Content.Shared.Actions; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.StatusEffect; +using Content.Shared.Damage; +using Content.Shared.Revenant.Components; +using Content.Server.Guardian; +using Content.Server.Bible.Components; +using Content.Server.Popups; +using Content.Shared.Backmen.Abilities.Psionics; +using Robust.Shared.Prototypes; +using Robust.Shared.Player; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server.Backmen.Abilities.Psionics; + +public sealed class DispelPowerSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly GuardianSystem _guardianSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + + SubscribeLocalEvent(OnDispelled); + SubscribeLocalEvent(OnDmgDispelled); + // Upstream stuff we're just gonna handle here + SubscribeLocalEvent(OnGuardianDispelled); + SubscribeLocalEvent(OnFamiliarDispelled); + SubscribeLocalEvent(OnRevenantDispelled); + } + + private void OnInit(EntityUid uid, DispelPowerComponent component, ComponentInit args) + { + if (!_prototypeManager.TryIndex("Dispel", out var action)) + return; + + component.DispelPowerAction = new EntityTargetAction(action); + if (action.UseDelay != null) + component.DispelPowerAction.Cooldown = (_gameTiming.CurTime, _gameTiming.CurTime + (TimeSpan) action.UseDelay); + _actions.AddAction(uid, component.DispelPowerAction, null); + + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + psionic.PsionicAbility = component.DispelPowerAction; + } + + private void OnShutdown(EntityUid uid, DispelPowerComponent component, ComponentShutdown args) + { + if (_prototypeManager.TryIndex("Dispel", out var action)) + _actions.RemoveAction(uid, new EntityTargetAction(action), null); + } + + private void OnPowerUsed(DispelPowerActionEvent args) + { + if (HasComp(args.Target)) + return; + + var ev = new DispelledEvent(); + RaiseLocalEvent(args.Target, ev, false); + + if (ev.Handled) + { + args.Handled = true; + _psionics.LogPowerUsed(args.Performer, "dispel"); + } + } + + private void OnDispelled(EntityUid uid, DispellableComponent component, DispelledEvent args) + { + QueueDel(uid); + Spawn("Ash", Transform(uid).Coordinates); + _popupSystem.PopupCoordinates(Loc.GetString("psionic-burns-up", ("item", uid)), Transform(uid).Coordinates, Filter.Pvs(uid), true, Shared.Popups.PopupType.MediumCaution); + _audioSystem.Play("/Audio/Effects/lightburn.ogg", Filter.Pvs(uid), uid, true); + args.Handled = true; + } + + private void OnDmgDispelled(EntityUid uid, DamageOnDispelComponent component, DispelledEvent args) + { + var damage = component.Damage; + var modifier = (1 + component.Variance) - (_random.NextFloat(0, component.Variance * 2)); + + damage *= modifier; + DealDispelDamage(uid, damage); + args.Handled = true; + } + + private void OnGuardianDispelled(EntityUid uid, GuardianComponent guardian, DispelledEvent args) + { + if (TryComp(guardian.Host, out var host)) + _guardianSystem.ToggleGuardian(guardian.Host, host); + + DealDispelDamage(uid); + args.Handled = true; + } + + private void OnFamiliarDispelled(EntityUid uid, FamiliarComponent component, DispelledEvent args) + { + if (component.Source != null) + EnsureComp(component.Source.Value); + + args.Handled = true; + } + + private void OnRevenantDispelled(EntityUid uid, RevenantComponent component, DispelledEvent args) + { + DealDispelDamage(uid); + _statusEffects.TryAddStatusEffect(uid, "Corporeal", TimeSpan.FromSeconds(30), false, "Corporeal"); + args.Handled = true; + } + + public void DealDispelDamage(EntityUid uid, DamageSpecifier? damage = null) + { + if (Deleted(uid)) + return; + + _popupSystem.PopupCoordinates(Loc.GetString("psionic-burn-resist", ("item", uid)), Transform(uid).Coordinates, Filter.Pvs(uid), true, Shared.Popups.PopupType.SmallCaution); + _audioSystem.Play("/Audio/Effects/lightburn.ogg", Filter.Pvs(uid), uid, true); + + if (damage == null) + { + damage = new(); + damage.DamageDict.Add("Blunt", 100); + } + _damageableSystem.TryChangeDamage(uid, damage, true, true); + } +} + +public sealed partial class DispelPowerActionEvent : EntityTargetActionEvent {} + +public sealed partial class DispelledEvent : HandledEntityEventArgs {} diff --git a/Content.Server/Backmen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs b/Content.Server/Backmen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs new file mode 100644 index 00000000000..7efb078b222 --- /dev/null +++ b/Content.Server/Backmen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs @@ -0,0 +1,70 @@ +using Content.Shared.Actions; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Shared.StatusEffect; +using Content.Shared.Popups; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Server.Backmen.Abilities.Psionics +{ + public sealed class MetapsionicPowerSystem : EntitySystem + { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedPopupSystem _popups = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + } + + private void OnInit(EntityUid uid, MetapsionicPowerComponent component, ComponentInit args) + { + if (!_prototypeManager.TryIndex("MetapsionicPulse", out var metapsionicPulse)) + return; + + component.MetapsionicPowerAction = new InstantAction(metapsionicPulse); + if (metapsionicPulse.UseDelay != null) + component.MetapsionicPowerAction.Cooldown = (_gameTiming.CurTime, _gameTiming.CurTime + (TimeSpan) metapsionicPulse.UseDelay); + _actions.AddAction(uid, component.MetapsionicPowerAction, null); + + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + psionic.PsionicAbility = component.MetapsionicPowerAction; + } + + private void OnShutdown(EntityUid uid, MetapsionicPowerComponent component, ComponentShutdown args) + { + if (_prototypeManager.TryIndex("MetapsionicPulse", out var metapsionicPulse)) + _actions.RemoveAction(uid, new InstantAction(metapsionicPulse), null); + } + + private void OnPowerUsed(EntityUid uid, MetapsionicPowerComponent component, MetapsionicPowerActionEvent args) + { + foreach (var entity in _lookup.GetEntitiesInRange(uid, component.Range)) + { + if (HasComp(entity) && entity != uid && !HasComp(entity) && + !(HasComp(entity) && Transform(entity).ParentUid == uid)) + { + _popups.PopupEntity(Loc.GetString("metapsionic-pulse-success"), uid, uid, PopupType.LargeCaution); + args.Handled = true; + return; + } + } + _popups.PopupEntity(Loc.GetString("metapsionic-pulse-failure"), uid, uid, PopupType.Large); + _psionics.LogPowerUsed(uid, "metapsionic pulse", 2, 4); + + args.Handled = true; + } + } + + public sealed partial class MetapsionicPowerActionEvent : InstantActionEvent {} +} diff --git a/Content.Server/Backmen/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs b/Content.Server/Backmen/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs new file mode 100644 index 00000000000..c07754bd8a7 --- /dev/null +++ b/Content.Server/Backmen/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs @@ -0,0 +1,243 @@ +using Content.Server.Backmen.Psionics; +using Content.Shared.Actions; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Speech; +using Content.Shared.Stealth.Components; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs; +using Content.Shared.Damage; +using Content.Server.Mind; +using Content.Shared.Mobs.Systems; +using Content.Server.Popups; +using Content.Server.GameTicking; +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Shared.Mind; +using Content.Shared.Mind.Components; +using Robust.Server.GameObjects; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Server.Backmen.Abilities.Psionics; + +public sealed class MindSwapPowerSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly ActorSystem _actorSystem = default!; + [Dependency] private readonly MetaDataSystem _metaDataSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + SubscribeLocalEvent(OnPowerReturned); + SubscribeLocalEvent(OnDispelled); + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnGhostAttempt); + // + SubscribeLocalEvent(OnSwapInit); + } + + private void OnInit(EntityUid uid, MindSwapPowerComponent component, ComponentInit args) + { + if (!_prototypeManager.TryIndex("MindSwap", out var mindSwap)) + return; + + component.MindSwapPowerAction = new EntityTargetAction(mindSwap); + if (mindSwap.UseDelay != null) + component.MindSwapPowerAction.Cooldown = (_gameTiming.CurTime, _gameTiming.CurTime + (TimeSpan) mindSwap.UseDelay); + _actions.AddAction(uid, component.MindSwapPowerAction, null); + + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + psionic.PsionicAbility = component.MindSwapPowerAction; + } + + private void OnShutdown(EntityUid uid, MindSwapPowerComponent component, ComponentShutdown args) + { + if (_prototypeManager.TryIndex("MindSwap", out var action)) + _actions.RemoveAction(uid, new EntityTargetAction(action), null); + } + + private void OnPowerUsed(MindSwapPowerActionEvent args) + { + if (!(TryComp(args.Target, out var damageable) && damageable.DamageContainerID == "Biological")) + return; + + if (HasComp(args.Target)) + return; + + Swap(args.Performer, args.Target); + + _psionics.LogPowerUsed(args.Performer, "mind swap"); + args.Handled = true; + } + + private void OnPowerReturned(EntityUid uid, MindSwappedComponent component, MindSwapPowerReturnActionEvent args) + { + if (HasComp(component.OriginalEntity) || HasComp(uid)) + return; + + if (HasComp(uid) && !_mobStateSystem.IsAlive(uid)) + return; + + // How do we get trapped? + // 1. Original target doesn't exist + if (!component.OriginalEntity.IsValid() || Deleted(component.OriginalEntity)) + { + GetTrapped(uid); + return; + } + // 1. Original target is no longer mindswapped + if (!TryComp(component.OriginalEntity, out var targetMindSwap)) + { + GetTrapped(uid); + return; + } + + // 2. Target has undergone a different mind swap + if (targetMindSwap.OriginalEntity != uid) + { + GetTrapped(uid); + return; + } + + // 3. Target is dead + if (HasComp(component.OriginalEntity) && _mobStateSystem.IsDead(component.OriginalEntity)) + { + GetTrapped(uid); + return; + } + + Swap(uid, component.OriginalEntity, true); + } + + private void OnDispelled(EntityUid uid, MindSwappedComponent component, DispelledEvent args) + { + Swap(uid, component.OriginalEntity, true); + args.Handled = true; + } + + private void OnMobStateChanged(EntityUid uid, MindSwappedComponent component, MobStateChangedEvent args) + { + if (args.NewMobState == MobState.Dead) + RemComp(uid); + } + + private void OnGhostAttempt(GhostAttemptHandleEvent args) + { + if (args.Handled) + return; + + if (!HasComp(args.Mind.CurrentEntity)) + return; + + /* + if (!args.ViaCommand) + return; + */ + + args.Result = false; + args.Handled = true; + } + + private void OnSwapInit(EntityUid uid, MindSwappedComponent component, ComponentInit args) + { + if (_prototypeManager.TryIndex("MindSwapReturn", out var mindSwap)) + { + var action = new InstantAction(mindSwap); + action.Cooldown = (_gameTiming.CurTime, _gameTiming.CurTime + TimeSpan.FromSeconds(15)); + _actions.AddAction(uid, action, null); + } + } + + public void Swap(EntityUid performer, EntityUid target, bool end = false) + { + if (end && (!HasComp(performer) || !HasComp(target))) + { + return; + } + else if (!end && (HasComp(performer) || HasComp(target))) + { + return; // Повторный свап!? TODO: chain swap, in current mode broken chained in no return (has no mind error) + } + // This is here to prevent missing MindContainerComponent Resolve errors. + + // Do the transfer. + if (_mindSystem.TryGetMind(performer, out var performerMindId, out var performerMind)) + { + _actorSystem.Detach(target); + _mindSystem.TransferTo(performerMindId, target, true, false, performerMind); + if (_mindSystem.TryGetSession(performerMind!, out var playerSession)) + { + _actorSystem.Attach(target, playerSession, true, out _); + } + } + + if (_mindSystem.TryGetMind(target, out var targetMindId, out var targetMind)) + { + _actorSystem.Detach(performer); + if (_mindSystem.TryGetSession(targetMindId!, out var playerSession)) + { + _actorSystem.Attach(performer, playerSession, true, out _); + } + _mindSystem.TransferTo(targetMindId, performer, true, false, targetMind); + } + + if (end) + { + if (_prototypeManager.TryIndex("MindSwapReturn", out var mindSwap)) + { + _actions.RemoveAction(performer, new InstantAction(mindSwap), null); + _actions.RemoveAction(target, new InstantAction(mindSwap), null); + } + + RemComp(performer); + RemComp(target); + return; + } + + var perfComp = EnsureComp(performer); + var targetComp = EnsureComp(target); + + perfComp.OriginalEntity = target; + perfComp.OriginalMindId = targetMindId; + targetComp.OriginalEntity = performer; + targetComp.OriginalMindId = performerMindId; + } + + public void GetTrapped(EntityUid uid) + { + if (!_prototypeManager.TryIndex("MindSwapReturn", out var action)) + return; + + _popupSystem.PopupEntity(Loc.GetString("mindswap-trapped"), uid, uid, Shared.Popups.PopupType.LargeCaution); + _actions.RemoveAction(uid, action); + + if (HasComp(uid)) + { + RemComp(uid); + RemComp(uid); + EnsureComp(uid); + EnsureComp(uid); + _metaDataSystem.SetEntityName(uid,Loc.GetString("telegnostic-trapped-entity-name")); + _metaDataSystem.SetEntityDescription(uid, Loc.GetString("telegnostic-trapped-entity-desc")); + } + } +} + +public sealed partial class MindSwapPowerActionEvent : EntityTargetActionEvent +{ + +} + +public sealed partial class MindSwapPowerReturnActionEvent : InstantActionEvent +{ + +} diff --git a/Content.Server/Backmen/Abilities/Psionics/Abilities/MindSwappedComponent.cs b/Content.Server/Backmen/Abilities/Psionics/Abilities/MindSwappedComponent.cs new file mode 100644 index 00000000000..58c7a7eeff1 --- /dev/null +++ b/Content.Server/Backmen/Abilities/Psionics/Abilities/MindSwappedComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Server.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class MindSwappedComponent : Component +{ + [ViewVariables] + public EntityUid OriginalEntity = default!; + + [ViewVariables] + public EntityUid OriginalMindId = default!; +} diff --git a/Content.Server/Backmen/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs b/Content.Server/Backmen/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs new file mode 100644 index 00000000000..6ba5738622d --- /dev/null +++ b/Content.Server/Backmen/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs @@ -0,0 +1,70 @@ +using Content.Server.Backmen.Psionics; +using Content.Shared.Actions; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.StatusEffect; +using Content.Server.Stunnable; +using Content.Server.Beam; +using Content.Shared.Backmen.Abilities.Psionics; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Server.Backmen.Abilities.Psionics; + +public sealed class NoosphericZapPowerSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly StunSystem _stunSystem = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly BeamSystem _beam = default!; + + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + } + + private void OnInit(EntityUid uid, NoosphericZapPowerComponent component, ComponentInit args) + { + if (!_prototypeManager.TryIndex("NoosphericZap", out var noosphericZap)) + return; + + component.NoosphericZapPowerAction = new EntityTargetAction(noosphericZap); + if (noosphericZap.UseDelay != null) + component.NoosphericZapPowerAction.Cooldown = (_gameTiming.CurTime, _gameTiming.CurTime + (TimeSpan) noosphericZap.UseDelay); + _actions.AddAction(uid, component.NoosphericZapPowerAction, null); + + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + psionic.PsionicAbility = component.NoosphericZapPowerAction; + } + + private void OnShutdown(EntityUid uid, NoosphericZapPowerComponent component, ComponentShutdown args) + { + if (_prototypeManager.TryIndex("NoosphericZap", out var noosphericZap)) + _actions.RemoveAction(uid, new EntityTargetAction(noosphericZap), null); + } + + private void OnPowerUsed(NoosphericZapPowerActionEvent args) + { + if (!HasComp(args.Target)) + return; + + if (HasComp(args.Target)) + return; + + _beam.TryCreateBeam(args.Performer, args.Target, "LightningNoospheric"); + + _stunSystem.TryParalyze(args.Target, TimeSpan.FromSeconds(5), false); + _statusEffectsSystem.TryAddStatusEffect(args.Target, "Stutter", TimeSpan.FromSeconds(10), false, "StutteringAccent"); + + _psionics.LogPowerUsed(args.Performer, "noospheric zap"); + args.Handled = true; + } +} + +public sealed partial class NoosphericZapPowerActionEvent : EntityTargetActionEvent {} diff --git a/Content.Server/Backmen/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs b/Content.Server/Backmen/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs new file mode 100644 index 00000000000..2830d12a8a5 --- /dev/null +++ b/Content.Server/Backmen/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs @@ -0,0 +1,129 @@ +using Content.Server.Backmen.Psionics; +using Content.Shared.Actions; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.CombatMode.Pacification; +using Content.Shared.Damage; +using Content.Shared.Stunnable; +using Content.Shared.Stealth; +using Content.Shared.Stealth.Components; +using Content.Shared.Backmen.Abilities.Psionics; +using Robust.Shared.Prototypes; +using Robust.Shared.Player; +using Robust.Shared.Audio; +using Robust.Shared.Timing; + +namespace Content.Server.Backmen.Abilities.Psionics; + +public sealed class PsionicInvisibilityPowerSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SharedStunSystem _stunSystem = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly SharedStealthSystem _stealth = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + SubscribeLocalEvent(OnPowerOff); + SubscribeLocalEvent(OnStart); + SubscribeLocalEvent(OnEnd); + SubscribeLocalEvent(OnDamageChanged); + } + + private void OnInit(EntityUid uid, PsionicInvisibilityPowerComponent component, ComponentInit args) + { + if (!_prototypeManager.TryIndex("PsionicInvisibility", out var invis)) + return; + + component.PsionicInvisibilityPowerAction = new InstantAction(invis); + if (invis.UseDelay != null) + component.PsionicInvisibilityPowerAction.Cooldown = (_gameTiming.CurTime, _gameTiming.CurTime + (TimeSpan) invis.UseDelay); + _actions.AddAction(uid, component.PsionicInvisibilityPowerAction, null); + + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + psionic.PsionicAbility = component.PsionicInvisibilityPowerAction; + } + + private void OnShutdown(EntityUid uid, PsionicInvisibilityPowerComponent component, ComponentShutdown args) + { + if (_prototypeManager.TryIndex("PsionicInvisibility", out var invis)) + _actions.RemoveAction(uid, new InstantAction(invis), null); + } + + private void OnPowerUsed(EntityUid uid, PsionicInvisibilityPowerComponent component, PsionicInvisibilityPowerActionEvent args) + { + if (HasComp(uid)) + return; + + ToggleInvisibility(args.Performer); + + if (_prototypeManager.TryIndex("PsionicInvisibilityOff", out var invis)) + _actions.AddAction(args.Performer, new InstantAction(invis), null); + + _psionics.LogPowerUsed(uid, "psionic invisibility"); + args.Handled = true; + } + + private void OnPowerOff(PsionicInvisibilityPowerOffActionEvent args) + { + if (!HasComp(args.Performer)) + return; + + ToggleInvisibility(args.Performer); + args.Handled = true; + } + + private void OnStart(EntityUid uid, PsionicInvisibilityUsedComponent component, ComponentInit args) + { + EnsureComp(uid); + EnsureComp(uid); + var stealth = EnsureComp(uid); + _stealth.SetVisibility(uid, 0.66f, stealth); + SoundSystem.Play("/Audio/Effects/toss.ogg", Filter.Pvs(uid), uid); + + } + + private void OnEnd(EntityUid uid, PsionicInvisibilityUsedComponent component, ComponentShutdown args) + { + if (Terminating(uid)) + return; + + RemComp(uid); + RemComp(uid); + RemComp(uid); + SoundSystem.Play("/Audio/Effects/toss.ogg", Filter.Pvs(uid), uid); + + if (_prototypeManager.TryIndex("PsionicInvisibilityOff", out var invis)) + _actions.RemoveAction(uid, new InstantAction(invis), null); + + _stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(8), false); + DirtyEntity(uid); + } + + private void OnDamageChanged(EntityUid uid, PsionicInvisibilityUsedComponent component, DamageChangedEvent args) + { + if (!args.DamageIncreased) + return; + + ToggleInvisibility(uid); + } + + public void ToggleInvisibility(EntityUid uid) + { + if (!HasComp(uid)) + { + EnsureComp(uid); + } else + { + RemComp(uid); + } + } +} + +public sealed partial class PsionicInvisibilityPowerActionEvent : InstantActionEvent {} +public sealed partial class PsionicInvisibilityPowerOffActionEvent : InstantActionEvent {} diff --git a/Content.Server/Backmen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs b/Content.Server/Backmen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs new file mode 100644 index 00000000000..ef82ea0950a --- /dev/null +++ b/Content.Server/Backmen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs @@ -0,0 +1,120 @@ +using Robust.Shared.Audio; +using Robust.Server.GameObjects; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Content.Server.Body.Components; +using Content.Server.Body.Systems; +using Content.Server.Chemistry.EntitySystems; +using Content.Server.DoAfter; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Actions; +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Shared.Chemistry.Components; +using Content.Shared.DoAfter; +using Content.Shared.FixedPoint; +using Content.Shared.Popups; +using Content.Shared.Backmen.Psionics.Events; +using Content.Shared.Tag; +using Content.Shared.Examine; +using static Content.Shared.Examine.ExamineSystemShared; +using Robust.Shared.Timing; + +namespace Content.Server.Backmen.Abilities.Psionics; + +public sealed class PsionicRegenerationPowerSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!; + [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; + [Dependency] private readonly AudioSystem _audioSystem = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + + SubscribeLocalEvent(OnDispelled); + SubscribeLocalEvent(OnDoAfter); + } + + private void OnInit(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentInit args) + { + if (!_prototypeManager.TryIndex("PsionicRegeneration", out var metapsionic)) + return; + + component.PsionicRegenerationPowerAction = new InstantAction(metapsionic); + if (metapsionic.UseDelay != null) + component.PsionicRegenerationPowerAction.Cooldown = (_gameTiming.CurTime, _gameTiming.CurTime + (TimeSpan) metapsionic.UseDelay); + _actions.AddAction(uid, component.PsionicRegenerationPowerAction, null); + + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + psionic.PsionicAbility = component.PsionicRegenerationPowerAction; + } + + private void OnPowerUsed(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationPowerActionEvent args) + { + var ev = new PsionicRegenerationDoAfterEvent(_gameTiming.CurTime); + var doAfterArgs = new DoAfterArgs(uid, component.UseDelay, ev, uid); + + _doAfterSystem.TryStartDoAfter(doAfterArgs, out var doAfterId); + + component.DoAfter = doAfterId; + + _popupSystem.PopupEntity(Loc.GetString("psionic-regeneration-begin", ("entity", uid)), + uid, + // TODO: Use LoS-based Filter when one is available. + Filter.Pvs(uid).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(uid, entity, ExamineRange, null)), + true, + PopupType.Medium); + + _audioSystem.PlayPvs(component.SoundUse, component.Owner, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f)); + _psionics.LogPowerUsed(uid, "psionic regeneration"); + args.Handled = true; + } + + private void OnShutdown(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentShutdown args) + { + if (_prototypeManager.TryIndex("PsionicRegeneration", out var metapsionic)) + _actions.RemoveAction(uid, new InstantAction(metapsionic), null); + } + + private void OnDispelled(EntityUid uid, PsionicRegenerationPowerComponent component, DispelledEvent args) + { + if (component.DoAfter == null) + return; + + _doAfterSystem.Cancel(component.DoAfter); + component.DoAfter = null; + + args.Handled = true; + } + + private void OnDoAfter(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationDoAfterEvent args) + { + component.DoAfter = null; + + if (!TryComp(uid, out var stream)) + return; + + // DoAfter has no way to run a callback during the process to give + // small doses of the reagent, so we wait until either the action + // is cancelled (by being dispelled) or complete to give the + // appropriate dose. A timestamp delta is used to accomplish this. + var percentageComplete = Math.Min(1f, (_gameTiming.CurTime - args.StartedAt).TotalSeconds / component.UseDelay); + + var solution = new Solution(); + solution.AddReagent("PsionicRegenerationEssence", FixedPoint2.New(component.EssenceAmount * percentageComplete)); + _bloodstreamSystem.TryAddToChemicals(uid, solution, stream); + } +} + +public sealed partial class PsionicRegenerationPowerActionEvent : InstantActionEvent {} diff --git a/Content.Server/Backmen/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs b/Content.Server/Backmen/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs new file mode 100644 index 00000000000..c53ecbf4039 --- /dev/null +++ b/Content.Server/Backmen/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs @@ -0,0 +1,64 @@ +using Content.Shared.Actions; +using Content.Shared.Actions.ActionTypes; +using Content.Server.Atmos.Components; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Popups; +using Content.Shared.Backmen.Abilities.Psionics; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Server.Backmen.Abilities.Psionics +{ + public sealed class PyrokinesisPowerSystem : EntitySystem + { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly FlammableSystem _flammableSystem = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + } + + private void OnInit(EntityUid uid, PyrokinesisPowerComponent component, ComponentInit args) + { + if (!_prototypeManager.TryIndex("Pyrokinesis", out var pyrokinesis)) + return; + + component.PyrokinesisPowerAction = new EntityTargetAction(pyrokinesis); + if (pyrokinesis.UseDelay != null) + component.PyrokinesisPowerAction.Cooldown = (_gameTiming.CurTime, _gameTiming.CurTime + (TimeSpan) pyrokinesis.UseDelay); + _actions.AddAction(uid, component.PyrokinesisPowerAction, null); + + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + psionic.PsionicAbility = component.PyrokinesisPowerAction; + } + + private void OnShutdown(EntityUid uid, PyrokinesisPowerComponent component, ComponentShutdown args) + { + if (_prototypeManager.TryIndex("Pyrokinesis", out var pyrokinesis)) + _actions.RemoveAction(uid, new EntityTargetAction(pyrokinesis), null); + } + + private void OnPowerUsed(PyrokinesisPowerActionEvent args) + { + if (!TryComp(args.Target, out var flammableComponent)) + return; + + flammableComponent.FireStacks += 5; + _flammableSystem.Ignite(args.Target, args.Performer, flammableComponent); + _popupSystem.PopupEntity(Loc.GetString("pyrokinesis-power-used", ("target", args.Target)), args.Target, Shared.Popups.PopupType.LargeCaution); + + _psionics.LogPowerUsed(args.Performer, "pyrokinesis"); + args.Handled = true; + } + } + + public sealed partial class PyrokinesisPowerActionEvent : EntityTargetActionEvent {} +} diff --git a/Content.Server/Backmen/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs b/Content.Server/Backmen/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs new file mode 100644 index 00000000000..80a9a2cfec2 --- /dev/null +++ b/Content.Server/Backmen/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs @@ -0,0 +1,64 @@ +using Content.Shared.Actions; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Shared.Mind.Components; +using Content.Shared.StatusEffect; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Server.Backmen.Abilities.Psionics; + +public sealed class TelegnosisPowerSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly MindSwapPowerSystem _mindSwap = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + SubscribeLocalEvent(OnMindRemoved); + } + + private void OnInit(EntityUid uid, TelegnosisPowerComponent component, ComponentInit args) + { + if (!_prototypeManager.TryIndex("Telegnosis", out var telegnosis)) + return; + + component.TelegnosisPowerAction = new InstantAction(telegnosis); + if (telegnosis.UseDelay != null) + component.TelegnosisPowerAction.Cooldown = (_gameTiming.CurTime, _gameTiming.CurTime + (TimeSpan) telegnosis.UseDelay); + _actions.AddAction(uid, component.TelegnosisPowerAction, null); + + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + psionic.PsionicAbility = component.TelegnosisPowerAction; + } + + private void OnShutdown(EntityUid uid, TelegnosisPowerComponent component, ComponentShutdown args) + { + if (_prototypeManager.TryIndex("Telegnosis", out var metapsionic)) + _actions.RemoveAction(uid, new InstantAction(metapsionic), null); + } + + private void OnPowerUsed(EntityUid uid, TelegnosisPowerComponent component, TelegnosisPowerActionEvent args) + { + var projection = Spawn(component.Prototype, Transform(uid).Coordinates); + Transform(projection).AttachToGridOrMap(); + _mindSwap.Swap(uid, projection); + + _psionics.LogPowerUsed(uid, "telegnosis"); + args.Handled = true; + } + private void OnMindRemoved(EntityUid uid, TelegnosticProjectionComponent component, MindRemovedMessage args) + { + QueueDel(uid); + } +} + +public sealed partial class TelegnosisPowerActionEvent : InstantActionEvent {} diff --git a/Content.Server/Backmen/Abilities/Psionics/PsionicAbilitiesSystem.cs b/Content.Server/Backmen/Abilities/Psionics/PsionicAbilitiesSystem.cs new file mode 100644 index 00000000000..d52090ee338 --- /dev/null +++ b/Content.Server/Backmen/Abilities/Psionics/PsionicAbilitiesSystem.cs @@ -0,0 +1,130 @@ +using Content.Server.Backmen.Psionics; +using Content.Shared.Actions; +using Content.Shared.Backmen.Psionics.Glimmer; +using Content.Shared.Random; +using Content.Shared.Random.Helpers; +using Content.Server.EUI; +using Content.Server.Mind; +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Shared.StatusEffect; +using Robust.Shared.Random; +using Robust.Shared.Prototypes; +using Robust.Server.GameObjects; +using Robust.Server.Player; + +namespace Content.Server.Backmen.Abilities.Psionics; + +public sealed class PsionicAbilitiesSystem : EntitySystem +{ + [Dependency] private readonly IComponentFactory _componentFactory = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly EuiManager _euiManager = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnPlayerAttached); + } + + private void OnPlayerAttached(EntityUid uid, PsionicAwaitingPlayerComponent component, PlayerAttachedEvent args) + { + if (TryComp(uid, out var bonus) && bonus.Warn == true) + _euiManager.OpenEui(new AcceptPsionicsEui(uid, this), args.Player); + else + AddRandomPsionicPower(uid); + RemCompDeferred(uid); + } + + public void AddPsionics(EntityUid uid, bool warn = true) + { + if (Deleted(uid)) + return; + + if (HasComp(uid)) + return; + + if (!_mindSystem.TryGetMind(uid, out var mindId,out var mind)) + { + EnsureComp(uid); + return; + } + + if (!_mindSystem.TryGetSession(mind, out var client)) + return; + + if (warn && TryComp(uid, out var actor)) + _euiManager.OpenEui(new AcceptPsionicsEui(uid, this), client); + else + AddRandomPsionicPower(uid); + } + + public void AddPsionics(EntityUid uid, string powerComp) + { + if (Deleted(uid)) + return; + + if (HasComp(uid)) + return; + + AddComp(uid); + + var newComponent = (Component) _componentFactory.GetComponent(powerComp); + newComponent.Owner = uid; + + EntityManager.AddComponent(uid, newComponent); + } + + public void AddRandomPsionicPower(EntityUid uid) + { + AddComp(uid); + + if (!_prototypeManager.TryIndex("RandomPsionicPowerPool", out var pool)) + { + Logger.Error("Can't index the random psionic power pool!"); + return; + } + + // uh oh, stinky! + var newComponent = (Component) _componentFactory.GetComponent(pool.Pick()); + newComponent.Owner = uid; + + EntityManager.AddComponent(uid, newComponent); + + _glimmerSystem.Glimmer += _random.Next(1, 5); + } + + public void RemovePsionics(EntityUid uid) + { + if (!TryComp(uid, out var psionic)) + return; + + if (!psionic.Removable) + return; + + if (!_prototypeManager.TryIndex("RandomPsionicPowerPool", out var pool)) + { + Logger.Error("Can't index the random psionic power pool!"); + return; + } + + foreach (var compName in pool.Weights.Keys) + { + // component moment + var comp = _componentFactory.GetComponent(compName); + if (EntityManager.TryGetComponent(uid, comp.GetType(), out var psionicPower)) + RemComp(uid, psionicPower); + } + if (psionic.PsionicAbility != null) + _actionsSystem.RemoveAction(uid, psionic.PsionicAbility); + + _statusEffectsSystem.TryAddStatusEffect(uid, "Stutter", TimeSpan.FromMinutes(5), false, "StutteringAccent"); + + RemComp(uid); + } +} diff --git a/Content.Server/Backmen/Audio/GlimmerSoundComponent.cs b/Content.Server/Backmen/Audio/GlimmerSoundComponent.cs new file mode 100644 index 00000000000..e2be567d63e --- /dev/null +++ b/Content.Server/Backmen/Audio/GlimmerSoundComponent.cs @@ -0,0 +1,19 @@ +using Content.Server.Backmen.Psionics.Glimmer; +using Content.Shared.Audio; +using Content.Shared.Backmen.Psionics.Glimmer; +using Robust.Shared.Audio; + +namespace Content.Server.Backmen.Audio; + +[RegisterComponent] +[Access(typeof(SharedAmbientSoundSystem), typeof(GlimmerReactiveSystem))] +public sealed partial class GlimmerSoundComponent : Component +{ + [DataField("glimmerTier", required: true), ViewVariables(VVAccess.ReadWrite)] // only for map editing + public Dictionary Sound { get; set; } = new(); + + public bool GetSound(GlimmerTier glimmerTier, out SoundSpecifier? spec) + { + return Sound.TryGetValue(glimmerTier.ToString(), out spec); + } +} diff --git a/Content.Server/Backmen/CartridgeLoader/Cartridges/GlimmerMonitorCartridgeComponent.cs b/Content.Server/Backmen/CartridgeLoader/Cartridges/GlimmerMonitorCartridgeComponent.cs new file mode 100644 index 00000000000..b04cb27bdb9 --- /dev/null +++ b/Content.Server/Backmen/CartridgeLoader/Cartridges/GlimmerMonitorCartridgeComponent.cs @@ -0,0 +1,5 @@ +namespace Content.Server.Backmen.CartridgeLoader.Cartridges; + +[RegisterComponent] +public sealed partial class GlimmerMonitorCartridgeComponent : Component +{ } diff --git a/Content.Server/Backmen/CartridgeLoader/Cartridges/GlimmerMonitorCartridgeSystem.cs b/Content.Server/Backmen/CartridgeLoader/Cartridges/GlimmerMonitorCartridgeSystem.cs new file mode 100644 index 00000000000..1d83924ab45 --- /dev/null +++ b/Content.Server/Backmen/CartridgeLoader/Cartridges/GlimmerMonitorCartridgeSystem.cs @@ -0,0 +1,45 @@ +using Content.Server.Backmen.Psionics.Glimmer; +using Content.Server.CartridgeLoader; +using Content.Shared.Backmen.CartridgeLoader; +using Content.Shared.Backmen.CartridgeLoader.Cartridges; +using Content.Shared.CartridgeLoader; + +namespace Content.Server.Backmen.CartridgeLoader.Cartridges; + +public sealed class GlimmerMonitorCartridgeSystem : EntitySystem +{ + [Dependency] private readonly CartridgeLoaderSystem? _cartridgeLoaderSystem = default!; + [Dependency] private readonly PassiveGlimmerReductionSystem _glimmerReductionSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnUiReady); + SubscribeLocalEvent(OnMessage); + } + + /// + /// This gets called when the ui fragment needs to be updated for the first time after activating + /// + private void OnUiReady(EntityUid uid, GlimmerMonitorCartridgeComponent component, CartridgeUiReadyEvent args) + { + UpdateUiState(uid, args.Loader, component); + } + + private void OnMessage(EntityUid uid, GlimmerMonitorCartridgeComponent component, CartridgeMessageEvent args) + { + if (args is not GlimmerMonitorSyncMessageEvent) + return; + + UpdateUiState(uid, args.LoaderUid, component); + } + + public void UpdateUiState(EntityUid uid, EntityUid loaderUid, GlimmerMonitorCartridgeComponent? component) + { + if (!Resolve(uid, ref component)) + return; + + var state = new GlimmerMonitorUiState(_glimmerReductionSystem.GlimmerValues); + _cartridgeLoaderSystem?.UpdateCartridgeUiState(loaderUid, state); + } +} diff --git a/Content.Server/Backmen/Chat/NyanoChatSystem.cs b/Content.Server/Backmen/Chat/NyanoChatSystem.cs new file mode 100644 index 00000000000..fdad2857c70 --- /dev/null +++ b/Content.Server/Backmen/Chat/NyanoChatSystem.cs @@ -0,0 +1,104 @@ +using Content.Server.Administration.Logs; +using Content.Server.Administration.Managers; +using Content.Server.Chat.Managers; +using Content.Server.Chat.Systems; +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Shared.Bed.Sleep; +using Content.Shared.Chat; +using Content.Shared.Database; +using Content.Shared.Drugs; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Random; +using System.Linq; +using Content.Shared.Backmen.Psionics.Glimmer; + +namespace Content.Server.Backmen.Chat; + +/// +/// Extensions for nyano's chat stuff +/// +public sealed class NyanoChatSystem : EntitySystem +{ + [Dependency] private readonly IAdminManager _adminManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; + private IEnumerable GetPsionicChatClients() + { + return Filter.Empty() + .AddWhereAttachedEntity(IsEligibleForTelepathy) + .Recipients + .Select(p => p.ConnectedClient); + } + + private IEnumerable GetAdminClients() + { + return _adminManager.ActiveAdmins + .Select(p => p.ConnectedClient); + } + + private List GetDreamers(IEnumerable removeList) + { + var filtered = Filter.Empty() + .AddWhereAttachedEntity(entity => HasComp(entity) || HasComp(entity) && !HasComp(entity) && !HasComp(entity)) + .Recipients + .Select(p => p.ConnectedClient); + + var filteredList = filtered.ToList(); + + foreach (var entity in removeList) + filteredList.Remove(entity); + + return filteredList; + } + + private bool IsEligibleForTelepathy(EntityUid entity) + { + return HasComp(entity) + && !HasComp(entity) + && !HasComp(entity) + && (!TryComp(entity, out var mobstate) || mobstate.CurrentState == MobState.Alive); + } + + public void SendTelepathicChat(EntityUid source, string message, bool hideChat) + { + if (!IsEligibleForTelepathy(source)) + return; + + var clients = GetPsionicChatClients().ToArray(); + var admins = GetAdminClients().ToArray(); + string messageWrap; + string adminMessageWrap; + + messageWrap = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message", + ("telepathicChannelName", Loc.GetString("chat-manager-telepathic-channel-name")), ("message", message)); + + adminMessageWrap = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message-admin", + ("source", source), ("message", message)); + + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Telepathic chat from {ToPrettyString(source):Player}: {message}"); + + _chatManager.ChatMessageToMany(ChatChannel.Telepathic, message, messageWrap, source, hideChat, true, clients.Where(x=>admins.All(z=>z.UserId != x.UserId)).ToList(), Color.PaleVioletRed); + _chatManager.ChatMessageToMany(ChatChannel.Telepathic, message, adminMessageWrap, source, hideChat, true, admins, Color.PaleVioletRed); + + if (_random.Prob(0.1f)) + _glimmerSystem.Glimmer++; + + if (_random.Prob(Math.Min(0.33f + ((float) _glimmerSystem.Glimmer / 1500), 1))) + { + float obfuscation = (0.25f + (float) _glimmerSystem.Glimmer / 2000); + var obfuscated = _chatSystem.ObfuscateMessageReadability(message, obfuscation); + _chatManager.ChatMessageToMany(ChatChannel.Telepathic, obfuscated, messageWrap, source, hideChat, false, GetDreamers(clients), Color.PaleVioletRed); + } + + foreach (var repeater in EntityQuery()) + { + _chatSystem.TrySendInGameICMessage(repeater.Owner, message, InGameICChatType.Speak, false); + } + } +} diff --git a/Content.Server/Backmen/Chat/TSayCommand.cs b/Content.Server/Backmen/Chat/TSayCommand.cs new file mode 100644 index 00000000000..7498807e4ce --- /dev/null +++ b/Content.Server/Backmen/Chat/TSayCommand.cs @@ -0,0 +1,42 @@ +using Content.Server.Chat.Systems; +using Content.Shared.Administration; +using Robust.Server.Player; +using Robust.Shared.Console; +using Robust.Shared.Enums; + +namespace Content.Server.Backmen.Chat.Commands; + +[AnyCommand] +internal sealed class TSayCommand : IConsoleCommand +{ + public string Command => "tsay"; + public string Description => "Send chat messages to the telepathic."; + public string Help => "tsay "; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (shell.Player is not IPlayerSession 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; + IoCManager.Resolve().System().TrySendInGameICMessage(playerEntity, message, InGameICChatType.Telepathic, + ChatTransmitRange.Normal, false, shell, player); + } +} diff --git a/Content.Server/Backmen/Chat/TelepathicRepeaterComponent.cs b/Content.Server/Backmen/Chat/TelepathicRepeaterComponent.cs new file mode 100644 index 00000000000..a6008f9683f --- /dev/null +++ b/Content.Server/Backmen/Chat/TelepathicRepeaterComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Server.Backmen.Chat; + +/// +/// Repeats whatever is happening in telepathic chat. +/// +[RegisterComponent] +public sealed partial class TelepathicRepeaterComponent : Component +{ + +} diff --git a/Content.Server/Backmen/Interaction/NoNormalInteractionComponent.cs b/Content.Server/Backmen/Interaction/NoNormalInteractionComponent.cs new file mode 100644 index 00000000000..2ebbca4a70b --- /dev/null +++ b/Content.Server/Backmen/Interaction/NoNormalInteractionComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Server.Backmen.Interaction; + +/// +/// Generic component for entities that can move around, emote, etc but just not physically interact with things. +/// +[RegisterComponent] +public sealed partial class NoNormalInteractionComponent : Component +{ + +} diff --git a/Content.Server/Backmen/Interaction/NoNormalInteractionSystem.cs b/Content.Server/Backmen/Interaction/NoNormalInteractionSystem.cs new file mode 100644 index 00000000000..f79eebc5bc6 --- /dev/null +++ b/Content.Server/Backmen/Interaction/NoNormalInteractionSystem.cs @@ -0,0 +1,17 @@ +using Content.Shared.Interaction.Events; + +namespace Content.Server.Backmen.Interaction; + +public sealed class NoNormalInteractionSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInteractionAttempt); + } + + private void OnInteractionAttempt(EntityUid uid, NoNormalInteractionComponent component, InteractionAttemptEvent args) + { + args.Cancel(); + } +} diff --git a/Content.Server/Backmen/Psionics/AcceptPsionicsEui.cs b/Content.Server/Backmen/Psionics/AcceptPsionicsEui.cs new file mode 100644 index 00000000000..33c22cf0cec --- /dev/null +++ b/Content.Server/Backmen/Psionics/AcceptPsionicsEui.cs @@ -0,0 +1,34 @@ +using Content.Shared.Backmen.Psionics; +using Content.Shared.Eui; +using Content.Server.EUI; +using Content.Server.Backmen.Abilities.Psionics; + +namespace Content.Server.Backmen.Psionics +{ + public sealed class AcceptPsionicsEui : BaseEui + { + private readonly PsionicAbilitiesSystem _psionicsSystem; + private readonly EntityUid _entity; + + public AcceptPsionicsEui(EntityUid entity, PsionicAbilitiesSystem psionicsSys) + { + _entity = entity; + _psionicsSystem = psionicsSys; + } + + public override void HandleMessage(EuiMessageBase msg) + { + base.HandleMessage(msg); + + if (msg is not AcceptPsionicsChoiceMessage choice || + choice.Button == AcceptPsionicsUiButton.Deny) + { + Close(); + return; + } + + _psionicsSystem.AddRandomPsionicPower(_entity); + Close(); + } + } +} diff --git a/Content.Server/Backmen/Psionics/AntiPsychicWeaponComponent.cs b/Content.Server/Backmen/Psionics/AntiPsychicWeaponComponent.cs new file mode 100644 index 00000000000..aa681ae2aac --- /dev/null +++ b/Content.Server/Backmen/Psionics/AntiPsychicWeaponComponent.cs @@ -0,0 +1,24 @@ +using Content.Shared.Damage; + +namespace Content.Server.Backmen.Psionics +{ + [RegisterComponent] + public sealed partial class AntiPsionicWeaponComponent : Component + { + + [DataField("modifiers", required: true)] + public DamageModifierSet Modifiers = default!; + + [DataField("psychicStaminaDamage")] + public float PsychicStaminaDamage = 30f; + + [DataField("disableChance")] + public float DisableChance = 0.3f; + + /// + /// Punish when used against a non-psychic. + /// DreamSetPrototypes = new[] + { + "adjectives", + "names_first", + "verbs", + }; + + public override void Update(float frameTime) + { + base.Update(frameTime); + _accumulator += frameTime; + if (_accumulator < _updateRate) + return; + + _accumulator -= _updateRate; + _updateRate = _random.NextFloat(10f, 30f); + + foreach (var sleeper in EntityQuery()) + { + if (!TryComp(sleeper.Owner, out var actor)) + continue; + + var setName = _random.Pick(DreamSetPrototypes); + + if (!_prototypeManager.TryIndex(setName, out var set)) + return; + + var msg = _random.Pick(set.Values) + "..."; //todo... does the seperator need loc? + + var messageWrap = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message", + ("telepathicChannelName", Loc.GetString("chat-manager-telepathic-channel-name")), ("message", msg)); + + _chatManager.ChatMessageToOne(Shared.Chat.ChatChannel.Telepathic, + msg, messageWrap, sleeper.Owner, false, actor.PlayerSession.ConnectedClient, Color.PaleVioletRed); + } + } + } +} diff --git a/Content.Server/Backmen/Psionics/Glimmer/GlimmerCommands.cs b/Content.Server/Backmen/Psionics/Glimmer/GlimmerCommands.cs new file mode 100644 index 00000000000..cb73bbc502c --- /dev/null +++ b/Content.Server/Backmen/Psionics/Glimmer/GlimmerCommands.cs @@ -0,0 +1,39 @@ +using Content.Server.Administration; +using Content.Shared.Administration; +using Content.Shared.Backmen.Psionics.Glimmer; +using Robust.Shared.Console; + +namespace Content.Server.Backmen.Psionics.Glimmer; + +[AdminCommand(AdminFlags.Logs)] +public sealed class GlimmerShowCommand : IConsoleCommand +{ + public string Command => "glimmershow"; + public string Description => Loc.GetString("command-glimmershow-description"); + public string Help => Loc.GetString("command-glimmershow-help"); + public async void Execute(IConsoleShell shell, string argStr, string[] args) + { + var entMan = IoCManager.Resolve(); + shell.WriteLine(entMan.EntitySysManager.GetEntitySystem().Glimmer.ToString()); + } +} + +[AdminCommand(AdminFlags.Debug)] +public sealed class GlimmerSetCommand : IConsoleCommand +{ + public string Command => "glimmerset"; + public string Description => Loc.GetString("command-glimmerset-description"); + public string Help => Loc.GetString("command-glimmerset-help"); + + public async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 1) + return; + + if (!int.TryParse(args[0], out var glimmerValue)) + return; + + var entMan = IoCManager.Resolve(); + entMan.EntitySysManager.GetEntitySystem().Glimmer = glimmerValue; + } +} diff --git a/Content.Server/Backmen/Psionics/Glimmer/GlimmerReactiveSystem.cs b/Content.Server/Backmen/Psionics/Glimmer/GlimmerReactiveSystem.cs new file mode 100644 index 00000000000..236496ef9f0 --- /dev/null +++ b/Content.Server/Backmen/Psionics/Glimmer/GlimmerReactiveSystem.cs @@ -0,0 +1,405 @@ +using Content.Server.Backmen.Audio; +using Content.Server.Power.Components; +using Content.Server.Electrocution; +using Content.Server.Lightning; +using Content.Server.Explosion.EntitySystems; +using Content.Server.Ghost; +using Content.Server.Revenant.EntitySystems; +using Content.Shared.Audio; +using Content.Shared.Backmen.Psionics.Glimmer; +using Content.Shared.Construction.EntitySystems; +using Content.Shared.GameTicking; +using Content.Shared.Psionics.Glimmer; +using Content.Shared.Verbs; +using Content.Shared.StatusEffect; +using Content.Shared.Damage; +using Content.Shared.Destructible; +using Content.Shared.Construction.Components; +using Robust.Shared.Audio; +using Robust.Shared.Map; +using Robust.Shared.Random; +using Robust.Shared.Physics.Components; +using Robust.Shared.Utility; + +namespace Content.Server.Backmen.Psionics.Glimmer +{ + public sealed class GlimmerReactiveSystem : EntitySystem + { + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!; + [Dependency] private readonly SharedAudioSystem _sharedAudioSystem = default!; + [Dependency] private readonly SharedAmbientSoundSystem _sharedAmbientSoundSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly LightningSystem _lightning = default!; + [Dependency] private readonly ExplosionSystem _explosionSystem = default!; + [Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!; + [Dependency] private readonly AnchorableSystem _anchorableSystem = default!; + [Dependency] private readonly SharedDestructibleSystem _destructibleSystem = default!; + [Dependency] private readonly GhostSystem _ghostSystem = default!; + [Dependency] private readonly RevenantSystem _revenantSystem = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + + public float Accumulator = 0; + public const float UpdateFrequency = 15f; + public float BeamCooldown = 3; + public GlimmerTier LastGlimmerTier = GlimmerTier.Minimal; + public bool GhostsVisible = false; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(Reset); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnComponentRemove); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnTierChanged); + SubscribeLocalEvent>(AddShockVerb); + SubscribeLocalEvent(OnDamageChanged); + SubscribeLocalEvent(OnDestroyed); + SubscribeLocalEvent(OnUnanchorAttempt); + } + + /// + /// Update relevant state on an Entity. + /// + /// The number of steps in tier + /// difference since last update. This can be zero for the sake of + /// toggling the enabled states. + private void UpdateEntityState(EntityUid uid, SharedGlimmerReactiveComponent component, GlimmerTier currentGlimmerTier, int glimmerTierDelta) + { + var isEnabled = true; + + if (component.RequiresApcPower) + if (TryComp(uid, out ApcPowerReceiverComponent? apcPower)) + isEnabled = apcPower.Powered; + + _appearanceSystem.SetData(uid, GlimmerReactiveVisuals.GlimmerTier, isEnabled ? currentGlimmerTier : GlimmerTier.Minimal); + + // update ambient sound + if (TryComp(uid, out GlimmerSoundComponent? glimmerSound) + && TryComp(uid, out AmbientSoundComponent? ambientSoundComponent) + && glimmerSound.GetSound(currentGlimmerTier, out SoundSpecifier? spec)) + { + if (spec != null) + _sharedAmbientSoundSystem.SetSound(uid, spec, ambientSoundComponent); + } + + if (component.ModulatesPointLight) + if (TryComp(uid, out SharedPointLightComponent? pointLight)) + { + pointLight.Enabled = isEnabled ? currentGlimmerTier != GlimmerTier.Minimal : false; + + // The light energy and radius are kept updated even when off + // to prevent the need to store additional state. + // + // Note that this doesn't handle edge cases where the + // PointLightComponent is removed while the + // GlimmerReactiveComponent is still present. + pointLight.Energy += glimmerTierDelta * component.GlimmerToLightEnergyFactor; + pointLight.Radius += glimmerTierDelta * component.GlimmerToLightRadiusFactor; + } + + } + + /// + /// Track when the component comes online so it can be given the + /// current status of the glimmer tier, if it wasn't around when an + /// update went out. + /// + private void OnMapInit(EntityUid uid, SharedGlimmerReactiveComponent component, MapInitEvent args) + { + if (component.RequiresApcPower && !HasComp(uid)) + Logger.Warning($"{ToPrettyString(uid)} had RequiresApcPower set to true but no ApcPowerReceiverComponent was found on init."); + + if (component.ModulatesPointLight && !HasComp(uid)) + Logger.Warning($"{ToPrettyString(uid)} had ModulatesPointLight set to true but no PointLightComponent was found on init."); + + UpdateEntityState(uid, component, LastGlimmerTier, (int) LastGlimmerTier); + } + + /// + /// Reset the glimmer tier appearance data if the component's removed, + /// just in case some objects can temporarily become reactive to the + /// glimmer. + /// + private void OnComponentRemove(EntityUid uid, SharedGlimmerReactiveComponent component, ComponentRemove args) + { + UpdateEntityState(uid, component, GlimmerTier.Minimal, -1 * (int) LastGlimmerTier); + } + + /// + /// If the Entity has RequiresApcPower set to true, this will force an + /// update to the entity's state. + /// + private void OnPowerChanged(EntityUid uid, SharedGlimmerReactiveComponent component, ref PowerChangedEvent args) + { + if (component.RequiresApcPower) + UpdateEntityState(uid, component, LastGlimmerTier, 0); + } + + /// + /// Enable / disable special effects from higher tiers. + /// + private void OnTierChanged(EntityUid uid, SharedGlimmerReactiveComponent component, GlimmerTierChangedEvent args) + { + if (!TryComp(uid, out var receiver)) + return; + + if (args.CurrentTier >= GlimmerTier.Dangerous) + { + if (!Transform(uid).Anchored) + AnchorOrExplode(uid); + + receiver.PowerDisabled = false; + receiver.NeedsPower = false; + } else + { + receiver.NeedsPower = true; + } + } + + private void AddShockVerb(EntityUid uid, SharedGlimmerReactiveComponent component, GetVerbsEvent args) + { + if(!args.CanAccess || !args.CanInteract) + return; + + if (!TryComp(uid, out var receiver)) + return; + + if (receiver.NeedsPower) + return; + + AlternativeVerb verb = new() + { + Act = () => + { + _sharedAudioSystem.PlayPvs(component.ShockNoises, args.User); + _electrocutionSystem.TryDoElectrocution(args.User, null, _glimmerSystem.Glimmer / 200, TimeSpan.FromSeconds((float) _glimmerSystem.Glimmer / 100), false); + }, + Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/Spare/poweronoff.svg.192dpi.png")), + Text = Loc.GetString("power-switch-component-toggle-verb"), + Priority = -3 + }; + args.Verbs.Add(verb); + } + + private void OnDamageChanged(EntityUid uid, SharedGlimmerReactiveComponent component, DamageChangedEvent args) + { + if (args.Origin == null) + return; + + if (!_random.Prob((float) _glimmerSystem.Glimmer / 1000)) + return; + + var tier = _glimmerSystem.GetGlimmerTier(); + if (tier < GlimmerTier.High) + return; + Beam(uid, args.Origin.Value, tier); + } + + private void OnDestroyed(EntityUid uid, SharedGlimmerReactiveComponent component, DestructionEventArgs args) + { + Spawn("MaterialBluespace1", Transform(uid).Coordinates); + + var tier = _glimmerSystem.GetGlimmerTier(); + if (tier < GlimmerTier.High) + return; + + var totalIntensity = (float) (_glimmerSystem.Glimmer * 2); + var slope = (float) (11 - _glimmerSystem.Glimmer / 100); + var maxIntensity = 20; + + var removed = (float) _glimmerSystem.Glimmer * _random.NextFloat(0.1f, 0.15f); + _glimmerSystem.Glimmer -= (int) removed; + BeamRandomNearProber(uid, _glimmerSystem.Glimmer / 350, _glimmerSystem.Glimmer / 50); + _explosionSystem.QueueExplosion(uid, "Default", totalIntensity, slope, maxIntensity); + } + + private void OnUnanchorAttempt(EntityUid uid, SharedGlimmerReactiveComponent component, UnanchorAttemptEvent args) + { + if (_glimmerSystem.GetGlimmerTier() >= GlimmerTier.Dangerous) + { + _sharedAudioSystem.PlayPvs(component.ShockNoises, args.User); + _electrocutionSystem.TryDoElectrocution(args.User, null, _glimmerSystem.Glimmer / 200, TimeSpan.FromSeconds((float) _glimmerSystem.Glimmer / 100), false); + args.Cancel(); + } + } + + public void BeamRandomNearProber(EntityUid prober, int targets, float range = 10f) + { + List targetList = new(); + foreach (var target in _entityLookupSystem.GetComponentsInRange(Transform(prober).Coordinates, range)) + { + if (target.AllowedEffects.Contains("Electrocution")) + targetList.Add(target.Owner); + } + + foreach(var reactive in _entityLookupSystem.GetComponentsInRange(Transform(prober).Coordinates, range)) + { + targetList.Add(reactive.Owner); + } + + _random.Shuffle(targetList); + foreach (var target in targetList) + { + if (targets <= 0) + return; + + Beam(prober, target, _glimmerSystem.GetGlimmerTier(), false); + targets--; + } + } + + private void Beam(EntityUid prober, EntityUid target, GlimmerTier tier, bool obeyCD = true) + { + if (obeyCD && BeamCooldown != 0) + return; + + if (Deleted(prober) || Deleted(target)) + return; + + var lxform = Transform(prober); + var txform = Transform(target); + + if (!lxform.Coordinates.TryDistance(EntityManager, txform.Coordinates, out var distance)) + return; + if (distance > (float) (_glimmerSystem.Glimmer / 100)) + return; + + string beamproto; + + switch (tier) + { + case GlimmerTier.Dangerous: + beamproto = "SuperchargedLightning"; + break; + case GlimmerTier.Critical: + beamproto = "HyperchargedLightning"; + break; + default: + beamproto = "ChargedLightning"; + break; + } + + + _lightning.ShootLightning(prober, target, beamproto); + BeamCooldown += 3f; + } + + private void AnchorOrExplode(EntityUid uid) + { + var xform = Transform(uid); + if (xform.Anchored) + return; + + if (!TryComp(uid, out var physics)) + return; + + var coordinates = xform.Coordinates; + var gridUid = xform.GridUid; + + if (_mapManager.TryGetGrid(gridUid, out var grid)) + { + var tileIndices = grid.TileIndicesFor(coordinates); + + if (_anchorableSystem.TileFree(grid, tileIndices, physics.CollisionLayer, physics.CollisionMask) && + _transformSystem.AnchorEntity(uid, xform)) + { + return; + } + } + + // Wasn't able to get a grid or a free tile, so explode. + _destructibleSystem.DestroyEntity(uid); + } + + private void Reset(RoundRestartCleanupEvent args) + { + Accumulator = 0; + + // It is necessary that the GlimmerTier is reset to the default + // tier on round restart. This system will persist through + // restarts, and an undesired event will fire as a result after the + // start of the new round, causing modulatable PointLights to have + // negative Energy if the tier was higher than Minimal on restart. + LastGlimmerTier = GlimmerTier.Minimal; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + Accumulator += frameTime; + BeamCooldown = Math.Max(0, BeamCooldown - frameTime); + + if (Accumulator > UpdateFrequency) + { + var currentGlimmerTier = _glimmerSystem.GetGlimmerTier(); + + var reactives = EntityQuery(); + if (currentGlimmerTier != LastGlimmerTier) { + var glimmerTierDelta = (int) currentGlimmerTier - (int) LastGlimmerTier; + var ev = new GlimmerTierChangedEvent(LastGlimmerTier, currentGlimmerTier, glimmerTierDelta); + + foreach (var reactive in reactives) + { + UpdateEntityState(reactive.Owner, reactive, currentGlimmerTier, glimmerTierDelta); + RaiseLocalEvent(reactive.Owner, ev); + } + + LastGlimmerTier = currentGlimmerTier; + } + if (currentGlimmerTier == GlimmerTier.Critical) + { + _ghostSystem.MakeVisible(true); + _revenantSystem.MakeVisible(true); + GhostsVisible = true; + foreach (var reactive in reactives) + { + BeamRandomNearProber(reactive.Owner, 1, 12); + } + } else if (GhostsVisible == true) + { + _ghostSystem.MakeVisible(false); + _revenantSystem.MakeVisible(false); + GhostsVisible = false; + } + Accumulator = 0; + } + } + } + + /// + /// This event is fired when the broader glimmer tier has changed, + /// not on every single adjustment to the glimmer count. + /// + /// has the exact + /// values corresponding to tiers. + /// + public class GlimmerTierChangedEvent : EntityEventArgs + { + /// + /// What was the last glimmer tier before this event fired? + /// + public readonly GlimmerTier LastTier; + + /// + /// What is the current glimmer tier? + /// + public readonly GlimmerTier CurrentTier; + + /// + /// What is the change in tiers between the last and current tier? + /// + public readonly int TierDelta; + + public GlimmerTierChangedEvent(GlimmerTier lastTier, GlimmerTier currentTier, int tierDelta) + { + LastTier = lastTier; + CurrentTier = currentTier; + TierDelta = tierDelta; + } + } +} + diff --git a/Content.Server/Backmen/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs b/Content.Server/Backmen/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs new file mode 100644 index 00000000000..6adda26c2a5 --- /dev/null +++ b/Content.Server/Backmen/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs @@ -0,0 +1,80 @@ +using Content.Server.Backmen.CartridgeLoader.Cartridges; +using Content.Shared.Backmen.Psionics.Glimmer; +using Robust.Shared.Random; +using Robust.Shared.Timing; +using Robust.Shared.Configuration; +using Content.Shared.Backmen.CCVar; +using Content.Shared.GameTicking; + +namespace Content.Server.Backmen.Psionics.Glimmer +{ + /// + /// Handles the passive reduction of glimmer. + /// + public sealed class PassiveGlimmerReductionSystem : EntitySystem + { + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly GlimmerMonitorCartridgeSystem _cartridgeSys = default!; + + /// List of glimmer values spaced by minute. + public List GlimmerValues = new(); + + public TimeSpan TargetUpdatePeriod = TimeSpan.FromSeconds(6); + + private int _updateIncrementor; + public TimeSpan NextUpdateTime = default!; + public TimeSpan LastUpdateTime = default!; + + private float _glimmerLostPerSecond; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnRoundRestartCleanup); + _cfg.OnValueChanged(CCVars.GlimmerLostPerSecond, UpdatePassiveGlimmer, true); + } + + private void OnRoundRestartCleanup(RoundRestartCleanupEvent args) + { + GlimmerValues.Clear(); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var curTime = _timing.CurTime; + if (NextUpdateTime > curTime) + return; + + + var delta = curTime - LastUpdateTime; + var maxGlimmerLost = (int) Math.Round(delta.TotalSeconds * _glimmerLostPerSecond); + + // It used to be 75% to lose one glimmer per ten seconds, but now it's 50% per six seconds. + // The probability is exactly the same over the same span of time. (0.25 ^ 3 == 0.5 ^ 6) + // This math is just easier to do for pausing's sake. + var actualGlimmerLost = _random.Next(0, 1 + maxGlimmerLost); + + _glimmerSystem.Glimmer -= actualGlimmerLost; + + _updateIncrementor++; + + // Since we normally update every 6 seconds, this works out to a minute. + if (_updateIncrementor == 10) + { + GlimmerValues.Add(_glimmerSystem.Glimmer); + + _updateIncrementor = 0; + } + + NextUpdateTime = curTime + TargetUpdatePeriod; + LastUpdateTime = curTime; + } + + private void UpdatePassiveGlimmer(float value) => _glimmerLostPerSecond = value; + } +} diff --git a/Content.Server/Backmen/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs b/Content.Server/Backmen/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs new file mode 100644 index 00000000000..9662a2ede59 --- /dev/null +++ b/Content.Server/Backmen/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs @@ -0,0 +1,26 @@ +namespace Content.Server.Backmen.Psionics.Glimmer; + +[RegisterComponent] +/// +/// Adds to glimmer at regular intervals. We'll use it for glimmer drains too when we get there. +/// +public sealed partial class GlimmerSourceComponent : Component +{ + [DataField("accumulator"), ViewVariables(VVAccess.ReadWrite)] + public float Accumulator = 0f; + + [DataField("active")] + public bool Active = true; + + /// + /// Since glimmer is an int, we'll do it like this. + /// + [DataField("secondsPerGlimmer"), ViewVariables(VVAccess.ReadWrite)] + public float SecondsPerGlimmer = 10f; + + /// + /// True if it produces glimmer, false if it subtracts it. + /// + [DataField("addToGlimmer")] + public bool AddToGlimmer = true; +} diff --git a/Content.Server/Backmen/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs b/Content.Server/Backmen/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs new file mode 100644 index 00000000000..1e992361563 --- /dev/null +++ b/Content.Server/Backmen/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs @@ -0,0 +1,83 @@ +using Content.Server.Anomaly.Components; +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Shared.Anomaly.Components; +using Content.Shared.Backmen.Psionics.Glimmer; + +namespace Content.Server.Backmen.Psionics.Glimmer; + +/// +/// Handles structures which add/subtract glimmer. +/// +public sealed class GlimmerStructuresSystem : EntitySystem +{ + [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!; + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAnomalyVesselPowerChanged); + + SubscribeLocalEvent(OnAnomalyPulse); + SubscribeLocalEvent(OnAnomalySupercritical); + } + + private void OnAnomalyVesselPowerChanged(EntityUid uid, AnomalyVesselComponent component, ref PowerChangedEvent args) + { + if (TryComp(component.Anomaly, out var glimmerSource)) + glimmerSource.Active = args.Powered; + } + + private void OnAnomalyPulse(EntityUid uid, GlimmerSourceComponent component, ref AnomalyPulseEvent args) + { + // Anomalies are meant to have GlimmerSource on them with the + // active flag set to false, as they will be set to actively + // generate glimmer when scanned to an anomaly vessel for + // harvesting research points. + // + // It is not a bug that glimmer increases on pulse or + // supercritical with an inactive glimmer source. + // + // However, this will need to be reworked if a distinction + // needs to be made in the future. I suggest a GlimmerAnomaly + // component. + + if (TryComp(uid, out var anomaly)) + _glimmerSystem.Glimmer += (int) (5f * anomaly.Severity); + } + + private void OnAnomalySupercritical(EntityUid uid, GlimmerSourceComponent component, ref AnomalySupercriticalEvent args) + { + _glimmerSystem.Glimmer += 100; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + foreach (var source in EntityQuery()) + { + if (!_powerReceiverSystem.IsPowered(source.Owner)) + continue; + + if (!source.Active) + continue; + + source.Accumulator += frameTime; + + if (source.Accumulator > source.SecondsPerGlimmer) + { + source.Accumulator -= source.SecondsPerGlimmer; + if (source.AddToGlimmer) + { + _glimmerSystem.Glimmer++; + } + else + { + _glimmerSystem.Glimmer--; + } + } + } + } +} diff --git a/Content.Server/Backmen/Psionics/Invisbility/PsionicInvisibilitySystem.cs b/Content.Server/Backmen/Psionics/Invisbility/PsionicInvisibilitySystem.cs new file mode 100644 index 00000000000..8a85593a04a --- /dev/null +++ b/Content.Server/Backmen/Psionics/Invisbility/PsionicInvisibilitySystem.cs @@ -0,0 +1,140 @@ +using Content.Server.Backmen.Abilities.Psionics; +using Content.Shared.Vehicle.Components; +using Content.Server.Visible; +using Content.Server.NPC.Systems; +using Content.Server.Psionics; +using Content.Shared.Backmen.Abilities.Psionics; +using Robust.Shared.Containers; +using Robust.Server.GameObjects; + +namespace Content.Server.Backmen.Psionics; + +public sealed class PsionicInvisibilitySystem : EntitySystem +{ + [Dependency] private readonly VisibilitySystem _visibilitySystem = default!; + [Dependency] private readonly PsionicInvisibilityPowerSystem _invisSystem = default!; + [Dependency] private readonly NpcFactionSystem _npcFactonSystem = default!; + public override void Initialize() + { + base.Initialize(); + /// Masking + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnInsulInit); + SubscribeLocalEvent(OnInsulShutdown); + SubscribeLocalEvent(OnEyeInit); + + /// Layer + SubscribeLocalEvent(OnInvisInit); + SubscribeLocalEvent(OnInvisShutdown); + + // PVS Stuff + SubscribeLocalEvent(OnEntInserted); + SubscribeLocalEvent(OnEntRemoved); + } + + private void OnInit(EntityUid uid, PotentialPsionicComponent component, ComponentInit args) + { + SetCanSeePsionicInvisiblity(uid, false); + } + + private void OnInsulInit(EntityUid uid, PsionicInsulationComponent component, ComponentInit args) + { + if (!HasComp(uid)) + return; + + if (HasComp(uid)) + _invisSystem.ToggleInvisibility(uid); + + if (_npcFactonSystem.ContainsFaction(uid, "PsionicInterloper")) + { + component.SuppressedFactions.Add("PsionicInterloper"); + _npcFactonSystem.RemoveFaction(uid, "PsionicInterloper"); + } + + if (_npcFactonSystem.ContainsFaction(uid, "GlimmerMonster")) + { + component.SuppressedFactions.Add("GlimmerMonster"); + _npcFactonSystem.RemoveFaction(uid, "GlimmerMonster"); + } + + SetCanSeePsionicInvisiblity(uid, true); + } + + private void OnInsulShutdown(EntityUid uid, PsionicInsulationComponent component, ComponentShutdown args) + { + if (!HasComp(uid)) + return; + + SetCanSeePsionicInvisiblity(uid, false); + + if (!HasComp(uid)) + { + component.SuppressedFactions.Clear(); + return; + } + + foreach (var faction in component.SuppressedFactions) + { + _npcFactonSystem.AddFaction(uid, faction); + } + component.SuppressedFactions.Clear(); + } + + private void OnInvisInit(EntityUid uid, PsionicallyInvisibleComponent component, ComponentInit args) + { + var visibility = EntityManager.EnsureComponent(uid); + + _visibilitySystem.AddLayer(visibility, (int) VisibilityFlags.PsionicInvisibility, false); + _visibilitySystem.RemoveLayer(visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.RefreshVisibility(visibility); + + SetCanSeePsionicInvisiblity(uid, true); + } + + + private void OnInvisShutdown(EntityUid uid, PsionicallyInvisibleComponent component, ComponentShutdown args) + { + if (TryComp(uid, out var visibility)) + { + _visibilitySystem.RemoveLayer(visibility, (int) VisibilityFlags.PsionicInvisibility, false); + _visibilitySystem.AddLayer(visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.RefreshVisibility(visibility); + } + if (HasComp(uid) && !HasComp(uid)) + SetCanSeePsionicInvisiblity(uid, false); + } + + private void OnEyeInit(EntityUid uid, EyeComponent component, ComponentInit args) + { + if (HasComp(uid) || HasComp(uid)) + return; + + SetCanSeePsionicInvisiblity(uid, true); + } + private void OnEntInserted(EntityUid uid, PsionicallyInvisibleComponent component, EntInsertedIntoContainerMessage args) + { + DirtyEntity(args.Entity); + } + + private void OnEntRemoved(EntityUid uid, PsionicallyInvisibleComponent component, EntRemovedFromContainerMessage args) + { + DirtyEntity(args.Entity); + } + + public void SetCanSeePsionicInvisiblity(EntityUid uid, bool set) + { + if (set == true) + { + if (EntityManager.TryGetComponent(uid, out EyeComponent? eye)) + { + eye.VisibilityMask |= (uint) VisibilityFlags.PsionicInvisibility; + } + } else + { + if (EntityManager.TryGetComponent(uid, out EyeComponent? eye)) + { + eye.VisibilityMask &= ~(uint) VisibilityFlags.PsionicInvisibility; + } + } + } +} diff --git a/Content.Server/Backmen/Psionics/Invisbility/PsionicInvisibleContactsComponent.cs b/Content.Server/Backmen/Psionics/Invisbility/PsionicInvisibleContactsComponent.cs new file mode 100644 index 00000000000..bda6df34068 --- /dev/null +++ b/Content.Server/Backmen/Psionics/Invisbility/PsionicInvisibleContactsComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Whitelist; + +namespace Content.Server.Backmen.Psionics; + +[RegisterComponent] +public sealed partial class PsionicInvisibleContactsComponent : Component +{ + [DataField("whitelist", required: true)] + public EntityWhitelist Whitelist = default!; + + /// + /// This tracks how many valid entities are being contacted, + /// so when you stop touching one, you don't immediately lose invisibility. + /// + [DataField("stages")] + public int Stages = 0; +} diff --git a/Content.Server/Backmen/Psionics/Invisbility/PsionicInvisibleContactsSystem.cs b/Content.Server/Backmen/Psionics/Invisbility/PsionicInvisibleContactsSystem.cs new file mode 100644 index 00000000000..baabae143e5 --- /dev/null +++ b/Content.Server/Backmen/Psionics/Invisbility/PsionicInvisibleContactsSystem.cs @@ -0,0 +1,67 @@ +using Content.Shared.Stealth; +using Content.Shared.Stealth.Components; +using Robust.Shared.Physics.Events; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Timing; + +namespace Content.Server.Backmen.Psionics; + +/// +/// Allows an entity to become psionically invisible when touching certain entities. +/// +public sealed class PsionicInvisibleContactsSystem : EntitySystem +{ + [Dependency] private readonly SharedStealthSystem _stealth = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnEntityEnter); + SubscribeLocalEvent(OnEntityExit); + + UpdatesAfter.Add(typeof(SharedPhysicsSystem)); + } + + private void OnEntityEnter(EntityUid uid, PsionicInvisibleContactsComponent component, ref StartCollideEvent args) + { + var otherUid = args.OtherEntity; + var ourEntity = args.OurEntity; + + if (!component.Whitelist.IsValid(otherUid)) + return; + + // This will go up twice per web hit, since webs also have a flammable fixture. + // It goes down twice per web exit, so everything's fine. + ++component.Stages; + + if (HasComp(ourEntity)) + return; + + EnsureComp(ourEntity); + var stealth = EnsureComp(ourEntity); + _stealth.SetVisibility(ourEntity, 0.66f, stealth); + } + + private void OnEntityExit(EntityUid uid, PsionicInvisibleContactsComponent component, ref EndCollideEvent args) + { + var otherUid = args.OtherEntity; + var ourEntity = args.OurEntity; + + if (!component.Whitelist.IsValid(otherUid)) + return; + + if (!HasComp(ourEntity)) + return; + + if (--component.Stages > 0) + return; + + RemComp(ourEntity); + var stealth = EnsureComp(ourEntity); + // Just to be sure... + _stealth.SetVisibility(ourEntity, 1f, stealth); + + RemComp(ourEntity); + } +} diff --git a/Content.Server/Backmen/Psionics/Invisbility/PsionicallyInvisibleComponent.cs b/Content.Server/Backmen/Psionics/Invisbility/PsionicallyInvisibleComponent.cs new file mode 100644 index 00000000000..55ce5db5e78 --- /dev/null +++ b/Content.Server/Backmen/Psionics/Invisbility/PsionicallyInvisibleComponent.cs @@ -0,0 +1,5 @@ +namespace Content.Server.Backmen.Psionics; + +[RegisterComponent] +public sealed partial class PsionicallyInvisibleComponent : Component +{} diff --git a/Content.Server/Backmen/Psionics/NPC/GlimmerWisp/GlimmerWispComponent.cs b/Content.Server/Backmen/Psionics/NPC/GlimmerWisp/GlimmerWispComponent.cs new file mode 100644 index 00000000000..50fcf7af430 --- /dev/null +++ b/Content.Server/Backmen/Psionics/NPC/GlimmerWisp/GlimmerWispComponent.cs @@ -0,0 +1,26 @@ +/* +using Robust.Shared.Audio; + +namespace Content.Server.Backmen.Psionics.NPC.GlimmerWisp +{ + [RegisterComponent] + public sealed partial class GlimmerWispComponent : Component + { + public bool IsDraining = false; + /// + /// The time (in seconds) that it takes to drain an entity. + /// + [DataField("drainDelay")] + public float DrainDelay = 8.35f; + + [DataField("drainSound")] + public SoundSpecifier DrainSoundPath = new SoundPathSpecifier("/Audio/Effects/clang2.ogg"); + + [DataField("drainFinishSound")] + public SoundSpecifier DrainFinishSoundPath = new SoundPathSpecifier("/Audio/Effects/guardian_inject.ogg"); + + public IPlayingAudioStream? DrainStingStream; + public EntityUid? DrainTarget; + } +} +*/ diff --git a/Content.Server/Backmen/Psionics/NPC/GlimmerWisp/GlimmerWispSystem.cs b/Content.Server/Backmen/Psionics/NPC/GlimmerWisp/GlimmerWispSystem.cs new file mode 100644 index 00000000000..d1a6952d41c --- /dev/null +++ b/Content.Server/Backmen/Psionics/NPC/GlimmerWisp/GlimmerWispSystem.cs @@ -0,0 +1,134 @@ +/* +using Content.Server.DoAfter; +using Content.Server.NPC.Systems; +using Content.Shared.DoAfter; +using Content.Shared.Mobs.Systems; +using Content.Shared.Damage; +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Shared.Verbs; +using Content.Shared.Backmen.Psionics.Events; +using Content.Shared.Rejuvenate; +using Content.Shared.ActionBlocker; +using Content.Shared.Pulling.Components; +using Content.Server.Popups; +using Robust.Shared.Player; +using Robust.Shared.Utility; +using Robust.Server.GameObjects; + +namespace Content.Server.Backmen.Psionics.NPC.GlimmerWisp +{ + public sealed class GlimmerWispSystem : EntitySystem + { + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly DoAfterSystem _doAfter = default!; + [Dependency] private readonly MobStateSystem _mobs = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly PopupSystem _popups = default!; + [Dependency] private readonly AudioSystem _audioSystem = default!; + [Dependency] private readonly NPCJukeSystem _combatTargetSystem = default!; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent>(AddDrainVerb); + SubscribeLocalEvent(OnDrain); + } + + private void AddDrainVerb(EntityUid uid, GlimmerWispComponent component, GetVerbsEvent args) + { + if (args.User == args.Target) + return; + if (!args.CanAccess) + return; + if (!HasComp(args.Target)) + return; + if (!_mobs.IsCritical(args.Target)) + return; + + InnateVerb verb = new() + { + Act = () => + { + StartLifeDrain(uid, args.Target, component); + }, + Text = Loc.GetString("verb-life-drain"), + Icon = new SpriteSpecifier.Texture(new ("/Textures/Nyanotrasen/Icons/verbiconfangs.png")), + Priority = 2 + }; + args.Verbs.Add(verb); + } + + private void OnDrain(EntityUid uid, GlimmerWispComponent component, GlimmerWispDrainDoAfterEvent args) + { + component.IsDraining = false; + if (args.Handled || args.Args.Target == null) + { + component.DrainStingStream?.Stop(); + return; + } + + if (args.Cancelled) + { + if (TryComp(args.Args.Target.Value, out var pullable) && pullable.Puller != null) + _combatTargetSystem.StartHostility(uid, pullable.Puller.Value); + + if (TryComp(args.Args.Target.Value, out var carried)) + _combatTargetSystem.StartHostility(uid, carried.Carrier); + + return; + } + + _popups.PopupEntity(Loc.GetString("life-drain-second-end", ("wisp", uid)), args.Args.Target.Value, args.Args.Target.Value, Shared.Popups.PopupType.LargeCaution); + _popups.PopupEntity(Loc.GetString("life-drain-third-end", ("wisp", uid), ("target", args.Args.Target.Value)), args.Args.Target.Value, Filter.PvsExcept(args.Args.Target.Value), true, Shared.Popups.PopupType.LargeCaution); + + var rejEv = new RejuvenateEvent(); + RaiseLocalEvent(uid, rejEv); + + _audioSystem.PlayPvs(component.DrainFinishSoundPath, uid); + + DamageSpecifier damage = new(); + damage.DamageDict.Add("Asphyxiation", 200); + _damageable.TryChangeDamage(args.Args.Target.Value, damage, true, origin:uid); + } + + + public bool NPCStartLifedrain(EntityUid uid, EntityUid target, GlimmerWispComponent? component = null) + { + if (!Resolve(uid, ref component)) + return false; + if (!HasComp(target)) + return false; + if (!_mobs.IsCritical(target)) + return false; + if (!_actionBlocker.CanInteract(uid, target)) + return false; + + StartLifeDrain(uid, target, component); + return true; + } + + public void StartLifeDrain(EntityUid uid, EntityUid target, GlimmerWispComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + component.DrainTarget = target; + _popups.PopupEntity(Loc.GetString("life-drain-second-start", ("wisp", uid)), target, target, Shared.Popups.PopupType.LargeCaution); + _popups.PopupEntity(Loc.GetString("life-drain-third-start", ("wisp", uid), ("target", target)), target, Filter.PvsExcept(target), true, Shared.Popups.PopupType.LargeCaution); + + component.DrainStingStream = _audioSystem.PlayPvs(component.DrainSoundPath, target); + component.IsDraining = true; + + var ev = new GlimmerWispDrainDoAfterEvent(); + var args = new DoAfterArgs(uid, component.DrainDelay, ev, uid, target: target) + { + BreakOnTargetMove = true, + BreakOnUserMove = false, + DistanceThreshold = 2f, + NeedHand = false + }; + + _doAfter.TryStartDoAfter(args); + } + } +} +*/ diff --git a/Content.Server/Backmen/Psionics/NPC/PsionicNPCCombatSystem.cs b/Content.Server/Backmen/Psionics/NPC/PsionicNPCCombatSystem.cs new file mode 100644 index 00000000000..2d8b838e4fa --- /dev/null +++ b/Content.Server/Backmen/Psionics/NPC/PsionicNPCCombatSystem.cs @@ -0,0 +1,40 @@ +using Content.Shared.Actions; +using Content.Server.NPC.Events; +using Content.Server.NPC.Components; +using Content.Shared.Backmen.Abilities.Psionics; +using Robust.Shared.Timing; + +namespace Content.Server.Backmen.Psionics.NPC +{ + public sealed class PsionicNPCCombatSystem : EntitySystem + { + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(ZapCombat); + } + + private void ZapCombat(EntityUid uid, NoosphericZapPowerComponent component, ref NPCSteeringEvent args) + { + if (component.NoosphericZapPowerAction?.Event == null) + return; + + if (component.NoosphericZapPowerAction!.Cooldown.HasValue && component.NoosphericZapPowerAction?.Cooldown.Value.End > _timing.CurTime) + return; + + if (!TryComp(uid, out var combat)) + return; + + if (_actions.ValidateEntityTarget(uid, combat.Target, component.NoosphericZapPowerAction!)) + { + var ev = component.NoosphericZapPowerAction!.Event; + ev.Performer = uid; + ev.Target = combat.Target; + + _actions.PerformAction(uid, null, component.NoosphericZapPowerAction!, ev, _timing.CurTime, false); + } + } + } +} diff --git a/Content.Server/Backmen/Psionics/PotentialPsionicComponent.cs b/Content.Server/Backmen/Psionics/PotentialPsionicComponent.cs new file mode 100644 index 00000000000..10eb51a18e9 --- /dev/null +++ b/Content.Server/Backmen/Psionics/PotentialPsionicComponent.cs @@ -0,0 +1,13 @@ +namespace Content.Server.Backmen.Psionics; + +[RegisterComponent] +public sealed partial class PotentialPsionicComponent : Component +{ + [DataField("chance"), ViewVariables(VVAccess.ReadWrite)] + public float Chance = 0.04f; + + /// + /// YORO (you only reroll once) + /// + public bool Rerolled = false; +} diff --git a/Content.Server/Backmen/Psionics/PsionicAwaitingPlayerComponent.cs b/Content.Server/Backmen/Psionics/PsionicAwaitingPlayerComponent.cs new file mode 100644 index 00000000000..d700b619591 --- /dev/null +++ b/Content.Server/Backmen/Psionics/PsionicAwaitingPlayerComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Server.Backmen.Psionics; + +/// +/// Will open the 'accept psionics' UI when a player attaches. +/// +[RegisterComponent] +public sealed partial class PsionicAwaitingPlayerComponent : Component +{} diff --git a/Content.Server/Backmen/Psionics/PsionicBonusChanceComponent.cs b/Content.Server/Backmen/Psionics/PsionicBonusChanceComponent.cs new file mode 100644 index 00000000000..a3a64337e96 --- /dev/null +++ b/Content.Server/Backmen/Psionics/PsionicBonusChanceComponent.cs @@ -0,0 +1,17 @@ +namespace Content.Server.Backmen.Psionics; + +[RegisterComponent] +public sealed partial class PsionicBonusChanceComponent : Component +{ + [DataField("multiplier"), ViewVariables(VVAccess.ReadWrite)] + public float Multiplier = 1f; + [DataField("flatBonus"), ViewVariables(VVAccess.ReadWrite)] + public float FlatBonus = 0; + + /// + /// Whether we should warn the user they are about to receive psionics. + /// It's here because AddComponentSpecial can't overwrite a component, and this is very role dependent. + /// + [DataField("warn")] + public bool Warn = true; +} diff --git a/Content.Server/Backmen/Psionics/PsionicsCommands.cs b/Content.Server/Backmen/Psionics/PsionicsCommands.cs new file mode 100644 index 00000000000..966f4a3d831 --- /dev/null +++ b/Content.Server/Backmen/Psionics/PsionicsCommands.cs @@ -0,0 +1,26 @@ +using Content.Server.Administration; +using Content.Shared.Administration; +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Shared.Mobs.Components; +using Robust.Shared.Console; +using Robust.Server.GameObjects; + +namespace Content.Server.Backmen.Psionics; + +[AdminCommand(AdminFlags.Logs)] +public sealed class ListPsionicsCommand : IConsoleCommand +{ + public string Command => "lspsionics"; + public string Description => Loc.GetString("command-lspsionic-description"); + public string Help => Loc.GetString("command-lspsionic-help"); + public async void Execute(IConsoleShell shell, string argStr, string[] args) + { + var entMan = IoCManager.Resolve(); + foreach (var (actor, mob, psionic, meta) in entMan.EntityQuery()) + { + // filter out xenos, etc, with innate telepathy + if (psionic.PsionicAbility?.DisplayName != null) + shell.WriteLine(meta.EntityName + " (" + meta.Owner + ") - " + actor.PlayerSession.Name + " - " + Loc.GetString(psionic.PsionicAbility.DisplayName)); + } + } +} diff --git a/Content.Server/Backmen/Psionics/PsionicsSystem.cs b/Content.Server/Backmen/Psionics/PsionicsSystem.cs new file mode 100644 index 00000000000..d543a1d42a9 --- /dev/null +++ b/Content.Server/Backmen/Psionics/PsionicsSystem.cs @@ -0,0 +1,191 @@ +using Content.Shared.StatusEffect; +using Content.Shared.Mobs; +using Content.Shared.Weapons.Melee.Events; +using Content.Shared.Damage.Events; +using Content.Shared.IdentityManagement; +using Content.Shared.Backmen.CCVar; +using Content.Server.Backmen.Abilities.Psionics; +using Content.Server.Chat.Systems; +using Content.Server.Electrocution; +using Content.Server.NPC.Components; +using Content.Server.NPC.Systems; +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Shared.Backmen.Psionics.Glimmer; +using Robust.Shared.Audio; +using Robust.Shared.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Random; + +namespace Content.Server.Backmen.Psionics; + +public sealed class PsionicsSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly PsionicAbilitiesSystem _psionicAbilitiesSystem = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; + [Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!; + [Dependency] private readonly MindSwapPowerSystem _mindSwapPowerSystem = default!; + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly NpcFactionSystem _npcFactonSystem = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + + /// + /// Unfortunately, since spawning as a normal role and anything else is so different, + /// this is the only way to unify them, for now at least. + /// + Queue<(PotentialPsionicComponent component, EntityUid uid)> _rollers = new(); + public override void Update(float frameTime) + { + base.Update(frameTime); + foreach (var roller in _rollers) + { + RollPsionics(roller.uid, roller.component, false); + } + _rollers.Clear(); + } + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnMeleeHit); + SubscribeLocalEvent(OnStamHit); + + SubscribeLocalEvent(OnDeathGasp); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnRemove); + } + + private void OnStartup(EntityUid uid, PotentialPsionicComponent component, MapInitEvent args) + { + if (HasComp(uid)) + return; + + _rollers.Enqueue((component, uid)); + } + + private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, MeleeHitEvent args) + { + foreach (var entity in args.HitEntities) + { + if (HasComp(entity)) + { + SoundSystem.Play("/Audio/Effects/lightburn.ogg", Filter.Pvs(entity), entity); + args.ModifiersList.Add(component.Modifiers); + if (_random.Prob(component.DisableChance)) + _statusEffects.TryAddStatusEffect(entity, "PsionicsDisabled", TimeSpan.FromSeconds(10), true, "PsionicsDisabled"); + } + + if (TryComp(entity, out var swapped)) + { + _mindSwapPowerSystem.Swap(entity, swapped.OriginalEntity, true); + return; + } + + if (component.Punish && HasComp(entity) && !HasComp(entity) && _random.Prob(0.5f)) + _electrocutionSystem.TryDoElectrocution(args.User, null, 20, TimeSpan.FromSeconds(5), false); + } + } + + private void OnDeathGasp(EntityUid uid, PotentialPsionicComponent component, MobStateChangedEvent args) + { + if (args.NewMobState != MobState.Dead) + return; + + string message; + + switch (_glimmerSystem.GetGlimmerTier()) + { + case GlimmerTier.Critical: + message = Loc.GetString("death-gasp-high", ("ent", Identity.Entity(uid, EntityManager))); + break; + case GlimmerTier.Dangerous: + message = Loc.GetString("death-gasp-medium", ("ent",Identity.Entity(uid, EntityManager))); + break; + default: + message = Loc.GetString("death-gasp-normal", ("ent", Identity.Entity(uid, EntityManager))); + break; + } + + _chat.TrySendInGameICMessage(uid, message, InGameICChatType.Emote, false, ignoreActionBlocker:true); + } + + private void OnInit(EntityUid uid, PsionicComponent component, ComponentInit args) + { + if (!component.Removable) + return; + + if (!TryComp(uid, out var factions)) + return; + + if (_npcFactonSystem.ContainsFaction(uid, "GlimmerMonster", factions)) + return; + + _npcFactonSystem.AddFaction(uid, "PsionicInterloper"); + } + + private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove args) + { + if (!TryComp(uid, out var factions)) + return; + + _npcFactonSystem.RemoveFaction(uid, "PsionicInterloper"); + } + + private void OnStamHit(EntityUid uid, AntiPsionicWeaponComponent component, StaminaMeleeHitEvent args) + { + var bonus = false; + foreach (var stam in args.HitList) + { + if (HasComp(stam.Entity)) + bonus = true; + } + + if (!bonus) + return; + + + args.FlatModifier += component.PsychicStaminaDamage; + } + + public void RollPsionics(EntityUid uid, PotentialPsionicComponent component, bool applyGlimmer = true, float multiplier = 1f) + { + if (HasComp(uid)) + return; + + if (!_cfg.GetCVar(CCVars.PsionicRollsEnabled)) + return; + + var chance = component.Chance; + var warn = true; + if (TryComp(uid, out var bonus)) + { + chance += bonus.FlatBonus; + chance *= bonus.Multiplier; + warn = bonus.Warn; + } + + if (applyGlimmer) + chance += ((float) _glimmerSystem.Glimmer / 1000); + + chance *= multiplier; + + chance = Math.Clamp(chance, 0, 1); + + if (_random.Prob(chance)) + _psionicAbilitiesSystem.AddPsionics(uid, warn); + } + + public void RerollPsionics(EntityUid uid, PotentialPsionicComponent? psionic = null, float bonusMuliplier = 1f) + { + if (!Resolve(uid, ref psionic, false)) + return; + + if (psionic.Rerolled) + return; + + RollPsionics(uid, psionic, multiplier: bonusMuliplier); + psionic.Rerolled = true; + } +} diff --git a/Content.Server/Backmen/Psionics/PsychokinesisPowerSystem.cs b/Content.Server/Backmen/Psionics/PsychokinesisPowerSystem.cs new file mode 100644 index 00000000000..ac5c250304b --- /dev/null +++ b/Content.Server/Backmen/Psionics/PsychokinesisPowerSystem.cs @@ -0,0 +1,64 @@ +using Content.Shared.Actions; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Backmen.Abilities.Psionics; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Robust.Shared.Audio; + +namespace Content.Server.Backmen.Abilities.Psionics; + +public sealed class PsychokinesisPowerSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + } + + private void OnInit(EntityUid uid, PsychokinesisPowerComponent component, ComponentInit args) + { + if (!_prototypeManager.TryIndex("Psychokinesis", out var psychokinesis)) + return; + + component.PsychokinesisPowerAction = new WorldTargetAction(psychokinesis); + if (psychokinesis.UseDelay != null) + component.PsychokinesisPowerAction.Cooldown = (_gameTiming.CurTime, _gameTiming.CurTime + (TimeSpan) psychokinesis.UseDelay); + _actions.AddAction(uid, component.PsychokinesisPowerAction, null); + + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + psionic.PsionicAbility = component.PsychokinesisPowerAction; + } + + private void OnShutdown(EntityUid uid, PsychokinesisPowerComponent component, ComponentShutdown args) + { + if (_prototypeManager.TryIndex("Psychokinesis", out var psychokinesis)) + _actions.RemoveAction(uid, new EntityTargetAction(psychokinesis), null); + } + + private void OnPowerUsed(EntityUid uid ,PsychokinesisPowerComponent comp, PsychokinesisPowerActionEvent args) + { + var transform = Transform(args.Performer); + + if (transform.MapID != args.Target.GetMapId(EntityManager)) return; + + _transformSystem.SetCoordinates(args.Performer, args.Target); + transform.AttachToGridOrMap(); + _audio.PlayPvs(comp.WaveSound, args.Performer, AudioParams.Default.WithVolume(comp.WaveVolume)); + + _psionics.LogPowerUsed(uid, "psychokinesis"); + + args.Handled = true; + } +} + +public sealed partial class PsychokinesisPowerActionEvent : WorldTargetActionEvent {} diff --git a/Content.Server/Backmen/StationEvents/Components/GlimmerEventComponent.cs b/Content.Server/Backmen/StationEvents/Components/GlimmerEventComponent.cs new file mode 100644 index 00000000000..feb0871420d --- /dev/null +++ b/Content.Server/Backmen/StationEvents/Components/GlimmerEventComponent.cs @@ -0,0 +1,34 @@ +namespace Content.Server.Backmen.StationEvents.Components; + +[RegisterComponent] +public sealed partial class GlimmerEventComponent : Component +{ + /// + /// Minimum glimmer value for event to be eligible. (Should be 100 at lowest.) + /// + [DataField("minimumGlimmer")] + public int MinimumGlimmer = 100; + + /// + /// Maximum glimmer value for event to be eligible. (Remember 1000 is max glimmer period.) + /// + [DataField("maximumGlimmer")] + public int MaximumGlimmer = 1000; + + /// + /// Will be used for _random.Next and subtracted from glimmer. + /// Lower bound. + /// + [DataField("glimmerBurnLower")] + public int GlimmerBurnLower = 25; + + /// + /// Will be used for _random.Next and subtracted from glimmer. + /// Upper bound. + /// + [DataField("glimmerBurnUpper")] + public int GlimmerBurnUpper = 70; + + [DataField("report")] + public string SophicReport = "glimmer-event-report-generic"; +} diff --git a/Content.Server/Backmen/StationEvents/Components/MassMindSwapRuleComponent.cs b/Content.Server/Backmen/StationEvents/Components/MassMindSwapRuleComponent.cs new file mode 100644 index 00000000000..8678c12b696 --- /dev/null +++ b/Content.Server/Backmen/StationEvents/Components/MassMindSwapRuleComponent.cs @@ -0,0 +1,13 @@ +using Content.Server.Backmen.StationEvents.Events; + +namespace Content.Server.Backmen.StationEvents.Components; + +[RegisterComponent, Access(typeof(MassMindSwapRule))] +public sealed partial class MassMindSwapRuleComponent : Component +{ + /// + /// The mind swap is only temporary if true. + /// + [DataField("isTemporary")] + public bool IsTemporary; +} diff --git a/Content.Server/Backmen/StationEvents/Components/NoosphericFryRuleComponent.cs b/Content.Server/Backmen/StationEvents/Components/NoosphericFryRuleComponent.cs new file mode 100644 index 00000000000..8c74897038e --- /dev/null +++ b/Content.Server/Backmen/StationEvents/Components/NoosphericFryRuleComponent.cs @@ -0,0 +1,8 @@ +using Content.Server.Backmen.StationEvents.Events; + +namespace Content.Server.Backmen.StationEvents.Components; + +[RegisterComponent, Access(typeof(NoosphericFryRule))] +public sealed partial class NoosphericFryRuleComponent : Component +{ +} diff --git a/Content.Server/Backmen/StationEvents/Components/NoosphericStormRuleComponent.cs b/Content.Server/Backmen/StationEvents/Components/NoosphericStormRuleComponent.cs new file mode 100644 index 00000000000..6a3c67161f6 --- /dev/null +++ b/Content.Server/Backmen/StationEvents/Components/NoosphericStormRuleComponent.cs @@ -0,0 +1,29 @@ +using Content.Server.Backmen.StationEvents.Events; + +namespace Content.Server.Backmen.StationEvents.Components; + +[RegisterComponent, Access(typeof(NoosphericStormRule))] +public sealed partial class NoosphericStormRuleComponent : Component +{ + /// + /// How many potential psionics should be awakened at most. + /// + [DataField("maxAwaken")] + public int MaxAwaken { get; private set; } = 3; + + /// + /// + [DataField("baseGlimmerAddMin")] + public int BaseGlimmerAddMin { get; private set; } = 65; + + /// + /// + [DataField("baseGlimmerAddMax")] + public int BaseGlimmerAddMax { get; private set; } = 85; + + /// + /// Multiply the EventSeverityModifier by this to determine how much extra glimmer to add. + /// + [DataField("glimmerSeverityCoefficient")] + public float GlimmerSeverityCoefficient { get; private set; }= 0.25f; +} diff --git a/Content.Server/Backmen/StationEvents/Components/NoosphericZapRuleComponent.cs b/Content.Server/Backmen/StationEvents/Components/NoosphericZapRuleComponent.cs new file mode 100644 index 00000000000..4a3bb19235c --- /dev/null +++ b/Content.Server/Backmen/StationEvents/Components/NoosphericZapRuleComponent.cs @@ -0,0 +1,8 @@ +using Content.Server.Backmen.StationEvents.Events; + +namespace Content.Server.Backmen.StationEvents.Components; + +[RegisterComponent, Access(typeof(NoosphericZapRule))] +public sealed partial class NoosphericZapRuleComponent : Component +{ +} diff --git a/Content.Server/Backmen/StationEvents/Components/PsionicCatGotYourTongueRuleComponent.cs b/Content.Server/Backmen/StationEvents/Components/PsionicCatGotYourTongueRuleComponent.cs new file mode 100644 index 00000000000..bcb49e12e10 --- /dev/null +++ b/Content.Server/Backmen/StationEvents/Components/PsionicCatGotYourTongueRuleComponent.cs @@ -0,0 +1,17 @@ +using Content.Server.Backmen.StationEvents.Events; +using Robust.Shared.Audio; + +namespace Content.Server.Backmen.StationEvents.Components; + +[RegisterComponent, Access(typeof(PsionicCatGotYourTongueRule))] +public sealed partial class PsionicCatGotYourTongueRuleComponent : Component +{ + [DataField("minDuration")] + public TimeSpan MinDuration = TimeSpan.FromSeconds(20); + + [DataField("maxDuration")] + public TimeSpan MaxDuration = TimeSpan.FromSeconds(80); + + [DataField("sound")] + public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Nyanotrasen/Voice/Felinid/cat_scream1.ogg"); +} diff --git a/Content.Server/Backmen/StationEvents/Events/GlimmerEventSystem.cs b/Content.Server/Backmen/StationEvents/Events/GlimmerEventSystem.cs new file mode 100644 index 00000000000..0df9b567f7f --- /dev/null +++ b/Content.Server/Backmen/StationEvents/Events/GlimmerEventSystem.cs @@ -0,0 +1,35 @@ +using Content.Server.Backmen.StationEvents.Components; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.StationEvents.Events; +using Content.Shared.Backmen.Psionics.Glimmer; + +namespace Content.Server.Backmen.StationEvents.Events; + +public sealed class GlimmerEventSystem: StationEventSystem +{ + [Dependency] private readonly GlimmerSystem GlimmerSystem = default!; + + protected override void Ended(EntityUid uid, GlimmerEventComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args) + { + base.Ended(uid, component, gameRule, args); + + var glimmerBurned = RobustRandom.Next(component.GlimmerBurnLower, component.GlimmerBurnUpper); + GlimmerSystem.Glimmer -= glimmerBurned; + + var reportEv = new GlimmerEventEndedEvent(component.SophicReport, glimmerBurned); + RaiseLocalEvent(reportEv); + } +} + + +public sealed class GlimmerEventEndedEvent : EntityEventArgs +{ + public string Message = ""; + public int GlimmerBurned = 0; + + public GlimmerEventEndedEvent(string message, int glimmerBurned) + { + Message = message; + GlimmerBurned = glimmerBurned; + } +} diff --git a/Content.Server/Backmen/StationEvents/Events/MassMindSwapRule.cs b/Content.Server/Backmen/StationEvents/Events/MassMindSwapRule.cs new file mode 100644 index 00000000000..79245a5abd0 --- /dev/null +++ b/Content.Server/Backmen/StationEvents/Events/MassMindSwapRule.cs @@ -0,0 +1,78 @@ +using Content.Server.Backmen.Abilities.Psionics; +using Content.Server.Backmen.Psionics; +using Content.Server.Backmen.StationEvents.Components; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.StationEvents.Events; +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Robust.Server.GameObjects; +using Robust.Shared.Random; + +namespace Content.Server.Backmen.StationEvents.Events; + +/// +/// Forces a mind swap on all non-insulated potential psionic entities. +/// +internal sealed class MassMindSwapRule : StationEventSystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly MindSwapPowerSystem _mindSwap = default!; + + protected override void Started(EntityUid uid, MassMindSwapRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) + { + base.Started(uid, component, gameRule, args); + + List psionicPool = new(); + List psionicActors = new(); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var psion, out _, out _)) + { + if (_mobStateSystem.IsAlive(psion) && !HasComp(psion)) + { + psionicPool.Add(psion); + + if (HasComp(psion)) + { + // This is so we don't bother mindswapping NPCs with NPCs. + psionicActors.Add(psion); + } + } + } + + // Shuffle the list of candidates. + _random.Shuffle(psionicPool); + + foreach (var actor in psionicActors) + { + do + { + if (psionicPool.Count == 0) + // We ran out of candidates. Exit early. + return; + + // Pop the last entry off. + var other = psionicPool[^1]; + psionicPool.RemoveAt(psionicPool.Count - 1); + + if (other == actor) + // Don't be yourself. Find someone else. + continue; + + // A valid swap target has been found. + // Remove this actor from the pool of swap candidates before they go. + psionicPool.Remove(actor); + + // Do the swap. + _mindSwap.Swap(actor, other); + if (!component.IsTemporary) + { + _mindSwap.GetTrapped(actor); + _mindSwap.GetTrapped(other); + } + } while (true); + } + } +} diff --git a/Content.Server/Backmen/StationEvents/Events/NoosphericFryRule.cs b/Content.Server/Backmen/StationEvents/Events/NoosphericFryRule.cs new file mode 100644 index 00000000000..779bf97f6c4 --- /dev/null +++ b/Content.Server/Backmen/StationEvents/Events/NoosphericFryRule.cs @@ -0,0 +1,130 @@ +using Content.Server.Atmos.Components; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Backmen.Psionics.Glimmer; +using Content.Server.Backmen.StationEvents.Components; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Popups; +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Server.StationEvents.Events; +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Shared.Backmen.Psionics.Glimmer; +using Content.Shared.Construction.EntitySystems; +using Content.Shared.Damage; +using Content.Shared.Inventory; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Robust.Shared.Map; +using Robust.Shared.Physics.Components; +using Robust.Shared.Player; + +namespace Content.Server.Backmen.StationEvents.Events; + +/// +/// Fries tinfoil hats and cages +/// +internal sealed class NoosphericFryRule : StationEventSystem +{ + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + [Dependency] private readonly FlammableSystem _flammableSystem = default!; + [Dependency] private readonly GlimmerReactiveSystem _glimmerReactiveSystem = default!; + [Dependency] private readonly AnchorableSystem _anchorableSystem = default!; + [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!; + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + + protected override void Started(EntityUid uid, NoosphericFryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) + { + base.Started(uid, component, gameRule, args); + + List<(EntityUid wearer, TinfoilHatComponent worn)> psionicList = new(); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var psion, out _, out _)) + { + if (!_mobStateSystem.IsAlive(psion)) + continue; + + if (!_inventorySystem.TryGetSlotEntity(psion, "head", out var headItem)) + continue; + + if (!TryComp(headItem, out var tinfoil)) + continue; + + psionicList.Add((psion, tinfoil)); + } + + foreach (var pair in psionicList) + { + if (pair.worn.DestroyOnFry) + { + QueueDel(pair.worn.Owner); + Spawn("Ash", Transform(pair.wearer).Coordinates); + _popupSystem.PopupEntity(Loc.GetString("psionic-burns-up", ("item", pair.worn.Owner)), pair.wearer, Filter.Pvs(pair.worn.Owner), true, Shared.Popups.PopupType.MediumCaution); + _audioSystem.Play("/Audio/Effects/lightburn.ogg", Filter.Pvs(pair.worn.Owner), pair.worn.Owner, true); + } else + { + _popupSystem.PopupEntity(Loc.GetString("psionic-burn-resist", ("item", pair.worn.Owner)), pair.wearer, Filter.Pvs(pair.worn.Owner), true, Shared.Popups.PopupType.SmallCaution); + _audioSystem.Play("/Audio/Effects/lightburn.ogg", Filter.Pvs(pair.worn.Owner), pair.worn.Owner, true); + } + + DamageSpecifier damage = new(); + damage.DamageDict.Add("Heat", 2.5); + damage.DamageDict.Add("Shock", 2.5); + + if (_glimmerSystem.Glimmer > 500 && _glimmerSystem.Glimmer < 750) + { + damage *= 2; + if (TryComp(pair.wearer, out var flammableComponent)) + { + flammableComponent.FireStacks += 1; + _flammableSystem.Ignite(pair.wearer,uid, flammableComponent); + } + } else if (_glimmerSystem.Glimmer > 750) + { + damage *= 3; + if (TryComp(pair.wearer, out var flammableComponent)) + { + flammableComponent.FireStacks += 2; + _flammableSystem.Ignite(pair.wearer, uid,flammableComponent); + } + } + + _damageableSystem.TryChangeDamage(pair.wearer, damage, true, true); + } + + // for probers: + var queryReactive = EntityQueryEnumerator(); + while (queryReactive.MoveNext(out var reactive, out _, out var xform, out var physics)) + { + // shoot out three bolts of lighting... + _glimmerReactiveSystem.BeamRandomNearProber(reactive, 3, 12); + + // try to anchor if we can + if (!xform.Anchored) + { + var coordinates = xform.Coordinates; + var gridUid = xform.GridUid; + if (!_mapManager.TryGetGrid(gridUid, out var grid)) + continue; + + var tileIndices = grid.TileIndicesFor(coordinates); + + if (_anchorableSystem.TileFree(grid, tileIndices, physics.CollisionLayer, physics.CollisionMask)) + _transformSystem.AnchorEntity(reactive, xform); + } + + if (!TryComp(reactive, out var power)) + continue; + + // If it's been turned off, turn it back on. + if (power.PowerDisabled) + _powerReceiverSystem.TogglePower(reactive, false); + } + } +} diff --git a/Content.Server/Backmen/StationEvents/Events/NoosphericStormRule.cs b/Content.Server/Backmen/StationEvents/Events/NoosphericStormRule.cs new file mode 100644 index 00000000000..382c125d583 --- /dev/null +++ b/Content.Server/Backmen/StationEvents/Events/NoosphericStormRule.cs @@ -0,0 +1,59 @@ +using Content.Server.Backmen.Abilities.Psionics; +using Content.Server.Backmen.Psionics; +using Content.Server.Backmen.StationEvents.Components; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.StationEvents.Events; +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Shared.Backmen.Psionics.Glimmer; +using Content.Shared.Mobs.Systems; +using Robust.Shared.Random; + +namespace Content.Server.Backmen.StationEvents.Events; + +internal sealed class NoosphericStormRule : StationEventSystem +{ + [Dependency] private readonly PsionicAbilitiesSystem _psionicAbilitiesSystem = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; + + protected override void Started(EntityUid uid, NoosphericStormRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) + { + base.Started(uid, component, gameRule, args); + + List validList = new(); + + var query = EntityManager.EntityQueryEnumerator(); + while (query.MoveNext(out var potentialPsionic, out var potentialPsionicComponent)) + { + if (_mobStateSystem.IsDead(potentialPsionic)) + continue; + + // Skip over those who are already psionic or those who are insulated. + if (HasComp(potentialPsionic) || HasComp(potentialPsionic)) + continue; + + validList.Add(potentialPsionic); + } + + // Give some targets psionic abilities. + RobustRandom.Shuffle(validList); + + var toAwaken = RobustRandom.Next(1, component.MaxAwaken); + + foreach (var target in validList) + { + if (toAwaken-- == 0) + break; + + _psionicAbilitiesSystem.AddPsionics(target); + } + + // Increase glimmer. + var baseGlimmerAdd = _robustRandom.Next(component.BaseGlimmerAddMin, component.BaseGlimmerAddMax); + var glimmerSeverityMod = 1 + (component.GlimmerSeverityCoefficient * (GetSeverityModifier() - 1f)); + var glimmerAdded = (int) Math.Round(baseGlimmerAdd * glimmerSeverityMod); + + _glimmerSystem.Glimmer += glimmerAdded; + } +} diff --git a/Content.Server/Backmen/StationEvents/Events/NoosphericZapRule.cs b/Content.Server/Backmen/StationEvents/Events/NoosphericZapRule.cs new file mode 100644 index 00000000000..b855f164018 --- /dev/null +++ b/Content.Server/Backmen/StationEvents/Events/NoosphericZapRule.cs @@ -0,0 +1,56 @@ +using Content.Server.Backmen.Psionics; +using Content.Server.Backmen.StationEvents.Components; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Popups; +using Content.Server.StationEvents.Events; +using Content.Server.Stunnable; +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.StatusEffect; + +namespace Content.Server.Backmen.StationEvents.Events; + +/// +/// Zaps everyone, rolling psionics and disorienting them +/// +internal sealed class NoosphericZapRule : StationEventSystem +{ + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly StunSystem _stunSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly PsionicsSystem _psionicsSystem = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; + + protected override void Started(EntityUid uid, NoosphericZapRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) + { + base.Started(uid, component, gameRule, args); + + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var psion, out var potentialPsionicComponent, out _)) + { + if (!_mobStateSystem.IsAlive(psion) || HasComp(psion)) + continue; + + _stunSystem.TryParalyze(psion, TimeSpan.FromSeconds(5), false); + _statusEffectsSystem.TryAddStatusEffect(psion, "Stutter", TimeSpan.FromSeconds(10), false, "StutteringAccent"); + + if (HasComp(psion)) + _popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize"), psion, psion, Shared.Popups.PopupType.LargeCaution); + else + { + if (potentialPsionicComponent.Rerolled) + { + potentialPsionicComponent.Rerolled = false; + _popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize-potential-regained"), psion, psion, Shared.Popups.PopupType.LargeCaution); + } + else + { + _psionicsSystem.RollPsionics(psion, potentialPsionicComponent, multiplier: 0.25f); + _popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize"), psion, psion, Shared.Popups.PopupType.LargeCaution); + } + } + } + } +} diff --git a/Content.Server/Backmen/StationEvents/Events/PsionicCatGotYourTongueRule.cs b/Content.Server/Backmen/StationEvents/Events/PsionicCatGotYourTongueRule.cs new file mode 100644 index 00000000000..7d888964bb3 --- /dev/null +++ b/Content.Server/Backmen/StationEvents/Events/PsionicCatGotYourTongueRule.cs @@ -0,0 +1,51 @@ +using Content.Server.Backmen.Psionics; +using Content.Server.Backmen.StationEvents.Components; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.StationEvents.Events; +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.StatusEffect; +using Robust.Shared.Player; +using Robust.Shared.Random; + +namespace Content.Server.Backmen.StationEvents.Events; + +/// +/// Mutes everyone for a random amount of time. +/// +internal sealed class PsionicCatGotYourTongueRule : StationEventSystem +{ + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly SharedAudioSystem _sharedAudioSystem = default!; + + + protected override void Started(EntityUid uid, PsionicCatGotYourTongueRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) + { + base.Started(uid, component, gameRule, args); + + List psionicList = new(); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var psion, out _, out _)) + { + if (_mobStateSystem.IsAlive(psion) && !HasComp(psion)) + psionicList.Add(psion); + } + + foreach (var psion in psionicList) + { + var duration = _robustRandom.Next(component.MinDuration, component.MaxDuration); + + _statusEffectsSystem.TryAddStatusEffect(psion, + "Muted", + duration, + false, + "Muted"); + + _sharedAudioSystem.PlayGlobal(component.Sound, Filter.Entities(psion), false); + } + } +} diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index e2b0591f970..e93b66675ab 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -3,6 +3,7 @@ using System.Text; using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; +using Content.Server.Backmen.Chat; using Content.Server.Chat.Managers; using Content.Server.GameTicking; using Content.Server.Players; @@ -56,6 +57,7 @@ public sealed partial class ChatSystem : SharedChatSystem [Dependency] private readonly MobStateSystem _mobStateSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly NyanoChatSystem _nyanoChatSystem = default!; public const int VoiceRange = 10; // how far voice goes in world units public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units @@ -242,6 +244,9 @@ public void TrySendInGameICMessage( case InGameICChatType.Emote: SendEntityEmote(source, message, range, nameOverride, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker); break; + case InGameICChatType.Telepathic: + _nyanoChatSystem.SendTelepathicChat(source, message, range == ChatTransmitRange.HideChat); + break; } } @@ -795,7 +800,7 @@ public readonly record struct ICChatRecipientData(float Range, bool Observer, bo { } - private string ObfuscateMessageReadability(string message, float chance) + public string ObfuscateMessageReadability(string message, float chance) { var modifiedMessage = new StringBuilder(message); @@ -887,7 +892,8 @@ public enum InGameICChatType : byte { Speak, Emote, - Whisper + Whisper, + Telepathic } /// diff --git a/Content.Server/NPC/Systems/NpcFactionSystem.cs b/Content.Server/NPC/Systems/NpcFactionSystem.cs index 79c4bbc7af0..c6648e31a4e 100644 --- a/Content.Server/NPC/Systems/NpcFactionSystem.cs +++ b/Content.Server/NPC/Systems/NpcFactionSystem.cs @@ -43,6 +43,14 @@ private void OnProtoReload(PrototypesReloadedEventArgs obj) RefreshFactions(); } + public bool ContainsFaction(EntityUid uid, string faction, NpcFactionMemberComponent? component = null) //Backmen:return good func + { + if (!Resolve(uid, ref component, false)) + return false; + + return component.Factions.Contains(faction); + } + private void OnFactionStartup(EntityUid uid, NpcFactionMemberComponent memberComponent, ComponentStartup args) { RefreshFactions(memberComponent); diff --git a/Content.Server/Visible/VisibilityFlags.cs b/Content.Server/Visible/VisibilityFlags.cs index 03c2022fea8..5876307b546 100644 --- a/Content.Server/Visible/VisibilityFlags.cs +++ b/Content.Server/Visible/VisibilityFlags.cs @@ -6,5 +6,6 @@ public enum VisibilityFlags : uint None = 0, Normal = 1 << 0, Ghost = 1 << 1, + PsionicInvisibility = 1 << 2, } } diff --git a/Content.Shared.Database/LogType.cs b/Content.Shared.Database/LogType.cs index 2212cc68c35..badd764f215 100644 --- a/Content.Shared.Database/LogType.cs +++ b/Content.Shared.Database/LogType.cs @@ -90,5 +90,6 @@ public enum LogType DeviceLinking = 85, Tile = 86, //backmen - Transactions = 120 + Transactions = 120, + Psionics = 121 } diff --git a/Content.Shared/Backmen/Abilities/DogVisionComponent.cs b/Content.Shared/Backmen/Abilities/DogVisionComponent.cs new file mode 100644 index 00000000000..4c4ac112f22 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/DogVisionComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Backmen.Abilities; + +[RegisterComponent] +[NetworkedComponent] + +public sealed partial class DogVisionComponent : Component +{} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Abilities/AcceptPsionicsEuiMessage.cs b/Content.Shared/Backmen/Abilities/Psionics/Abilities/AcceptPsionicsEuiMessage.cs new file mode 100644 index 00000000000..53558d397a5 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Abilities/AcceptPsionicsEuiMessage.cs @@ -0,0 +1,22 @@ +using Content.Shared.Eui; +using Robust.Shared.Serialization; + +namespace Content.Shared.Backmen.Psionics; + +[Serializable, NetSerializable] +public enum AcceptPsionicsUiButton +{ + Deny, + Accept, +} + +[Serializable, NetSerializable] +public sealed class AcceptPsionicsChoiceMessage : EuiMessageBase +{ + public readonly AcceptPsionicsUiButton Button; + + public AcceptPsionicsChoiceMessage(AcceptPsionicsUiButton button) + { + Button = button; + } +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs new file mode 100644 index 00000000000..d9ba9429df9 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Damage; + +namespace Content.Shared.Backmen.Abilities.Psionics +{ + /// + /// Takes damage when dispelled. + /// + [RegisterComponent] + public sealed partial class DamageOnDispelComponent : Component + { + [DataField("damage", required: true), ViewVariables(VVAccess.ReadWrite)] + public DamageSpecifier Damage = default!; + + [DataField("variance")] + public float Variance = 0.5f; + } +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Abilities/Dispel/DispelPowerComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Abilities/Dispel/DispelPowerComponent.cs new file mode 100644 index 00000000000..8c396c42e09 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Abilities/Dispel/DispelPowerComponent.cs @@ -0,0 +1,12 @@ +using Content.Shared.Actions.ActionTypes; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class DispelPowerComponent : Component +{ + [DataField("range"), ViewVariables(VVAccess.ReadWrite)] + public float Range = 10f; + + public EntityTargetAction? DispelPowerAction = null; +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Abilities/Dispel/DispellableComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Abilities/Dispel/DispellableComponent.cs new file mode 100644 index 00000000000..c4c8f7f6fde --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Abilities/Dispel/DispellableComponent.cs @@ -0,0 +1,5 @@ +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class DispellableComponent : Component +{} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs new file mode 100644 index 00000000000..10eec922a51 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs @@ -0,0 +1,11 @@ +using Content.Shared.Actions.ActionTypes; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class MassSleepPowerComponent : Component +{ + public WorldTargetAction? MassSleepPowerAction = null; + + public float Radius = 1.25f; +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs b/Content.Shared/Backmen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs new file mode 100644 index 00000000000..8de09b05616 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs @@ -0,0 +1,62 @@ +using Content.Shared.Actions; +using Content.Shared.Bed.Sleep; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Damage; +using Content.Shared.Mobs.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +public sealed class MassSleepPowerSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + } + + private void OnInit(EntityUid uid, MassSleepPowerComponent component, ComponentInit args) + { + if (!_prototypeManager.TryIndex("MassSleep", out var massSleep)) + return; + + component.MassSleepPowerAction = new WorldTargetAction(massSleep); + if (massSleep.UseDelay != null) + component.MassSleepPowerAction.Cooldown = (_gameTiming.CurTime, _gameTiming.CurTime + (TimeSpan) massSleep.UseDelay); + _actions.AddAction(uid, component.MassSleepPowerAction, null); + + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + psionic.PsionicAbility = component.MassSleepPowerAction; + } + + private void OnShutdown(EntityUid uid, MassSleepPowerComponent component, ComponentShutdown args) + { + if (_prototypeManager.TryIndex("MassSleep", out var massSleep)) + _actions.RemoveAction(uid, new WorldTargetAction(massSleep), null); + } + + private void OnPowerUsed(EntityUid uid, MassSleepPowerComponent component, MassSleepPowerActionEvent args) + { + foreach (var entity in _lookup.GetEntitiesInRange(args.Target, component.Radius)) + { + if (HasComp(entity) && entity != uid && !HasComp(entity)) + { + if (TryComp(entity, out var damageable) && damageable.DamageContainerID == "Biological") + EnsureComp(entity); + } + } + _psionics.LogPowerUsed(uid, "mass sleep"); + args.Handled = true; + } +} + +public sealed partial class MassSleepPowerActionEvent : WorldTargetActionEvent {} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs new file mode 100644 index 00000000000..edb8f8579d6 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs @@ -0,0 +1,12 @@ +using Content.Shared.Actions.ActionTypes; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class MetapsionicPowerComponent : Component +{ + [DataField("range"), ViewVariables(VVAccess.ReadWrite)] + public float Range = 5f; + + public InstantAction? MetapsionicPowerAction = null; +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs new file mode 100644 index 00000000000..96891ae993c --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs @@ -0,0 +1,9 @@ +using Content.Shared.Actions.ActionTypes; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class MindSwapPowerComponent : Component +{ + public EntityTargetAction? MindSwapPowerAction = null; +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs new file mode 100644 index 00000000000..41f3570a33b --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs @@ -0,0 +1,9 @@ +using Content.Shared.Actions.ActionTypes; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class NoosphericZapPowerComponent : Component +{ + public EntityTargetAction? NoosphericZapPowerAction = null; +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs new file mode 100644 index 00000000000..d26aa6a9be2 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs @@ -0,0 +1,9 @@ +using Content.Shared.Actions.ActionTypes; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class PsionicInvisibilityPowerComponent : Component +{ + public InstantAction? PsionicInvisibilityPowerAction = null; +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs new file mode 100644 index 00000000000..0b3d8792954 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs @@ -0,0 +1,5 @@ +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class PsionicInvisibilityUsedComponent : Component +{} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs new file mode 100644 index 00000000000..3c88edd2474 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.Audio; +using Content.Shared.DoAfter; +using Content.Shared.Actions.ActionTypes; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class PsionicRegenerationPowerComponent : Component +{ + [DataField("doAfter")] + public DoAfterId? DoAfter; + + [DataField("essence"), ViewVariables(VVAccess.ReadWrite)] + public float EssenceAmount = 20; + + [DataField("useDelay"), ViewVariables(VVAccess.ReadWrite)] + public float UseDelay = 8f; + + [DataField("soundUse")] + public SoundSpecifier SoundUse = new SoundPathSpecifier("/Audio/Backmen/heartbeat_fast.ogg"); + + public InstantAction? PsionicRegenerationPowerAction = null; +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs new file mode 100644 index 00000000000..006b42de748 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs @@ -0,0 +1,9 @@ +using Content.Shared.Actions.ActionTypes; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class PyrokinesisPowerComponent : Component +{ + public EntityTargetAction? PyrokinesisPowerAction = null; +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs new file mode 100644 index 00000000000..da4238114bc --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs @@ -0,0 +1,11 @@ +using Content.Shared.Actions.ActionTypes; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class TelegnosisPowerComponent : Component +{ + [DataField("prototype")] + public string Prototype = "MobObserverTelegnostic"; + public InstantAction? TelegnosisPowerAction = null; +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs new file mode 100644 index 00000000000..750185b16d0 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs @@ -0,0 +1,5 @@ +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class TelegnosticProjectionComponent : Component +{} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Items/ClothingGrantPsionicPowerComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Items/ClothingGrantPsionicPowerComponent.cs new file mode 100644 index 00000000000..b2c9c2ba9ca --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Items/ClothingGrantPsionicPowerComponent.cs @@ -0,0 +1,9 @@ +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class ClothingGrantPsionicPowerComponent : Component +{ + [DataField("power", required: true)] + public string Power = ""; + public bool IsActive = false; +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Items/HeadCageComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Items/HeadCageComponent.cs new file mode 100644 index 00000000000..8721e1b7979 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Items/HeadCageComponent.cs @@ -0,0 +1,27 @@ +using System.Threading; +using Robust.Shared.Audio; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class HeadCageComponent : Component +{ + public CancellationTokenSource? CancelToken; + public bool IsActive = false; + + [DataField("startBreakoutSound")] + public SoundSpecifier StartBreakoutSound { get; set; } = new SoundPathSpecifier("/Audio/Items/Handcuffs/cuff_breakout_start.ogg"); + + [DataField("startUncageSound")] + public SoundSpecifier StartUncageSound { get; set; } = new SoundPathSpecifier("/Audio/Items/Handcuffs/cuff_takeoff_start.ogg"); + + [DataField("endUncageSound")] + public SoundSpecifier EndUncageSound { get; set; } = new SoundPathSpecifier("/Audio/Items/Handcuffs/cuff_takeoff_end.ogg"); + + [DataField("startCageSound")] + public SoundSpecifier StartCageSound { get; set; } = new SoundPathSpecifier("/Audio/Items/Handcuffs/cuff_start.ogg"); + + [DataField("endCageSound")] + public SoundSpecifier EndCageSound { get; set; } = new SoundPathSpecifier("/Audio/Items/Handcuffs/cuff_end.ogg"); + +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Items/HeadCagedComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Items/HeadCagedComponent.cs new file mode 100644 index 00000000000..29a17004c9c --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Items/HeadCagedComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +/// +/// Tracking comp so we can subscribe to alt verbs +/// +public sealed partial class HeadCagedComponent : Component +{} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Items/PsionicItemsSystem.cs b/Content.Shared/Backmen/Abilities/Psionics/Items/PsionicItemsSystem.cs new file mode 100644 index 00000000000..4dcdb1708b6 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Items/PsionicItemsSystem.cs @@ -0,0 +1,80 @@ +using Content.Shared.Inventory.Events; +using Content.Shared.Clothing.Components; +using Content.Shared.StatusEffect; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +public sealed class PsionicItemsSystem : EntitySystem +{ + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; + [Dependency] private readonly IComponentFactory _componentFactory = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psiAbilities = default!; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnTinfoilEquipped); + SubscribeLocalEvent(OnTinfoilUnequipped); + SubscribeLocalEvent(OnGranterEquipped); + SubscribeLocalEvent(OnGranterUnequipped); + } + private void OnTinfoilEquipped(EntityUid uid, TinfoilHatComponent component, GotEquippedEvent args) + { + // This only works on clothing + if (!TryComp(uid, out var clothing)) + return; + // Is the clothing in its actual slot? + if (!clothing.Slots.HasFlag(args.SlotFlags)) + return; + + var insul = EnsureComp(args.Equipee); + insul.Passthrough = component.Passthrough; + component.IsActive = true; + _psiAbilities.SetPsionicsThroughEligibility(args.Equipee); + } + + private void OnTinfoilUnequipped(EntityUid uid, TinfoilHatComponent component, GotUnequippedEvent args) + { + if (!component.IsActive) + return; + + if (!_statusEffects.HasStatusEffect(uid, "PsionicallyInsulated")) + RemComp(args.Equipee); + + component.IsActive = false; + _psiAbilities.SetPsionicsThroughEligibility(args.Equipee); + } + + private void OnGranterEquipped(EntityUid uid, ClothingGrantPsionicPowerComponent component, GotEquippedEvent args) + { + // This only works on clothing + if (!TryComp(uid, out var clothing)) + return; + // Is the clothing in its actual slot? + if (!clothing.Slots.HasFlag(args.SlotFlags)) + return; + // does the user already has this power? + var componentType = _componentFactory.GetRegistration(component.Power).Type; + if (EntityManager.HasComponent(args.Equipee, componentType)) return; + + + var newComponent = (Component) _componentFactory.GetComponent(componentType); + newComponent.Owner = args.Equipee; + + EntityManager.AddComponent(args.Equipee, newComponent); + + component.IsActive = true; + } + + private void OnGranterUnequipped(EntityUid uid, ClothingGrantPsionicPowerComponent component, GotUnequippedEvent args) + { + if (!component.IsActive) + return; + + component.IsActive = false; + var componentType = _componentFactory.GetRegistration(component.Power).Type; + if (EntityManager.HasComponent(args.Equipee, componentType)) + { + EntityManager.RemoveComponent(args.Equipee, componentType); + } + } +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/Items/TinfoilHatComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/Items/TinfoilHatComponent.cs new file mode 100644 index 00000000000..4cfa1706ddb --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/Items/TinfoilHatComponent.cs @@ -0,0 +1,17 @@ +namespace Content.Shared.Backmen.Abilities.Psionics +{ + [RegisterComponent] + public sealed partial class TinfoilHatComponent : Component + { + public bool IsActive = false; + + [DataField("passthrough")] + public bool Passthrough = false; + + /// + /// Whether this will turn to ash when its psionically fried. + /// + [DataField("destroyOnFry")] + public bool DestroyOnFry = true; + } +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/PsionicComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/PsionicComponent.cs new file mode 100644 index 00000000000..55d47b81cb8 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/PsionicComponent.cs @@ -0,0 +1,16 @@ +using Content.Shared.Actions.ActionTypes; +using Robust.Shared.GameStates; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent, NetworkedComponent] +public sealed partial class PsionicComponent : Component +{ + public ActionType? PsionicAbility = null; + + /// + /// Ifrits, revenants, etc are explicitly magical beings that shouldn't get mindbreakered. + /// + [DataField("removable")] + public bool Removable = true; +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/PsionicInsulationComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/PsionicInsulationComponent.cs new file mode 100644 index 00000000000..c0318c5118d --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/PsionicInsulationComponent.cs @@ -0,0 +1,9 @@ +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class PsionicInsulationComponent : Component +{ + public bool Passthrough = false; + + public List SuppressedFactions = new(); +} diff --git a/Content.Shared/Backmen/Abilities/Psionics/PsionicsDisabledComponent.cs b/Content.Shared/Backmen/Abilities/Psionics/PsionicsDisabledComponent.cs new file mode 100644 index 00000000000..26668e501c3 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/PsionicsDisabledComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +/// +/// Only use this for the status effect, please. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class PsionicsDisabledComponent : Component +{} diff --git a/Content.Shared/Backmen/Abilities/Psionics/SharedPsionicAbilitiesSystem.cs b/Content.Shared/Backmen/Abilities/Psionics/SharedPsionicAbilitiesSystem.cs new file mode 100644 index 00000000000..23f64b86492 --- /dev/null +++ b/Content.Shared/Backmen/Abilities/Psionics/SharedPsionicAbilitiesSystem.cs @@ -0,0 +1,113 @@ +using Content.Shared.Backmen.Abilities.Psionics; +using Content.Shared.Actions; +using Content.Shared.Administration.Logs; +using Content.Shared.Backmen.Psionics.Glimmer; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Popups; +using Content.Shared.Backmen.Psionics.Glimmer; +using Robust.Shared.Random; +using Robust.Shared.Serialization; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +public sealed class SharedPsionicAbilitiesSystem : EntitySystem +{ + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedPopupSystem _popups = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + + SubscribeLocalEvent(OnMobStateChanged); + } + + private void OnPowerUsed(EntityUid uid, PsionicComponent component, PsionicPowerUsedEvent args) + { + foreach (var entity in _lookup.GetEntitiesInRange(uid, 10f)) + { + if (HasComp(entity) && entity != uid && !(TryComp(entity, out var insul) && !insul.Passthrough)) + { + _popups.PopupEntity(Loc.GetString("metapsionic-pulse-power", ("power", args.Power)), entity, entity, PopupType.LargeCaution); + args.Handled = true; + return; + } + } + } + + private void OnInit(EntityUid uid, PsionicsDisabledComponent component, ComponentInit args) + { + SetPsionicsThroughEligibility(uid); + } + + private void OnShutdown(EntityUid uid, PsionicsDisabledComponent component, ComponentShutdown args) + { + SetPsionicsThroughEligibility(uid); + } + + private void OnMobStateChanged(EntityUid uid, PsionicComponent component, MobStateChangedEvent args) + { + SetPsionicsThroughEligibility(uid); + } + + /// + /// Checks whether the entity is eligible to use its psionic ability. This should be run after anything that could effect psionic eligibility. + /// + public void SetPsionicsThroughEligibility(EntityUid uid) + { + PsionicComponent? component = null; + if (!Resolve(uid, ref component, false)) + return; + + if (component.PsionicAbility == null) + return; + + _actions.SetEnabled(component.PsionicAbility, IsEligibleForPsionics(uid)); + } + + private bool IsEligibleForPsionics(EntityUid uid) + { + return !HasComp(uid) + && (!TryComp(uid, out var mobstate) || mobstate.CurrentState == MobState.Alive); + } + + public void LogPowerUsed(EntityUid uid, string power, int minGlimmer = 8, int maxGlimmer = 12) + { + _adminLogger.Add(Database.LogType.Psionics, Database.LogImpact.Medium, $"{ToPrettyString(uid):player} used {power}"); + var ev = new PsionicPowerUsedEvent(uid, power); + RaiseLocalEvent(uid, ev, false); + + _glimmerSystem.Glimmer += _robustRandom.Next(minGlimmer, maxGlimmer); + } +} + +public sealed class PsionicPowerUsedEvent : HandledEntityEventArgs +{ + public EntityUid User { get; } + public string Power = string.Empty; + + public PsionicPowerUsedEvent(EntityUid user, string power) + { + User = user; + Power = power; + } +} + +[Serializable] +[NetSerializable] +public sealed class PsionicsChangedEvent : EntityEventArgs +{ + public readonly EntityUid Euid; + public PsionicsChangedEvent(EntityUid euid) + { + Euid = euid; + } +} diff --git a/Content.Shared/Backmen/CCVar/CCVars.cs b/Content.Shared/Backmen/CCVar/CCVars.cs index 3ccbe42f539..ddf707048c7 100644 --- a/Content.Shared/Backmen/CCVar/CCVars.cs +++ b/Content.Shared/Backmen/CCVar/CCVars.cs @@ -18,4 +18,28 @@ public static readonly CVarDef /// public static readonly CVarDef Shipyard = CVarDef.Create("shuttle.shipyard", true, CVar.SERVERONLY); + + /* + * Glimmer + */ + + /// + /// Whether glimmer is enabled. + /// + public static readonly CVarDef GlimmerEnabled = + CVarDef.Create("glimmer.enabled", true, CVar.REPLICATED); + + /// + /// Passive glimmer drain per second. + /// Note that this is randomized and this is an average value. + /// + public static readonly CVarDef GlimmerLostPerSecond = + CVarDef.Create("glimmer.passive_drain_per_second", 0.1f, CVar.SERVERONLY); + + /// + /// Whether random rolls for psionics are allowed. + /// Guaranteed psionics will still go through. + /// + public static readonly CVarDef PsionicRollsEnabled = + CVarDef.Create("psionics.rolls_enabled", true, CVar.SERVERONLY); } diff --git a/Content.Shared/Backmen/CartridgeLoader/Cartridges/GlimmerMonitorUiState.cs b/Content.Shared/Backmen/CartridgeLoader/Cartridges/GlimmerMonitorUiState.cs new file mode 100644 index 00000000000..cb229f90409 --- /dev/null +++ b/Content.Shared/Backmen/CartridgeLoader/Cartridges/GlimmerMonitorUiState.cs @@ -0,0 +1,22 @@ +using Content.Shared.CartridgeLoader; +using Robust.Shared.Serialization; + +namespace Content.Shared.Backmen.CartridgeLoader.Cartridges; + +[Serializable, NetSerializable] +public sealed class GlimmerMonitorUiState : BoundUserInterfaceState +{ + public List GlimmerValues; + + public GlimmerMonitorUiState(List glimmerValues) + { + GlimmerValues = glimmerValues; + } +} + +[Serializable, NetSerializable] +public sealed class GlimmerMonitorSyncMessageEvent : CartridgeMessageEvent +{ + public GlimmerMonitorSyncMessageEvent() + {} +} diff --git a/Content.Shared/Backmen/Clothing/Components/ClothingGrantComponentComponent.cs b/Content.Shared/Backmen/Clothing/Components/ClothingGrantComponentComponent.cs new file mode 100644 index 00000000000..34e7c65fc35 --- /dev/null +++ b/Content.Shared/Backmen/Clothing/Components/ClothingGrantComponentComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Backmen.Clothing; + +[RegisterComponent] +public sealed partial class ClothingGrantComponent : Component +{ + [DataField("component", required: true)] + [AlwaysPushInheritance] + public ComponentRegistry Components { get; private set; } = new(); + + [ViewVariables(VVAccess.ReadWrite)] + public bool IsActive = false; +} diff --git a/Content.Shared/Backmen/Clothing/Systems/ClothingGrantingSystem.cs b/Content.Shared/Backmen/Clothing/Systems/ClothingGrantingSystem.cs new file mode 100644 index 00000000000..7c4dd0a8b2c --- /dev/null +++ b/Content.Shared/Backmen/Clothing/Systems/ClothingGrantingSystem.cs @@ -0,0 +1,63 @@ +using Content.Shared.Clothing.Components; +using Content.Shared.Inventory.Events; +using Robust.Shared.Serialization.Manager; +using Content.Shared.Tag; + +namespace Content.Shared.Backmen.Clothing; + +public sealed class ClothingGrantingSystem : EntitySystem +{ + [Dependency] private readonly IComponentFactory _componentFactory = default!; + [Dependency] private readonly ISerializationManager _serializationManager = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnCompEquip); + SubscribeLocalEvent(OnCompUnequip); + } + + private void OnCompEquip(EntityUid uid, ClothingGrantComponent component, GotEquippedEvent args) + { + if (!TryComp(uid, out var clothing)) return; + + if (!clothing.Slots.HasFlag(args.SlotFlags)) return; + + if (component.Components.Count > 1) + { + Logger.Error("Although a component registry supports multiple components, we cannot bookkeep more than 1 component for ClothingGrantComponent at this time."); + return; + } + + foreach (var (name, data) in component.Components) + { + var newComp = (Component) _componentFactory.GetComponent(name); + + if (HasComp(args.Equipee, newComp.GetType())) + continue; + + newComp.Owner = args.Equipee; + + var temp = (object) newComp; + _serializationManager.CopyTo(data.Component, ref temp); + EntityManager.AddComponent(args.Equipee, (Component)temp!); + + component.IsActive = true; + } + } + + private void OnCompUnequip(EntityUid uid, ClothingGrantComponent component, GotUnequippedEvent args) + { + if (!component.IsActive) return; + + foreach (var (name, data) in component.Components) + { + var newComp = (Component) _componentFactory.GetComponent(name); + + RemComp(args.Equipee, newComp.GetType()); + } + + component.IsActive = false; + } +} diff --git a/Content.Shared/Backmen/EntityHealthBar/ShowHealthBarsComponent.cs b/Content.Shared/Backmen/EntityHealthBar/ShowHealthBarsComponent.cs new file mode 100644 index 00000000000..4301cb94696 --- /dev/null +++ b/Content.Shared/Backmen/EntityHealthBar/ShowHealthBarsComponent.cs @@ -0,0 +1,19 @@ +using Content.Shared.Damage.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Shared.Backmen.EntityHealthBar; + +/// +/// This component allows you to see health bars above damageable mobs. +/// +[RegisterComponent] +public sealed partial class ShowHealthBarsComponent : Component +{ + /// + /// If null, displays all health bars. + /// If not null, displays health bars of only that damage container. + /// + + [DataField("damageContainers", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List DamageContainers = new(); +} diff --git a/Content.Shared/Backmen/Psionics/Events.cs b/Content.Shared/Backmen/Psionics/Events.cs new file mode 100644 index 00000000000..ea6d9082382 --- /dev/null +++ b/Content.Shared/Backmen/Psionics/Events.cs @@ -0,0 +1,27 @@ +using Robust.Shared.Serialization; +using Content.Shared.DoAfter; + +namespace Content.Shared.Backmen.Psionics.Events; + +[Serializable, NetSerializable] +public sealed partial class PsionicRegenerationDoAfterEvent : DoAfterEvent +{ + [DataField("startedAt", required: true)] + public TimeSpan StartedAt; + + private PsionicRegenerationDoAfterEvent() + { + } + + public PsionicRegenerationDoAfterEvent(TimeSpan startedAt) + { + StartedAt = startedAt; + } + + public override DoAfterEvent Clone() => this; +} + +[Serializable, NetSerializable] +public sealed partial class GlimmerWispDrainDoAfterEvent : SimpleDoAfterEvent +{ +} diff --git a/Content.Shared/Backmen/Psionics/Glimmer/GlimmerSystem.cs b/Content.Shared/Backmen/Psionics/Glimmer/GlimmerSystem.cs new file mode 100644 index 00000000000..e88860d9983 --- /dev/null +++ b/Content.Shared/Backmen/Psionics/Glimmer/GlimmerSystem.cs @@ -0,0 +1,64 @@ +using Robust.Shared.Serialization; +using Robust.Shared.Configuration; +using Content.Shared.Backmen.CCVar; +using Content.Shared.GameTicking; + +namespace Content.Shared.Backmen.Psionics.Glimmer; + +/// +/// This handles setting / reading the value of glimmer. +/// +public sealed class GlimmerSystem : EntitySystem +{ + [Dependency] private readonly IConfigurationManager _cfg = default!; + private int _glimmer = 0; + public int Glimmer + { + get { return _glimmer; } + set { _glimmer = _enabled ? Math.Clamp(value, 0, 1000) : 0; } + } + private bool _enabled; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(Reset); + _enabled = _cfg.GetCVar(CCVars.GlimmerEnabled); + _cfg.OnValueChanged(CCVars.GlimmerEnabled, value => _enabled = value, true); + } + + private void Reset(RoundRestartCleanupEvent args) + { + Glimmer = 0; + } + + /// + /// Return an abstracted range of a glimmer count. + /// + /// What glimmer count to check. Uses the current glimmer by default. + public GlimmerTier GetGlimmerTier(int? glimmer = null) + { + if (glimmer == null) + glimmer = Glimmer; + + return (glimmer) switch + { + <= 49 => GlimmerTier.Minimal, + >= 50 and <= 99 => GlimmerTier.Low, + >= 100 and <= 299 => GlimmerTier.Moderate, + >= 300 and <= 499 => GlimmerTier.High, + >= 500 and <= 899 => GlimmerTier.Dangerous, + _ => GlimmerTier.Critical, + }; + } +} + +[Serializable, NetSerializable] +public enum GlimmerTier : byte +{ + Minimal, + Low, + Moderate, + High, + Dangerous, + Critical, +} diff --git a/Content.Shared/Backmen/Psionics/Glimmer/SharedGlimmerReactiveComponent.cs b/Content.Shared/Backmen/Psionics/Glimmer/SharedGlimmerReactiveComponent.cs new file mode 100644 index 00000000000..412bdf44238 --- /dev/null +++ b/Content.Shared/Backmen/Psionics/Glimmer/SharedGlimmerReactiveComponent.cs @@ -0,0 +1,40 @@ +using Robust.Shared.Audio; + +namespace Content.Shared.Backmen.Psionics.Glimmer; + +[RegisterComponent] +public partial class SharedGlimmerReactiveComponent : Component +{ + /// + /// Do the effects of this component require power from an APC? + /// + [DataField("requiresApcPower"), ViewVariables(VVAccess.ReadWrite)] + public bool RequiresApcPower = false; + + /// + /// Does this component try to modulate the strength of a PointLight + /// component on the same entity based on the Glimmer tier? + /// + [DataField("modulatesPointLight")] + public bool ModulatesPointLight = false; + + /// + /// What is the correlation between the Glimmer tier and how strongly + /// the light grows? The result is added to the base Energy. + /// + [DataField("glimmerToLightEnergyFactor"), ViewVariables(VVAccess.ReadWrite)] + public float GlimmerToLightEnergyFactor = 1.0f; + + /// + /// What is the correlation between the Glimmer tier and how much + /// distance the light covers? The result is added to the base Radius. + /// + [DataField("glimmerToLightRadiusFactor"), ViewVariables(VVAccess.ReadWrite)] + public float GlimmerToLightRadiusFactor = 1.0f; + + /// + /// Noises to play on failed turn off. + /// + [DataField("shockNoises")] + public SoundSpecifier ShockNoises { get; private set; } = new SoundCollectionSpecifier("sparks"); +} diff --git a/Content.Shared/Backmen/Psionics/Glimmer/SharedGlimmerReactiveVisuals.cs b/Content.Shared/Backmen/Psionics/Glimmer/SharedGlimmerReactiveVisuals.cs new file mode 100644 index 00000000000..4b2c9ac9b15 --- /dev/null +++ b/Content.Shared/Backmen/Psionics/Glimmer/SharedGlimmerReactiveVisuals.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Psionics.Glimmer +{ + [Serializable, NetSerializable] + public enum GlimmerReactiveVisuals : byte + { + GlimmerTier, + } +} diff --git a/Content.Shared/Backmen/Psionics/PsychokinesisComponent.cs b/Content.Shared/Backmen/Psionics/PsychokinesisComponent.cs new file mode 100644 index 00000000000..72da02af791 --- /dev/null +++ b/Content.Shared/Backmen/Psionics/PsychokinesisComponent.cs @@ -0,0 +1,19 @@ +using Content.Shared.Actions.ActionTypes; +using Robust.Shared.Audio; + +namespace Content.Shared.Backmen.Abilities.Psionics; + +[RegisterComponent] +public sealed partial class PsychokinesisPowerComponent : Component +{ + public WorldTargetAction? PsychokinesisPowerAction = null; + + [DataField("waveSound")] + public SoundSpecifier WaveSound = new SoundPathSpecifier("/Audio/Nyanotrasen/Mobs/SilverGolem/wave.ogg"); + + /// + /// Volume control for the spell. + /// + [DataField("waveVolume")] + public float WaveVolume = 5f; +} diff --git a/Content.Shared/Chat/ChatChannel.cs b/Content.Shared/Chat/ChatChannel.cs index f14f33666ac..899ca4239c1 100644 --- a/Content.Shared/Chat/ChatChannel.cs +++ b/Content.Shared/Chat/ChatChannel.cs @@ -79,10 +79,16 @@ public enum ChatChannel : ushort /// Unspecified = 1 << 13, + + /// + /// Telepathic. + /// + Telepathic = 1 << 14, + /// /// Channels considered to be IC. /// - IC = Local | Whisper | Radio | Dead | Emotes | Damage | Visual, + IC = Local | Whisper | Radio | Telepathic | Dead | Emotes | Damage | Visual, AdminRelated = Admin | AdminAlert | AdminChat, } diff --git a/Content.Shared/Chat/ChatSelectChannel.cs b/Content.Shared/Chat/ChatSelectChannel.cs index c18bb9b8ee3..3f9d1ed4d15 100644 --- a/Content.Shared/Chat/ChatSelectChannel.cs +++ b/Content.Shared/Chat/ChatSelectChannel.cs @@ -41,6 +41,11 @@ public enum ChatSelectChannel : ushort /// Emotes = ChatChannel.Emotes, + /// + /// Telepathic + /// + Telepathic = ChatChannel.Telepathic, + /// /// Deadchat /// diff --git a/Content.Shared/Chat/SharedChatSystem.cs b/Content.Shared/Chat/SharedChatSystem.cs index 05b39055e6a..dd18e267600 100644 --- a/Content.Shared/Chat/SharedChatSystem.cs +++ b/Content.Shared/Chat/SharedChatSystem.cs @@ -21,6 +21,7 @@ public abstract class SharedChatSystem : EntitySystem public const char AdminPrefix = ']'; public const char WhisperPrefix = ','; public const char DefaultChannelKey = 'р'; // Corvax-Localization + public const char TelepathicPrefix = '='; // backmen: Psionic [ValidatePrototypeId] public const string CommonChannel = "Common"; diff --git a/Resources/Audio/Nyanotrasen/Ambience/Objects/attributions.yml b/Resources/Audio/Nyanotrasen/Ambience/Objects/attributions.yml new file mode 100644 index 00000000000..26e2fc0c8b6 --- /dev/null +++ b/Resources/Audio/Nyanotrasen/Ambience/Objects/attributions.yml @@ -0,0 +1,4 @@ +- files: ["welding_sparks.ogg"] + license: "CC-BY-3.0" + copyright: "https://freesound.org/people/Tomlija/" + source: "https://freesound.org/people/Tomlija/sounds/103333/" diff --git a/Resources/Audio/Nyanotrasen/Ambience/Objects/welding_sparks.ogg b/Resources/Audio/Nyanotrasen/Ambience/Objects/welding_sparks.ogg new file mode 100644 index 00000000000..de710070680 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Ambience/Objects/welding_sparks.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Animals/moth_chitter.ogg b/Resources/Audio/Nyanotrasen/Animals/moth_chitter.ogg new file mode 100644 index 00000000000..efeafa555ad Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Animals/moth_chitter.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_idle_phrase_1.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_idle_phrase_1.ogg new file mode 100644 index 00000000000..188b1e3c547 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_idle_phrase_1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_idle_phrase_2.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_idle_phrase_2.ogg new file mode 100644 index 00000000000..e9021c883fa Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_idle_phrase_2.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_idle_phrase_3.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_idle_phrase_3.ogg new file mode 100644 index 00000000000..38b3b9fcc46 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_idle_phrase_3.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_qa_user_interface.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_qa_user_interface.ogg new file mode 100644 index 00000000000..9629e25c50f Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_qa_user_interface.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_attention_1.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_attention_1.ogg new file mode 100644 index 00000000000..cbc12fedfc2 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_attention_1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_attention_2.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_attention_2.ogg new file mode 100644 index 00000000000..3d316299b7c Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_attention_2.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_attention_3.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_attention_3.ogg new file mode 100644 index 00000000000..0e01102ebb7 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_attention_3.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_buy_sell.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_buy_sell.ogg new file mode 100644 index 00000000000..1ccebaa0a35 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_buy_sell.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_bye_1.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_bye_1.ogg new file mode 100644 index 00000000000..480e8e42fc9 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_bye_1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_bye_2.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_bye_2.ogg new file mode 100644 index 00000000000..a2a992bee69 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_bye_2.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_bye_3.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_bye_3.ogg new file mode 100644 index 00000000000..b6b74b97544 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_bye_3.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_hello_1.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_hello_1.ogg new file mode 100644 index 00000000000..7cc0fa1fbab Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_hello_1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_hello_2.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_hello_2.ogg new file mode 100644 index 00000000000..223dc15fccb Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_hello_2.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_hello_3.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_hello_3.ogg new file mode 100644 index 00000000000..90ec2d4869c Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_hello_3.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_help-2.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_help-2.ogg new file mode 100644 index 00000000000..aba780bcab5 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_help-2.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_help.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_help.ogg new file mode 100644 index 00000000000..9da242f53cb Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_help.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_name.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_name.ogg new file mode 100644 index 00000000000..203493b0e22 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_name.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_sorry_1.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_sorry_1.ogg new file mode 100644 index 00000000000..be45001dee0 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_sorry_1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_sorry_2.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_sorry_2.ogg new file mode 100644 index 00000000000..59ab8f1e8f8 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_sorry_2.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_sorry_3.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_sorry_3.ogg new file mode 100644 index 00000000000..2ba30774d5c Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_response_sorry_3.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_told_name_1.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_told_name_1.ogg new file mode 100644 index 00000000000..408750c9e8f Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_told_name_1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_told_name_2.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_told_name_2.ogg new file mode 100644 index 00000000000..155f5acec29 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_told_name_2.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_told_name_3.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_told_name_3.ogg new file mode 100644 index 00000000000..6d5b582acb5 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/hecate_told_name_3.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_aftercrash_sitrep.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_aftercrash_sitrep.ogg new file mode 100644 index 00000000000..315d30c868a Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_aftercrash_sitrep.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_launch.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_launch.ogg new file mode 100644 index 00000000000..5619fdf1e10 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_launch.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_report_alert.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_report_alert.ogg new file mode 100644 index 00000000000..44b5dfa305e Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_report_alert.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_report_decouple_engine.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_report_decouple_engine.ogg new file mode 100644 index 00000000000..2253d678698 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_report_decouple_engine.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_report_entering_atmosphere.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_report_entering_atmosphere.ogg new file mode 100644 index 00000000000..f3f0d3d62d0 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_report_entering_atmosphere.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_report_interstellar_body.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_report_interstellar_body.ogg new file mode 100644 index 00000000000..4bc9bbde1dc Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_report_interstellar_body.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_food.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_food.ogg new file mode 100644 index 00000000000..5005b0d23db Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_food.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_generator.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_generator.ogg new file mode 100644 index 00000000000..46b9e19360b Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_generator.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_generator_access.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_generator_access.ogg new file mode 100644 index 00000000000..6c7874002c5 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_generator_access.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_job.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_job.ogg new file mode 100644 index 00000000000..35f4a67b089 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_job.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_launch.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_launch.ogg new file mode 100644 index 00000000000..c13b8f0fed6 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_launch.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_launch_need_console.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_launch_need_console.ogg new file mode 100644 index 00000000000..e70fb542e0a Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_launch_need_console.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_launch_need_generator.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_launch_need_generator.ogg new file mode 100644 index 00000000000..a5525f21447 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_launch_need_generator.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_launch_need_thrusters.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_launch_need_thrusters.ogg new file mode 100644 index 00000000000..17a4a784703 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_launch_need_thrusters.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_rescue.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_rescue.ogg new file mode 100644 index 00000000000..87f00e968ba Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_rescue.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_scans.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_scans.ogg new file mode 100644 index 00000000000..5903d080e92 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_scans.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status.ogg new file mode 100644 index 00000000000..7ad3cb5aeb6 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status_all_green_again.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status_all_green_again.ogg new file mode 100644 index 00000000000..a61f2768fd9 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status_all_green_again.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status_all_green_first.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status_all_green_first.ogg new file mode 100644 index 00000000000..993e4f5e70b Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status_all_green_first.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status_need_console.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status_need_console.ogg new file mode 100644 index 00000000000..b4d6a122613 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status_need_console.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status_need_generator.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status_need_generator.ogg new file mode 100644 index 00000000000..b0934b572d8 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status_need_generator.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status_need_thrusters.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status_need_thrusters.ogg new file mode 100644 index 00000000000..54ee5d36d8e Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_status_need_thrusters.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_supplies.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_supplies.ogg new file mode 100644 index 00000000000..9f02cdb87b3 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_supplies.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_weapons_after.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_weapons_after.ogg new file mode 100644 index 00000000000..52864a5aea4 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_weapons_after.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_weapons_before.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_weapons_before.ogg new file mode 100644 index 00000000000..5badb3fe2a4 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_response_weapons_before.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_shuttle_crashed.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_shuttle_crashed.ogg new file mode 100644 index 00000000000..6205497163c Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_shuttle_crashed.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_shuttle_distress_signal.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_shuttle_distress_signal.ogg new file mode 100644 index 00000000000..4c4912446f7 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_shuttle_distress_signal.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_shuttle_in_transit.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_shuttle_in_transit.ogg new file mode 100644 index 00000000000..578ab56e09b Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_shuttle_in_transit.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_shuttle_prepare_for_launch.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_shuttle_prepare_for_launch.ogg new file mode 100644 index 00000000000..a6eedec0256 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_shuttle_prepare_for_launch.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_shuttle_turbulence_nebula.ogg b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_shuttle_turbulence_nebula.ogg new file mode 100644 index 00000000000..abe83141392 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Dialogue/Hecate/shipwrecked_hecate_shuttle_turbulence_nebula.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Effects/attributions.yml b/Resources/Audio/Nyanotrasen/Effects/attributions.yml new file mode 100644 index 00000000000..a8ba76636f4 --- /dev/null +++ b/Resources/Audio/Nyanotrasen/Effects/attributions.yml @@ -0,0 +1,8 @@ +- files: ["double_beep_whirr.ogg"] + license: "CC-BY-SA-4.0" + copyright: "@Vordenburg" + +- files: ["crash_impact_metal.ogg"] + license: "CC-BY-4.0" + copyright: "https://freesound.org/people/sandyrb/" + source: "https://freesound.org/people/sandyrb/sounds/95078/" diff --git a/Resources/Audio/Nyanotrasen/Effects/crash_impact_metal.ogg b/Resources/Audio/Nyanotrasen/Effects/crash_impact_metal.ogg new file mode 100644 index 00000000000..55e27d3fe6a Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Effects/crash_impact_metal.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Effects/double_beep_whirr.ogg b/Resources/Audio/Nyanotrasen/Effects/double_beep_whirr.ogg new file mode 100644 index 00000000000..ad933182c92 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Effects/double_beep_whirr.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Machines/attributions.yml b/Resources/Audio/Nyanotrasen/Machines/attributions.yml new file mode 100644 index 00000000000..a3cbc35bb7e --- /dev/null +++ b/Resources/Audio/Nyanotrasen/Machines/attributions.yml @@ -0,0 +1,7 @@ +- files: + - "washer_open.ogg" + - "washer_close.ogg" + license: "CC-BY-4.0" + copyright: "https://freesound.org/people/soundmary/" + source: "https://freesound.org/people/soundmary/sounds/194994/" + diff --git a/Resources/Audio/Nyanotrasen/Machines/washer_close.ogg b/Resources/Audio/Nyanotrasen/Machines/washer_close.ogg new file mode 100644 index 00000000000..956e7f6717b Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Machines/washer_close.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Machines/washer_open.ogg b/Resources/Audio/Nyanotrasen/Machines/washer_open.ogg new file mode 100644 index 00000000000..2c6346b7caf Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Machines/washer_open.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Mobs/Borg/attributions.yml b/Resources/Audio/Nyanotrasen/Mobs/Borg/attributions.yml new file mode 100644 index 00000000000..969fe5329fd --- /dev/null +++ b/Resources/Audio/Nyanotrasen/Mobs/Borg/attributions.yml @@ -0,0 +1,9 @@ +- files: ["robot_melt_down.ogg"] + license: "CC-BY-NC-4.0" + copyright: "https://freesound.org/people/chimerical/" + source: "https://freesound.org/people/chimerical/sounds/103687/" + +- files: ["robot_treads.ogg"] + license: "CC0-1.0" + copyright: "https://freesound.org/people/craigsmith/ ; @Vordenburg clipped and slowed down a section" + source: "https://freesound.org/people/craigsmith/sounds/438662/" diff --git a/Resources/Audio/Nyanotrasen/Mobs/Borg/borg_deathsound.ogg b/Resources/Audio/Nyanotrasen/Mobs/Borg/borg_deathsound.ogg new file mode 100644 index 00000000000..bb11022abec Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Mobs/Borg/borg_deathsound.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Mobs/Borg/robot_melt_down.ogg b/Resources/Audio/Nyanotrasen/Mobs/Borg/robot_melt_down.ogg new file mode 100644 index 00000000000..4d452951175 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Mobs/Borg/robot_melt_down.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Mobs/Borg/robot_treads.ogg b/Resources/Audio/Nyanotrasen/Mobs/Borg/robot_treads.ogg new file mode 100644 index 00000000000..e98f7edb8c2 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Mobs/Borg/robot_treads.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/attributions.yml b/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/attributions.yml new file mode 100644 index 00000000000..90b3bf42bb2 --- /dev/null +++ b/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/attributions.yml @@ -0,0 +1,24 @@ +- files: ["wail.ogg"] + license: "CC-BY-SA-4.0" + copyright: "From battle for wesnoth" + source: "https://github.com/wesnoth/wesnoth" + +- files: ["magic_missile_1.ogg"] + license: "CC-BY-SA-4.0" + copyright: "From battle for wesnoth" + source: "https://github.com/wesnoth/wesnoth" + +- files: ["magic_missile_2.ogg"] + license: "CC-BY-SA-4.0" + copyright: "From battle for wesnoth" + source: "https://github.com/wesnoth/wesnoth" + +- files: ["magic_missile_3.ogg"] + license: "CC-BY-SA-4.0" + copyright: "From battle for wesnoth" + source: "https://github.com/wesnoth/wesnoth" + +- files: ["wisp_ambience.ogg"] + license: "CC-BY-4.0" + copyright: "Created by AlienXXX, touched up by Rane"" + source: "https://freesound.org/people/AlienXXX/sounds/123647/" diff --git a/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/magic_missile_1.ogg b/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/magic_missile_1.ogg new file mode 100644 index 00000000000..331a3efc54e Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/magic_missile_1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/magic_missile_2.ogg b/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/magic_missile_2.ogg new file mode 100644 index 00000000000..8aac11d665e Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/magic_missile_2.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/magic_missile_3.ogg b/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/magic_missile_3.ogg new file mode 100644 index 00000000000..b7f4e941117 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/magic_missile_3.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/wail.ogg b/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/wail.ogg new file mode 100644 index 00000000000..b40ec5ab252 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/wail.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/wisp_ambience.ogg b/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/wisp_ambience.ogg new file mode 100644 index 00000000000..83f823ec818 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Mobs/GlimmerWisp/wisp_ambience.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Mobs/Hologram/attributions.yml b/Resources/Audio/Nyanotrasen/Mobs/Hologram/attributions.yml new file mode 100644 index 00000000000..258f72a656b --- /dev/null +++ b/Resources/Audio/Nyanotrasen/Mobs/Hologram/attributions.yml @@ -0,0 +1,5 @@ +- files: ["hologram_start.ogg"] + license: "Custom" + copyright: "Original sound sleeping_wave.mp3 by Taira Komori (小森平)" + source: "https://taira-komori.jpn.org/freesounden.html" + diff --git a/Resources/Audio/Nyanotrasen/Mobs/Hologram/hologram_start.ogg b/Resources/Audio/Nyanotrasen/Mobs/Hologram/hologram_start.ogg new file mode 100644 index 00000000000..63707f9db22 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Mobs/Hologram/hologram_start.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Mobs/SilverGolem/sonic_jackhammer.ogg b/Resources/Audio/Nyanotrasen/Mobs/SilverGolem/sonic_jackhammer.ogg new file mode 100644 index 00000000000..be22f36e3eb Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Mobs/SilverGolem/sonic_jackhammer.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Mobs/SilverGolem/wave.ogg b/Resources/Audio/Nyanotrasen/Mobs/SilverGolem/wave.ogg new file mode 100644 index 00000000000..b7bf7d260fa Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Mobs/SilverGolem/wave.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/attributions.yml b/Resources/Audio/Nyanotrasen/Voice/Felinid/attributions.yml new file mode 100644 index 00000000000..bca921c51df --- /dev/null +++ b/Resources/Audio/Nyanotrasen/Voice/Felinid/attributions.yml @@ -0,0 +1,24 @@ +- files: ["cat_hiss1.ogg", "cat_hiss2.ogg"] + license: "CC-BY-4.0" + copyright: "Original sound by https://freesound.org/people/secondbody/ - cut out two clips of cat hissing, cleaned up, and converted to ogg." + source: "https://freesound.org/people/secondbody/sounds/50357/" + +- files: ["cat_meow1.ogg", "cat_meow2.ogg", "cat_meow3.ogg"] + license: "CC-BY-3.0" + copyright: "Original sound by https://freesound.org/people/ignotus/ - cut out three clips of cat meowing, cleaned up, and converted to ogg." + source: "https://freesound.org/people/ignotus/sounds/26104/" + +- files: ["cat_mew1.ogg", "cat_mew2.ogg"] + license: "CC0-1.0" + copyright: "Original sound by https://freesound.org/people/videog/ - cut out two clips of kittens mewing, cleaned up, and converted to ogg." + source: "https://freesound.org/people/videog/sounds/149191/" + +- files: ["cat_purr1.ogg"] + license: "CC-BY-4.0" + copyright: "Original sound by https://freesound.org/people/klangstrand/ - cut out short clip, fade in and out, converted to ogg." + source: "https://freesound.org/people/klangstrand/sounds/213951/" + +- files: ["cat_growl1.ogg"] + license: "CC0-1.0" + copyright: "Original sound by https://freesound.org/people/Zabuhailo/ - fade in and out, converted to ogg." + source: "https://freesound.org/people/Zabuhailo/sounds/146968/" diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_growl1.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_growl1.ogg new file mode 100644 index 00000000000..f7c5b43cee1 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_growl1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_hiss1.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_hiss1.ogg new file mode 100644 index 00000000000..10cfe9670de Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_hiss1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_hiss2.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_hiss2.ogg new file mode 100644 index 00000000000..faaa953ee11 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_hiss2.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow1.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow1.ogg new file mode 100644 index 00000000000..6ed99f0d581 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow2.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow2.ogg new file mode 100644 index 00000000000..34bb375fe5d Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow2.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow3.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow3.ogg new file mode 100644 index 00000000000..0af0cb0e07b Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_meow3.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_mew1.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_mew1.ogg new file mode 100644 index 00000000000..e41650e0fb1 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_mew1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_mew2.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_mew2.ogg new file mode 100644 index 00000000000..d82657b9a47 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_mew2.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_purr1.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_purr1.ogg new file mode 100644 index 00000000000..d7ef89fc50d Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_purr1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream1.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream1.ogg new file mode 100644 index 00000000000..2ecc1cf64da Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream2.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream2.ogg new file mode 100644 index 00000000000..abb19bcba1e Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream2.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream3.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream3.ogg new file mode 100644 index 00000000000..5e297520468 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_scream3.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_wilhelm.ogg b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_wilhelm.ogg new file mode 100644 index 00000000000..45934ebeb34 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Felinid/cat_wilhelm.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Felinid/license.txt b/Resources/Audio/Nyanotrasen/Voice/Felinid/license.txt new file mode 100644 index 00000000000..89905a5d65b --- /dev/null +++ b/Resources/Audio/Nyanotrasen/Voice/Felinid/license.txt @@ -0,0 +1,4 @@ +cat_scream1.ogg licensed under CC0 1.0 taken from Queen_Westeros at https://freesound.org/people/queen_westeros/sounds/222590/ +cat_scream2.ogg licensed under CC4.0 taken from InspectorJ at https://freesound.org/people/InspectorJ/sounds/415209/ +cat_scream3.ogg licensed under CC sampling plus 1.0 taken from Hamface at https://freesound.org/people/Hamface/sounds/98669/ +cat_wilhelm.ogg used with apologies to type moon diff --git a/Resources/Audio/Nyanotrasen/Voice/Moth/laugh_moth.ogg b/Resources/Audio/Nyanotrasen/Voice/Moth/laugh_moth.ogg new file mode 100644 index 00000000000..1f2177b7e53 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Moth/laugh_moth.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Moth/license.txt b/Resources/Audio/Nyanotrasen/Voice/Moth/license.txt new file mode 100644 index 00000000000..fc90c23a037 --- /dev/null +++ b/Resources/Audio/Nyanotrasen/Voice/Moth/license.txt @@ -0,0 +1,6 @@ +Sounds taken from https://github.com/tgstation/tgstation/commit/3d049e69fe71a0be2133005e65ea469135d648c8 +scream_moth.ogg + +Sounds taken from https://github.com/BeeStation/BeeStation-Hornet/commit/11ba3fa04105c93dd96a63ad4afaef4b20c02d0d +laugh_moth.ogg +squeak_moth.ogg \ No newline at end of file diff --git a/Resources/Audio/Nyanotrasen/Voice/Moth/scream_moth.ogg b/Resources/Audio/Nyanotrasen/Voice/Moth/scream_moth.ogg new file mode 100644 index 00000000000..482086fb630 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Moth/scream_moth.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Moth/squeak_moth.ogg b/Resources/Audio/Nyanotrasen/Voice/Moth/squeak_moth.ogg new file mode 100644 index 00000000000..5b77d98e565 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Moth/squeak_moth.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Talk/attributions.yml b/Resources/Audio/Nyanotrasen/Voice/Talk/attributions.yml new file mode 100644 index 00000000000..21b848f4845 --- /dev/null +++ b/Resources/Audio/Nyanotrasen/Voice/Talk/attributions.yml @@ -0,0 +1,14 @@ +- files: ["snake_speak.ogg"] + license: "CC0" + copyright: "From scheibsel, edited by Rane" + source: "https://freesound.org/people/schreibsel/sounds/540162/" + +- files: ["snake_exclaim.ogg"] + license: "CC-BY-4.0" + copyright: "From Garuda1982, edited by Rane" + source: "https://freesound.org/people/Garuda1982/sounds/541656/" + +- files: ["snake_ask.ogg"] + license: "CC0" + copyright: "From xoiziox, edited by Rane" + source: "https://freesound.org/people/xoiziox/sounds/553374/" diff --git a/Resources/Audio/Nyanotrasen/Voice/Talk/snake_ask.ogg b/Resources/Audio/Nyanotrasen/Voice/Talk/snake_ask.ogg new file mode 100644 index 00000000000..3f44596b402 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Talk/snake_ask.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Talk/snake_exclaim.ogg b/Resources/Audio/Nyanotrasen/Voice/Talk/snake_exclaim.ogg new file mode 100644 index 00000000000..d59f6a07d93 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Talk/snake_exclaim.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Voice/Talk/snake_speak.ogg b/Resources/Audio/Nyanotrasen/Voice/Talk/snake_speak.ogg new file mode 100644 index 00000000000..3e37ca09d2e Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Voice/Talk/snake_speak.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Weapons/Guns/Gunshots/universal.ogg b/Resources/Audio/Nyanotrasen/Weapons/Guns/Gunshots/universal.ogg new file mode 100644 index 00000000000..fe322c18eed Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Weapons/Guns/Gunshots/universal.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Weapons/electricguitarhit.ogg b/Resources/Audio/Nyanotrasen/Weapons/electricguitarhit.ogg new file mode 100644 index 00000000000..09d43ccdb59 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Weapons/electricguitarhit.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Weapons/guitarhit.ogg b/Resources/Audio/Nyanotrasen/Weapons/guitarhit.ogg new file mode 100644 index 00000000000..0ee89eec244 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/Weapons/guitarhit.ogg differ diff --git a/Resources/Audio/Nyanotrasen/Weapons/licenses.txt b/Resources/Audio/Nyanotrasen/Weapons/licenses.txt new file mode 100644 index 00000000000..f78007c3ca3 --- /dev/null +++ b/Resources/Audio/Nyanotrasen/Weapons/licenses.txt @@ -0,0 +1,2 @@ +electricguitarhit.ogg is modified from a file made by guitarguy1985 at https://freesound.org/people/guitarguy1985/sounds/52391/ which is licensed under the Creative Commons 0 License. This modified file follows the same license. +universal.ogg is modified from free sound effects from https://www.fesliyanstudios.com which are licensed under the CC0 1.0 License. This modified file follows the same license. diff --git a/Resources/Audio/Nyanotrasen/blowout1.ogg b/Resources/Audio/Nyanotrasen/blowout1.ogg new file mode 100644 index 00000000000..edac38f0d51 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/blowout1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/bottle_clunk.ogg b/Resources/Audio/Nyanotrasen/bottle_clunk.ogg new file mode 100644 index 00000000000..1c6b435f3f1 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/bottle_clunk.ogg differ diff --git a/Resources/Audio/Nyanotrasen/bottle_clunk_2.ogg b/Resources/Audio/Nyanotrasen/bottle_clunk_2.ogg new file mode 100644 index 00000000000..81afc70a7da Binary files /dev/null and b/Resources/Audio/Nyanotrasen/bottle_clunk_2.ogg differ diff --git a/Resources/Audio/Nyanotrasen/fireball1.ogg b/Resources/Audio/Nyanotrasen/fireball1.ogg new file mode 100644 index 00000000000..68de2342b15 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/fireball1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/flamethrower1.ogg b/Resources/Audio/Nyanotrasen/flamethrower1.ogg new file mode 100644 index 00000000000..1939bd21625 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/flamethrower1.ogg differ diff --git a/Resources/Audio/Nyanotrasen/flamethrower2.ogg b/Resources/Audio/Nyanotrasen/flamethrower2.ogg new file mode 100644 index 00000000000..2ec5e4f0247 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/flamethrower2.ogg differ diff --git a/Resources/Audio/Nyanotrasen/flamethrower3.ogg b/Resources/Audio/Nyanotrasen/flamethrower3.ogg new file mode 100644 index 00000000000..34082376cf2 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/flamethrower3.ogg differ diff --git a/Resources/Audio/Nyanotrasen/heartbeat_fast.ogg b/Resources/Audio/Nyanotrasen/heartbeat_fast.ogg new file mode 100644 index 00000000000..85a034d9bd0 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/heartbeat_fast.ogg differ diff --git a/Resources/Audio/Nyanotrasen/license.txt b/Resources/Audio/Nyanotrasen/license.txt new file mode 100644 index 00000000000..c7d32e7e2f1 --- /dev/null +++ b/Resources/Audio/Nyanotrasen/license.txt @@ -0,0 +1,6 @@ +heartbeat_fast.ogg is modified from https://freesound.org/people/greyseraphim/sounds/21409/ which is licensed under the Creative Commons 0 License. This modified file follows the same license. +fireball1.ogg is modified from https://freesound.org/people/wjl/sounds/267887/ which is licensed under the Creative Commons 0 License. This modified file follows the same license. +flamethrower1.ogg is modified from https://freesound.org/people/pugaeme/sounds/396890/ which is licensed under the Creative Commons Attribution 3.0 License. This modified file follows the same license. +flamethrower2.ogg is modified from https://freesound.org/people/pugaeme/sounds/396890/ which is licensed under the Creative Commons Attribution 3.0 License. This modified file follows the same license. +flamethrower3.ogg is modified from https://freesound.org/people/pugaeme/sounds/396890/ which is licensed under the Creative Commons Attribution 3.0 License. This modified file follows the same license. +blowout1.ogg is licensed under the Creative Commons Attribution-ShareAlike 4.0 License by the author @Vordenburg. diff --git a/Resources/Audio/Nyanotrasen/shogi_piece_clack.ogg b/Resources/Audio/Nyanotrasen/shogi_piece_clack.ogg new file mode 100644 index 00000000000..e4ba5088f90 Binary files /dev/null and b/Resources/Audio/Nyanotrasen/shogi_piece_clack.ogg differ diff --git a/Resources/Locale/en-US/backmen/abilities/psionic.ftl b/Resources/Locale/en-US/backmen/abilities/psionic.ftl new file mode 100644 index 00000000000..91ae21233a3 --- /dev/null +++ b/Resources/Locale/en-US/backmen/abilities/psionic.ftl @@ -0,0 +1,73 @@ +cage-resist-second-person = You start removing your {$cage}. +cage-resist-third-person = {CAPITALIZE(THE($user))} starts removing {POSS-ADJ($user)} {$cage}. + +cage-uncage-verb = Uncage + +action-name-metapsionic = Metapsionic Pulse +action-description-metapsionic = Send a mental pulse through the area to see if there are any psychics nearby. + +metapsionic-pulse-success = You detect psychic presence nearby. +metapsionic-pulse-failure = You don't detect any psychic presence nearby. +metapsionic-pulse-power = You detect that {$power} was used nearby. + +action-name-dispel = Dispel +action-description-dispel = Dispel summoned entities such as familiars or forcewalls. + +action-name-mass-sleep = Mass Sleep +action-description-mass-sleep = Put targets in a small area to sleep. + +accept-psionics-window-title = Psionic! +accept-psionics-window-prompt-text-part = You rolled a psionic power! + It's possible that certain anti-psychic forces may hunt you, + so you should consider keeping it secret. + Do you still wish to be psionic? + +action-name-psionic-invisibility = Psionic Invisibility +action-description-psionic-invisibility = Render yourself invisible to any entity that could potentially be psychic. Borgs, animals, and so on are not affected. + +action-name-psionic-invisibility = Psionic Invisibility +action-description-psionic-invisibility = Render yourself invisible to any entity that could potentially be psychic. Borgs, animals, and so on are not affected. + +action-name-psionic-invisibility-off = Turn Off Psionic Invisibility +action-description-psionic-invisibility-off = Return to visibility, and receive a stun. + +action-name-mind-swap = Mind Swap +action-description-mind-swap = Swap minds with the target. Either can change back after 20 seconds. + +action-name-mind-swap-return = Reverse Mind Swap +action-description-mind-swap-return = Return to your original body. + +action-name-telegnosis = Telegnosis +action-description-telegnosis = Create a telegnostic projection to remotely observe things. + +action-name-psionic-regeneration = Psionic Regeneration +action-description-psionic-regeneration = Push your natural metabolism to the limit to power your body's regenerative capability. + +glimmer-report = Current Glimmer Level: {$level}Ψ. +glimmer-event-report-generic = Noöspheric discharge detected. Glimmer level has decreased by {$decrease} to {$level}Ψ. +glimmer-event-report-signatures = New psionic signatures manifested. Glimmer level has decreased by {$decrease} to {$level}Ψ. +glimmer-event-awakened-prefix = awakened {$entity} + +noospheric-zap-seize = You seize up! +noospheric-zap-seize-potential-regained = You seize up! Some mental block seems to have cleared, too. + +mindswap-trapped = Seems you're trapped in this vessel. + +telegnostic-trapped-entity-name = severed telegnostic projection +telegnostic-trapped-entity-desc = Its many eyes betray sadness. + +psionic-burns-up = {CAPITALIZE(THE($item))} burns up with arcs of strange energy! +psionic-burn-resist = Strange arcs dance across {THE($item)}! + +action-name-noospheric-zap = Noospheric Zap +action-description-noospheric-zap = Shocks the conciousness of the target and leaves them stunned and stuttering. + +action-name-pyrokinesis = Pyrokinesis +action-description-pyrokinesis = Light a flammable target on fire. +pyrokinesis-power-used = A wisp of flame engulfs {THE($target)}, igniting {OBJECT($target)}! + +action-name-psychokinesis = Psychokinesis +action-description-psychokinesis = Bend the fabric of space to instantly move across it. + +action-name-rf-sensitivity = Toggle RF Sensitivity +action-desc-rf-sensitivity = Toggle your ability to interpret radio waves on and off. diff --git a/Resources/Locale/en-US/backmen/chat/managers/chat-manager.ftl b/Resources/Locale/en-US/backmen/chat/managers/chat-manager.ftl new file mode 100644 index 00000000000..64d37c64361 --- /dev/null +++ b/Resources/Locale/en-US/backmen/chat/managers/chat-manager.ftl @@ -0,0 +1,3 @@ +chat-manager-send-telepathic-chat-wrap-message = {$telepathicChannelName}: {$message} +chat-manager-send-telepathic-chat-wrap-message-admin = {$source} (Ψ): {$message} +chat-manager-telepathic-channel-name = TELEPATHIC diff --git a/Resources/Locale/en-US/backmen/objectives/conditions/become-psionic-condition.ftl b/Resources/Locale/en-US/backmen/objectives/conditions/become-psionic-condition.ftl new file mode 100644 index 00000000000..2bc901b25e2 --- /dev/null +++ b/Resources/Locale/en-US/backmen/objectives/conditions/become-psionic-condition.ftl @@ -0,0 +1,2 @@ +objective-condition-become-psionic-title = Become psionic +objective-condition-become-psionic-description = We need you to acquire psionics and keep them until your mission is complete. diff --git a/Resources/Locale/en-US/backmen/station-events/events/noospheric-storm.ftl b/Resources/Locale/en-US/backmen/station-events/events/noospheric-storm.ftl new file mode 100644 index 00000000000..649f5d3986c --- /dev/null +++ b/Resources/Locale/en-US/backmen/station-events/events/noospheric-storm.ftl @@ -0,0 +1 @@ +station-event-noospheric-storm-announcement = Noöspheric storm detected. Psionic registries may need updating, and mantes should be on high alert. diff --git a/Resources/Locale/ru-RU/backmen/abilities/psionic.ftl b/Resources/Locale/ru-RU/backmen/abilities/psionic.ftl new file mode 100644 index 00000000000..d2372335884 --- /dev/null +++ b/Resources/Locale/ru-RU/backmen/abilities/psionic.ftl @@ -0,0 +1,70 @@ +cage-resist-second-person = Вы пытаетесь вырваться из {$cage}. +cage-resist-third-person = {CAPITALIZE(THE($user))} начал выбираться {POSS-ADJ($user)} {$cage}. + +cage-uncage-verb = Освободиться + +action-name-metapsionic = Метапсионический импульс +action-description-metapsionic = Отправьте мысленный импульс вокруг себя, чтобы увидеть, есть ли поблизости псионики. + +metapsionic-pulse-success = Вы чувствуете присутствие псионика поблизости +metapsionic-pulse-failure = Вы не чувствуете поблизости никакого психического присутствия. +metapsionic-pulse-power = Вы замечаете использование {$power} рядом с собой. + +action-name-dispel = Рассеять +action-description-dispel = Рассеивайте призванные сущности, такие как фамильяры или силовые стены. + +action-name-mass-sleep = Массовый сон +action-description-mass-sleep = Усыпите цели на небольшом участке. + +accept-psionics-window-title = Псионика! +accept-psionics-window-prompt-text-part = Вы использовали псионическую силу! + Вполне возможно, что за вами могут охотиться определенные антипсихические силы, + поэтому вам следует сохранить это в секрете. + Вы все еще хотите стать псиоником? + +action-name-psionic-invisibility = Псионическая невидимость +action-description-psionic-invisibility = Сделайте себя невидимым для любой сущности, которая потенциально может быть экстрасенсом. Борги, животные и так далее не затрагиваются. + +action-name-psionic-invisibility-off = Отменить псионическую невидимость +action-description-psionic-invisibility-off = Станьте видимым и получите оглушение. + +action-name-mind-swap = Обмен разумом +action-description-mind-swap = Поменяйтесь мыслями с целью. Любой из них может измениться обратно через 20 секунд. + +action-name-mind-swap-return = Обратный обмен разумом +action-description-mind-swap-return = Возвращайся в свое первоначальное тело. + +action-name-telegnosis = Телегноз +action-description-telegnosis = Создайте телегностическую проекцию, чтобы удаленно наблюдать за происходящим. + +action-name-psionic-regeneration = Псионическая регенерация +action-description-psionic-regeneration = Доведите свой естественный метаболизм до предела, чтобы усилить способность вашего организма к регенерации. + +glimmer-report = Текущий уровень мерцания: {$level}Ψ. +glimmer-event-report-generic = Ноосферический разряд не обнаружен. Уровень мерцания снизился на {$decrease} до {$level}Ψ. +glimmer-event-report-signatures = Проявились новые псионические сигнатуры. Уровень мерцания снизился на {$decrease} до {$level}Ψ. +glimmer-event-awakened-prefix = пробудился {$entity} + +noospheric-zap-seize = Тебя схватили! +noospheric-zap-seize-potential-regained = Ты захвачен! Какой-то ментальный блок, похоже, тоже снялся. + +mindswap-trapped = Кажется, ты заперт в этом сосуде. + +telegnostic-trapped-entity-name = разорванная телегностическая проекция +telegnostic-trapped-entity-desc = Его многочисленные глаза выдают печаль. + +psionic-burns-up = {CAPITALIZE(THE($item))} вспыхивает дугами странной энергии! +psionic-burn-resist = Странные дуги танцуют поперек {THE($item)}! + +action-name-noospheric-zap = Ноосферический удар +action-description-noospheric-zap = Шокирует сознание цели и оставляет ее ошеломленной и заикающейся. + +action-name-pyrokinesis = Пирокинез +action-description-pyrokinesis = Подожгите легковоспламеняющуюся мишень. +pyrokinesis-power-used = Струйка пламени охватывает {THE($target)}, воспламеняет {OBJECT($target)}! + +action-name-psychokinesis = Психокинез +action-description-psychokinesis = Согните ткань пространства, чтобы мгновенно перемещаться по ней. + +action-name-rf-sensitivity = Переключение чувствительности к радиочастотному излучению +action-desc-rf-sensitivity = Включайте и выключайте свою способность интерпретировать радиоволны. diff --git a/Resources/Locale/ru-RU/backmen/chat/managers/chat-manager.ftl b/Resources/Locale/ru-RU/backmen/chat/managers/chat-manager.ftl new file mode 100644 index 00000000000..45fe52c7487 --- /dev/null +++ b/Resources/Locale/ru-RU/backmen/chat/managers/chat-manager.ftl @@ -0,0 +1,4 @@ +chat-manager-send-telepathic-chat-wrap-message = {$telepathicChannelName}: {$message} +chat-manager-send-telepathic-chat-wrap-message-admin = {$source} (Ψ): {$message} +chat-manager-telepathic-channel-name = Телепатия +hud-chatbox-select-channel-Telepathic = Телепатия diff --git a/Resources/Locale/ru-RU/backmen/medical/components/health-analyzer-component.ftl b/Resources/Locale/ru-RU/backmen/medical/components/health-analyzer-component.ftl new file mode 100644 index 00000000000..32c882bf65c --- /dev/null +++ b/Resources/Locale/ru-RU/backmen/medical/components/health-analyzer-component.ftl @@ -0,0 +1,2 @@ +health-analyzer-window-damage-group-Immaterial = Не материальный +health-analyzer-window-damage-type-Holy = Святой diff --git a/Resources/Locale/ru-RU/backmen/objectives/conditions/become-psionic-condition.ftl b/Resources/Locale/ru-RU/backmen/objectives/conditions/become-psionic-condition.ftl new file mode 100644 index 00000000000..3eb15975fac --- /dev/null +++ b/Resources/Locale/ru-RU/backmen/objectives/conditions/become-psionic-condition.ftl @@ -0,0 +1,2 @@ +objective-condition-become-psionic-title = Стать псиоником +objective-condition-become-psionic-description = Нам нужно, чтобы вы стали псиоником и оставались им до завершения миссии. diff --git a/Resources/Locale/ru-RU/backmen/station-events/events/noospheric-storm.ftl b/Resources/Locale/ru-RU/backmen/station-events/events/noospheric-storm.ftl new file mode 100644 index 00000000000..b806b9c56e6 --- /dev/null +++ b/Resources/Locale/ru-RU/backmen/station-events/events/noospheric-storm.ftl @@ -0,0 +1,2 @@ +station-event-noospheric-storm-announcement = Обнаружена ноосферная буря. +#Псионические реестры, возможно, нуждаются в обновлении, и манты должны быть в состоянии повышенной готовности. diff --git a/Resources/Prototypes/Backmen/Actions/arachne.yml b/Resources/Prototypes/Backmen/Actions/arachne.yml index 3186c08e731..0a3c214ae79 100644 --- a/Resources/Prototypes/Backmen/Actions/arachne.yml +++ b/Resources/Prototypes/Backmen/Actions/arachne.yml @@ -5,11 +5,3 @@ description: spider-web-action-description serverEvent: !type:SpiderWebActionEvent useDelay: 180 - -- type: worldTargetAction - id: SpinWeb - name: action-name-spin-web - description: action-desc-spin-web - icon: { sprite: Backmen/Structures/web.rsi, state: web1 } - range: 1.5 - serverEvent: !type:SpinWebActionEvent diff --git a/Resources/Prototypes/Backmen/Actions/types.yml b/Resources/Prototypes/Backmen/Actions/types.yml index b145e94d724..4f74358220e 100644 --- a/Resources/Prototypes/Backmen/Actions/types.yml +++ b/Resources/Prototypes/Backmen/Actions/types.yml @@ -5,6 +5,123 @@ icon: Backmen/Icons/verbiconfangs.png serverEvent: !type:EatMouseActionEvent +- type: instantAction + id: MetapsionicPulse + name: action-name-metapsionic + description: action-description-metapsionic + icon: Backmen/Interface/VerbIcons/metapsionic.png + useDelay: 45 + serverEvent: !type:MetapsionicPowerActionEvent + +- type: entityTargetAction + id: Dispel + name: action-name-dispel + description: action-description-dispel + icon: Backmen/Interface/VerbIcons/dispel.png + useDelay: 45 + checkCanAccess: false + range: 6 + canTargetSelf: false + serverEvent: !type:DispelPowerActionEvent + +- type: worldTargetAction + id: MassSleep + name: action-name-mass-sleep + description: action-description-mass-sleep + icon: Backmen/Interface/VerbIcons/mass_sleep.png + useDelay: 60 + checkCanAccess: false + range: 8 + event: !type:MassSleepPowerActionEvent + +- type: instantAction + id: PsionicInvisibility + name: action-name-psionic-invisibility + description: action-description-psionic-invisibility + icon: Backmen/Interface/VerbIcons/psionic_invisibility.png + useDelay: 120 + serverEvent: !type:PsionicInvisibilityPowerActionEvent + +- type: instantAction + id: PsionicInvisibilityOff + name: action-name-psionic-invisibility-off + description: action-description-psionic-invisibility-off + icon: Backmen/Interface/VerbIcons/psionic_invisibility_off.png + serverEvent: !type:PsionicInvisibilityPowerOffActionEvent + +- type: entityTargetAction + id: MindSwap + name: action-name-mind-swap + description: action-description-mind-swap + icon: Backmen/Interface/VerbIcons/mind_swap.png + useDelay: 240 + checkCanAccess: false + range: 8 + serverEvent: !type:MindSwapPowerActionEvent + +- type: instantAction + id: MindSwapReturn + name: action-name-mind-swap-return + description: action-description-mind-swap-return + icon: Backmen/Interface/VerbIcons/mind_swap_return.png + checkCanInteract: false + useDelay: 20 + serverEvent: !type:MindSwapPowerReturnActionEvent + +- type: instantAction + id: Telegnosis + name: action-name-telegnosis + description: action-description-telegnosis + icon: Backmen/Interface/VerbIcons/telegnosis.png + useDelay: 150 + serverEvent: !type:TelegnosisPowerActionEvent + +- type: instantAction + id: PsionicRegeneration + name: action-name-psionic-regeneration + description: action-description-psionic-regeneration + icon: Backmen/Interface/VerbIcons/psionic_regeneration.png + useDelay: 120 + serverEvent: !type:PsionicRegenerationPowerActionEvent + +- type: entityTargetAction + id: NoosphericZap + name: action-name-noospheric-zap + description: action-description-noospheric-zap + icon: Backmen/Interface/VerbIcons/noospheric_zap.png + useDelay: 100 + range: 5 + serverEvent: !type:NoosphericZapPowerActionEvent + +- type: entityTargetAction + id: Pyrokinesis + name: action-name-pyrokinesis + description: action-description-pyrokinesis + icon: Backmen/Interface/VerbIcons/pyrokinesis.png + useDelay: 50 + checkCanAccess: false + range: 6 + serverEvent: !type:PyrokinesisPowerActionEvent + +- type: worldTargetAction + id: Psychokinesis + name: action-name-psychokinesis + description: action-description-psychokinesis + icon: { sprite: Objects/Misc/guardian_info.rsi, state: icon} + useDelay: 20 + itemIconStyle: BigAction + checkCanAccess: false + range: 30 + serverEvent: !type:PsychokinesisPowerActionEvent + +- type: worldTargetAction + id: SpinWeb + name: action-name-spin-web + description: action-desc-spin-web + icon: { sprite: Backmen/Structures/web.rsi, state: web1 } + range: 1.5 + serverEvent: !type:SpinWebActionEvent + - type: instantAction id: HairballAction name: hairball-action diff --git a/Resources/Prototypes/Backmen/Damage/containers.yml b/Resources/Prototypes/Backmen/Damage/containers.yml index 3ee3509d132..27eee38cd9a 100644 --- a/Resources/Prototypes/Backmen/Damage/containers.yml +++ b/Resources/Prototypes/Backmen/Damage/containers.yml @@ -7,3 +7,16 @@ - Immaterial supportedTypes: - Poison + +- type: damageContainer + id: Spirit + supportedGroups: + - Burn + - Immaterial + +- type: damageContainer + id: CorporealSpirit + supportedGroups: + - Burn + - Brute + - Immaterial diff --git a/Resources/Prototypes/Backmen/Damage/modifier_sets.yml b/Resources/Prototypes/Backmen/Damage/modifier_sets.yml index 5bb61009fa9..1961fb285e9 100644 --- a/Resources/Prototypes/Backmen/Damage/modifier_sets.yml +++ b/Resources/Prototypes/Backmen/Damage/modifier_sets.yml @@ -1,4 +1,23 @@ - type: damageModifierSet + id: Spirit + coefficients: + Cold: 0.0 + Shock: 0.2 + Heat: 3.0 + Holy: 5.0 + +- type: damageModifierSet + id: CorporealSpirit + coefficients: + Cold: 0.0 + Shock: 0.5 + Blunt: 0.5 + Slash: 0.5 + Piercing: 0.5 + Heat: 1.5 + Holy: 3.0 + +- type: damageModifierSet id: HalfSpirit coefficients: Cold: 0.5 diff --git a/Resources/Prototypes/Backmen/Entities/Clothing/psionic_clothing.yml b/Resources/Prototypes/Backmen/Entities/Clothing/psionic_clothing.yml new file mode 100644 index 00000000000..5d02397efbe --- /dev/null +++ b/Resources/Prototypes/Backmen/Entities/Clothing/psionic_clothing.yml @@ -0,0 +1,70 @@ +- type: entity + parent: ClothingEyesBase + id: ClothingEyesTelegnosisSpectacles + suffix: Псионика + name: странные очки + description: Пара эффектных и странных очков с линзами, изготовленными из неизвестного материала. + components: + - type: Sprite + sprite: Clothing/Eyes/Glasses/glasses.rsi + - type: Clothing + sprite: Clothing/Eyes/Glasses/glasses.rsi + - type: VisionCorrection + - type: ClothingGrantPsionicPower + power: TelegnosisPower + - type: Psionic + +- type: entity + id: BedsheetInvisibilityCloak + suffix: Псионика + parent: BedsheetBase + name: рваная простыня + description: Изодранная простыня, она выглядит так, словно прошла через ад и вернулась обратно. + components: + - type: Sprite + state: sheetblack + - type: Clothing + sprite: Clothing/Neck/Bedsheets/black.rsi + - type: ClothingGrantPsionicPower + power: PsionicInvisibilityPower + - type: Psionic + +- type: entity + parent: ClothingHandsBase + id: ClothingHandsDispelGloves + suffix: Псионика + name: лоскутные перчатки + description: Эти кожаные перчатки сильно изношены, однако они были отремонтированы в нескольких местах. + components: + - type: Sprite + sprite: Clothing/Hands/Gloves/leather.rsi + - type: Clothing + sprite: Clothing/Hands/Gloves/leather.rsi + - type: GloveHeatResistance + heatResistance: 1400 + - type: Fiber + fiberMaterial: fibers-leather + fiberColor: fibers-brown + - type: FingerprintMask + - type: ClothingGrantPsionicPower + power: DispelPower + - type: Psionic + +- type: entity + parent: ClothingHandsBase + id: ClothingHandsGlovesColorYellowUnsulated + suffix: Псионика + name: изолированные перчатки + description: Тонкие рабочие перчатки из синтетической резины, выполненные в тускло-желтом цвете. Они не имеют электрической изоляции и не обеспечивают никакой защиты от ударов. + components: + - type: Sprite + sprite: Clothing/Hands/Gloves/Color/yellow.rsi + - type: Clothing + sprite: Clothing/Hands/Gloves/Color/yellow.rsi + - type: Fiber + fiberMaterial: fibers-insulative + fiberColor: fibers-yellow + - type: FingerprintMask + - type: ClothingGrantPsionicPower + power: NoosphericZapPower + - type: Psionic diff --git a/Resources/Prototypes/Backmen/Entities/Effects/lightning.yml b/Resources/Prototypes/Backmen/Entities/Effects/lightning.yml index e06a298472e..9508889d5f1 100644 --- a/Resources/Prototypes/Backmen/Entities/Effects/lightning.yml +++ b/Resources/Prototypes/Backmen/Entities/Effects/lightning.yml @@ -130,3 +130,14 @@ softness: 1 autoRot: true castShadows: false + +- type: entity + parent: BaseLightning + id: LightningNoospheric + name: noospheric lightning + noSpawn: true + components: + - type: Electrified + enabled: false + - type: Lightning + canArc: false diff --git a/Resources/Prototypes/Backmen/Entities/Mobs/NPC/familiars.yml b/Resources/Prototypes/Backmen/Entities/Mobs/NPC/familiars.yml new file mode 100644 index 00000000000..7432d2b88c7 --- /dev/null +++ b/Resources/Prototypes/Backmen/Entities/Mobs/NPC/familiars.yml @@ -0,0 +1,141 @@ +- type: entity + parent: SimpleSpaceMobBase + id: MobIfritFamiliar + name: Ифрит + description: Мистический слуга + components: + - type: Sprite + drawdepth: Mobs + sprite: Backmen/Mobs/Aliens/Guardians/guardians.rsi + layers: + - state: magic_base + map: [ "enum.DamageStateVisualLayers.Base" ] + - state: magic_flare + map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ] + color: "#40a7d7" + shader: unshaded + - type: RandomSprite + available: + - enum.DamageStateVisualLayers.BaseUnshaded: + magic_flare: Sixteen + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.35 + density: 225 + mask: + - FlyingMobMask + layer: + - FlyingMobLayer + - type: Bloodstream + bloodReagent: Phlogiston + - type: MeleeWeapon + damage: + groups: + Burn: 10 + - type: MobState + - type: MobThresholds + thresholds: + 0: Alive + 120: Dead + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 120 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: Access + tags: + - Chapel + - Research + - type: MindContainer + showExamineInfo: true + - type: Familiar + - type: Dispellable + - type: Damageable + damageContainer: CorporealSpirit + damageModifierSet: CorporealSpirit + - type: Speech + speechSounds: Bass + - type: Puller + needsHands: false + - type: Tag + tags: + - CannotSuicide + - DoorBumpOpener + - type: Body + prototype: Primate + - type: InnateTool + tools: + - id: WelderIfrit + - id: WelderIfrit + - type: Hands + - type: RandomMetadata + nameSegments: [names_golem] + - type: PotentialPsionic + - type: Psionic + removable: false + - type: PyrokinesisPower + - type: Grammar + attributes: + proper: true + - type: MovementIgnoreGravity + - type: EyeProtection + - type: NpcFactionMember + factions: + - PsionicInterloper + - NanoTrasen + +- type: entity + parent: WelderExperimental + id: WelderIfrit + name: рука ифрита + description: Пылающая рука ифрита + components: + - type: Sprite + sprite: Backmen/Objects/Tools/ifrit_hand.rsi + - type: Item + sprite: Backmen/Objects/Tools/ifrit_hand.rsi + - type: SolutionContainerManager + solutions: + Welder: + reagents: + - ReagentId: Phlogiston + Quantity: 1000 + maxVol: 1000 + - type: Welder + fuelReagent: Phlogiston + tankSafe: true + litMeleeDamageBonus: + types: # When lit, negate standard melee damage and replace with heat + Heat: 10 + Blunt: -10 + welderOnSounds: + collection: WelderIfritHandOn + welderOffSounds: + collection: WelderIfritHandOff + - type: Tool + useSound: + collection: WelderIfritHand + - type: MeleeWeapon + soundHit: + collection: WelderIfritHand + # TODO some way to give the MeleeWeapon a separate soundHit for when it's on, + # similar to EnergySword. will have to go in ToolSystem.Welder.cs + # for now, just make it sound like it's on fire no matter what because it + # sounds cooler than the default fizzling noise. + - type: SolutionRegeneration + solution: Welder + generated: + reagents: + - ReagentId: Phlogiston + Quantity: 1 + - type: PointLight + enabled: false + radius: 2 + energy: 1.5 + color: orange diff --git a/Resources/Prototypes/Backmen/Entities/Mobs/NPC/mutants.yml b/Resources/Prototypes/Backmen/Entities/Mobs/NPC/mutants.yml index f19a9dca755..4242eedb5d8 100644 --- a/Resources/Prototypes/Backmen/Entities/Mobs/NPC/mutants.yml +++ b/Resources/Prototypes/Backmen/Entities/Mobs/NPC/mutants.yml @@ -71,11 +71,20 @@ - type: NoSlip - type: entity - name: oneirophage + name: Чёрная вдова parent: SimpleMobBase id: MobGiantSpiderVampire description: The 'dream-eater' spider, rumored to be one of the potential genetic sources for arachne. components: + - type: PotentialPsionic + - type: Psionic + removable: false + - type: MetapsionicPower + - type: AntiPsionicWeapon + punish: false + modifiers: + coefficients: + Piercing: 2.25 - type: Sprite drawdepth: Mobs layers: @@ -168,10 +177,16 @@ - ForcedSleep - TemporaryBlindness - Pacified + - PsionicsDisabled + - PsionicallyInsulated - type: Tag tags: - Oneirophage - type: MovementAlwaysTouching + - type: PsionicInvisibleContacts + whitelist: + tags: + - ArachneWeb - type: entity name: oneirophage @@ -340,3 +355,4 @@ - type: ActiveRadio channels: - Common + diff --git a/Resources/Prototypes/Backmen/Entities/Mobs/Player/arachne.yml b/Resources/Prototypes/Backmen/Entities/Mobs/Player/arachne.yml index 19a9b82975f..7bd65616358 100644 --- a/Resources/Prototypes/Backmen/Entities/Mobs/Player/arachne.yml +++ b/Resources/Prototypes/Backmen/Entities/Mobs/Player/arachne.yml @@ -33,3 +33,4 @@ - type: NpcFactionMember factions: - NanoTrasen + - type: PotentialPsionic diff --git a/Resources/Prototypes/Backmen/Entities/Mobs/Player/felinid.yml b/Resources/Prototypes/Backmen/Entities/Mobs/Player/felinid.yml index 4a489d49871..4b1ab474dbf 100644 --- a/Resources/Prototypes/Backmen/Entities/Mobs/Player/felinid.yml +++ b/Resources/Prototypes/Backmen/Entities/Mobs/Player/felinid.yml @@ -33,3 +33,4 @@ interactSuccessString: hugging-success-generic interactSuccessSound: /Audio/Effects/thudswoosh.ogg messagePerceivedByOthers: hugging-success-generic-others + - type: PotentialPsionic diff --git a/Resources/Prototypes/Backmen/Entities/Mobs/Player/special.yml b/Resources/Prototypes/Backmen/Entities/Mobs/Player/special.yml new file mode 100644 index 00000000000..1fa58fea3c4 --- /dev/null +++ b/Resources/Prototypes/Backmen/Entities/Mobs/Player/special.yml @@ -0,0 +1,50 @@ +- type: entity + id: MobObserverTelegnostic + name: Телегностическая проекция + description: Зловещий. Спрайт-заполнитель. + noSpawn: true + components: + - type: Sprite + overrideContainerOcclusion: true # Ghosts always show up regardless of where they're contained. + noRot: true + drawdepth: Ghosts + sprite: Objects/Consumable/Food/bowl.rsi + state: eyeball + color: "#90EE90" + layers: + - state: eyeball + shader: unshaded + - type: Psionic + - type: MindContainer + - type: Clickable + - type: InteractionOutline + - type: Physics + bodyType: KinematicController + fixedRotation: true + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.35 + density: 13 + mask: + - GhostImpassable + - type: MovementSpeedModifier + baseSprintSpeed: 8 + baseWalkSpeed: 5 + - type: MovementIgnoreGravity + - type: PsionicallyInvisible + - type: InputMover + - type: Appearance + - type: Eye + drawFov: false + - type: Input + context: "ghost" + - type: Examiner + - type: TelegnosticProjection + - type: Stealth + lastVisibility: 0.66 + - type: Visibility + layer: 4 + - type: NoNormalInteraction diff --git a/Resources/Prototypes/Backmen/Entities/Mobs/Species/arachne.yml b/Resources/Prototypes/Backmen/Entities/Mobs/Species/arachne.yml index b986c123aa6..99f911b35ad 100644 --- a/Resources/Prototypes/Backmen/Entities/Mobs/Species/arachne.yml +++ b/Resources/Prototypes/Backmen/Entities/Mobs/Species/arachne.yml @@ -100,11 +100,8 @@ prototype: ArachneClassic requiredLegs: 8 - type: Damageable - damageContainer: Biological - #damageModifierSet: ArachneClassic TODO! -# - type: DiseaseCarrier -# naturalImmunities: -# - BleedersBite + damageContainer: HalfSpirit + damageModifierSet: HalfSpirit - type: Speech speechSounds: Alto - type: DamageOnHighSpeedImpact @@ -138,7 +135,7 @@ - type: Barotrauma damage: types: - Blunt: 0.05 #per second, scales with pressure and other constants. Reduced Damage. This allows medicine to heal faster than damage. + Blunt: 0.03 #per second, scales with pressure and other constants. Reduced Damage. This allows medicine to heal faster than damage. - type: MovementAlwaysTouching - type: MovementSpeedModifier baseWalkSpeed : 3.0 diff --git a/Resources/Prototypes/Backmen/Entities/Objects/Weapons/Melee/knives.yml b/Resources/Prototypes/Backmen/Entities/Objects/Weapons/Melee/knives.yml new file mode 100644 index 00000000000..c5bc65ba400 --- /dev/null +++ b/Resources/Prototypes/Backmen/Entities/Objects/Weapons/Melee/knives.yml @@ -0,0 +1,31 @@ +- type: entity + parent: CombatKnife + id: AntiPsychicKnife + name: Анти-психический нож + suffix: Псионика + description: Специальный нож, предназначенный для убийства экстрасенсов. + components: + - type: MeleeWeapon + attackRate: 1.5 + damage: + types: + Slash: 10 + Holy: 5 + - type: StaminaDamageOnHit + damage: 0 + - type: AntiPsionicWeapon + modifiers: + coefficients: + Blunt: 1.2 + Slash: 1.2 + Piercing: 1.2 + Holy: 1.2 + - type: Sprite + sprite: Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi + state: icon + - type: Item + sprite: Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi + - type: Tag + tags: + - ForensicBeltEquip + - HighRiskItem diff --git a/Resources/Prototypes/Backmen/GameRules/events.yml b/Resources/Prototypes/Backmen/GameRules/events.yml index fe0b6194704..1ee035e6e0d 100644 --- a/Resources/Prototypes/Backmen/GameRules/events.yml +++ b/Resources/Prototypes/Backmen/GameRules/events.yml @@ -15,3 +15,71 @@ noSpawn: true components: - type: WageSchedulerRule + +## Regular station events +- type: entity + id: NoosphericStorm + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + startAnnouncement: station-event-noospheric-storm-announcement + weight: 5 + earliestStart: 15 + - type: NoosphericStormRule + +# Base glimmer event +- type: entity + id: BaseGlimmerEvent + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + # Favor glimmer events just a little more than regular events. + weight: 12 + - type: GlimmerEvent + +## Glimmer events +- type: entity + id: NoosphericZap + parent: BaseGlimmerEvent + noSpawn: true + components: + - type: GlimmerEvent + - type: NoosphericZapRule + +- type: entity + id: NoosphericFry + parent: BaseGlimmerEvent + noSpawn: true + components: + - type: GlimmerEvent + minimumGlimmer: 300 + maximumGlimmer: 900 + - type: NoosphericFryRule + + +- type: entity + id: PsionicCatGotYourTongue + parent: BaseGlimmerEvent + noSpawn: true + components: + - type: GlimmerEvent + minimumGlimmer: 200 + maximumGlimmer: 500 + glimmerBurnLower: 18 + glimmerBurnUpper: 40 + - type: PsionicCatGotYourTongueRule + +- type: entity + id: MassMindSwap + parent: BaseGameRule + noSpawn: true + components: + - type: GlimmerEvent + minimumGlimmer: 900 + glimmerBurnLower: 50 + glimmerBurnUpper: 110 + - type: MassMindSwapRule + + diff --git a/Resources/Prototypes/Backmen/Psionics/psionicsPowers.yml b/Resources/Prototypes/Backmen/Psionics/psionicsPowers.yml new file mode 100644 index 00000000000..612cb5298e7 --- /dev/null +++ b/Resources/Prototypes/Backmen/Psionics/psionicsPowers.yml @@ -0,0 +1,10 @@ +- type: weightedRandom + id: RandomPsionicPowerPool + weights: + MetapsionicPower: 1 + DispelPower: 1 + TelegnosisPower: 1 + PsionicRegenerationPower: 1 + MassSleepPower: 0.3 + PsionicInvisibilityPower: 0.15 + MindSwapPower: 0.15 diff --git a/Resources/Prototypes/Backmen/Psionics/status_effects.yml b/Resources/Prototypes/Backmen/Psionics/status_effects.yml new file mode 100644 index 00000000000..f536425e85f --- /dev/null +++ b/Resources/Prototypes/Backmen/Psionics/status_effects.yml @@ -0,0 +1,5 @@ +- type: statusEffect + id: PsionicsDisabled + +- type: statusEffect + id: PsionicallyInsulated diff --git a/Resources/Prototypes/Backmen/Reagents/psionic.yml b/Resources/Prototypes/Backmen/Reagents/psionic.yml new file mode 100644 index 00000000000..d0169c969fe --- /dev/null +++ b/Resources/Prototypes/Backmen/Reagents/psionic.yml @@ -0,0 +1,38 @@ +- type: reagent + id: PsionicRegenerationEssence + name: reagent-name-prometheum + group: Biological + desc: reagent-desc-prometheum + flavor: metallic + color: "#700055" + physicalDesc: reagent-physical-desc-viscous + metabolisms: + Medicine: + effects: + # These messages would be more appropriate if there was a LoS-based Filter type. TODO + # - !type:PopupMessage + # type: Pvs + # visualType: Small + # messages: [ + # "psionic-regeneration-essence-sweat", + # "psionic-regeneration-essence-veins", + # "psionic-regeneration-essence-breath" + # ] + # probability: 0.15 + - !type:SatiateThirst + factor: -3 + - !type:SatiateHunger + factor: -3 + - !type:ModifyBleedAmount + amount: -1 + - !type:ModifyBloodLevel + amount: 2 + - !type:HealthChange + damage: + groups: + Burn: -5 + Brute: -4 + types: + Caustic: -3 + Poison: -3 + Bloodloss: -3 diff --git a/Resources/Prototypes/Backmen/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Backmen/Recipes/Lathes/electronics.yml index b2168d422b1..7f358714f32 100644 --- a/Resources/Prototypes/Backmen/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Backmen/Recipes/Lathes/electronics.yml @@ -5,3 +5,13 @@ materials: Steel: 100 Glass: 900 + +- type: latheRecipe + id: AntiPsychicKnifeRecipe + result: AntiPsychicKnife + completetime: 4 + materials: + Steel: 300 + Glass: 300 + Plastic: 200 + diff --git a/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/cargotech.yml b/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/cargotech.yml index b6c013a0e99..990f55377d4 100644 --- a/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/cargotech.yml +++ b/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/cargotech.yml @@ -31,6 +31,8 @@ components: - type: SpecForce actionName: CentcomFtlAction + - type: PsionicBonusChance + multiplier: 3 minBankBalance: 400 maxBankBalance: 600 wageDepartment: CentCom diff --git a/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/centcom_admiral.yml b/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/centcom_admiral.yml index 999dbf3d0b3..61971bd88c9 100644 --- a/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/centcom_admiral.yml +++ b/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/centcom_admiral.yml @@ -30,11 +30,14 @@ components: - type: SpecForce actionName: CentcomFtlAction + - type: Psionic + - type: DispelPower minBankBalance: 800 maxBankBalance: 1200 wageDepartment: CentCom wage: 200 + - type: entity parent: CentcomPDABackmen id: CCAdmiralPDA diff --git a/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/centcom_official.yml b/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/centcom_official.yml index 3add069de75..b1067d06df9 100644 --- a/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/centcom_official.yml +++ b/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/centcom_official.yml @@ -34,6 +34,8 @@ components: - type: SpecForce actionName: CentcomFtlAction + - type: Psionic + - type: DispelPower minBankBalance: 800 maxBankBalance: 1200 wageDepartment: CentCom diff --git a/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/head_of_security.yml b/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/head_of_security.yml index 004ab00331d..d47ca6e781f 100644 --- a/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/head_of_security.yml +++ b/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/head_of_security.yml @@ -37,6 +37,8 @@ components: - type: SpecForce actionName: CentcomFtlAction + - type: Psionic + - type: PyrokinesisPower minBankBalance: 800 maxBankBalance: 1200 wageDepartment: CentCom diff --git a/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/intern.yml b/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/intern.yml index c5c33e9020a..4f47738bcec 100644 --- a/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/intern.yml +++ b/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/intern.yml @@ -30,6 +30,8 @@ components: - type: SpecForce actionName: CentcomFtlAction + - type: PsionicBonusChance + multiplier: 3 minBankBalance: 200 maxBankBalance: 500 wageDepartment: CentCom diff --git a/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/operator.yml b/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/operator.yml index c1373a70cf7..c3447d36e81 100644 --- a/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/operator.yml +++ b/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/operator.yml @@ -34,6 +34,8 @@ components: - type: SpecForce actionName: CentcomFtlAction + - type: Psionic + - type: DispelPower minBankBalance: 800 maxBankBalance: 1200 wageDepartment: CentCom diff --git a/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/private_officer.yml b/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/private_officer.yml index 91bb706cf6d..01b45272cfc 100644 --- a/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/private_officer.yml +++ b/Resources/Prototypes/Backmen/Roles/Jobs/CentCom/private_officer.yml @@ -31,6 +31,8 @@ components: - type: SpecForce actionName: CentcomFtlAction + - type: PsionicBonusChance + multiplier: 3 minBankBalance: 200 maxBankBalance: 500 wageDepartment: CentCom diff --git a/Resources/Prototypes/Backmen/SoundCollections/glimmer_wisp.yml b/Resources/Prototypes/Backmen/SoundCollections/glimmer_wisp.yml new file mode 100644 index 00000000000..b082d374a94 --- /dev/null +++ b/Resources/Prototypes/Backmen/SoundCollections/glimmer_wisp.yml @@ -0,0 +1,6 @@ +- type: soundCollection + id: MagicMissile + files: + - /Audio/Nyanotrasen/Mobs/GlimmerWisp/magic_missile_1.ogg + - /Audio/Nyanotrasen/Mobs/GlimmerWisp/magic_missile_2.ogg + - /Audio/Nyanotrasen/Mobs/GlimmerWisp/magic_missile_3.ogg diff --git a/Resources/Prototypes/Backmen/SoundCollections/ifrit_hand.yml b/Resources/Prototypes/Backmen/SoundCollections/ifrit_hand.yml new file mode 100644 index 00000000000..c7a74e9a667 --- /dev/null +++ b/Resources/Prototypes/Backmen/SoundCollections/ifrit_hand.yml @@ -0,0 +1,16 @@ +- type: soundCollection + id: WelderIfritHandOn + files: + - /Audio/Nyanotrasen/fireball1.ogg + +- type: soundCollection + id: WelderIfritHandOff + files: + - /Audio/Nyanotrasen/blowout1.ogg + +- type: soundCollection + id: WelderIfritHand + files: + - /Audio/Nyanotrasen/flamethrower1.ogg + - /Audio/Nyanotrasen/flamethrower2.ogg + - /Audio/Nyanotrasen/flamethrower3.ogg diff --git a/Resources/Prototypes/Backmen/tags.yml b/Resources/Prototypes/Backmen/tags.yml index 52b64299843..f67db24c0ec 100644 --- a/Resources/Prototypes/Backmen/tags.yml +++ b/Resources/Prototypes/Backmen/tags.yml @@ -6,3 +6,6 @@ - type: Tag id: Oneirophage + +- type: Tag + id: ForensicBeltEquip diff --git a/Resources/Prototypes/Body/Prototypes/primate.yml b/Resources/Prototypes/Body/Prototypes/primate.yml index 2af9273be4c..475c69c454a 100644 --- a/Resources/Prototypes/Body/Prototypes/primate.yml +++ b/Resources/Prototypes/Body/Prototypes/primate.yml @@ -6,7 +6,8 @@ torso: part: TorsoAnimal connections: - - hands + - hands 1 + - hands 2 - legs organs: lungs: OrganAnimalLungs @@ -14,7 +15,9 @@ liver: OrganAnimalLiver heart: OrganAnimalHeart kidneys: OrganAnimalKidneys - hands: + hands 1: + part: HandsAnimal + hands 2: part: HandsAnimal legs: part: LegsAnimal diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index 480cc20d9fd..f8292a9379d 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -23,6 +23,7 @@ - id: RubberStampQm - id: ClothingHeadsetAltCargo - id: BoxEncryptionKeyCargo + - id: AntiPsychicKnife - type: entity id: LockerCaptainFilledHardsuit @@ -57,6 +58,7 @@ - id: WeaponAntiqueLaser - id: JetpackCaptainFilled - id: MedalCase + - id: AntiPsychicKnife - type: entity id: LockerCaptainFilled @@ -96,6 +98,7 @@ - id: ClothingHeadCaptainHat - id: ClothingOuterCoatCaptain # Corvax-Resprite-End + - id: AntiPsychicKnife - type: entity id: LockerHeadOfPersonnelFilled @@ -129,6 +132,7 @@ prob: 0.5 - id: ClothingOuterCoatHOP # Corvax-Resprite - id: AccessConfigurator + - id: AntiPsychicKnife - type: entity id: LockerChiefEngineerFilledHardsuit @@ -157,6 +161,7 @@ - id: ClothingHeadsetAltEngineering - id: BoxEncryptionKeyEngineering - id: AccessConfigurator + - id: AntiPsychicKnife - type: entity id: LockerChiefEngineerFilled @@ -181,6 +186,7 @@ - id: ClothingHeadsetAltEngineering - id: BoxEncryptionKeyEngineering - id: AccessConfigurator + - id: AntiPsychicKnife - type: entity id: LockerChiefMedicalOfficerFilledHardsuit @@ -205,6 +211,7 @@ - id: RubberStampCMO - id: MedicalTechFabCircuitboard - id: BoxEncryptionKeyMedical + - id: AntiPsychicKnife - type: entity id: LockerChiefMedicalOfficerFilled @@ -214,7 +221,7 @@ - type: StorageFill contents: - id: Eftpos # backmen - amount: 3 # backmen + amount: 3 # backmen - id: MedkitFilled - id: ClothingHandsGlovesNitrile #- name: ClothingEyesHudMedical #Removed until working properly @@ -230,6 +237,7 @@ - id: RubberStampCMO - id: MedicalTechFabCircuitboard - id: BoxEncryptionKeyMedical + - id: AntiPsychicKnife - type: entity id: LockerResearchDirectorFilledHardsuit @@ -252,6 +260,7 @@ - id: RubberStampRd - id: ClothingHeadsetAltScience - id: BoxEncryptionKeyScience + - id: AntiPsychicKnife - type: entity id: LockerResearchDirectorFilled @@ -275,6 +284,7 @@ - id: RubberStampRd - id: ClothingHeadsetAltScience - id: BoxEncryptionKeyScience + - id: AntiPsychicKnife - type: entity id: LockerHeadOfSecurityFilledHardsuit @@ -316,6 +326,7 @@ - id: HoloprojectorSecurity - id: BookSecretDocuments - id: BookBusido # Corvax-Books + - id: AntiPsychicKnife - type: entity id: LockerHeadOfSecurityFilled @@ -361,3 +372,4 @@ - id: BoxEncryptionKeySecurity - id: HoloprojectorSecurity - id: BookSecretDocuments + - id: AntiPsychicKnife diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/medical.yml b/Resources/Prototypes/Catalog/Fills/Lockers/medical.yml index 705b0112196..4ec7055d96f 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/medical.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/medical.yml @@ -68,6 +68,7 @@ prob: 0.05 orGroup: Surgshrubs - id: ClothingMaskSterile + - id: AntiPsychicKnife - type: entity parent: LockerWallMedical diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/science.yml b/Resources/Prototypes/Catalog/Fills/Lockers/science.yml index 4f09061f9a6..d7df92e0d20 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/science.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/science.yml @@ -13,3 +13,5 @@ - id: NodeScanner - id: NetworkConfigurator prob: 0.5 + - id: AntiPsychicKnife + amount: 2 diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/security.yml b/Resources/Prototypes/Catalog/Fills/Lockers/security.yml index 0917f393440..76a11e2953c 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/security.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/security.yml @@ -23,6 +23,8 @@ - id: ClothingOuterHardsuitWarden - id: HoloprojectorSecurity - id: ClothingEyesHudSecurity + - id: AntiPsychicKnife + amount: 3 - type: entity id: LockerWardenFilled @@ -50,6 +52,8 @@ - id: DoorRemoteArmory - id: HoloprojectorSecurity - id: ClothingEyesHudSecurity + - id: AntiPsychicKnife + amount: 3 - type: entity id: LockerSecurityFilled @@ -77,6 +81,8 @@ - id: ClothingOuterCoatSecurityOvercoat # Corvax-SecFashion prob: 0.2 - id: ClothingEyesHudSecurity + - id: AntiPsychicKnife + amount: 3 - type: entity id: LockerBrigmedicFilled @@ -117,6 +123,8 @@ prob: 0.5 - id: ClothingOuterCoatLabSecurityMedic # Corvax-Resprite prob: 0.5 + - id: AntiPsychicKnife + amount: 3 - type: entity id: LockerDetectiveFilled @@ -137,6 +145,8 @@ - id: DrinkDetFlask - id: ClothingHandsGlovesForensic - id: BookBusido # Corvax-Books + - id: AntiPsychicKnife + amount: 3 - type: entity id: ClosetBombFilled @@ -158,6 +168,8 @@ contents: - id: WeaponDisabler amount: 5 + - id: AntiPsychicKnife + amount: 3 - type: entity parent: GunSafe diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml index 9fec38d9de9..3470c3d8deb 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml @@ -5,7 +5,7 @@ components: - type: StorageFill contents: - - id: Eftpos # backmen + - id: Eftpos # backmen - id: ClothingOuterArmorBasicSlim - id: WeaponShotgunDoubleBarreledRubber - id: DrinkShaker @@ -50,6 +50,7 @@ - id: ReagentContainerMilk amount: 2 - id: ReagentContainerMilkSoy + - id: AntiPsychicKnife - type: entity id: ClosetJanitorFilled @@ -75,6 +76,7 @@ amount: 2 - id: Plunger amount: 2 + - id: AntiPsychicKnife - type: entity id: ClosetLegalFilled @@ -109,6 +111,7 @@ - id: ClothingUniformOveralls - id: ClothingHeadHatTrucker prob: 0.1 + - id: AntiPsychicKnife - type: entity id: LockerBotanistLoot diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml b/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml index 9b505654c99..c09387f4a65 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml @@ -15,6 +15,12 @@ name: medical hud description: A heads-up display that scans the humanoids in view and provides accurate data about their health status. components: + - type: ClothingGrant + component: + - type: ShowHealthBars + damageContainers: + - Biological + - HalfSpirit - type: Sprite sprite: Clothing/Eyes/Hud/med.rsi - type: Clothing diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index f7f14ae35fd..fff1ef978d1 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -125,6 +125,7 @@ - type: GuideHelp guides: - MinorAntagonists + - type: PotentialPsionic - type: entity id: MobRatKingBuff diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 3eaee32998e..4e063993a39 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -113,6 +113,10 @@ molsPerSecondPerUnitMass: 0.0005 - type: Speech speechVerb: LargeMob + - type: PotentialPsionic + chance: -2 + - type: Psionic + removable: false - type: entity name: Praetorian diff --git a/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml index 94b240e5011..e91e6e7fec1 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml @@ -2,4 +2,6 @@ save: false name: Urist McHands The Dwarf parent: [BaseMobDwarf, BaseMob] - id: MobDwarf \ No newline at end of file + id: MobDwarf + components: + - type: PotentialPsionic diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml index 8792151dfd4..9e5fe1da9b4 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml @@ -3,6 +3,8 @@ name: Urist McHands parent: [BaseMobHuman, BaseMob] id: MobHuman + components: + - type: PotentialPsionic #Syndie - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/Player/moth.yml b/Resources/Prototypes/Entities/Mobs/Player/moth.yml index 0a64385419d..78d56e83bfb 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/moth.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/moth.yml @@ -2,4 +2,6 @@ save: false name: Urist McFluff parent: [BaseMobMoth, BaseMob] - id: MobMoth \ No newline at end of file + id: MobMoth + components: + - type: PotentialPsionic diff --git a/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml index d3cbe11b494..750b023d51f 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml @@ -3,5 +3,6 @@ name: Urisst' Mzhand parent: [BaseMobReptilian, BaseMob] id: MobReptilian - + components: + - type: PotentialPsionic #Weh diff --git a/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml b/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml index 82fff045d91..ba2c311e361 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml @@ -23,6 +23,7 @@ - type: NpcFactionMember factions: - NanoTrasen + - type: PotentialPsionic - type: entity name: Skeleton Pirate diff --git a/Resources/Prototypes/Entities/Mobs/Player/slime.yml b/Resources/Prototypes/Entities/Mobs/Player/slime.yml index 6763a7dbb2f..e2f103562bf 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/slime.yml @@ -1,4 +1,6 @@ - type: entity save: false parent: [BaseMobSlimePerson, BaseMob] - id: MobSlimePerson \ No newline at end of file + id: MobSlimePerson + components: + - type: PotentialPsionic diff --git a/Resources/Prototypes/Entities/Mobs/Player/vox.yml b/Resources/Prototypes/Entities/Mobs/Player/vox.yml index 1e1883b8f76..a6a1ffef0c7 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/vox.yml @@ -3,3 +3,5 @@ name: Vox parent: [BaseMobVox, BaseMob] id: MobVox + components: + - type: PotentialPsionic diff --git a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml index 0328a7b1498..2c741205a1e 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml @@ -9,8 +9,9 @@ - type: Bible damage: groups: - Brute: -15 - Burn: -15 + Brute: -35 + Burn: -35 + Immaterial: -10 damageOnFail: groups: Brute: 15 diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 681d733246c..69c93cce693 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -132,6 +132,7 @@ - CellRechargerCircuitboard - BorgChargerCircuitboard - WeaponCapacitorRechargerCircuitboard + - AntiPsychicKnifeRecipe - type: EmagLatheRecipes emagStaticRecipes: - CartridgePistol diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml index 3342444759d..87f98865f36 100644 --- a/Resources/Prototypes/Reagents/medicine.yml +++ b/Resources/Prototypes/Reagents/medicine.yml @@ -776,6 +776,7 @@ Toxin: -2 Airloss: -2 Brute: -2 + Immaterial: -2 - type: reagent id: Ultravasculine diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml index ebb187121b8..5e209ae2e17 100644 --- a/Resources/Prototypes/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml @@ -677,4 +677,4 @@ materials: Steel: 100 Glass: 900 - Gold: 100 + Gold: 100 diff --git a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml index 92ec4064519..96e3b4ad59a 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml @@ -49,6 +49,11 @@ - Cargo - Atmospherics - Medical + special: + - !type:AddComponentSpecial + components: + - type: PsionicBonusChance + flatBonus: 0.025 #start-backmen: currency minBankBalance: 800 maxBankBalance: 1200 diff --git a/Resources/Prototypes/ai_factions.yml b/Resources/Prototypes/ai_factions.yml index c6cdc1923da..eee37e622f8 100644 --- a/Resources/Prototypes/ai_factions.yml +++ b/Resources/Prototypes/ai_factions.yml @@ -23,7 +23,7 @@ #- FleshHuman #- Flesh #- MalfunctioningRobot - + - type: npcFaction id: NanoTrasen hostile: @@ -93,3 +93,13 @@ - Passive - PetsNT - Blob + +- type: npcFaction + id: GlimmerMonster # Crew hostile psionic + hostile: + - PsionicInterloper + +- type: npcFaction + id: PsionicInterloper # Crew aligned psionic + hostile: + - GlimmerMonster diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/dispel.png b/Resources/Textures/Backmen/Interface/VerbIcons/dispel.png new file mode 100644 index 00000000000..e2df7950ae0 Binary files /dev/null and b/Resources/Textures/Backmen/Interface/VerbIcons/dispel.png differ diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/dispel.png.yml b/Resources/Textures/Backmen/Interface/VerbIcons/dispel.png.yml new file mode 100644 index 00000000000..5c43e233050 --- /dev/null +++ b/Resources/Textures/Backmen/Interface/VerbIcons/dispel.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/license.txt b/Resources/Textures/Backmen/Interface/VerbIcons/license.txt new file mode 100644 index 00000000000..ad1d595d9bd --- /dev/null +++ b/Resources/Textures/Backmen/Interface/VerbIcons/license.txt @@ -0,0 +1,13 @@ +The eleven files below are licensed under CC-BY-SA 4.0 by the author who can be found on GitHub @Vordenburg. + +dispel.png +mass_sleep.png +metapsionic.png +mind_swap.png +mind_swap_return.png +noospheric_zap.png +psionic_invisibility.png +psionic_invisibility_off.png +psionic_regeneration.png +pyrokinesis.png +telegnosis.png diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/mass_sleep.png b/Resources/Textures/Backmen/Interface/VerbIcons/mass_sleep.png new file mode 100644 index 00000000000..f1d41b86f29 Binary files /dev/null and b/Resources/Textures/Backmen/Interface/VerbIcons/mass_sleep.png differ diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/mass_sleep.png.yml b/Resources/Textures/Backmen/Interface/VerbIcons/mass_sleep.png.yml new file mode 100644 index 00000000000..5c43e233050 --- /dev/null +++ b/Resources/Textures/Backmen/Interface/VerbIcons/mass_sleep.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/metapsionic.png b/Resources/Textures/Backmen/Interface/VerbIcons/metapsionic.png new file mode 100644 index 00000000000..8f917844b3c Binary files /dev/null and b/Resources/Textures/Backmen/Interface/VerbIcons/metapsionic.png differ diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/metapsionic.png.yml b/Resources/Textures/Backmen/Interface/VerbIcons/metapsionic.png.yml new file mode 100644 index 00000000000..5c43e233050 --- /dev/null +++ b/Resources/Textures/Backmen/Interface/VerbIcons/metapsionic.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/mind_swap.png b/Resources/Textures/Backmen/Interface/VerbIcons/mind_swap.png new file mode 100644 index 00000000000..9be92bcc1c3 Binary files /dev/null and b/Resources/Textures/Backmen/Interface/VerbIcons/mind_swap.png differ diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/mind_swap.png.yml b/Resources/Textures/Backmen/Interface/VerbIcons/mind_swap.png.yml new file mode 100644 index 00000000000..5c43e233050 --- /dev/null +++ b/Resources/Textures/Backmen/Interface/VerbIcons/mind_swap.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/mind_swap_return.png b/Resources/Textures/Backmen/Interface/VerbIcons/mind_swap_return.png new file mode 100644 index 00000000000..ef9019403e7 Binary files /dev/null and b/Resources/Textures/Backmen/Interface/VerbIcons/mind_swap_return.png differ diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/mind_swap_return.png.yml b/Resources/Textures/Backmen/Interface/VerbIcons/mind_swap_return.png.yml new file mode 100644 index 00000000000..5c43e233050 --- /dev/null +++ b/Resources/Textures/Backmen/Interface/VerbIcons/mind_swap_return.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/noospheric_zap.png b/Resources/Textures/Backmen/Interface/VerbIcons/noospheric_zap.png new file mode 100644 index 00000000000..408a7acc2af Binary files /dev/null and b/Resources/Textures/Backmen/Interface/VerbIcons/noospheric_zap.png differ diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/noospheric_zap.png.yml b/Resources/Textures/Backmen/Interface/VerbIcons/noospheric_zap.png.yml new file mode 100644 index 00000000000..5c43e233050 --- /dev/null +++ b/Resources/Textures/Backmen/Interface/VerbIcons/noospheric_zap.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/psionic_invisibility.png b/Resources/Textures/Backmen/Interface/VerbIcons/psionic_invisibility.png new file mode 100644 index 00000000000..788381ea0a2 Binary files /dev/null and b/Resources/Textures/Backmen/Interface/VerbIcons/psionic_invisibility.png differ diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/psionic_invisibility.png.yml b/Resources/Textures/Backmen/Interface/VerbIcons/psionic_invisibility.png.yml new file mode 100644 index 00000000000..5c43e233050 --- /dev/null +++ b/Resources/Textures/Backmen/Interface/VerbIcons/psionic_invisibility.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/psionic_invisibility_off.png b/Resources/Textures/Backmen/Interface/VerbIcons/psionic_invisibility_off.png new file mode 100644 index 00000000000..fd74888adc1 Binary files /dev/null and b/Resources/Textures/Backmen/Interface/VerbIcons/psionic_invisibility_off.png differ diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/psionic_invisibility_off.png.yml b/Resources/Textures/Backmen/Interface/VerbIcons/psionic_invisibility_off.png.yml new file mode 100644 index 00000000000..5c43e233050 --- /dev/null +++ b/Resources/Textures/Backmen/Interface/VerbIcons/psionic_invisibility_off.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/psionic_regeneration.png b/Resources/Textures/Backmen/Interface/VerbIcons/psionic_regeneration.png new file mode 100644 index 00000000000..38f389a0370 Binary files /dev/null and b/Resources/Textures/Backmen/Interface/VerbIcons/psionic_regeneration.png differ diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/psionic_regeneration.png.yml b/Resources/Textures/Backmen/Interface/VerbIcons/psionic_regeneration.png.yml new file mode 100644 index 00000000000..5c43e233050 --- /dev/null +++ b/Resources/Textures/Backmen/Interface/VerbIcons/psionic_regeneration.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/pyrokinesis.png b/Resources/Textures/Backmen/Interface/VerbIcons/pyrokinesis.png new file mode 100644 index 00000000000..beeac197e71 Binary files /dev/null and b/Resources/Textures/Backmen/Interface/VerbIcons/pyrokinesis.png differ diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/pyrokinesis.png.yml b/Resources/Textures/Backmen/Interface/VerbIcons/pyrokinesis.png.yml new file mode 100644 index 00000000000..5c43e233050 --- /dev/null +++ b/Resources/Textures/Backmen/Interface/VerbIcons/pyrokinesis.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/telegnosis.png b/Resources/Textures/Backmen/Interface/VerbIcons/telegnosis.png new file mode 100644 index 00000000000..6fc1a2e3dde Binary files /dev/null and b/Resources/Textures/Backmen/Interface/VerbIcons/telegnosis.png differ diff --git a/Resources/Textures/Backmen/Interface/VerbIcons/telegnosis.png.yml b/Resources/Textures/Backmen/Interface/VerbIcons/telegnosis.png.yml new file mode 100644 index 00000000000..5c43e233050 --- /dev/null +++ b/Resources/Textures/Backmen/Interface/VerbIcons/telegnosis.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/magic.png b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/magic.png new file mode 100644 index 00000000000..245d9d35c2e Binary files /dev/null and b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/magic.png differ diff --git a/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/magic_base.png b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/magic_base.png new file mode 100644 index 00000000000..cca7a46c34a Binary files /dev/null and b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/magic_base.png differ diff --git a/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/magic_flare.png b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/magic_flare.png new file mode 100644 index 00000000000..3639229c4fe Binary files /dev/null and b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/magic_flare.png differ diff --git a/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/meta.json b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/meta.json new file mode 100644 index 00000000000..b8d7bcd6c9f --- /dev/null +++ b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/meta.json @@ -0,0 +1,69 @@ +{ + "version": 1, + "license": "CC-BY-NC-SA-3.0", + "copyright": "taken from /tg/ station at commit https://github.com/tgstation/tgstation/commit/02756c2bc2cf3000080d030955e994242bab39b5", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "magic", + "directions": 4 + }, + { + "name": "tech", + "directions": 4 + }, + { + "name": "miner", + "directions": 4 + }, + { + "name": "magic_flare", + "directions": 4 + }, + { + "name": "magic_base", + "directions": 4 + }, + { + "name": "miner_flare", + "directions": 4 + }, + { + "name": "miner_base", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "tech_flare", + "directions": 4 + }, + { + "name": "tech_base", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/miner.png b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/miner.png new file mode 100644 index 00000000000..f4dd065628c Binary files /dev/null and b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/miner.png differ diff --git a/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/miner_base.png b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/miner_base.png new file mode 100644 index 00000000000..f22a027edc1 Binary files /dev/null and b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/miner_base.png differ diff --git a/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/miner_flare.png b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/miner_flare.png new file mode 100644 index 00000000000..73d1ba44d95 Binary files /dev/null and b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/miner_flare.png differ diff --git a/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/tech.png b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/tech.png new file mode 100644 index 00000000000..ada4a5ca142 Binary files /dev/null and b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/tech.png differ diff --git a/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/tech_base.png b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/tech_base.png new file mode 100644 index 00000000000..3a0a5972545 Binary files /dev/null and b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/tech_base.png differ diff --git a/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/tech_flare.png b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/tech_flare.png new file mode 100644 index 00000000000..6a38a65f70c Binary files /dev/null and b/Resources/Textures/Backmen/Mobs/Aliens/Guardians/guardians.rsi/tech_flare.png differ diff --git a/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/icon.png b/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/icon.png new file mode 100644 index 00000000000..701d239380d Binary files /dev/null and b/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/icon.png differ diff --git a/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/inhand-left-flame.png b/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/inhand-left-flame.png new file mode 100644 index 00000000000..fe057033b3e Binary files /dev/null and b/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/inhand-left-flame.png differ diff --git a/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/inhand-left.png b/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/inhand-left.png new file mode 100644 index 00000000000..bb5db873e8b Binary files /dev/null and b/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/inhand-left.png differ diff --git a/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/inhand-right-flame.png b/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/inhand-right-flame.png new file mode 100644 index 00000000000..ac69bbfec68 Binary files /dev/null and b/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/inhand-right-flame.png differ diff --git a/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/inhand-right.png b/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/inhand-right.png new file mode 100644 index 00000000000..bb5db873e8b Binary files /dev/null and b/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/inhand-right.png differ diff --git a/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/meta.json b/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/meta.json new file mode 100644 index 00000000000..5ebb110f132 --- /dev/null +++ b/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/meta.json @@ -0,0 +1,136 @@ +{ + "version": 1, + "license": "CC-BY-SA-4.0", + "copyright": "@Vordenburg", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "welder_flame", + "delays": [ + [ + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13 + ] + ] + }, + { + "name": "inhand-left" + }, + { + "name": "inhand-right" + }, + { + "name": "inhand-left-flame", + "directions": 4, + "delays": [ + [ + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13 + ], + [ + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13 + ], + [ + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13 + ], + [ + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13 + ] + ] + }, + { + "name": "inhand-right-flame", + "directions": 4, + "delays": [ + [ + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13 + ], + [ + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13 + ], + [ + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13 + ], + [ + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13 + ] + ] + } + ] +} diff --git a/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/welder_flame.png b/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/welder_flame.png new file mode 100644 index 00000000000..39b317ed76f Binary files /dev/null and b/Resources/Textures/Backmen/Objects/Tools/ifrit_hand.rsi/welder_flame.png differ diff --git a/Resources/Textures/Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi/equipped-BELT.png b/Resources/Textures/Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi/equipped-BELT.png new file mode 100644 index 00000000000..55a399a592b Binary files /dev/null and b/Resources/Textures/Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi/equipped-BELT.png differ diff --git a/Resources/Textures/Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi/icon.png b/Resources/Textures/Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi/icon.png new file mode 100644 index 00000000000..3c63e14e529 Binary files /dev/null and b/Resources/Textures/Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi/icon.png differ diff --git a/Resources/Textures/Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi/inhand-left.png b/Resources/Textures/Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi/inhand-left.png new file mode 100644 index 00000000000..f89a538c50a Binary files /dev/null and b/Resources/Textures/Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi/inhand-left.png differ diff --git a/Resources/Textures/Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi/inhand-right.png b/Resources/Textures/Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi/inhand-right.png new file mode 100644 index 00000000000..cdac70e7264 Binary files /dev/null and b/Resources/Textures/Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi/inhand-right.png differ diff --git a/Resources/Textures/Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi/meta.json b/Resources/Textures/Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi/meta.json new file mode 100644 index 00000000000..3571d2a653a --- /dev/null +++ b/Resources/Textures/Backmen/Objects/Weapons/Melee/anti_psychic_knife.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-4.0", + "copyright": "@Vordenburg", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-BELT", + "directions": 4 + } + ] +}