diff --git a/Content.Client/Nyanotrasen/Abilities/Psionics/TelegnosisPowerSystem.cs b/Content.Client/Nyanotrasen/Abilities/Psionics/TelegnosisPowerSystem.cs deleted file mode 100644 index 8ddc15347c..0000000000 --- a/Content.Client/Nyanotrasen/Abilities/Psionics/TelegnosisPowerSystem.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Content.Shared.Abilities.Psionics; - -namespace Content.Client.Abilities.Psionics; - -public sealed class TelegnosisPowerSystem : SharedTelegnosisPowerSystem; diff --git a/Content.Client/Nyanotrasen/Chat/PsionicChatUpdateSystem.cs b/Content.Client/Nyanotrasen/Chat/PsionicChatUpdateSystem.cs index 84602052fe..13a89a5e33 100644 --- a/Content.Client/Nyanotrasen/Chat/PsionicChatUpdateSystem.cs +++ b/Content.Client/Nyanotrasen/Chat/PsionicChatUpdateSystem.cs @@ -1,5 +1,5 @@ -using Content.Shared.Abilities.Psionics; using Content.Client.Chat.Managers; +using Content.Shared._DV.Psionics.Components; using Robust.Client.Player; namespace Content.Client.Nyanotrasen.Chat diff --git a/Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsEUI.cs b/Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsEUI.cs deleted file mode 100644 index 87d11a92ee..0000000000 --- a/Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsEUI.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Content.Client.Eui; -using Content.Shared.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/Nyanotrasen/Psionics/UI/AcceptPsionicsWindow.cs b/Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsWindow.cs deleted file mode 100644 index 883d9f0797..0000000000 --- a/Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsWindow.cs +++ /dev/null @@ -1,62 +0,0 @@ -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/_DV/Abilities/Psionics/FracturedFormPowerSystem.cs b/Content.Client/_DV/Abilities/Psionics/FracturedFormPowerSystem.cs deleted file mode 100644 index 0150a49df6..0000000000 --- a/Content.Client/_DV/Abilities/Psionics/FracturedFormPowerSystem.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Content.Shared._DV.Abilities.Psionics; - -namespace Content.Client._DV.Abilities.Psionics; - -public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem; diff --git a/Content.Client/_DV/Psionics/Systems/PsionicPowers/DispelPowerSystem.cs b/Content.Client/_DV/Psionics/Systems/PsionicPowers/DispelPowerSystem.cs new file mode 100644 index 0000000000..55ce55e57a --- /dev/null +++ b/Content.Client/_DV/Psionics/Systems/PsionicPowers/DispelPowerSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared._DV.Psionics.Systems.PsionicPowers; + +namespace Content.Client._DV.Psionics.Systems.PsionicPowers; + +/// +/// This solely exist for client-side prediction. +/// +public sealed class DispelPowerSystem : SharedDispelPowerSystem; diff --git a/Content.Client/_DV/Psionics/Systems/PsionicPowers/FracturedFormPowerSystem.cs b/Content.Client/_DV/Psionics/Systems/PsionicPowers/FracturedFormPowerSystem.cs new file mode 100644 index 0000000000..73959e2dc6 --- /dev/null +++ b/Content.Client/_DV/Psionics/Systems/PsionicPowers/FracturedFormPowerSystem.cs @@ -0,0 +1,6 @@ +using Content.Shared._DV.Psionics.Systems.PsionicPowers; + +namespace Content.Client._DV.Psionics.Systems.PsionicPowers; + +// This does nothing here. The code is all in the shared/server version. +public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem; diff --git a/Content.Client/_DV/Psionics/Systems/PsionicPowers/MassSleepPowerSystem.cs b/Content.Client/_DV/Psionics/Systems/PsionicPowers/MassSleepPowerSystem.cs new file mode 100644 index 0000000000..1f16a8ee40 --- /dev/null +++ b/Content.Client/_DV/Psionics/Systems/PsionicPowers/MassSleepPowerSystem.cs @@ -0,0 +1,15 @@ +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; + +namespace Content.Client._DV.Psionics.Systems.PsionicPowers; + +/// +/// This solely exists for prediction. +/// +public sealed class MassSleepPowerSystem : SharedMassSleepPowerSystem +{ + protected override void OnPowerUsed(Entity psionic, ref MassSleepPowerActionEvent args) + { + } +} diff --git a/Content.Client/_DV/Psionics/Systems/PsionicPowers/MindSwapPowerSystem.cs b/Content.Client/_DV/Psionics/Systems/PsionicPowers/MindSwapPowerSystem.cs new file mode 100644 index 0000000000..234d40a88c --- /dev/null +++ b/Content.Client/_DV/Psionics/Systems/PsionicPowers/MindSwapPowerSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared._DV.Psionics.Systems.PsionicPowers; + +namespace Content.Client._DV.Psionics.Systems.PsionicPowers; + +/// +/// This solely exists for prediction. +/// +public sealed class MindSwapPowerSystem : SharedMindSwapPowerSystem; diff --git a/Content.Client/_DV/Psionics/Systems/PsionicPowers/NoosphericZapPowerSystem.cs b/Content.Client/_DV/Psionics/Systems/PsionicPowers/NoosphericZapPowerSystem.cs new file mode 100644 index 0000000000..59ab44be7a --- /dev/null +++ b/Content.Client/_DV/Psionics/Systems/PsionicPowers/NoosphericZapPowerSystem.cs @@ -0,0 +1,15 @@ +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; + +namespace Content.Client._DV.Psionics.Systems.PsionicPowers; + +/// +/// This is solely for prediction. +/// +public sealed class NoosphericZapPowerSystem : SharedNoosphericZapPowerSystem +{ + protected override void OnPowerUsed(Entity psionic, ref NoosphericZapPowerActionEvent args) + { + } +} diff --git a/Content.Client/_DV/Psionics/Systems/PsionicPowers/PrecognitionPowerSystem.cs b/Content.Client/_DV/Psionics/Systems/PsionicPowers/PrecognitionPowerSystem.cs new file mode 100644 index 0000000000..bd19d6533a --- /dev/null +++ b/Content.Client/_DV/Psionics/Systems/PsionicPowers/PrecognitionPowerSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared._DV.Psionics.Systems.PsionicPowers; + +namespace Content.Client._DV.Psionics.Systems.PsionicPowers; + +/// +/// This solely exists for prediction. +/// +public sealed class PrecognitionPowerSystem : SharedPrecognitionPowerSystem; diff --git a/Content.Client/_DV/Psionics/Systems/PsionicPowers/PyrokinesisPowerSystem.cs b/Content.Client/_DV/Psionics/Systems/PsionicPowers/PyrokinesisPowerSystem.cs new file mode 100644 index 0000000000..7208968f7d --- /dev/null +++ b/Content.Client/_DV/Psionics/Systems/PsionicPowers/PyrokinesisPowerSystem.cs @@ -0,0 +1,14 @@ +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; + +namespace Content.Client._DV.Psionics.Systems.PsionicPowers; + +/// +/// This exists solely for prediction. +/// +public sealed class PyrokinesisPowerSystem : SharedPyrokinesisPowerSystem +{ + protected override void OnPowerUsed(Entity psionic, ref Shared._DV.Psionics.Events.PowerActionEvents.PyrokinesisPowerActionEvent args) + { + } +} diff --git a/Content.Client/_DV/Psionics/Systems/PsionicPowers/TelegnosisPowerSystem.cs b/Content.Client/_DV/Psionics/Systems/PsionicPowers/TelegnosisPowerSystem.cs new file mode 100644 index 0000000000..36644fe534 --- /dev/null +++ b/Content.Client/_DV/Psionics/Systems/PsionicPowers/TelegnosisPowerSystem.cs @@ -0,0 +1,16 @@ +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; + +namespace Content.Client._DV.Psionics.Systems.PsionicPowers; + +/// +/// This is here solely for predictive handling of using the power and not being able to. +/// It'll send popups this way. +/// +public sealed class TelegnosisPowerSystem : SharedTelegnosisPowerSystem +{ + protected override void OnPowerUsed(Entity psionic, ref TelegnosisPowerActionEvent args) + { + } +} diff --git a/Content.Client/_DV/Psionics/Systems/PsionicSystem.cs b/Content.Client/_DV/Psionics/Systems/PsionicSystem.cs new file mode 100644 index 0000000000..eadc50aba3 --- /dev/null +++ b/Content.Client/_DV/Psionics/Systems/PsionicSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared._DV.Psionics.Systems; + +namespace Content.Client._DV.Psionics.Systems; + +public sealed class PsionicSystem : SharedPsionicSystem; diff --git a/Content.Client/_DV/Psionics/UI/AcceptPsionicsEUI.cs b/Content.Client/_DV/Psionics/UI/AcceptPsionicsEUI.cs new file mode 100644 index 0000000000..ebe1e1b8dd --- /dev/null +++ b/Content.Client/_DV/Psionics/UI/AcceptPsionicsEUI.cs @@ -0,0 +1,36 @@ +using Content.Client.Eui; +using Content.Shared.Psionics; +using JetBrains.Annotations; +using Robust.Client.Graphics; + +namespace Content.Client._DV.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/_DV/Psionics/UI/AcceptPsionicsWindow.cs b/Content.Client/_DV/Psionics/UI/AcceptPsionicsWindow.cs new file mode 100644 index 0000000000..21941b6acc --- /dev/null +++ b/Content.Client/_DV/Psionics/UI/AcceptPsionicsWindow.cs @@ -0,0 +1,45 @@ +using System.Numerics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using static Robust.Client.UserInterface.Controls.BoxContainer; + +namespace Content.Client._DV.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.Server/Anomaly/AnomalySystem.Psionics.cs b/Content.Server/Anomaly/AnomalySystem.Psionics.cs deleted file mode 100644 index 8aaf3aac6b..0000000000 --- a/Content.Server/Anomaly/AnomalySystem.Psionics.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Content.Server.Abilities.Psionics; -using Content.Shared._DV.CosmicCult; -using Content.Shared.Anomaly; -using Content.Shared.Anomaly.Components; -using Robust.Shared.Random; - -namespace Content.Server.Anomaly; - -public sealed partial class AnomalySystem -{ - [Dependency] private readonly DispelPowerSystem _dispel = default!; - - private void InitializePsionics() - { - SubscribeLocalEvent(OnDispelled); - } - - //Nyano - Summary: gives dispellable behavior to Anomalies. - private void OnDispelled(Entity ent, ref DispelledEvent args) - { - if (HasComp(ent)) // begone nyanocode interference with cosmic cult - return; - _dispel.DealDispelDamage(ent); - ChangeAnomalyHealth(ent, 0 - _random.NextFloat(0.4f, 0.8f), ent.Comp); - args.Handled = true; - } -} diff --git a/Content.Server/Anomaly/AnomalySystem.cs b/Content.Server/Anomaly/AnomalySystem.cs index 7a39544178..4955b4e336 100644 --- a/Content.Server/Anomaly/AnomalySystem.cs +++ b/Content.Server/Anomaly/AnomalySystem.cs @@ -57,8 +57,6 @@ public sealed partial class AnomalySystem : SharedAnomalySystem InitializeGenerator(); InitializeVessel(); InitializeCommands(); - - InitializePsionics(); //Nyano - Summary: stats up psionic related behavior. } private void OnMapInit(Entity anomaly, ref MapInitEvent args) diff --git a/Content.Server/Cloning/CloningPodSystem.cs b/Content.Server/Cloning/CloningPodSystem.cs index 96c660b136..1b7f6ae457 100644 --- a/Content.Server/Cloning/CloningPodSystem.cs +++ b/Content.Server/Cloning/CloningPodSystem.cs @@ -7,7 +7,7 @@ using Content.Server.Fluids.EntitySystems; using Content.Server.Materials; using Content.Server.Popups; using Content.Server.Power.EntitySystems; -using Content.Server.Psionics; // DeltaV +using Content.Shared._DV.Psionics.Components; // DeltaV using Content.Shared._EE.Silicon.Components; // Goobstation using Content.Shared.Atmos; using Content.Shared.CCVar; diff --git a/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs index 19eb516c85..c41e07e583 100644 --- a/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs +++ b/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs @@ -1,6 +1,6 @@ using Content.Server.Ghost.Roles.Components; using Content.Server.Speech.Components; -using Content.Server.Psionics; // DeltaV +using Content.Shared._DV.Psionics.Components; // DeltaV using Content.Shared.EntityEffects; using Content.Shared.EntityEffects.Effects; using Content.Shared.Mind.Components; diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/DispelPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/DispelPowerSystem.cs deleted file mode 100644 index 7599cd8b01..0000000000 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/DispelPowerSystem.cs +++ /dev/null @@ -1,150 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.StatusEffect; -using Content.Shared.Abilities.Psionics; -using Content.Shared.Damage; -using Content.Shared.Damage.Systems; -using Content.Shared.Revenant.Components; -using Content.Server.Guardian; -using Content.Server.Bible.Components; -using Content.Server.Popups; -using Robust.Shared.Player; -using Robust.Shared.Random; -using Content.Shared.Actions.Events; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; - -namespace Content.Server.Abilities.Psionics -{ - public sealed class DispelPowerSystem : EntitySystem - { - [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!; - - - 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) - { - _actions.AddAction(uid, ref component.DispelActionEntity, component.DispelActionId ); - if (_actions.GetAction(component.DispelActionEntity) is not { } actionData) - return; - - if (actionData.Comp.UseDelay is not null) - { - _actions.StartUseDelay(component.DispelActionEntity); - } - - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.DispelActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, DispelPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.DispelActionEntity); - - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - - 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.PlayEntity(new SoundPathSpecifier("/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.Value, 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.PlayEntity(new SoundPathSpecifier("/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 class DispelledEvent : HandledEntityEventArgs {} -} - - diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs deleted file mode 100644 index fc4669c82a..0000000000 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Abilities.Psionics; -using Content.Shared.Popups; -using Content.Shared.Actions.Events; - -namespace Content.Server.Abilities.Psionics -{ - public sealed class MetapsionicPowerSystem : EntitySystem - { - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly EntityLookupSystem _lookup = default!; - [Dependency] private readonly SharedPopupSystem _popups = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - } - - private void OnInit(EntityUid uid, MetapsionicPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.MetapsionicActionEntity, component.MetapsionicActionId ); - - if (_actions.GetAction(component.MetapsionicActionEntity) is { Comp.UseDelay: not null }) - { - _actions.StartUseDelay(component.MetapsionicActionEntity); - } - - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.MetapsionicActionEntity; - psionic.ActivePowers.Add(component); - } - - } - - private void OnShutdown(EntityUid uid, MetapsionicPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.MetapsionicActionEntity); - - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - - 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; - } - } -} diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs deleted file mode 100644 index c7b76b20d7..0000000000 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs +++ /dev/null @@ -1,245 +0,0 @@ -using Content.Server.GameTicking; -using Content.Server.Ghost; -using Content.Server.Mind; -using Content.Server.Popups; -using Content.Server.Psionics; -using Content.Shared.Actions; -using Content.Shared.Actions.Events; -using Content.Shared.Abilities.Psionics; -using Content.Shared.Speech; -using Content.Shared.Stealth.Components; -using Content.Shared.Mind; -using Content.Shared.Mobs.Components; -using Content.Shared.Mobs; -using Content.Shared.Damage; -using Content.Shared.Damage.Components; -using Content.Shared.Mobs.Systems; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Shared._DV.Abilities.Psionics; - -namespace Content.Server.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 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) - { - _actions.AddAction(uid, ref component.MindSwapActionEntity, component.MindSwapActionId ); - - if (_actions.GetAction(component.MindSwapActionEntity) is not { Comp.UseDelay: not null }) - { - _actions.StartUseDelay(component.MindSwapActionEntity); - } - - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.MindSwapActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, MindSwapPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.MindSwapActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - - 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; - - //No idea where the viaCommand went. It's on the internal OnGhostAttempt, but not this layer. Maybe unnecessary. - /*if (!args.viaCommand) - return;*/ - - // DeltaV - start of trapped ghost fix - // If you're able to swap back to your original body, you should swap back before you ghost. - if (TryComp(args.Mind.CurrentEntity, out var component) - && _actions.GetAction(component.MindSwapReturnActionEntity) is { } action - && action.Comp.AttachedEntity is not null) - { - args.Result = false; - args.Handled = true; - } - // DeltaV - end of trapped ghost fix - } - - private void OnSwapInit(EntityUid uid, MindSwappedComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.MindSwapReturnActionEntity, component.MindSwapReturnActionId ); - - if (_actions.GetAction(component.MindSwapReturnActionEntity) is not { Comp.UseDelay: not null }) - { - _actions.StartUseDelay(component.MindSwapReturnActionEntity); - } - - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - psionic.PsionicAbility = component.MindSwapReturnActionEntity; - } - - public void Swap(EntityUid performer, EntityUid target, bool end = false, int ReturnSwapCooldown = 0) - { - if (end && (!HasComp(performer) || !HasComp(target))) - return; - - // Get the minds first. On transfer, they'll be gone. - MindComponent? performerMind = null; - MindComponent? targetMind = null; - - // This is here to prevent missing MindContainerComponent Resolve errors. - if(!_mindSystem.TryGetMind(performer, out var performerMindId, out performerMind)){ - performerMind = null; - }; - - if(!_mindSystem.TryGetMind(target, out var targetMindId, out targetMind)){ - targetMind = null; - }; - //This is a terrible way to 'unattach' minds. I wanted to use UnVisit but in TransferTo's code they say - //To unnatch the minds, do it like this. - //Have to unnattach the minds before we reattach them via transfer. Still feels weird, but seems to work well. - _mindSystem.TransferTo(performerMindId, null); - // Do the transfer. - if (targetMind != null) - _mindSystem.TransferTo(targetMindId, performer, ghostCheckOverride: true, false, targetMind); - if (performerMind != null) - _mindSystem.TransferTo(performerMindId, target, ghostCheckOverride: true, false, performerMind); - - if (end) - { - var performerMindPowerComp = Comp(performer); - var targetMindPowerComp = Comp(target); - _actions.RemoveAction(performer, performerMindPowerComp.MindSwapReturnActionEntity); - _actions.RemoveAction(target, targetMindPowerComp.MindSwapReturnActionEntity); - - RemComp(performer); - RemComp(target); - return; - } - - var perfComp = EnsureComp(performer); - var targetComp = EnsureComp(target); - - // Delta V - Cooldown for Returning back - if (ReturnSwapCooldown > 0) - { - var cooldown = TimeSpan.FromSeconds(ReturnSwapCooldown); - _actions.SetCooldown(perfComp.MindSwapReturnActionEntity, cooldown); - _actions.SetCooldown(targetComp.MindSwapReturnActionEntity, cooldown); - } - // Delta V - - perfComp.OriginalEntity = target; - targetComp.OriginalEntity = performer; - } - - public void GetTrapped(EntityUid uid) - { - - _popupSystem.PopupEntity(Loc.GetString("mindswap-trapped"), uid, uid, Shared.Popups.PopupType.LargeCaution); - var perfComp = EnsureComp(uid); - _actions.RemoveAction(perfComp.MindSwapReturnActionEntity); - - 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")); - } - } - } -} diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs deleted file mode 100644 index 24968f335f..0000000000 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Abilities.Psionics; -using Content.Server.Psionics; -using Content.Server.Lightning; -using Content.Shared.Actions.Events; - -namespace Content.Server.Abilities.Psionics -{ - public sealed class NoosphericZapPowerSystem : EntitySystem - { - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly LightningSystem _lightning = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - } - - private void OnInit(EntityUid uid, NoosphericZapPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.NoosphericZapActionEntity, component.NoosphericZapActionId ); - - if (_actions.GetAction(component.NoosphericZapActionEntity) is not { Comp.UseDelay: not null }) - { - _actions.StartUseDelay(component.NoosphericZapActionEntity); - } - - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.NoosphericZapActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, NoosphericZapPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.NoosphericZapActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - - private void OnPowerUsed(NoosphericZapPowerActionEvent args) - { - if (!HasComp(args.Target)) - return; - - if (HasComp(args.Target)) - return; - - _lightning.ShootLightning(args.Performer, args.Target); - - _psionics.LogPowerUsed(args.Performer, "noospheric zap"); - args.Handled = true; - } - } -} diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs deleted file mode 100644 index dff2d63225..0000000000 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs +++ /dev/null @@ -1,139 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.CombatMode.Pacification; -using Content.Shared.Abilities.Psionics; -using Content.Shared.Damage.Systems; -using Content.Shared.Stunnable; -using Content.Shared.Stealth; -using Content.Shared.Stealth.Components; -using Content.Server.Psionics; -using Robust.Shared.Prototypes; -using Robust.Shared.Player; -using Robust.Shared.Audio; -using Robust.Shared.Timing; -using Content.Server.Mind; -using Content.Shared.Actions.Events; -using Robust.Shared.Audio.Systems; - -namespace Content.Server.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!; - [Dependency] private readonly MindSystem _mindSystem = default!; - [Dependency] private readonly SharedAudioSystem _audio = 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) - { - _actions.AddAction(uid, ref component.PsionicInvisibilityActionEntity, component.PsionicInvisibilityActionId ); - - if (_actions.GetAction(component.PsionicInvisibilityActionEntity) is not { Comp.UseDelay: not null }) - { - _actions.StartUseDelay(component.PsionicInvisibilityActionEntity); - } - - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.PsionicInvisibilityActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, PsionicInvisibilityPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.PsionicInvisibilityActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - - private void OnPowerUsed(EntityUid uid, PsionicInvisibilityPowerComponent component, PsionicInvisibilityPowerActionEvent args) - { - if (HasComp(uid)) - return; - - ToggleInvisibility(args.Performer); - var action = Spawn(PsionicInvisibilityUsedComponent.PsionicInvisibilityUsedActionPrototype); - _actions.AddAction(uid, action, action); - - if (_actions.GetAction(action) is not { Comp.UseDelay: not null }) - { - _actions.StartUseDelay(action); - } - - _psionics.LogPowerUsed(uid, "psionic invisibility"); - args.Handled = true; - } - - private void OnPowerOff(RemovePsionicInvisibilityOffPowerActionEvent 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); - _audio.PlayPvs("/Audio/Effects/toss.ogg", uid); - - } - - private void OnEnd(EntityUid uid, PsionicInvisibilityUsedComponent component, ComponentShutdown args) - { - if (Terminating(uid)) - return; - - RemComp(uid); - RemComp(uid); - RemComp(uid); - _audio.PlayPvs("/Audio/Effects/toss.ogg", uid); - //Pretty sure this DOESN'T work as intended. - _actions.RemoveAction(uid, component.PsionicInvisibilityUsedActionEntity); - - _stunSystem.TryAddParalyzeDuration(uid, TimeSpan.FromSeconds(8)); - 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); - } - } - } -} diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs deleted file mode 100644 index ab6595e605..0000000000 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs +++ /dev/null @@ -1,129 +0,0 @@ -using Robust.Shared.Audio; -using Robust.Server.GameObjects; -using Robust.Shared.Player; -using Robust.Shared.Prototypes; -using Content.Server.Body.Systems; -using Content.Server.Chemistry.Containers.EntitySystems; -using Content.Server.Chemistry.EntitySystems; -using Content.Server.DoAfter; -using Content.Shared.Abilities.Psionics; -using Content.Shared.Actions; -using Content.Shared.Body.Components; -using Content.Shared.Chemistry.Components; -using Content.Shared.DoAfter; -using Content.Shared.FixedPoint; -using Content.Shared.Popups; -using Content.Shared.Psionics.Events; -using Content.Shared.Examine; -using static Content.Shared.Examine.ExamineSystemShared; -using Robust.Shared.Timing; -using Content.Server.Mind; -using Content.Shared.Actions.Events; -using Content.Shared.Chemistry.EntitySystems; -using Robust.Server.Audio; - -namespace Content.Server.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 DoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly SharedPopupSystem _popupSystem = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; - [Dependency] private readonly ExamineSystemShared _examine = 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) - { - _actions.AddAction(uid, ref component.PsionicRegenerationActionEntity, component.PsionicRegenerationActionId ); - - if (_actions.GetAction(component.PsionicRegenerationActionEntity) is not { Comp.UseDelay: not null }) - { - _actions.StartUseDelay(component.PsionicRegenerationActionEntity); - } - - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.PsionicRegenerationActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnPowerUsed(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationPowerActionEvent args) - { - var ev = new PsionicRegenerationDoAfterEvent(_gameTiming.CurTime); - var doAfterArgs = new DoAfterArgs(EntityManager, 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 => !_examine.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) - { - _actions.RemoveAction(uid, component.PsionicRegenerationActionEntity); - - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - - 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("Prometheum", FixedPoint2.New(component.EssenceAmount * percentageComplete)); - _bloodstreamSystem.TryAddToBloodstream((uid, stream), solution); - } - } -} - diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs deleted file mode 100644 index fc299b2ac8..0000000000 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Abilities.Psionics; -using Content.Shared.Atmos.Components; -using Content.Server.Atmos.EntitySystems; -using Content.Server.Popups; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Server.Mind; -using Content.Shared.Actions.Events; - -namespace Content.Server.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!; - [Dependency] private readonly MindSystem _mindSystem = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - } - - private void OnInit(EntityUid uid, PyrokinesisPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.PyrokinesisActionEntity, component.PyrokinesisActionId ); - - if (_actions.GetAction(component.PyrokinesisActionEntity) is not { Comp.UseDelay: not null }) - { - _actions.StartUseDelay(component.PyrokinesisActionEntity); - } - - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.PyrokinesisActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, PyrokinesisPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.PyrokinesisActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - - private void OnPowerUsed(PyrokinesisPowerActionEvent args) - { - if (!TryComp(args.Target, out var flammableComponent)) - return; - - flammableComponent.FireStacks += 3; - _flammableSystem.Ignite(args.Target, args.Target); - _popupSystem.PopupEntity(Loc.GetString("pyrokinesis-power-used", ("target", args.Target)), args.Target, Shared.Popups.PopupType.LargeCaution); - - _psionics.LogPowerUsed(args.Performer, "pyrokinesis"); - args.Handled = true; - } - } -} diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs deleted file mode 100644 index bb40d503fb..0000000000 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs +++ /dev/null @@ -1,115 +0,0 @@ -using Content.Server.Atmos.EntitySystems; -using Content.Server.Body.Systems; -using Content.Server.Disposal.Unit; -using Content.Shared._DV.Abilities.Psionics; -using Content.Shared.Abilities.Psionics; -using Content.Shared.Actions; -using Content.Shared.Actions.Events; -using Content.Shared.Atmos; -using Content.Shared.Body.Components; -using Content.Shared.Examine; -using Content.Shared.Mech.Components; -using Content.Shared.Medical.Cryogenics; -using Content.Shared.Mind; -using Content.Shared.Mind.Components; -using Content.Shared.Storage.Components; -using Robust.Server.GameObjects; -using System.Numerics; - -namespace Content.Server.Abilities.Psionics -{ - public sealed class TelegnosisPowerSystem : SharedTelegnosisPowerSystem - { - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly MindSwapPowerSystem _mindSwap = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly AtmosphereSystem _atmos = default!; - [Dependency] private readonly TransformSystem _transform = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - SubscribeLocalEvent(OnMindRemoved); - SubscribeLocalEvent(OnInhaleLocation, after: [typeof(InsideCryoPodComponent), typeof(InternalsComponent), typeof(BeingDisposedComponent), typeof(InsideEntityStorageComponent), typeof(MechPilotComponent)]); - SubscribeLocalEvent(OnExamine); - } - - private void OnInit(EntityUid uid, TelegnosisPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.TelegnosisActionEntity, component.TelegnosisActionId); - - if (_actions.GetAction(component.TelegnosisActionEntity) is not { Comp.UseDelay: not null }) - { - _actions.StartUseDelay(component.TelegnosisActionEntity); - } - - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.TelegnosisActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, TelegnosisPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.TelegnosisActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - - private void OnPowerUsed(EntityUid uid, TelegnosisPowerComponent component, TelegnosisPowerActionEvent args) - { - var projection = Spawn(component.Prototype, Transform(uid).Coordinates); - - _transform.AttachToGridOrMap(projection); - _mindSwap.Swap(uid, projection); - - _psionics.LogPowerUsed(uid, "telegnosis"); - args.Handled = true; - } - private void OnMindRemoved(EntityUid uid, TelegnosticProjectionComponent component, MindRemovedMessage args) - { - QueueDel(uid); - } - - public EntityUid GetCasterProjection(Entity entity) - { - if (!TryComp(entity, out var mindSwapped) || - !HasComp(mindSwapped.OriginalEntity)) - { - return default; - } - return mindSwapped.OriginalEntity; - } - - private void OnInhaleLocation(Entity entity, ref InhaleLocationEvent args) - { - var sensorUid = GetCasterProjection(entity); - if (sensorUid == default) - return; - // Determine the distance to the sensor, this will be used to dilute the amount of air we take in. - var sensorPosition = _transform.GetWorldPosition(sensorUid); - var projectionPosition = _transform.GetWorldPosition(entity); - // A linear curve from 1.0 at 7 tiles away, to 0 at 57 tiles away - var distance = Vector2.Distance(sensorPosition, projectionPosition); - float gasMult = Math.Clamp(1f - (distance - 7f) / 50f, 0f, 1f); - args.Gas = (args.Gas ?? _atmos.GetContainingMixture(entity.Owner, excite: true))?.RemoveVolume(Atmospherics.BreathVolume * gasMult); - if (args.Gas == null) - return; - args.Gas.Volume = Math.Min(args.Gas.Volume, Atmospherics.BreathVolume); - } - - private void OnExamine(Entity entity, ref ExaminedEvent args) - { - if (GetCasterProjection(entity) == default) - return; - - args.PushMarkup($"[color=yellow]{Loc.GetString("telegnosis-power-ssd", ("ent", entity))}[/color]"); - } - } -} diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/PsionicAbilitiesSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/PsionicAbilitiesSystem.cs deleted file mode 100644 index 12d712f974..0000000000 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/PsionicAbilitiesSystem.cs +++ /dev/null @@ -1,135 +0,0 @@ -using Content.Shared.Abilities.Psionics; -using Content.Shared.Actions; -using Content.Shared.Psionics.Glimmer; -using Content.Shared.Random; -using Content.Shared.Random.Helpers; -using Content.Server.EUI; -using Content.Server.Psionics; -using Content.Shared.Jittering; -using Content.Shared.StatusEffect; -using Robust.Shared.Random; -using Robust.Shared.Prototypes; -using Robust.Shared.Player; - -namespace Content.Server.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 EuiManager _euiManager = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; - [Dependency] private readonly SharedJitteringSystem _jittering = default!; - [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = 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; - - //Don't know if this will work. New mind state vs old. - if (!TryComp(uid, out var actor)) - { - EnsureComp(uid); - return; - } - - if (warn) - _euiManager.OpenEui(new AcceptPsionicsEui(uid, this), actor.PlayerSession); - 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){ - if (_actionsSystem.GetAction(psionic.PsionicAbility) is { } psiAbility) - { - _actionsSystem.RemoveAction(uid, psiAbility.Owner); - } - } - - _glimmerSystem.Glimmer -= _random.Next(50, 70); - - _statusEffectsSystem.TryAddStatusEffect(uid, "Stutter", TimeSpan.FromMinutes(1), false, "StutteringAccent"); - _statusEffectsSystem.TryAddStatusEffect(uid, "KnockedDown", TimeSpan.FromSeconds(3), false, "KnockedDown"); - _jittering.DoJitter(uid, TimeSpan.FromSeconds(10), false); - - RemComp(uid); - } - } -} diff --git a/Content.Server/Nyanotrasen/CartridgeLoader/GlimmerMonitorCartridgeComponent.cs b/Content.Server/Nyanotrasen/CartridgeLoader/GlimmerMonitorCartridgeComponent.cs index 40ba46647c..a93bf10929 100644 --- a/Content.Server/Nyanotrasen/CartridgeLoader/GlimmerMonitorCartridgeComponent.cs +++ b/Content.Server/Nyanotrasen/CartridgeLoader/GlimmerMonitorCartridgeComponent.cs @@ -1,5 +1,4 @@ namespace Content.Server.CartridgeLoader.Cartridges; [RegisterComponent] -public sealed partial class GlimmerMonitorCartridgeComponent : Component -{ } +public sealed partial class GlimmerMonitorCartridgeComponent : Component; diff --git a/Content.Server/Nyanotrasen/Chat/NyanoChatSystem.cs b/Content.Server/Nyanotrasen/Chat/NyanoChatSystem.cs index 246390cfd7..eff80cda57 100644 --- a/Content.Server/Nyanotrasen/Chat/NyanoChatSystem.cs +++ b/Content.Server/Nyanotrasen/Chat/NyanoChatSystem.cs @@ -2,7 +2,6 @@ using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; using Content.Server.Chat.Managers; using Content.Server.Chat.Systems; -using Content.Shared.Abilities.Psionics; using Content.Shared.Bed.Sleep; using Content.Shared.Chat; using Content.Shared.Database; @@ -15,114 +14,116 @@ using Robust.Shared.Player; using Robust.Shared.Random; using System.Linq; using System.Text; +using Content.Server._DV.Psionics.Systems; +using Content.Shared._DV.Chat.Components; -namespace Content.Server.Nyanotrasen.Chat +namespace Content.Server.Nyanotrasen.Chat; + +/// +/// Extensions for nyano's chat stuff +/// + +public sealed class NyanoChatSystem : EntitySystem { - /// - /// Extensions for nyano's chat stuff - /// + [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!; + [Dependency] private readonly PsionicSystem _psionicSystem = default!; - public sealed class NyanoChatSystem : EntitySystem + private IEnumerable GetPsionicChatClients() { - [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.Channel); + } + + private IEnumerable GetAdminClients() + { + return _adminManager.ActiveAdmins + .Select(p => p.Channel); + } + + private List GetDreamers(IEnumerable removeList) + { + var filtered = Filter.Empty() + .AddWhereAttachedEntity(entity => HasComp(entity) || HasComp(entity) && IsEligibleForTelepathy(entity)) + .Recipients + .Select(p => p.Channel); + + var filteredList = filtered.ToList(); + + foreach (var entity in removeList) + filteredList.Remove(entity); + + return filteredList; + } + + private bool IsEligibleForTelepathy(EntityUid entity) + { + return _psionicSystem.CanUsePsionicAbility(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(); + var admins = GetAdminClients(); + 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.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))) { - return Filter.Empty() - .AddWhereAttachedEntity(IsEligibleForTelepathy) - .Recipients - .Select(p => p.Channel); + float obfuscation = (0.25f + (float) _glimmerSystem.Glimmer / 2000); + var obfuscated = ObfuscateMessageReadability(message, obfuscation); + _chatManager.ChatMessageToMany(ChatChannel.Telepathic, obfuscated, messageWrap, source, hideChat, false, GetDreamers(clients), Color.PaleVioletRed); } - private IEnumerable GetAdminClients() + foreach (var repeater in EntityQuery()) { - return _adminManager.ActiveAdmins - .Select(p => p.Channel); - } - - private List GetDreamers(IEnumerable removeList) - { - var filtered = Filter.Empty() - .AddWhereAttachedEntity(entity => HasComp(entity) || HasComp(entity) && !HasComp(entity) && !HasComp(entity)) - .Recipients - .Select(p => p.Channel); - - 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(); - var admins = GetAdminClients(); - 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.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 = 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); - } - } - - private string ObfuscateMessageReadability(string message, float chance) - { - var modifiedMessage = new StringBuilder(message); - - for (var i = 0; i < message.Length; i++) - { - if (char.IsWhiteSpace((modifiedMessage[i]))) - { - continue; - } - - if (_random.Prob(1 - chance)) - { - modifiedMessage[i] = '~'; - } - } - - return modifiedMessage.ToString(); + _chatSystem.TrySendInGameICMessage(repeater.Owner, message, InGameICChatType.Speak, false); } } + + private string ObfuscateMessageReadability(string message, float chance) + { + var modifiedMessage = new StringBuilder(message); + + for (var i = 0; i < message.Length; i++) + { + if (char.IsWhiteSpace((modifiedMessage[i]))) + { + continue; + } + + if (_random.Prob(1 - chance)) + { + modifiedMessage[i] = '~'; + } + } + + return modifiedMessage.ToString(); + } } diff --git a/Content.Server/Nyanotrasen/Chat/TelepathicRepeaterComponent.cs b/Content.Server/Nyanotrasen/Chat/TelepathicRepeaterComponent.cs deleted file mode 100644 index fc199f4332..0000000000 --- a/Content.Server/Nyanotrasen/Chat/TelepathicRepeaterComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Content.Server.Nyanotrasen.Chat -{ - /// - /// Repeats whatever is happening in telepathic chat. - /// - [RegisterComponent] - public sealed partial class TelepathicRepeaterComponent : Component - { - - } -} diff --git a/Content.Server/Nyanotrasen/Objectives/Systems/BecomePsionicConditionSystem.cs b/Content.Server/Nyanotrasen/Objectives/Systems/BecomePsionicConditionSystem.cs index 7ee5da29c5..756fe67eae 100644 --- a/Content.Server/Nyanotrasen/Objectives/Systems/BecomePsionicConditionSystem.cs +++ b/Content.Server/Nyanotrasen/Objectives/Systems/BecomePsionicConditionSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Abilities.Psionics; using Content.Server.Objectives.Components; +using Content.Shared._DV.Psionics.Components; using Content.Shared.Mind; using Content.Shared.Objectives.Components; diff --git a/Content.Server/Nyanotrasen/Psionics/AcceptPsionicsEui.cs b/Content.Server/Nyanotrasen/Psionics/AcceptPsionicsEui.cs deleted file mode 100644 index 80fd8946f2..0000000000 --- a/Content.Server/Nyanotrasen/Psionics/AcceptPsionicsEui.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Content.Shared.Psionics; -using Content.Shared.Eui; -using Content.Server.EUI; -using Content.Server.Abilities.Psionics; - -namespace Content.Server.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/Nyanotrasen/Psionics/AntiPsychicWeaponComponent.cs b/Content.Server/Nyanotrasen/Psionics/AntiPsychicWeaponComponent.cs deleted file mode 100644 index ee81380a9c..0000000000 --- a/Content.Server/Nyanotrasen/Psionics/AntiPsychicWeaponComponent.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Content.Shared.Damage; - -namespace Content.Server.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. - /// +/// Adds to glimmer at regular intervals. We'll use it for glimmer drains too when we get there. +/// +[RegisterComponent] +public sealed partial class GlimmerSourceComponent : Component { - [RegisterComponent] + + [DataField] public float Accumulator = 0f; + + [DataField] public bool Active = true; + /// - /// Adds to glimmer at regular intervals. We'll use it for glimmer drains too when we get there. + /// Since glimmer is an int, we'll do it like this. /// - public sealed partial class GlimmerSourceComponent : Component - { - [DataField("accumulator")] - public float Accumulator = 0f; + [DataField] public float SecondsPerGlimmer = 10f; - [DataField("active")] - public bool Active = true; - - /// - /// Since glimmer is an int, we'll do it like this. - /// - [DataField("secondsPerGlimmer")] - public float SecondsPerGlimmer = 10f; - - /// - /// True if it produces glimmer, false if it subtracts it. - /// - [DataField("addToGlimmer")] - public bool AddToGlimmer = true; - } + /// + /// True if it produces glimmer, false if it subtracts it. + /// + [DataField] public bool AddToGlimmer = true; } + diff --git a/Content.Server/Nyanotrasen/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs b/Content.Server/Nyanotrasen/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs index 33adfec243..dc952ca1fa 100644 --- a/Content.Server/Nyanotrasen/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs +++ b/Content.Server/Nyanotrasen/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs @@ -1,85 +1,78 @@ using Content.Server.Anomaly.Components; -using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Anomaly.Components; using Content.Shared.Power; using Content.Shared.Psionics.Glimmer; -namespace Content.Server.Psionics.Glimmer +namespace Content.Server.Psionics.Glimmer; + +/// +/// Handles structures which add/subtract glimmer. +/// +public sealed class GlimmerStructuresSystem : EntitySystem { - /// - /// 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() { - [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!; - [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + base.Initialize(); + SubscribeLocalEvent(OnAnomalyVesselPowerChanged); + SubscribeLocalEvent(OnAnomalyPulse); + SubscribeLocalEvent(OnAnomalySupercritical); + } - public override void Initialize() + 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()) { - 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) { - 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) { - source.Accumulator -= source.SecondsPerGlimmer; - if (source.AddToGlimmer) - { - _glimmerSystem.Glimmer++; - } - else - { - _glimmerSystem.Glimmer--; - } + _glimmerSystem.Glimmer++; + } + else + { + _glimmerSystem.Glimmer--; } } } } } + diff --git a/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibilitySystem.cs b/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibilitySystem.cs deleted file mode 100644 index 325d55cbcc..0000000000 --- a/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibilitySystem.cs +++ /dev/null @@ -1,142 +0,0 @@ -using Content.Shared.Abilities.Psionics; -using Content.Server.Abilities.Psionics; -using Content.Shared.Eye; -using Content.Shared.NPC.Systems; -using Robust.Shared.Containers; -using Robust.Server.GameObjects; -using Content.Shared.NPC.Prototypes; -using Robust.Shared.Prototypes; - -namespace Content.Server.Psionics -{ - public sealed class PsionicInvisibilitySystem : EntitySystem - { - [Dependency] private readonly VisibilitySystem _visibility = default!; - [Dependency] private readonly PsionicInvisibilityPowerSystem _invisSystem = default!; - [Dependency] private readonly NpcFactionSystem _faction = default!; - [Dependency] private readonly SharedEyeSystem _eye = default!; - - private static readonly ProtoId PsionicInterloperProtoId = "PsionicInterloper"; - private static readonly ProtoId GlimmerMonsterProtoId = "GlimmerMonster"; - - - 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 (_faction.IsMember(uid, PsionicInterloperProtoId)) - { - component.SuppressedFactions.Add(PsionicInterloperProtoId); - _faction.RemoveFaction(uid, PsionicInterloperProtoId); - } - - if (_faction.IsMember(uid, GlimmerMonsterProtoId)) - { - component.SuppressedFactions.Add(GlimmerMonsterProtoId); - _faction.RemoveFaction(uid, GlimmerMonsterProtoId); - } - - 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) - { - _faction.AddFaction(uid, faction); - } - component.SuppressedFactions.Clear(); - } - - private void OnInvisInit(EntityUid uid, PsionicallyInvisibleComponent component, ComponentInit args) - { - var visibility = EnsureComp(uid); - var ent = (uid, visibility); - - _visibility.AddLayer(ent, (int) VisibilityFlags.PsionicInvisibility, false); - _visibility.RemoveLayer(ent, (int) VisibilityFlags.Normal, false); - _visibility.RefreshVisibility(ent); - } - - - private void OnInvisShutdown(EntityUid uid, PsionicallyInvisibleComponent component, ComponentShutdown args) - { - if (!TryComp(uid, out var visibility)) - return; - - var ent = (uid, visibility); - _visibility.RemoveLayer(ent, (int) VisibilityFlags.PsionicInvisibility, false); - _visibility.AddLayer(ent, (int) VisibilityFlags.Normal, false); - _visibility.RefreshVisibility(ent); - } - - private void OnEyeInit(EntityUid uid, EyeComponent component, ComponentInit args) - { - //SetCanSeePsionicInvisiblity(uid, true); //JJ Comment - Not allowed to modifies .yml on spawn any longer. See UninitializedSaveTest. - } - 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.SetVisibilityMask(uid, eye.VisibilityMask | (int) VisibilityFlags.PsionicInvisibility, eye); - } - } else - { - if (EntityManager.TryGetComponent(uid, out EyeComponent? eye)) - { - _eye.SetVisibilityMask(uid, eye.VisibilityMask & ~ (int) VisibilityFlags.PsionicInvisibility, eye); - } - } - } - } -} diff --git a/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs b/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs deleted file mode 100644 index 859ceb7b83..0000000000 --- a/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Shared.Whitelist; -using Robust.Shared.Timing; - -namespace Content.Server.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/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs b/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs deleted file mode 100644 index c0c91fcc69..0000000000 --- a/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Content.Shared.Stealth; -using Content.Shared.Stealth.Components; -using Content.Shared.Whitelist; -using Robust.Shared.Physics.Events; -using Robust.Shared.Physics.Systems; -using Robust.Shared.Timing; - -namespace Content.Server.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!; - [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = 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 (_whitelistSystem.IsWhitelistFail(component.Whitelist, 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 (_whitelistSystem.IsWhitelistFail(component.Whitelist, 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/Nyanotrasen/Psionics/Invisibility/PsionicallyInvisibleComponent.cs b/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicallyInvisibleComponent.cs deleted file mode 100644 index 5352f5737f..0000000000 --- a/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicallyInvisibleComponent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Content.Server.Psionics -{ - [RegisterComponent] - public sealed partial class PsionicallyInvisibleComponent : Component - {} -} diff --git a/Content.Server/Nyanotrasen/Psionics/NPC/PsionicNpcCombatSystem.cs b/Content.Server/Nyanotrasen/Psionics/NPC/PsionicNpcCombatSystem.cs index 15531d999b..d37613b99b 100644 --- a/Content.Server/Nyanotrasen/Psionics/NPC/PsionicNpcCombatSystem.cs +++ b/Content.Server/Nyanotrasen/Psionics/NPC/PsionicNpcCombatSystem.cs @@ -1,8 +1,7 @@ -using Content.Shared.Abilities.Psionics; using Content.Shared.Actions; using Content.Server.NPC.Events; using Content.Server.NPC.Components; -using Content.Server.Abilities.Psionics; +using Content.Shared._DV.Psionics.Components.PsionicPowers; using Content.Shared.Actions.Components; using Robust.Shared.Timing; @@ -24,7 +23,7 @@ public sealed class PsionicNpcCombatSystem : EntitySystem private void ZapCombat(Entity ent, ref NPCSteeringEvent args) { var (uid, comp) = ent; - if (comp.NoosphericZapActionEntity is not {} action) + if (comp.ActionEntity is not {} action) return; var target = Comp(action); diff --git a/Content.Server/Nyanotrasen/Psionics/PotentialPsionicComponent.cs b/Content.Server/Nyanotrasen/Psionics/PotentialPsionicComponent.cs deleted file mode 100644 index 65c30d079c..0000000000 --- a/Content.Server/Nyanotrasen/Psionics/PotentialPsionicComponent.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Content.Server.Psionics -{ - [RegisterComponent] - public sealed partial class PotentialPsionicComponent : Component - { - [DataField("chance")] - public float Chance = 0.04f; - - /// - /// YORO (you only reroll once) - /// - [DataField] - public bool Rerolled = false; - } -} diff --git a/Content.Server/Nyanotrasen/Psionics/PsionicAwaitingPlayerComponent.cs b/Content.Server/Nyanotrasen/Psionics/PsionicAwaitingPlayerComponent.cs deleted file mode 100644 index f9cc9339d4..0000000000 --- a/Content.Server/Nyanotrasen/Psionics/PsionicAwaitingPlayerComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Server.Psionics -{ - /// - /// Will open the 'accept psionics' UI when a player attaches. - /// - [RegisterComponent] - public sealed partial class PsionicAwaitingPlayerComponent : Component - {} -} diff --git a/Content.Server/Nyanotrasen/Psionics/PsionicBonusChanceComponent.cs b/Content.Server/Nyanotrasen/Psionics/PsionicBonusChanceComponent.cs deleted file mode 100644 index d9cbc51147..0000000000 --- a/Content.Server/Nyanotrasen/Psionics/PsionicBonusChanceComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Content.Server.Psionics -{ - [RegisterComponent] - public sealed partial class PsionicBonusChanceComponent : Component - { - [DataField("multiplier")] - public float Multiplier = 1f; - [DataField("flatBonus")] - 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/Nyanotrasen/Psionics/PsionicsCommands.cs b/Content.Server/Nyanotrasen/Psionics/PsionicsCommands.cs index 4810004c14..16c0d3e473 100644 --- a/Content.Server/Nyanotrasen/Psionics/PsionicsCommands.cs +++ b/Content.Server/Nyanotrasen/Psionics/PsionicsCommands.cs @@ -1,4 +1,5 @@ using Content.Server.Administration; +using Content.Shared._DV.Psionics.Components; using Content.Shared.Administration; using Content.Shared.Abilities.Psionics; using Content.Shared.Mobs.Components; @@ -21,15 +22,14 @@ public sealed class ListPsionicsCommand : IConsoleCommand var entMan = IoCManager.Resolve(); foreach (var (actor, mob, psionic, meta) in entMan.EntityQuery()){ // filter out xenos, etc, with innate telepathy - if (actions.GetAction(psionic.PsionicAbility) is not { } actionData) + if (psionic.PsionicPowersActionEntities.Count == 0) return; - if (actionData.Comp.ToString() == null) - return; - - var psiPowerName = actionData.ToString(); - if (psiPowerName == null) - return; + var psiPowerName = ""; + foreach (var power in psionic.PsionicPowersActionEntities) + { + psiPowerName += power; + } shell.WriteLine(meta.EntityName + " (" + meta.Owner + ") - " + actor.PlayerSession.Name + Loc.GetString(psiPowerName)); } diff --git a/Content.Server/Nyanotrasen/Psionics/PsionicsSystem.cs b/Content.Server/Nyanotrasen/Psionics/PsionicsSystem.cs deleted file mode 100644 index 1e8dd575ea..0000000000 --- a/Content.Server/Nyanotrasen/Psionics/PsionicsSystem.cs +++ /dev/null @@ -1,190 +0,0 @@ -using Content.Shared.Abilities.Psionics; -using Content.Shared.StatusEffect; -using Content.Shared.Mobs; -using Content.Shared.Psionics.Glimmer; -using Content.Shared.Weapons.Melee.Events; -using Content.Shared.Damage.Events; -using Content.Shared._DV.CCVars; -using Content.Shared.IdentityManagement; -using Content.Server.Abilities.Psionics; -using Content.Server.Chat.Systems; -using Content.Server.Electrocution; -using Content.Server.NPC.Components; -using Content.Server.NPC.Systems; -using Content.Shared.NPC.Components; -using Content.Shared.NPC.Systems; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Player; -using Robust.Shared.Configuration; -using Robust.Shared.Random; -using Content.Shared._DV.Abilities.Psionics; - -namespace Content.Server.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 _faction = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly SharedAudioSystem _audio = 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(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)) - { - _audio.PlayPvs("/Audio/Effects/lightburn.ogg", 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 OnInit(EntityUid uid, PsionicComponent component, ComponentInit args) - { - if (!component.Removable) - return; - - if (!TryComp(uid, out var factions)) - return; - - if (_faction.IsMember((uid, factions), "GlimmerMonster")) - return; - - _faction.AddFaction((uid, factions), "PsionicInterloper"); - } - - private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove args) - { - if (!TryComp(uid, out var factions)) - return; - - _faction.RemoveFaction((uid, factions), "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; - } - - /// - /// Makes the entity psionic if it is possible. - /// Ignores rolling and rerolling prevention. - /// - public bool TryMakePsionic(Entity ent) - { - if (HasComp(ent)) - return false; - - if (!_cfg.GetCVar(DCCVars.PsionicRollsEnabled)) - return false; - - var warn = CompOrNull(ent)?.Warn ?? true; - _psionicAbilitiesSystem.AddPsionics(ent, warn); - return true; - } - - public void RollPsionics(EntityUid uid, PotentialPsionicComponent component, bool applyGlimmer = true, float multiplier = 1f) - { - - var chance = component.Chance; - if (TryComp(uid, out var bonus)) - { - chance *= bonus.Multiplier; - chance += bonus.FlatBonus; - } - - if (applyGlimmer) - chance += ((float) _glimmerSystem.Glimmer / 1000); - - chance *= multiplier; - - chance = Math.Clamp(chance, 0, 1); - - if (_random.Prob(chance)) - TryMakePsionic((uid, component)); - } - - 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; - } - - public void GrantNewPsionicReroll(EntityUid uid, PotentialPsionicComponent? psionic = null) - { - if (!Resolve(uid, ref psionic, false)) - return; - - psionic.Rerolled = false; - } - } -} diff --git a/Content.Server/Nyanotrasen/Research/Oracle/OracleComponent.cs b/Content.Server/Nyanotrasen/Research/Oracle/OracleComponent.cs index 9651ff89bd..fbd8e03bd0 100644 --- a/Content.Server/Nyanotrasen/Research/Oracle/OracleComponent.cs +++ b/Content.Server/Nyanotrasen/Research/Oracle/OracleComponent.cs @@ -41,7 +41,7 @@ public sealed partial class OracleComponent : Component [DataField("rewardReagents", customTypeSerializer: typeof(PrototypeIdListSerializer))] public IReadOnlyList RewardReagents = new[] { - "LotophagoiOil", "LotophagoiOil", "LotophagoiOil", "LotophagoiOil", "LotophagoiOil", "Wine", "Blood", "Ichor" + "LotophagoiOil", "LotophagoiOil", "LotophagoiOil", "Ambrosia", "Nectar", "Wine", "Blood", "Ichor", }; [DataField("demandMessages")] diff --git a/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs b/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs index b04df990bd..d501ea0890 100644 --- a/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs +++ b/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs @@ -6,6 +6,7 @@ using Content.Server.Chat.Systems; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Fluids.EntitySystems; using Content.Server.Psionics; +using Content.Shared._DV.Psionics.Components; using Content.Shared.Abilities.Psionics; using Content.Shared.Chat; using Content.Shared.Chemistry.Components; @@ -29,15 +30,16 @@ namespace Content.Server.Research.Oracle; public sealed class OracleSystem : EntitySystem { + [Dependency] private readonly IAdminLogManager _adminLog = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly IChatManager _chatManager = default!; - [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!; + [Dependency] private readonly ISharedPlayerManager _playerManager = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionSystem = default!; + [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; [Dependency] private readonly PuddleSystem _puddleSystem = default!; - [Dependency] private readonly IAdminLogManager _adminLog = default!; - [Dependency] private readonly SharedResearchSystem _research = default!; + [Dependency] private readonly SharedResearchSystem _research = default!; public override void Update(float frameTime) { @@ -79,49 +81,41 @@ public sealed class OracleSystem : EntitySystem NextItem(component); } - private void OnInteractHand(EntityUid uid, OracleComponent component, InteractHandEvent args) + private void OnInteractHand(Entity oracle, ref InteractHandEvent args) { - if (!HasComp(args.User) || HasComp(args.User)) + if (!HasComp(args.User) + || !_playerManager.TryGetSessionByEntity(args.User, out var session)) return; - if (!TryComp(args.User, out var actor)) - return; - - var message = Loc.GetString("oracle-current-item", ("item", component.DesiredPrototype.Name)); + var message = Loc.GetString("oracle-current-item", ("item", oracle.Comp.DesiredPrototype.Name)); var messageWrap = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message", ("telepathicChannelName", Loc.GetString("chat-manager-telepathic-channel-name")), ("message", message)); _chatManager.ChatMessageToOne(ChatChannel.Telepathic, - message, messageWrap, uid, false, actor.PlayerSession.Channel, Color.PaleVioletRed); + message, messageWrap, oracle, false, session.Channel, Color.PaleVioletRed); - if (component.LastDesiredPrototype != null) + if (oracle.Comp.LastDesiredPrototype != null) { - var message2 = Loc.GetString("oracle-previous-item", ("item", component.LastDesiredPrototype.Name)); + var message2 = Loc.GetString("oracle-previous-item", ("item", oracle.Comp.LastDesiredPrototype.Name)); var messageWrap2 = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message", ("telepathicChannelName", Loc.GetString("chat-manager-telepathic-channel-name")), ("message", message2)); _chatManager.ChatMessageToOne(ChatChannel.Telepathic, - message2, messageWrap2, uid, false, actor.PlayerSession.Channel, Color.PaleVioletRed); + message2, messageWrap2, oracle, false, session.Channel, Color.PaleVioletRed); } } private void AddInsertDesiredItemVerb(Entity ent, ref GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract || args.Using == null) - return; - - if (HasComp(args.Using)) - return; - - if (!TryComp(args.Using, out MetaDataComponent? meta)) - return; - - if (HasComp(args.User)) - return; - - if (meta.EntityPrototype == null) + if (!args.CanAccess + || !args.CanInteract + || args.Using == null + || HasComp(args.Using) + || !TryComp(args.Using, out MetaDataComponent? meta) + || HasComp(args.User) + || meta.EntityPrototype == null) return; var argsUser = args.User; @@ -147,16 +141,10 @@ public sealed class OracleSystem : EntitySystem private void DoOnInteractUsing(Entity oracle, EntityUid user, EntityUid used) { - if (HasComp(used)) - return; - - if (!TryComp(used, out MetaDataComponent? meta)) - return; - - if (HasComp(user)) - return; - - if (meta.EntityPrototype == null) + if (HasComp(used) + || !TryComp(used, out MetaDataComponent? meta) + || HasComp(user) + || meta.EntityPrototype == null) return; var validItem = CheckValidity(meta.EntityPrototype, oracle.Comp.DesiredPrototype); @@ -291,7 +279,7 @@ public sealed class OracleSystem : EntitySystem { if ( researchServers.Count == 0 - || researchServers.Any(server => + || researchServers.Any(server => _research.IsTechnologyUnlocked(server.database.Owner, tech, server.database.Comp) || _research.IsTechnologyAvailable(server.database.Comp, tech, server.disciplineTiers) ) diff --git a/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs b/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs index ca0e494967..6320783b44 100644 --- a/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs +++ b/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs @@ -1,8 +1,8 @@ using Content.Server.Chat.Systems; -using Content.Server.Nyanotrasen.StationEvents.Events; using Content.Server.Radio.EntitySystems; using Content.Shared.Chat; -using Content.Shared._DV.Abilities.Psionics; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.StationEvents.Events; using Content.Shared.Interaction; using Content.Shared.Psionics.Glimmer; using Content.Shared.Radio; @@ -65,7 +65,7 @@ public sealed partial class SophicScribeSystem : EntitySystem _chat.TrySendInGameICMessage(uid, Loc.GetString("glimmer-report", ("level", _glimmerSystem.Glimmer)), InGameICChatType.Speak, true); } - private void OnGlimmerEventEnded(GlimmerEventEndedEvent args) + private void OnGlimmerEventEnded(ref GlimmerEventEndedEvent args) { var query = EntityQueryEnumerator(); while (query.MoveNext(out var scribe, out _)) @@ -74,7 +74,7 @@ public sealed partial class SophicScribeSystem : EntitySystem // mind entities when... var speaker = scribe; - if (TryComp(scribe, out var swapped)) + if (TryComp(scribe, out var swapped)) { speaker = swapped.OriginalEntity; } diff --git a/Content.Server/Nyanotrasen/StationEvents/Components/GlimmerEventComponent.cs b/Content.Server/Nyanotrasen/StationEvents/Components/GlimmerEventComponent.cs index 7d10f6a440..e989f7929e 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Components/GlimmerEventComponent.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Components/GlimmerEventComponent.cs @@ -6,27 +6,27 @@ public sealed partial class GlimmerEventComponent : Component /// /// Minimum glimmer value for event to be eligible. (Should be 100 at lowest.) /// - [DataField("minimumGlimmer")] + [DataField] public int MinimumGlimmer = 100; /// /// Maximum glimmer value for event to be eligible. (Remember 1000 is max glimmer period.) /// - [DataField("maximumGlimmer")] + [DataField] public int MaximumGlimmer = 1000; /// /// Will be used for _random.Next and subtracted from glimmer. /// Lower bound. /// - [DataField("glimmerBurnLower")] + [DataField] public int GlimmerBurnLower = 25; /// /// Will be used for _random.Next and subtracted from glimmer. /// Upper bound. /// - [DataField("glimmerBurnUpper")] + [DataField] public int GlimmerBurnUpper = 70; [DataField("report")] diff --git a/Content.Server/Nyanotrasen/StationEvents/Components/MassMindSwapRuleComponent.cs b/Content.Server/Nyanotrasen/StationEvents/Components/MassMindSwapRuleComponent.cs index 7c17495b1c..16706665c2 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Components/MassMindSwapRuleComponent.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Components/MassMindSwapRuleComponent.cs @@ -10,7 +10,7 @@ public sealed partial class MassMindSwapRuleComponent : Component /// /// The mind swap is only temporary if true. /// - [DataField("isTemporary")] + [DataField] public bool IsTemporary; [DataField] diff --git a/Content.Server/Nyanotrasen/StationEvents/Components/NoosphericFryRuleComponent.cs b/Content.Server/Nyanotrasen/StationEvents/Components/NoosphericFryRuleComponent.cs deleted file mode 100644 index 6f5b0f4d9c..0000000000 --- a/Content.Server/Nyanotrasen/StationEvents/Components/NoosphericFryRuleComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Content.Server.Nyanotrasen.StationEvents.Events; -using Content.Server.StationEvents.Events; - -namespace Content.Server.StationEvents.Components; - -[RegisterComponent, Access(typeof(NoosphericFryRule))] -public sealed partial class NoosphericFryRuleComponent : Component -{ -} diff --git a/Content.Server/Nyanotrasen/StationEvents/Components/NoosphericStormRuleComponent.cs b/Content.Server/Nyanotrasen/StationEvents/Components/NoosphericStormRuleComponent.cs deleted file mode 100644 index fe3911ccfa..0000000000 --- a/Content.Server/Nyanotrasen/StationEvents/Components/NoosphericStormRuleComponent.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Content.Server.Nyanotrasen.StationEvents.Events; -using Content.Server.StationEvents.Events; - -namespace Content.Server.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 = 3; - - /// - /// - [DataField("baseGlimmerAddMin")] - public int BaseGlimmerAddMin = 65; - - /// - /// - [DataField("baseGlimmerAddMax")] - public int BaseGlimmerAddMax = 85; - - /// - /// Multiply the EventSeverityModifier by this to determine how much extra glimmer to add. - /// - [DataField("glimmerSeverityCoefficient")] - public float GlimmerSeverityCoefficient = 0.25f; -} diff --git a/Content.Server/Nyanotrasen/StationEvents/Components/NoosphericZapRuleComponent.cs b/Content.Server/Nyanotrasen/StationEvents/Components/NoosphericZapRuleComponent.cs deleted file mode 100644 index e6fa370f93..0000000000 --- a/Content.Server/Nyanotrasen/StationEvents/Components/NoosphericZapRuleComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Content.Server.Nyanotrasen.StationEvents.Events; -using Content.Server.StationEvents.Events; - -namespace Content.Server.StationEvents.Components; - -[RegisterComponent, Access(typeof(NoosphericZapRule))] -public sealed partial class NoosphericZapRuleComponent : Component -{ -} diff --git a/Content.Server/Nyanotrasen/StationEvents/Components/PsionicCatGotYourTongueRuleComponent.cs b/Content.Server/Nyanotrasen/StationEvents/Components/PsionicCatGotYourTongueRuleComponent.cs deleted file mode 100644 index 8f35639982..0000000000 --- a/Content.Server/Nyanotrasen/StationEvents/Components/PsionicCatGotYourTongueRuleComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Server.Nyanotrasen.StationEvents.Events; -using Robust.Shared.Audio; -using Content.Server.StationEvents.Events; - -namespace Content.Server.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/Nyanotrasen/StationEvents/Events/GlimmerEventSystem.cs b/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerEventSystem.cs index 07356026c9..094c0df86a 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerEventSystem.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerEventSystem.cs @@ -1,35 +1,24 @@ using Content.Server.Psionics.Glimmer; using Content.Server.StationEvents.Events; +using Content.Shared._DV.StationEvents.Events; using Content.Shared.GameTicking.Components; using Content.Shared.Psionics.Glimmer; -namespace Content.Server.Nyanotrasen.StationEvents.Events +namespace Content.Server.Nyanotrasen.StationEvents.Events; + +public sealed class GlimmerEventSystem : StationEventSystem { - public sealed class GlimmerEventSystem : StationEventSystem + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + + protected override void Ended(EntityUid uid, GlimmerEventComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args) { - [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + base.Ended(uid, component, gameRule, args); + var glimmerBurned = RobustRandom.Next(component.GlimmerBurnLower, component.GlimmerBurnUpper); - protected override void Ended(EntityUid uid, GlimmerEventComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args) - { - base.Ended(uid, component, gameRule, args); + _glimmerSystem.Glimmer -= glimmerBurned; - 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; - } + var reportEv = new GlimmerEventEndedEvent(component.SophicReport, glimmerBurned); + RaiseLocalEvent(ref reportEv); } } + diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs index 5b0f24350a..bb8d744d6b 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs @@ -1,9 +1,9 @@ -using Content.Server.Abilities.Psionics; using Content.Server.Chat.Systems; -using Content.Server.Psionics; using Content.Server.StationEvents.Components; using Content.Server.StationEvents.Events; -using Content.Shared.Abilities.Psionics; +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Systems; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; using Content.Shared.GameTicking.Components; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; @@ -20,10 +20,11 @@ namespace Content.Server.Nyanotrasen.StationEvents.Events; internal sealed class MassMindSwapRule : StationEventSystem { [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - [Dependency] private readonly MindSwapPowerSystem _mindSwap = default!; - [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly SharedMindSwapPowerSystem _mindSwap = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly SharedPsionicSystem _psionic = default!; private TimeSpan _warningSoundLength; private ResolvedSoundSpecifier _resolvedWarningSound = String.Empty; @@ -70,17 +71,17 @@ internal sealed class MassMindSwapRule : StationEventSystem psionicActors = new(); var query = EntityQueryEnumerator(); - while (query.MoveNext(out var psion, out _, out _)) + while (query.MoveNext(out var psion, out _, out var mobState)) { - if (_mobStateSystem.IsAlive(psion) && !HasComp(psion)) - { - psionicPool.Add(psion); + if (!_mobStateSystem.IsAlive(psion, mobState) || !_psionic.CanBeTargeted(psion)) + continue; - if (HasComp(psion)) - { - // This is so we don't bother mindswapping NPCs with NPCs. - psionicActors.Add(psion); - } + psionicPool.Add(psion); + + if (HasComp(psion)) + { + // This is so we don't bother mindswapping NPCs with NPCs. + psionicActors.Add(psion); } } @@ -107,13 +108,8 @@ internal sealed class MassMindSwapRule : StationEventSystem -/// Fries tinfoil hats and cages -/// -internal sealed class NoosphericFryRule : StationEventSystem -{ - [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.PlayEntity("/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.PlayEntity("/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, pair.wearer, flammableComponent); - } - } else if (_glimmerSystem.Glimmer > 750) - { - damage *= 3; - if (TryComp(pair.wearer, out var flammableComponent)) - { - flammableComponent.FireStacks += 2; - _flammableSystem.Ignite(pair.wearer, pair.wearer, 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 one bolt of lighting... - _glimmerReactiveSystem.BeamRandomNearProber(reactive, 1, 12); - - // try to anchor if we can - if (!xform.Anchored) - { - var coordinates = xform.Coordinates; - var gridUid = xform.GridUid; - if (!TryComp(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/Nyanotrasen/StationEvents/Events/NoosphericZapRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericZapRule.cs deleted file mode 100644 index 39cdc77ab0..0000000000 --- a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericZapRule.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Content.Server.Popups; -using Content.Server.Psionics; -using Content.Server.StationEvents.Components; -using Content.Server.StationEvents.Events; -using Content.Server.Stunnable; -using Content.Shared.Abilities.Psionics; -using Content.Shared.GameTicking.Components; -using Content.Shared.Mobs.Components; -using Content.Shared.Mobs.Systems; -using Content.Shared.StatusEffect; - -namespace Content.Server.Nyanotrasen.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.TryAddParalyzeDuration(psion, TimeSpan.FromSeconds(5)); - _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/Nyanotrasen/StationEvents/Events/PsionicCatGotYourTongueRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/PsionicCatGotYourTongueRule.cs deleted file mode 100644 index fdc4b259ce..0000000000 --- a/Content.Server/Nyanotrasen/StationEvents/Events/PsionicCatGotYourTongueRule.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Content.Server.Psionics; -using Content.Server.StationEvents.Components; -using Content.Server.StationEvents.Events; -using Content.Shared.Abilities.Psionics; -using Content.Shared.GameTicking.Components; -using Content.Shared.Mobs.Components; -using Content.Shared.Mobs.Systems; -using Content.Shared.StatusEffect; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Player; -using Robust.Shared.Random; - -namespace Content.Server.Nyanotrasen.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/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs index c8149ef2b5..a0845a76c5 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -1,3 +1,4 @@ +using Content.Server._DV.Psionics.Systems; using Content.Server.Administration.Managers; using Content.Server.Atmos.Components; using Content.Server.Body.Components; @@ -13,7 +14,7 @@ using Content.Server.NPC.HTN; using Content.Server.NPC.Systems; using Content.Server.StationEvents.Components; using Content.Server.Speech.Components; -using Content.Shared.Abilities.Psionics; // DeltaV +using Content.Shared._DV.Psionics.Components; // DeltaV using Content.Shared.Body.Components; using Content.Shared.CombatMode; using Content.Shared.CombatMode.Pacification; @@ -71,6 +72,7 @@ public sealed partial class ZombieSystem [Dependency] private readonly NPCSystem _npc = default!; [Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly ISharedPlayerManager _player = default!; + [Dependency] private readonly PsionicSystem _psionic = default!; // DeltaV private static readonly ProtoId InvalidForGlobalSpawnSpellTag = "InvalidForGlobalSpawnSpell"; private static readonly ProtoId CannotSuicideTag = "CannotSuicide"; @@ -144,18 +146,11 @@ public sealed partial class ZombieSystem RemComp(target); RemComp(target); - if (TryComp(target, out var psionic)) // DeltaV - Prevent psionic zombies - { - if (psionic.ActivePowers.Count > 0) - { - foreach (var power in psionic.ActivePowers) - { - RemComp(target, power); - } - psionic.ActivePowers.Clear(); - } - RemComp(target); - } + // DeltaV Start - Prevent Psionic Zombies + RemComp(target); + if (HasComp(target)) + _psionic.MindBreakEntity(target, false, true); + // DeltaV End - Prevent Psionic Zombies //funny voice var accentType = "zombie"; diff --git a/Content.Server/_DV/Abilities/Psionics/FracturedFormPowerSystem.cs b/Content.Server/_DV/Abilities/Psionics/FracturedFormPowerSystem.cs deleted file mode 100644 index b3b1929f73..0000000000 --- a/Content.Server/_DV/Abilities/Psionics/FracturedFormPowerSystem.cs +++ /dev/null @@ -1,346 +0,0 @@ -using Content.Server.Abilities.Psionics; -using Content.Server.Chat.Systems; -using Content.Server.Cloning; -using Content.Server.DoAfter; -using Content.Server.Mind; -using Content.Server.Psionics; -using Content.Server.Station.Systems; -using Content.Shared._DV.Abilities.Psionics; -using Content.Shared._DV.Species; -using Content.Shared.Abilities.Psionics; -using Content.Shared.Actions; -using Content.Shared.Actions.Events; -using Content.Shared.Bed.Sleep; -using Content.Shared.Chat; -using Content.Shared.DoAfter; -using Content.Shared.Examine; -using Content.Shared.Humanoid.Prototypes; -using Content.Shared.Mind.Components; -using Content.Shared.Mobs.Components; -using Content.Shared.Mobs.Systems; -using Content.Shared.Popups; -using Content.Shared.Preferences; -using Content.Shared.Psionics.Events; -using Content.Shared.SSDIndicator; -using Robust.Server.GameObjects; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Timing; - -namespace Content.Server._DV.Abilities.Psionics; - -public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem -{ - [Dependency] private readonly ChatSystem _chat = default!; - [Dependency] private readonly CloningSystem _cloning = default!; - [Dependency] private readonly DoAfterSystem _doAfter = default!; - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly IPrototypeManager _prototype = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly MindSystem _mind = default!; - [Dependency] private readonly MobStateSystem _mobState = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedPopupSystem _popups = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly SleepingSystem _sleeping = default!; - [Dependency] private readonly StationSpawningSystem _stationSpawning = default!; - [Dependency] private readonly StationSystem _station = default!; - [Dependency] private readonly TransformSystem _transform = default!; - - // holy initialize perf? but better for it to happen once than the double dict lookup every tick!! - private EntityQuery _mindContainerQuery; - private EntityQuery _bodyQuery; - private EntityQuery _sleepingQuery; - private EntityQuery _ssdQuery; - private EntityQuery _fracturedQuery; - private EntityQuery _mobStateQuery; - private EntityQuery _forcedSleepQuery; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - SubscribeLocalEvent(OnDispelled); - SubscribeLocalEvent(OnDoAfter); - SubscribeLocalEvent(OnExamine); - - _sleepingQuery = GetEntityQuery(); - _ssdQuery = GetEntityQuery(); - _fracturedQuery = GetEntityQuery(); - _mindContainerQuery = GetEntityQuery(); - _bodyQuery = GetEntityQuery(); - _mobStateQuery = GetEntityQuery(); - _forcedSleepQuery = GetEntityQuery(); - } - - private void OnInit(Entity entity, ref ComponentInit args) - { - var component = entity.Comp; - _actions.AddAction(entity, ref component.FracturedFormActionEntity, component.FracturedFormActionId); - _actions.StartUseDelay(component.FracturedFormActionEntity); - if (TryComp(entity, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.FracturedFormActionEntity; - psionic.ActivePowers.Add(component); - } - - // Next random swap is between 5 to 20 minutes. - component.NextSwap = _timing.CurTime + TimeSpan.FromSeconds(_random.Next(300, 1200)); - - if (HasComp(entity)) return; // Don't generate a new body if we're already part of a network. - var bodyComp = AddComp(entity); - bodyComp.ControllingForm = entity.Owner; - component.Bodies.Add(entity); - GenerateForm(entity); - } - - private void OnShutdown(Entity entity, ref ComponentShutdown args) - { - _actions.RemoveAction(entity.Owner, entity.Comp.FracturedFormActionEntity); - - if (TryComp(entity, out var psionic)) - { - psionic.ActivePowers.Remove(entity.Comp); - } - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - var curTime = _timing.CurTime; - var warnThreshold = TimeSpan.FromSeconds(5); // really should go on the comp - - var ents = EntityQueryEnumerator(); - while (ents.MoveNext(out var uid, out var comp, out var mobState)) - { - // Check sleep warning - if (!comp.SleepWarned && curTime > comp.NextSwap - warnThreshold) - { - comp.SleepWarned = true; - _popups.PopupEntity(Loc.GetString("fractured-form-sleepy"), uid, uid, PopupType.LargeCaution); - _chat.TryEmoteWithChat(uid, "Yawn"); - } - - // Swap check - if (_sleepingQuery.HasComp(uid) || _mobState.IsIncapacitated(uid, mobState) || curTime > comp.NextSwap) - { - Swap((uid, comp)); - } - } - - // Process bodies - var bodies = EntityQueryEnumerator(); - while (bodies.MoveNext(out var uid, out var comp, out var mobState)) - { - // Put to sleep if no sleeping component and no mind - if (!_sleepingQuery.HasComp(uid) && !_mind.GetMind(uid).HasValue) - { - _sleeping.TrySleeping((uid, mobState)); - } - - // Handle SSD indicator - if (_ssdQuery.TryComp(uid, out var ssd) && ssd.IsSSD) - { - ssd.IsSSD = false; - } - - // Cleanup invalid bodies - if (!comp.ControllingForm.IsValid() - || Deleted(comp.ControllingForm) - || !_fracturedQuery.HasComp(comp.ControllingForm)) - { - RemCompDeferred(uid); - } - } - } - - private EntityUid GenerateForm(Entity original) - { - // Form: - // - Same appearance as original - // - Different apperance, still humanoid - // Equipment: - // - Same as original body - // - Nude and helpless - - var xform = Transform(original); - - bool hasClothes = _random.Prob(0.4f); - - EntityUid? newBody; - if (_random.Prob(0.6f) || !_cloning.TryCloning(original, _transform.GetMapCoordinates(original), hasClothes ? original.Comp.CopyClothed : original.Comp.CopyNaked, out newBody)) // Slightly lower chance to copy the original body - { - // Either the dice rolled poorly, or the cloning failed. Either way, make a new body instead. (Or try to) - var validSpecies = new List>(); - var speciesPrototypes = _prototype.EnumeratePrototypes(); - foreach (var proto in speciesPrototypes) - { - var speciesEntityPrototype = _prototype.Index(proto.Prototype); - - if (proto.RoundStart && speciesEntityPrototype.TryGetComponent(out var canBePsionic, Factory) && !SpeciesHiderSystem.IsHidden(proto.ID)) - { - var chance = canBePsionic.Chance; - - if (speciesEntityPrototype.TryGetComponent(out var bonusChance, Factory)) - chance = (chance * bonusChance.Multiplier) + bonusChance.FlatBonus; - - if (chance > 0) - validSpecies.Add(proto.ID); - } - } - var species = _random.Pick(validSpecies); - var character = HumanoidCharacterProfile.RandomWithSpecies(species); - newBody = _stationSpawning.SpawnPlayerMob(xform.Coordinates, hasClothes ? original.Comp.VisitorJob : original.Comp.NakedJob, character, _station.GetOwningStation(original.Owner)); - if (newBody is not { } bodyV || Deleted(bodyV)) - { - Log.Error($"Failed to create a new body for {ToPrettyString(original)}. This is a bug."); - return EntityUid.Invalid; - } - } - - if (newBody is { } body && !Deleted(body)) - { - var bodyComp = AddComp(body); - original.Comp.Bodies.Add(body); - bodyComp.ControllingForm = original.Owner; - return body; - } - - return default!; - } - - private bool IsValidBody(Entity entity, EntityUid body) - { - if (body == entity.Owner) - return false; - if (!entity.Comp.Bodies.Contains(body)) - return false; - if (!_mindContainerQuery.TryComp(body, out var cmind)) - return false; - if (cmind.HasMind) - return false; - if (_forcedSleepQuery.HasComp(body)) - return false; - if (!_mobStateQuery.TryComp(body, out var mobState)) - return false; - if (_mobState.IsIncapacitated(body, mobState)) - return false; - return true; - } - - private bool TryGetValidBody(Entity entity, out EntityUid validBody) - { - foreach (var body in entity.Comp.Bodies) - { - if (!IsValidBody(entity, body)) - continue; - - validBody = body; - return true; - } - validBody = default; - return false; - } - - public bool CanSwap(Entity entity) - { - foreach (var body in entity.Comp.Bodies) - { - if (IsValidBody(entity, body)) - return true; - } - return false; - } - - private void Swap(Entity entity) - { - if (!TryGetValidBody(entity, out var targetBody)) - return; - - _audio.PlayPredicted(entity.Comp.SwapSound, entity, entity); - - // Transfer mind if present - if (_mindContainerQuery.TryComp(entity, out var mindContainer) && mindContainer.Mind.HasValue) - { - _mind.TransferTo(mindContainer.Mind.Value, targetBody); - - } - - // Wake up the target body - if (_sleepingQuery.TryComp(targetBody, out var sleeping)) - { - _sleeping.TryWaking((targetBody, sleeping), false, entity); - } - - // Create new component on target and copy data - var duplicate = AddComp(targetBody); - duplicate.Bodies = entity.Comp.Bodies; - - // Update all body references - foreach (var body in duplicate.Bodies) - { - if (_bodyQuery.TryComp(body, out var bodyComp)) - { - bodyComp.ControllingForm = targetBody; - } - } - - RemCompDeferred(entity, entity.Comp); - } - - private void OnPowerUsed(Entity entity, ref FracturedFormPowerActionEvent args) - { - if (!CanSwap(entity)) - { - _popups.PopupEntity(Loc.GetString("fractured-form-nobodies"), entity, entity, PopupType.Large); - return; - } - - entity.Comp.SleepWarned = true; - _chat.TryEmoteWithChat(entity.Owner, "Yawn", ChatTransmitRange.Normal); - _popups.PopupEntity(Loc.GetString("fractured-form-sleepy"), entity, entity, PopupType.LargeCaution); - var ev = new FracturedFormDoAfterEvent(); - var doAfterArgs = new DoAfterArgs(EntityManager, entity, entity.Comp.ManualSwapTime, ev, entity); - _doAfter.TryStartDoAfter(doAfterArgs, out var doAfterId); - entity.Comp.DoAfter = doAfterId; - _psionics.LogPowerUsed(entity, "fractured form swap", 1, 3); - - args.Handled = true; - } - - private void OnDispelled(Entity entity, ref DispelledEvent args) - { - if (entity.Comp.DoAfter == null) - return; - - _doAfter.Cancel(entity.Comp.DoAfter); - entity.Comp.DoAfter = null; - - args.Handled = true; - } - - private void OnDoAfter(Entity entity, ref FracturedFormDoAfterEvent args) - { - entity.Comp.DoAfter = null; - - if (args.Cancelled || args.Handled) - return; - - _sleeping.TrySleeping(entity.Owner); - } - - private void OnExamine(Entity entity, ref ExaminedEvent args) - { - if (HasComp(entity)) - return; - if (TryComp(args.Examiner, out var fracturedHost) && fracturedHost.Bodies.Contains(entity.Owner)) - args.PushMarkup($"[color=yellow]{Loc.GetString("fractured-form-examine-self", ("ent", entity))}[/color]"); - else - args.PushMarkup($"[color=yellow]{Loc.GetString("fractured-form-ssd", ("ent", entity))}[/color]"); - } -} diff --git a/Content.Server/_DV/Abilities/Psionics/PrecognitionPowerSystem.cs b/Content.Server/_DV/Abilities/Psionics/PrecognitionPowerSystem.cs deleted file mode 100644 index d29b8ab66b..0000000000 --- a/Content.Server/_DV/Abilities/Psionics/PrecognitionPowerSystem.cs +++ /dev/null @@ -1,248 +0,0 @@ -using Content.Server.Chat.Managers; -using Content.Server.DoAfter; -using Content.Server._DV.StationEvents.NextEvent; -using Content.Server.GameTicking; -using Content.Server.Mind; -using Content.Shared.Abilities.Psionics; -using Content.Shared.Actions.Events; -using Content.Shared.Actions; -using Content.Shared.Actions.Components; -using Content.Shared.Chat; -using Content.Shared.DoAfter; -using Content.Shared.Eye.Blinding.Components; -using Content.Shared.Movement.Systems; -using Content.Shared.Popups; -using Content.Shared.Psionics.Events; -using Content.Shared.StatusEffect; -using Content.Shared.Stunnable; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Timing; -using Robust.Shared.Player; - -namespace Content.Server.Abilities.Psionics; - -public sealed class PrecognitionPowerSystem : EntitySystem -{ - [Dependency] private readonly DoAfterSystem _doAfter = default!; - [Dependency] private readonly GameTicker _gameTicker = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; - [Dependency] private readonly IChatManager _chat = default!; - [Dependency] private readonly IComponentFactory _factory = default!; - [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly MovementModStatusSystem _movementMod = default!; - - - /// - /// A map between game rule prototypes and their results to give. - /// - public Dictionary Results = new(); - - public override void Initialize() - { - base.Initialize(); - CachePrecognitionResults(); - - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - SubscribeLocalEvent(OnDoAfter); - SubscribeLocalEvent(OnPrototypesReloaded); - } - - private void OnMapInit(Entity ent, ref MapInitEvent args) - { - _actions.AddAction(ent, ref ent.Comp.PrecognitionActionEntity, ent.Comp.PrecognitionActionId); - _actions.StartUseDelay(ent.Comp.PrecognitionActionEntity); - if (TryComp(ent, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = ent.Comp.PrecognitionActionEntity; - psionic.ActivePowers.Add(ent.Comp); - } - } - - private void OnShutdown(EntityUid uid, PrecognitionPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.PrecognitionActionEntity); - if (TryComp(uid, out var psionic)) - psionic.ActivePowers.Remove(component); - } - - private void OnPowerUsed(EntityUid uid, PrecognitionPowerComponent component, PrecognitionPowerActionEvent args) - { - var ev = new PrecognitionDoAfterEvent(); - var doAfterArgs = new DoAfterArgs(EntityManager, uid, component.UseDelay, ev, uid) - { - BreakOnDamage = true - }; - - // A custom shader for seeing visions would be nice but this will do for now. - _statusEffects.TryAddStatusEffect(uid, "TemporaryBlindness", component.UseDelay, true); - _movementMod.TryUpdateMovementSpeedModDuration(uid, MovementModStatusSystem.PsionicSlowdown, component.UseDelay, 0.5f); - - _doAfter.TryStartDoAfter(doAfterArgs, out var doAfterId); - component.DoAfter = doAfterId; - - var player = _audio.PlayGlobal(component.VisionSound, Filter.Entities(uid), true); - if (player != null) - component.SoundStream = player.Value.Entity; - _psionics.LogPowerUsed(uid, "Precognition"); - args.Handled = true; - } - - /// - /// Upon completion will send a message to the user corrosponding to the next station event to occour. - /// - /// - /// - /// - private void OnDoAfter(EntityUid uid, PrecognitionPowerComponent component, PrecognitionDoAfterEvent args) - { - if (args.Handled) - return; - - if (args.Cancelled) - { - // Need to clean up the applied effects in case of cancel and alert the player. - component.SoundStream = _audio.Stop(component.SoundStream); - _statusEffects.TryRemoveStatusEffect(uid, "TemporaryBlindness"); - _statusEffects.TryRemoveStatusEffect(uid, "SlowedDown"); - - _popup.PopupEntity( - Loc.GetString("psionic-power-precognition-failure-by-damage"), - uid, - uid, - PopupType.SmallCaution); - - if (_actions.GetAction(component.PrecognitionActionEntity) is {} actionData) - { - _actions.SetCooldown( - actionData.Owner, - _gameTicker.RoundDuration(), - _gameTicker.RoundDuration() + TimeSpan.FromSeconds(15)); - } - } - - // Determines the window that will be looked at for events, avoiding events that are too close or too far to be useful. - var minDetectWindow = TimeSpan.FromSeconds(30); - var maxDetectWindow = TimeSpan.FromMinutes(10); - - if (!TryComp(uid, out var actor)) - return; - var nextEvent = FindEarliestNextEvent(minDetectWindow, maxDetectWindow); - LocId? message = nextEvent?.NextEventId is {} nextEventId - ? GetResultMessage(nextEventId) - // A special message given if there is no event within the time window. - : "psionic-power-precognition-no-event-result-message"; - - if (_random.Prob(component.RandomResultChance)) // This will replace the proper result message with a random one occasionaly to simulate some unreliablity. - message = GetRandomResult(); - - if (message is not {} locId) // If there is no message to send don't bother trying to send it. - return; - - // Send a message describing the vision they see - var msg = Loc.GetString(locId); - _chat.ChatMessageToOne(ChatChannel.Server, - msg, - Loc.GetString("chat-manager-server-wrap-message", ("message", msg)), - uid, - false, - actor.PlayerSession.Channel, - Color.PaleVioletRed); - - component.DoAfter = null; - } - - /// - /// Gets the precognition result message corosponding to the passed event id. - /// - /// message string corosponding to the event id passed - private LocId? GetResultMessage(EntProtoId eventId) - { - if (!Results.TryGetValue(eventId, out var result)) - { - Log.Error($"Prototype {eventId} does not have an associated precognitionResult!"); - return null; - } - - return result.Message; - } - - /// - /// - /// The locale message id of a weighted randomly chosen precognition result - public LocId? GetRandomResult() - { - // funny weighted random - var sumOfWeights = 0f; - foreach (var precognitionResult in Results.Values) - sumOfWeights += precognitionResult.Weight; - - sumOfWeights = (float) _random.Next((double) sumOfWeights); - foreach (var precognitionResult in Results.Values) - { - sumOfWeights -= precognitionResult.Weight; - - if (sumOfWeights <= 0f) - return precognitionResult.Message; - } - - Log.Error("Precognition result was not found after weighted pick process!"); - return null; - } - - /// - /// Gets the soonest nextEvent to occur within the window. - /// - /// The earliest reletive time that will be return a nextEvent - /// The latest reletive latest time that will be return a nextEvent - /// Component for the next event to occour if one exists in the window. - private NextEventComponent? FindEarliestNextEvent(TimeSpan minDetectWindow, TimeSpan maxDetectWindow) - { - TimeSpan? earliestNextEventTime = null; - NextEventComponent? earliestNextEvent = null; - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var nextEventComponent)) - { - // Update if the event is the most recent event that isnt too close or too far from happening to be of use - if (nextEventComponent.NextEventTime > _gameTicker.RoundDuration() + minDetectWindow - && nextEventComponent.NextEventTime < _gameTicker.RoundDuration() + maxDetectWindow - && earliestNextEvent == null - || nextEventComponent.NextEventTime < earliestNextEventTime) - { - earliestNextEvent ??= nextEventComponent; - } - } - return earliestNextEvent; - } - - private void OnPrototypesReloaded(PrototypesReloadedEventArgs args) - { - if (!args.WasModified()) - return; - - CachePrecognitionResults(); - } - - private void CachePrecognitionResults() - { - Results.Clear(); - foreach (var prototype in _proto.EnumeratePrototypes()) - { - if (prototype.Abstract) - continue; - - if (!prototype.TryGetComponent(out var precognitionResult, _factory)) - continue; - - Results.Add(prototype.ID, precognitionResult); - } - } -} diff --git a/Content.Server/_DV/Abilities/Psionics/PsionicEruption/PsionicEruptionComponent.cs b/Content.Server/_DV/Abilities/Psionics/PsionicEruption/PsionicEruptionComponent.cs deleted file mode 100644 index 752e2933b5..0000000000 --- a/Content.Server/_DV/Abilities/Psionics/PsionicEruption/PsionicEruptionComponent.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Robust.Shared.Audio; -using Content.Shared.Actions; -using Content.Shared.DoAfter; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared._DV.Abilities.Psionics; // why is this in the Shared namespace but the Server folder? - -[RegisterComponent, AutoGenerateComponentPause] -public sealed partial class PsionicEruptionPowerComponent : Component -{ - [DataField] - public DoAfterId? DoAfter; - [DataField] - public EntProtoId EruptionActionId = "ActionEruption"; - [DataField] - public EntityUid? EruptionActionEntity; - [DataField] - public SoundSpecifier SoundUse = new SoundPathSpecifier("/Audio/Nyanotrasen/Psionics/heartbeat_fast.ogg"); - [DataField] - public SoundSpecifier SoundDetonate = new SoundPathSpecifier("/Audio/Nyanotrasen/Psionics/eruption.ogg"); - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField] - public TimeSpan NextAnnoy = TimeSpan.FromSeconds(5); - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField] - public TimeSpan NextSpark = TimeSpan.MaxValue; - [DataField] - public bool Warned = false; -} diff --git a/Content.Server/_DV/Abilities/Psionics/PsionicEruption/PsionicEruptionSystem.cs b/Content.Server/_DV/Abilities/Psionics/PsionicEruption/PsionicEruptionSystem.cs deleted file mode 100644 index d01a2047e8..0000000000 --- a/Content.Server/_DV/Abilities/Psionics/PsionicEruption/PsionicEruptionSystem.cs +++ /dev/null @@ -1,235 +0,0 @@ -using Content.Server._DV.Psionics; -using Content.Server.Abilities.Psionics; -using Content.Server.DoAfter; -using Content.Server.EUI; -using Content.Server.Explosion.EntitySystems; -using Content.Server.Jittering; -using Content.Server.Lightning; -using Content.Server.Mind; -using Content.Shared._DV.Abilities.Psionics; -using Content.Shared.Abilities.Psionics; -using Content.Shared.Actions; -using Content.Shared.Actions.Events; -using Content.Shared.Body.Components; -using Content.Shared.Body.Systems; -using Content.Shared.DoAfter; -using Content.Shared.Gibbing; -using Content.Shared.Mind; -using Content.Shared.Popups; -using Content.Shared.Psionics.Events; -using Content.Shared.Psionics.Glimmer; -using Robust.Server.Audio; -using Robust.Server.Player; -using Robust.Shared.Audio; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Timing; - -namespace Content.Server._DV.Abilities.Psionics; - -public sealed class PsionicEruptionSystem : EntitySystem -{ - [Dependency] private readonly AudioSystem _audio = default!; - [Dependency] private readonly DoAfterSystem _doAfter = default!; - [Dependency] private readonly EuiManager _eui = default!; - [Dependency] private readonly ExplosionSystem _explosion = default!; - [Dependency] private readonly GlimmerSystem _glimmer = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IPlayerManager _player = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly JitteringSystem _jittering = default!; - [Dependency] private readonly LightningSystem _lightning = default!; - [Dependency] private readonly MindSystem _mind = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly SharedBodySystem _body = default!; - [Dependency] private readonly SharedPopupSystem _popups = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly GibbingSystem _gibbing = default!; - - private static readonly EntProtoId? Sparks = "EffectSparks"; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - - SubscribeLocalEvent(OnDispelled); - SubscribeLocalEvent(OnDoAfter); - } - - private void OnInit(Entity entity, ref ComponentInit args) - { - var component = entity.Comp; - _actions.AddAction(entity, ref component.EruptionActionEntity, component.EruptionActionId); - - if (_actions.GetAction(component.EruptionActionEntity) is { Comp.UseDelay: not null } action) - { - _actions.StartUseDelay(action.Owner); - } - - if (TryComp(entity, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.EruptionActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void ShowWarning(Entity entity) - { - var comp = entity.Comp; - if (comp.Warned) - return; - MindComponent? mind; - if (!_mind.TryGetMind(entity, out var _, out mind)) - return; - if (mind.UserId == null || !_player.TryGetSessionById(mind.UserId.Value, out var client)) - return; - _eui.OpenEui(new EruptionWarningEui(), client); - comp.Warned = true; - } - - private void OnShutdown(Entity entity, ref ComponentShutdown args) - { - _actions.RemoveAction(entity.Owner, entity.Comp.EruptionActionEntity); - - if (TryComp(entity, out var psionic)) - { - psionic.ActivePowers.Remove(entity.Comp); - } - } - - private void OnDispelled(Entity entity, ref DispelledEvent args) - { - if (entity.Comp.DoAfter == null) - return; - - _doAfter.Cancel(entity.Comp.DoAfter); - entity.Comp.DoAfter = null; - - args.Handled = true; - } - - private void OnPowerUsed(Entity entity, ref PsionicEruptionPowerActionEvent args) - { - var component = entity.Comp; - int detonateTime = 10; - int sparkFrom = 5; - - if (_glimmer.GetGlimmerTier(_glimmer.Glimmer) == GlimmerTier.Critical) - { - detonateTime = 30; - sparkFrom = 15; - } - - var ev = new PsionicEruptionDoAfterEvent(); - var doAfterArgs = new DoAfterArgs(EntityManager, entity, detonateTime, ev, entity); - _doAfter.TryStartDoAfter(doAfterArgs, out var doAfterId); - component.DoAfter = doAfterId; - _popups.PopupEntity(Loc.GetString("psionic-eruption-begin", ("user", entity)), entity, PopupType.LargeCaution); // Loc.GetString("psionic-regeneration-begin") - _audio.PlayPvs(component.SoundUse, entity, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f)); - _psionics.LogPowerUsed(entity, "psionic eruption", 2, 4); - // Start Jittering - _jittering.DoJitter(entity, TimeSpan.FromSeconds(sparkFrom), true, 10, 16); - - component.NextSpark = _gameTiming.CurTime + TimeSpan.FromSeconds(sparkFrom); - args.Handled = true; - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - // Occasionally pester users of the Psionic Eruption power to use it. - var t = _gameTiming.CurTime; - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var comp)) - { - // All of the timespan data should really get moved out of the system - ShowWarning((uid, comp)); // I'm a bad coder. - if (comp.DoAfter != null) - { - if (t > comp.NextSpark) - { - if (_glimmer.GetGlimmerTier(_glimmer.Glimmer) == GlimmerTier.Critical && _random.Prob(0.125f)) - { - _lightning.ShootRandomLightnings(uid, 5f, _random.Next(1, 3)); - } - _jittering.DoJitter(uid, TimeSpan.FromSeconds(5), true, 10, 32); - Spawn(Sparks, Transform(uid).Coordinates); - - comp.NextSpark = t + TimeSpan.FromMilliseconds(500); - } - continue; - } - if (t < comp.NextAnnoy) - continue; - var msg = "psionic-eruption-annoy-minimal"; - var msgSize = PopupType.Small; - var minwait = TimeSpan.FromSeconds(60); // How many seconds to wait before the next annoyance. - _glimmer.Glimmer += _random.Next(1, 5); // Increase glimmer by a random amount. - switch (_glimmer.GetGlimmerTier(_glimmer.Glimmer)) - { - case GlimmerTier.Minimal: - msg = "psionic-eruption-annoy-minimal"; - minwait = TimeSpan.FromSeconds(60); - break; - case GlimmerTier.Low: - msg = "psionic-eruption-annoy-low"; - minwait = TimeSpan.FromSeconds(45); - break; - case GlimmerTier.Moderate: - msg = "psionic-eruption-annoy-moderate"; - minwait = TimeSpan.FromSeconds(30); - break; - case GlimmerTier.High: - msg = "psionic-eruption-annoy-high"; - minwait = TimeSpan.FromSeconds(25); - msgSize = PopupType.Medium; - Spawn(Sparks, Transform(uid).Coordinates); - break; - case GlimmerTier.Dangerous: - msg = "psionic-eruption-annoy-dangerous"; - minwait = TimeSpan.FromSeconds(20); - msgSize = PopupType.Large; - Spawn(Sparks, Transform(uid).Coordinates); - break; - case GlimmerTier.Critical: - msg = "psionic-eruption-annoy-critical"; - minwait = TimeSpan.FromSeconds(10); - msgSize = PopupType.LargeCaution; - Spawn(Sparks, Transform(uid).Coordinates); - break; - } - // Prompt the user to use the power. - _popups.PopupEntity(Loc.GetString(msg, ("user", uid)), uid, uid, msgSize); - comp.NextAnnoy = t + minwait + TimeSpan.FromSeconds(_random.Next(0, 10)); // Add a random delay to the next annoyance. - } - } - private void OnDoAfter(Entity entity, ref PsionicEruptionDoAfterEvent args) - { - entity.Comp.DoAfter = null; - - if (args.Cancelled || args.Handled) - return; - - if (!TryComp(entity, out var body)) - return; - - var pos = _transform.GetMapCoordinates(entity); - _gibbing.Gib(entity); - int boom = _glimmer.GetGlimmerTier(_glimmer.Glimmer) switch - { - GlimmerTier.Minimal => 4, - GlimmerTier.Low => 8, - GlimmerTier.Moderate => 12, - GlimmerTier.High => 16, - GlimmerTier.Dangerous => 32, - GlimmerTier.Critical => 64, - _ => 0 - }; - _explosion.QueueExplosion(pos, ExplosionSystem.DefaultExplosionPrototypeId, boom, 1, 5, entity, maxTileBreak: 0); - } -} diff --git a/Content.Server/_DV/Abilities/Psionics/PsychokineticScreamPowerSystem.cs b/Content.Server/_DV/Abilities/Psionics/PsychokineticScreamPowerSystem.cs deleted file mode 100644 index 2dec6d625b..0000000000 --- a/Content.Server/_DV/Abilities/Psionics/PsychokineticScreamPowerSystem.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Content.Shared._DV.Abilities; -using Content.Shared.Abilities.Psionics; -using Content.Shared.Actions; -using Content.Shared.Coordinates; -using Robust.Server.Audio; - -namespace Content.Server._DV.Abilities; - -public sealed partial class PsychokineticScreamPowerSystem : EntitySystem -{ - [Dependency] private readonly AudioSystem _audio = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly ShatterLightsAbilitySystem _shatterLights = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnShatterLightsAction); - } - - private void OnMapInit(Entity ent, ref MapInitEvent args) - { - _actions.AddAction(ent, ref ent.Comp.PsychokineticScreamActionEntity, ent.Comp.ShatterLightsActionId); - _actions.StartUseDelay(ent.Comp.PsychokineticScreamActionEntity); - - if (TryComp(ent, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = ent.Comp.PsychokineticScreamActionEntity; - psionic.ActivePowers.Add(ent.Comp); - } - } - - private void OnShutdown(Entity entity, ref ComponentShutdown args) - { - _actions.RemoveAction(entity.Owner, entity.Comp.PsychokineticScreamActionEntity); - if (TryComp(entity, out var psionic)) - { - psionic.ActivePowers.Remove(entity.Comp); - } - } - - private void OnShatterLightsAction(Entity entity, ref ShatterLightsActionEvent args) - { - if (args.Handled) - return; - - if (entity.Comp.AbilitySound != null) - _audio.PlayPvs(entity.Comp.AbilitySound, entity); - - _shatterLights.ShatterLightsAround(entity.Owner, entity.Comp.Radius, entity.Comp.LineOfSight); - - SpawnAttachedTo(entity.Comp.Effect, entity.Owner.ToCoordinates()); - - _psionics.LogPowerUsed(entity.Owner, "psychokinetic scream", 3, 6); - - args.Handled = true; - } - -} diff --git a/Content.Server/_DV/Abilities/ShatterLightsAbilitySystem.cs b/Content.Server/_DV/Abilities/ShatterLightsAbilitySystem.cs deleted file mode 100644 index d355494c2b..0000000000 --- a/Content.Server/_DV/Abilities/ShatterLightsAbilitySystem.cs +++ /dev/null @@ -1,90 +0,0 @@ - -using Content.Server.Light.EntitySystems; -using Content.Shared._DV.Abilities; -using Content.Shared.Actions; -using Content.Shared.Coordinates; -using Content.Shared.Light.Components; -using Content.Shared.Physics; -using Robust.Server.Audio; -using Robust.Shared.Map; -using Robust.Shared.Physics; -using Robust.Shared.Physics.Systems; -using System.Linq; -using System.Numerics; - -namespace Content.Server._DV.Abilities; - -public sealed partial class ShatterLightsAbilitySystem : EntitySystem -{ - [Dependency] private readonly AudioSystem _audio = default!; - [Dependency] private readonly EntityLookupSystem _lookup = default!; - [Dependency] private readonly PoweredLightSystem _light = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly SharedPhysicsSystem _physics = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - - private readonly HashSet> _lightsInRange = new(); - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnShatterLightsAction); - } - - private void OnMapInit(Entity ent, ref MapInitEvent args) - { - _actions.AddAction(ent, ref ent.Comp.ShatterLightsActionEntity, ent.Comp.ShatterLightsActionId); - _actions.StartUseDelay(ent.Comp.ShatterLightsActionEntity); - } - - private void OnShutdown(Entity entity, ref ComponentShutdown args) - { - _actions.RemoveAction(entity.Owner, entity.Comp.ShatterLightsActionEntity); - } - - private void OnShatterLightsAction(Entity entity, ref ShatterLightsActionEvent args) - { - if (args.Handled) - return; - - if (entity.Comp.AbilitySound != null) - _audio.PlayPvs(entity.Comp.AbilitySound, entity); - - ShatterLightsAround(entity.Owner, entity.Comp.Radius, entity.Comp.LineOfSight, entity.Comp.PenetratingRadius); - - SpawnAttachedTo(entity.Comp.Effect, entity.Owner.ToCoordinates()); - args.Handled = true; - } - - public void ShatterLightsAround(EntityUid center, float range, bool lineOfSight, float penetratingRadius = 0f) - { - var pos = _transform.GetWorldPosition(center); - - // Get all light entities within the specified radius - _lightsInRange.Clear(); - _lookup.GetEntitiesInRange(Transform(center).Coordinates, range, _lightsInRange); - foreach (var light in _lightsInRange) - { - if (lineOfSight) // If LoS is required, test it. - { - var lightPos = _transform.GetWorldPosition(light); - var sqrDistance = Vector2.DistanceSquared(pos, lightPos); - if (sqrDistance > penetratingRadius * penetratingRadius) - { - // If the light is outside the penetrating radius, do a LoS check. - var ray = new CollisionRay(pos, (lightPos - pos).Normalized(), (int)CollisionGroup.Opaque); - var hit = _physics.IntersectRay(_transform.GetMapId(center), ray, MathF.Sqrt(sqrDistance) - 0.5f, returnOnFirstHit: true); - if (hit.Any() && hit.First().Distance != 0) - continue; - } - } - - // If we reach here, the light is unobstructed and within range, break it. - _light.TryDestroyBulb(light, light.Comp); - } - } - -} diff --git a/Content.Server/_DV/Chapel/SacrificialAltarSystem.cs b/Content.Server/_DV/Chapel/SacrificialAltarSystem.cs index d425c57243..7500bf0df2 100644 --- a/Content.Server/_DV/Chapel/SacrificialAltarSystem.cs +++ b/Content.Server/_DV/Chapel/SacrificialAltarSystem.cs @@ -1,11 +1,11 @@ using Content.Server.Bible.Components; -using Content.Shared.Abilities.Psionics; using Content.Shared.Administration.Logs; using Content.Shared.Body.Components; using Content.Shared.Body.Systems; using Content.Shared.Database; using Content.Shared.Gibbing; using Content.Shared._DV.Chapel; +using Content.Shared._DV.Psionics.Components; using Content.Shared.DoAfter; using Content.Shared.EntityTable; using Content.Shared.Humanoid; diff --git a/Content.Server/_DV/EntityEffects/Effects/Psionics/RemovePsionicAbilitiesEntityEffectSystem.cs b/Content.Server/_DV/EntityEffects/Effects/Psionics/RemovePsionicAbilitiesEntityEffectSystem.cs index b0587709c8..258b912b8c 100644 --- a/Content.Server/_DV/EntityEffects/Effects/Psionics/RemovePsionicAbilitiesEntityEffectSystem.cs +++ b/Content.Server/_DV/EntityEffects/Effects/Psionics/RemovePsionicAbilitiesEntityEffectSystem.cs @@ -1,27 +1,20 @@ +using Content.Server._DV.Psionics.Systems; using Content.Shared._DV.EntityEffects.Effects.Psionics; using Content.Shared.EntityEffects; -using Content.Shared.StatusEffect; -using Content.Server.Abilities.Psionics; -using Content.Server.Psionics; - -using Robust.Shared.Prototypes; +using Content.Shared._DV.Psionics.Components; namespace Content.Server._DV.EntityEffects.Effects.Psionics; /// -/// Removes psionic abilities when at least 1u of the reagent is in the system. +/// Removes psionic abilities. /// /// -public sealed partial class RemovePsionicAbilitiesEntityEffectSystem : EntityEffectSystem +public sealed partial class RemovePsionicAbilitiesEntityEffectSystem : EntityEffectSystem { - [Dependency] private readonly PsionicsSystem _psionic = default!; - [Dependency] private readonly PsionicAbilitiesSystem _psionicAbilities = default!; - protected override void Effect(Entity entity, ref EntityEffectEvent args) - { - if (args.Scale != 1f) - return; + [Dependency] private readonly PsionicSystem _psionicSystem = default!; - _psionicAbilities.RemovePsionics(entity); - _psionic.GrantNewPsionicReroll(entity); + protected override void Effect(Entity psionic, ref EntityEffectEvent args) + { + _psionicSystem.MindBreakEntity(psionic.Owner); } } diff --git a/Content.Server/_DV/EntityEffects/Effects/Psionics/RerollPsionicAbilitiesEntityEffectSystem.cs b/Content.Server/_DV/EntityEffects/Effects/Psionics/RerollPsionicAbilitiesEntityEffectSystem.cs deleted file mode 100644 index e2538fa0c1..0000000000 --- a/Content.Server/_DV/EntityEffects/Effects/Psionics/RerollPsionicAbilitiesEntityEffectSystem.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Content.Server.Psionics; -using Content.Shared._DV.EntityEffects.Effects.Psionics; -using Content.Shared.EntityEffects; -using Content.Shared.StatusEffect; -using Content.Server.Abilities.Psionics; -using Robust.Shared.Prototypes; - -namespace Content.Server._DV.EntityEffects.Effects.Psionics; - -/// -/// Rerolls psionic abilities when at least 1u of the reagent is in the system. -/// -/// -public sealed partial class RerollPsionicAbilitiesEntityEffectSystem : EntityEffectSystem -{ - [Dependency] private readonly PsionicsSystem _psionic = default!; - protected override void Effect(Entity entity, ref EntityEffectEvent args) - { - if (args.Scale != 1f) - return; - - _psionic.RerollPsionics(entity, bonusMuliplier: args.Effect.BonusMultiplier); - } -} diff --git a/Content.Server/_DV/EntityEffects/Effects/Psionics/RollPsionicAbilityEntityEffectSystem.cs b/Content.Server/_DV/EntityEffects/Effects/Psionics/RollPsionicAbilityEntityEffectSystem.cs new file mode 100644 index 0000000000..738bf38212 --- /dev/null +++ b/Content.Server/_DV/EntityEffects/Effects/Psionics/RollPsionicAbilityEntityEffectSystem.cs @@ -0,0 +1,19 @@ +using Content.Server._DV.Psionics.Systems; +using Content.Shared._DV.EntityEffects.Effects.Psionics; +using Content.Shared.EntityEffects; +using Content.Shared._DV.Psionics.Components; + +namespace Content.Server._DV.EntityEffects.Effects.Psionics; + +/// +/// Rolls for a new psionic power. +/// +/// +public sealed partial class RollPsionicAbilityEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly PsionicSystem _psionic = default!; + protected override void Effect(Entity psionic, ref EntityEffectEvent args) + { + _psionic.TryRollPsionic(psionic, args.Effect.BonusMultiplier); + } +} diff --git a/Content.Server/_DV/GameTicking/Rules/ParadoxCloneRuleSystem.DeltaV.cs b/Content.Server/_DV/GameTicking/Rules/ParadoxCloneRuleSystem.DeltaV.cs index 75929a86f8..f159861534 100644 --- a/Content.Server/_DV/GameTicking/Rules/ParadoxCloneRuleSystem.DeltaV.cs +++ b/Content.Server/_DV/GameTicking/Rules/ParadoxCloneRuleSystem.DeltaV.cs @@ -1,4 +1,5 @@ -using Content.Server.Psionics; +using Content.Server._DV.Psionics.Systems; +using Content.Shared._DV.Psionics.Components; using Content.Shared.Mind; using Content.Shared.Roles; using Content.Shared.Roles.Components; @@ -10,7 +11,7 @@ namespace Content.Server.GameTicking.Rules; /// public sealed partial class ParadoxCloneRuleSystem { - [Dependency] private readonly PsionicsSystem _psionics = default!; + [Dependency] private readonly PsionicSystem _psionic = default!; [Dependency] private readonly SharedRoleSystem _role = default!; private void FilterTargets(HashSet> minds) @@ -26,6 +27,6 @@ public sealed partial class ParadoxCloneRuleSystem { // guaranteed psionic power var psi = EnsureComp(mob); - _psionics.RollPsionics(mob, psi, false, 100); + _psionic.AddRandomPsionicPower((mob, psi), false); } } diff --git a/Content.Server/_DV/Light/BreakLightsOnSpawnSystem.cs b/Content.Server/_DV/Light/BreakLightsOnSpawnSystem.cs index b240782443..ed33d663c9 100644 --- a/Content.Server/_DV/Light/BreakLightsOnSpawnSystem.cs +++ b/Content.Server/_DV/Light/BreakLightsOnSpawnSystem.cs @@ -1,11 +1,11 @@ -using Content.Server._DV.Abilities; using Content.Shared._DV.Light; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; namespace Content.Server._DV.Light; public sealed partial class BreakLightsOnSpawnSystem : EntitySystem { - [Dependency] private readonly ShatterLightsAbilitySystem _shatterLights = default!; + [Dependency] private readonly PsychokineticScreamPowerSystem _psychokineticScream = default!; public override void Initialize() { @@ -15,6 +15,6 @@ public sealed partial class BreakLightsOnSpawnSystem : EntitySystem private void OnMapInit(Entity entity, ref MapInitEvent args) { - _shatterLights.ShatterLightsAround(entity.Owner, entity.Comp.Radius, entity.Comp.LineOfSight); + _psychokineticScream.ShatterLightsAround(entity.Owner, entity.Comp.Radius, entity.Comp.LineOfSight); } } diff --git a/Content.Server/_DV/Psionics/Systems/PsionicPowers/DispelPowerSystem.cs b/Content.Server/_DV/Psionics/Systems/PsionicPowers/DispelPowerSystem.cs new file mode 100644 index 0000000000..a9b15e6fcf --- /dev/null +++ b/Content.Server/_DV/Psionics/Systems/PsionicPowers/DispelPowerSystem.cs @@ -0,0 +1,36 @@ +using Content.Server.Bible.Components; +using Content.Server.Guardian; +using Content.Shared._DV.Psionics.Events; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; + +namespace Content.Server._DV.Psionics.Systems.PsionicPowers; + +public sealed class DispelPowerSystem : SharedDispelPowerSystem +{ + [Dependency] private readonly GuardianSystem _guardian = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGuardianDispelled); + SubscribeLocalEvent(OnFamiliarDispelled); + } + + private void OnGuardianDispelled(Entity guardian, ref DispelledEvent args) + { + if (TryComp(guardian.Comp.Host, out var host)) + _guardian.ToggleGuardian(guardian.Comp.Host.Value, host); + + DealDispelDamage(guardian, dispeller: args.Dispeller); + args.Handled = true; + } + + private void OnFamiliarDispelled(Entity familiar, ref DispelledEvent args) + { + if (familiar.Comp.Source != null) + EnsureComp(familiar.Comp.Source.Value); + + args.Handled = true; + } +} diff --git a/Content.Server/_DV/Psionics/Systems/PsionicPowers/FracturedFormPowerSystem.cs b/Content.Server/_DV/Psionics/Systems/PsionicPowers/FracturedFormPowerSystem.cs new file mode 100644 index 0000000000..43b3a25a29 --- /dev/null +++ b/Content.Server/_DV/Psionics/Systems/PsionicPowers/FracturedFormPowerSystem.cs @@ -0,0 +1,212 @@ +using Content.Server.Cloning; +using Content.Server.Mind; +using Content.Server.Station.Systems; +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; +using Content.Shared._DV.Species; +using Content.Shared.Bed.Sleep; +using Content.Shared.Body.Components; +using Content.Shared.Humanoid.Prototypes; +using Content.Shared.Mobs.Components; +using Content.Shared.Popups; +using Content.Shared.Preferences; +using Robust.Server.GameObjects; +using Robust.Server.GameStates; +using Robust.Server.Player; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server._DV.Psionics.Systems.PsionicPowers; + +public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly CloningSystem _cloning = default!; + [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly StationSpawningSystem _stationSpawning = default!; + [Dependency] private readonly StationSystem _station = default!; + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly PvsOverrideSystem _pvsOverride = default!; + + // holy initialize performance? but better for it to happen once than the double dict lookup every tick!! + private EntityQuery _bodyQuery; + private EntityQuery _sleepingQuery; + + public override void Initialize() + { + base.Initialize(); + + _bodyQuery = GetEntityQuery(); + _sleepingQuery = GetEntityQuery(); + } + + protected override void OnPowerInit(Entity power, ref MapInitEvent args) + { + base.OnPowerInit(power, ref args); + + // The next random swap is between 5 and 20 minutes. + var randomTime = Random.Next(power.Comp.NextSwapMinTime, power.Comp.NextSwapMaxTime); + power.Comp.NextSwap = Timing.CurTime + randomTime; + power.Comp.NextVoluntarySwap = Timing.CurTime + power.Comp.VoluntarySwapCooldown; + + // Don't generate a new body if we're already part of a network. + if (HasComp(power)) + return; + + // Don't make bodies if there is no body. This is solely for test fails. + if (!HasComp(power)) + return; + + var bodyComp = AddComp(power); + bodyComp.ControllingForm = power.Owner; + power.Comp.Bodies.Add(power); + var body = GenerateForm(power); + // hide the SSD indicator. + if (SsdQuery.TryComp(body, out var ssdComp)) + ssdComp.IsSSD = false; + } + + private EntityUid GenerateForm(Entity original) + { + // Form: + // - Same appearance as original + // - Different apperance, still humanoid + // Equipment: + // - Same as original body + // - Nude and helpless + + var xform = Transform(original); + + var hasGear = Random.Prob(original.Comp.HasGearChance); + + if (Random.Prob(original.Comp.DifferentSpeciesChance) || !_cloning.TryCloning(original, _transform.GetMapCoordinates(original), hasGear ? original.Comp.CopyClothed : original.Comp.CopyNaked, out var newBody)) // Slightly lower chance to copy the original body + { + // Either the dice rolled poorly, or the cloning failed. Either way, make a new body instead. (Or try to) + var validSpecies = new List>(); + var speciesPrototypes = _prototype.EnumeratePrototypes(); + foreach (var proto in speciesPrototypes) + { + var speciesEntityPrototype = _prototype.Index(proto.Prototype); + // If they have the PotentialPsionicComponent, they can be psionic. + if (proto.RoundStart && speciesEntityPrototype.TryGetComponent(out _, Factory) && !SpeciesHiderSystem.IsHidden(proto.ID)) + validSpecies.Add(proto.ID); + } + var species = Random.Pick(validSpecies); + var character = HumanoidCharacterProfile.RandomWithSpecies(species); + newBody = _stationSpawning.SpawnPlayerMob(xform.Coordinates, hasGear ? original.Comp.VisitorJob : original.Comp.NakedJob, character, _station.GetOwningStation(original.Owner)); + if (newBody is not { } bodyV || Deleted(bodyV)) + { + Log.Error($"Failed to create a new body for {ToPrettyString(original)}. This is a bug."); + return EntityUid.Invalid; + } + } + + if (newBody is not { } body || Deleted(body)) + return default!; + + var bodyComp = AddComp(body); + original.Comp.Bodies.Add(body); + bodyComp.ControllingForm = original.Owner; + + if (_player.TryGetSessionByEntity(original, out var session)) + _pvsOverride.AddSessionOverride(body, session); + + Dirty(original); + + return body; + } + + private bool TryGetValidBody(Entity psionic, out EntityUid validBody) + { + foreach (var body in psionic.Comp.Bodies) + { + if (!IsValidBody(psionic, body)) + continue; + + validBody = body; + return true; + } + validBody = default; + return false; + } + + private void Swap(Entity psionic) + { + if (!TryGetValidBody(psionic, out var targetBody)) + return; + + _audio.PlayPvs(psionic.Comp.SwapSound, psionic); + // Transfer mind if present + if (MindContainerQuery.TryComp(psionic, out var mindContainer) && mindContainer.Mind.HasValue) + _mind.TransferTo(mindContainer.Mind.Value, targetBody); + // Wake up the new body + Sleeping.TryWaking(targetBody); + // Remove the action. + Action.RemoveAction(psionic.Comp.ActionEntity); + // Create new component on target and copy data + var duplicate = EnsureComp(targetBody); + duplicate.Bodies = psionic.Comp.Bodies; + // Update all body references + foreach (var body in duplicate.Bodies) + { + if (_bodyQuery.TryComp(body, out var bodyComp)) + bodyComp.ControllingForm = targetBody; + } + + if (_player.TryGetSessionByEntity(targetBody, out var session)) + { + _pvsOverride.AddSessionOverride(psionic, session); + _pvsOverride.RemoveSessionOverride(targetBody, session); + } + + RemCompDeferred(psionic); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + List> swapTargets = []; + + var entities = EntityQueryEnumerator(); + while (entities.MoveNext(out var uid, out var comp, out var mobState)) + { + // Check sleep warning + if (!comp.SleepWarned && Timing.CurTime > comp.NextSwap - comp.WarningTimeBeforeSleep) + { + comp.SleepWarned = true; + Popup.PopupEntity(Loc.GetString("psionic-power-fractured-form-sleepy"), uid, uid, PopupType.LargeCaution); + Chat.TryEmoteWithChat(uid, "Yawn"); + } + // Swap check + if ((_sleepingQuery.HasComp(uid) || MobState.IsIncapacitated(uid, mobState)) && Timing.CurTime > comp.NextVoluntarySwap + || Timing.CurTime > comp.NextSwap) + swapTargets.Add((uid, comp)); + } + + foreach (var target in swapTargets) + { + Swap(target); + } + + // Process bodies + var bodies = EntityQueryEnumerator(); + while (bodies.MoveNext(out var uid, out var comp, out var mobState)) + { + // Put to sleep if no sleeping component and no mind + if (!_sleepingQuery.HasComp(uid) && !_mind.GetMind(uid).HasValue && !FracturedQuery.HasComp(uid)) + Sleeping.TrySleeping((uid, mobState)); + // Cleanup invalid bodies + if (!comp.ControllingForm.IsValid() + || Deleted(comp.ControllingForm) + || !FracturedQuery.HasComp(comp.ControllingForm)) + { + RemCompDeferred(uid); + } + } + } +} diff --git a/Content.Server/_DV/Psionics/Systems/PsionicPowers/MassSleepPowerSystem.cs b/Content.Server/_DV/Psionics/Systems/PsionicPowers/MassSleepPowerSystem.cs new file mode 100644 index 0000000000..44a1829bbb --- /dev/null +++ b/Content.Server/_DV/Psionics/Systems/PsionicPowers/MassSleepPowerSystem.cs @@ -0,0 +1,82 @@ +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared._DV.Psionics.Events.PowerDoAfterEvents; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; +using Content.Shared.Bed.Sleep; +using Content.Shared.DoAfter; +using Content.Shared.Movement.Systems; +using Content.Shared.Popups; +using Robust.Shared.Prototypes; + +namespace Content.Server._DV.Psionics.Systems.PsionicPowers; + +public sealed class MassSleepPowerSystem : SharedMassSleepPowerSystem +{ + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly Shared.StatusEffectNew.StatusEffectsSystem _statusEffects = default!; + [Dependency] private readonly MovementModStatusSystem _movementMod = default!; + + public static readonly EntProtoId MassSleepSlowdown = "MassSleepSlowdownStatusEffect"; + private EntityQuery _psionicDetectorQuery; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMassSleepDoAfter); + _psionicDetectorQuery = GetEntityQuery(); + } + + protected override void OnPowerUsed(Entity psionic, ref MassSleepPowerActionEvent args) + { + var ev = new MassSleepDoAfterEvent(); + var doAfterArgs = new DoAfterArgs(EntityManager, args.Performer, psionic.Comp.UseDelay, ev, args.Performer) + { + BreakOnDamage = true, + }; + + if (!DoAfter.TryStartDoAfter(doAfterArgs, out var doAfterId)) + return; + + foreach (var target in _lookup.GetEntitiesInRange(args.Performer, psionic.Comp.WarningRadius)) + { + // TODO: Metapsionic Users won't see this message, as it would otherwise overlap their usual power detected popup. Fix it. + if (args.Performer != target && Psionic.CanBeTargeted(target) && !_psionicDetectorQuery.HasComp(target)) + { + Popup.PopupEntity(Loc.GetString("psionic-power-mass-sleep-warning"), + target, + target, + PopupType.LargeCaution); + } + } + + _movementMod.TryUpdateMovementSpeedModDuration(args.Performer, MassSleepSlowdown, psionic.Comp.UseDelay, 0.5f); + + psionic.Comp.SaveDoAfterId(doAfterId.Value); + + Dirty(psionic); + AfterPowerUsed(psionic, args.Performer); + } + + private void OnMassSleepDoAfter(Entity psionic, ref MassSleepDoAfterEvent args) + { + if (args.Handled) + return; + args.Handled = true; + psionic.Comp.RemoveSavedDoAfterId(); + Dirty(psionic); + + if (args.Cancelled) + { + _statusEffects.TryRemoveStatusEffect(psionic, MassSleepSlowdown); + return; + } + + foreach (var target in _lookup.GetEntitiesInRange(args.User, psionic.Comp.Radius)) + { + if (args.Used != target && Psionic.CanBeTargeted(target)) + _statusEffects.TryUpdateStatusEffectDuration(target, SleepingSystem.StatusEffectForcedSleeping, psionic.Comp.Duration); + } + } +} diff --git a/Content.Server/_DV/Psionics/Systems/PsionicPowers/MindSwapPowerSystem.cs b/Content.Server/_DV/Psionics/Systems/PsionicPowers/MindSwapPowerSystem.cs new file mode 100644 index 0000000000..109a4cd779 --- /dev/null +++ b/Content.Server/_DV/Psionics/Systems/PsionicPowers/MindSwapPowerSystem.cs @@ -0,0 +1,30 @@ +using Content.Server.Ghost; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; + +namespace Content.Server._DV.Psionics.Systems.PsionicPowers; + +public sealed class MindSwapPowerSystem : SharedMindSwapPowerSystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGhostAttempt); + } + + private void OnGhostAttempt(GhostAttemptHandleEvent args) + { + if (args.Handled) + return; + + // If you're able to swap back to your original body, you should swap back before you ghost. + if (TryComp(args.Mind.CurrentEntity, out var component) + && Action.GetAction(component.ActionEntity) is { } action + && action.Comp.AttachedEntity is not null) + { + args.Result = false; + args.Handled = true; + } + } +} diff --git a/Content.Server/_DV/Psionics/Systems/PsionicPowers/NoosphericZapPowerSystem.cs b/Content.Server/_DV/Psionics/Systems/PsionicPowers/NoosphericZapPowerSystem.cs new file mode 100644 index 0000000000..d018abea50 --- /dev/null +++ b/Content.Server/_DV/Psionics/Systems/PsionicPowers/NoosphericZapPowerSystem.cs @@ -0,0 +1,24 @@ +using Content.Server.Electrocution; +using Content.Server.Lightning; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; + +namespace Content.Server._DV.Psionics.Systems.PsionicPowers; + +public sealed class NoosphericZapPowerSystem : SharedNoosphericZapPowerSystem +{ + [Dependency] private readonly LightningSystem _lightning = default!; + [Dependency] private readonly ElectrocutionSystem _electrocution = default!; + + protected override void OnPowerUsed(Entity psionic, ref NoosphericZapPowerActionEvent args) + { + if (!Psionic.CanBeTargeted(args.Target, hasAggressor: args.Performer)) + return; + + _lightning.ShootLightning(args.Performer, args.Target, psionic.Comp.LightningPrototpyeId); + _electrocution.TryDoElectrocution(args.Target, args.Performer, psionic.Comp.ShockDamage, psionic.Comp.StunDuration, true); + + AfterPowerUsed(psionic, args.Performer); + } +} diff --git a/Content.Server/_DV/Psionics/Systems/PsionicPowers/PrecognitionPowerSystem.cs b/Content.Server/_DV/Psionics/Systems/PsionicPowers/PrecognitionPowerSystem.cs new file mode 100644 index 0000000000..9201cffa5a --- /dev/null +++ b/Content.Server/_DV/Psionics/Systems/PsionicPowers/PrecognitionPowerSystem.cs @@ -0,0 +1,194 @@ +using Content.Server._DV.StationEvents.NextEvent; +using Content.Server.Chat.Managers; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerDoAfterEvents; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; +using Content.Shared.Abilities.Psionics; +using Content.Shared.Chat; +using Content.Shared.Popups; +using Robust.Server.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server._DV.Psionics.Systems.PsionicPowers; + +/// +/// This system lets a psionic user foretell the next event with some inconsistency. +/// +public sealed class PrecognitionPowerSystem : SharedPrecognitionPowerSystem +{ + [Dependency] private readonly IChatManager _chat = default!; + [Dependency] private readonly IComponentFactory _factory = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + /// + /// A map between game rule prototypes and their results to give. + /// + public Dictionary Results = new(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnPrototypesReloaded); + + CachePrecognitionResults(); + } + + /// + /// Send a message to the user about the next station event. + /// + /// The source of the psionic usage. + /// The doAfter event. + private void OnDoAfter(Entity psionic, ref PrecognitionDoAfterEvent args) + { + if (args.Handled) + return; + + args.Handled = true; + psionic.Comp.RemoveSavedDoAfterId(); + Dirty(psionic); + + if (args.Cancelled) + { + // Need to clean up the applied effects in case of cancel and alert the player. + // TODO: Port over the TemporaryBlindness effect to the new StatusEffectSystem. + // When Upstream ports it over, replace this with it. + psionic.Comp.SoundStream = Audio.Stop(psionic.Comp.SoundStream); + StatusEffects.TryRemoveStatusEffect(args.User, "TemporaryBlindness"); + Movement.TryUpdateMovementSpeedModDuration(args.User, PrecognitionSlowdown, TimeSpan.Zero, 0.5f); + + Popup.PopupEntity( + Loc.GetString("psionic-power-precognition-failure-by-damage"), + args.User, + args.User, + PopupType.SmallCaution); + + if (Action.GetAction(psionic.Comp.ActionEntity) is {} actionData) + Action.SetCooldown(actionData.Owner, _timing.CurTime, _timing.CurTime + psionic.Comp.CancellationCooldown); + return; + } + + // Determines the window that will be looked at for events, avoiding events that are too close or too far to be useful. + + if (!_player.TryGetSessionByEntity(args.User, out var session)) + return; + + var nextEvent = FindEarliestNextEvent(psionic.Comp.MinEventTimeDistance, psionic.Comp.MaxEventTimeDistance); + + LocId? message = nextEvent?.NextEventId is {} nextEventId + ? GetResultMessage(nextEventId) + // A special message given if there is no event within the time window. + : "psionic-power-precognition-no-event-result-message"; + + if (_random.Prob(psionic.Comp.RandomResultChance)) // This will replace the proper result message with a random one occasionally to simulate some unreliability. + message = GetRandomResult(); + + if (message is not {} locId) // If there is no message to send, don't bother trying to send it. + return; + + // Send a message describing the vision they see + var msg = Loc.GetString(locId); + _chat.ChatMessageToOne(ChatChannel.Server, + msg, + Loc.GetString("chat-manager-server-wrap-message", ("message", msg)), + psionic, + false, + session.Channel, + Color.PaleVioletRed); + } + + /// + /// Gets the precognition result message corosponding to the passed event id. + /// + /// message string corosponding to the event id passed + private LocId? GetResultMessage(EntProtoId eventId) + { + if (Results.TryGetValue(eventId, out var result)) + return result.Message; + + Log.Error($"Prototype {eventId} does not have an associated precognitionResult!"); + return null; + + } + + /// + /// + /// The locale message id of a weighted randomly chosen precognition result + public LocId? GetRandomResult() + { + // funny weighted random + var sumOfWeights = 0f; + foreach (var precognitionResult in Results.Values) + { + sumOfWeights += precognitionResult.Weight; + } + + sumOfWeights = (float) _random.Next(sumOfWeights); + foreach (var precognitionResult in Results.Values) + { + sumOfWeights -= precognitionResult.Weight; + + if (sumOfWeights <= 0f) + return precognitionResult.Message; + } + + Log.Error("Precognition result was not found after weighted pick process!"); + return null; + } + + /// + /// Gets the soonest nextEvent to occur within the window. + /// + /// The earliest reletive time that will be return a nextEvent + /// The latest reletive latest time that will be return a nextEvent + /// Component for the next event to occour if one exists in the window. + private NextEventComponent? FindEarliestNextEvent(TimeSpan minDetectWindow, TimeSpan maxDetectWindow) + { + TimeSpan? earliestNextEventTime = null; + NextEventComponent? earliestNextEvent = null; + var query = EntityQueryEnumerator(); + + var windowStart = _timing.CurTime + minDetectWindow; + var windowEnd = _timing.CurTime + maxDetectWindow; + + while (query.MoveNext(out var nextEventComponent)) + { + // Update if the event is the most recent event that isn't too close or too far from happening to be of use + if (windowStart < nextEventComponent.NextEventTime && nextEventComponent.NextEventTime < windowEnd + && earliestNextEvent == null + || nextEventComponent.NextEventTime < earliestNextEventTime) + { + earliestNextEvent ??= nextEventComponent; + } + } + return earliestNextEvent; + } + + + private void OnPrototypesReloaded(PrototypesReloadedEventArgs args) + { + if (!args.WasModified()) + return; + + CachePrecognitionResults(); + } + + private void CachePrecognitionResults() + { + Results.Clear(); + foreach (var prototype in _prototype.EnumeratePrototypes()) + { + if (prototype.Abstract + || !prototype.TryGetComponent(out var precognitionResult, _factory)) + continue; + + Results.Add(prototype.ID, precognitionResult); + } + } +} diff --git a/Content.Server/_DV/Psionics/Systems/PsionicPowers/PsionicEruptionPowerSystem.cs b/Content.Server/_DV/Psionics/Systems/PsionicPowers/PsionicEruptionPowerSystem.cs new file mode 100644 index 0000000000..f5bf8bedb5 --- /dev/null +++ b/Content.Server/_DV/Psionics/Systems/PsionicPowers/PsionicEruptionPowerSystem.cs @@ -0,0 +1,194 @@ +using Content.Server.DoAfter; +using Content.Server.EUI; +using Content.Server.Explosion.EntitySystems; +using Content.Server.Jittering; +using Content.Server.Lightning; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared._DV.Psionics.Events.PowerDoAfterEvents; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; +using Content.Shared.Body.Components; +using Content.Shared.DoAfter; +using Content.Shared.Gibbing; +using Content.Shared.Popups; +using Content.Shared.Psionics.Glimmer; +using Robust.Server.Audio; +using Robust.Server.Player; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server._DV.Psionics.Systems.PsionicPowers; + +public sealed class PsionicEruptionSystem : BasePsionicPowerSystem +{ + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly DoAfterSystem _doAfter = default!; + [Dependency] private readonly EuiManager _eui = default!; + [Dependency] private readonly ExplosionSystem _explosion = default!; + [Dependency] private readonly GibbingSystem _gibbing = default!; + [Dependency] private readonly GlimmerSystem _glimmer = default!; + [Dependency] private readonly JitteringSystem _jittering = default!; + [Dependency] private readonly LightningSystem _lightning = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + private static readonly EntProtoId? Sparks = "EffectSparks"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDoAfter); + } + + protected override void OnPowerInit(Entity power, ref MapInitEvent args) + { + base.OnPowerInit(power, ref args); + + if (!_player.TryGetSessionByEntity(power, out var session)) + return; + + _eui.OpenEui(new EruptionWarningEui(), session); + power.Comp.NextAnnoy = Timing.CurTime + TimeSpan.FromSeconds(60); // Minute grace period + } + + protected override void OnPowerUsed(Entity psionic, ref PsionicEruptionPowerActionEvent args) + { + TimeSpan detonateTime; + + if (_glimmer.GetGlimmerTier(_glimmer.Glimmer) == GlimmerTier.Critical) + detonateTime = psionic.Comp.MaxDetonateDelay; + else + detonateTime = psionic.Comp.MinDetonateDelay; + + var sparkFrom = detonateTime / 2; + + // Start the DoAfter. + var doAfterArgs = new DoAfterArgs(EntityManager, args.Performer, detonateTime, new PsionicEruptionDoAfterEvent(), args.Performer); + if (!_doAfter.TryStartDoAfter(doAfterArgs, out var doAfterId)) + return; + + psionic.Comp.SaveDoAfterId(doAfterId.Value); // Save the DoAfterID to reference it later. + + var message = Loc.GetString("psionic-eruption-begin", ("user", args.Performer)); + Popup.PopupEntity(message, args.Performer, PopupType.LargeCaution); + _audio.PlayPvs(psionic.Comp.SoundUse, args.Performer, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f)); + _jittering.DoJitter(args.Performer, sparkFrom, true, 10, 16); + + psionic.Comp.NextSpark = Timing.CurTime + sparkFrom; + AfterPowerUsed(psionic, args.Performer); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + // Occasionally pester users of the Psionic Eruption power to use it. + var curTime = Timing.CurTime; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var psionic, out var comp)) + { + if (comp.GetDoAfterId() != null) + { + if (curTime >= comp.NextSpark) + CauseSparks(psionic, comp, curTime); + continue; + } + + if (curTime < comp.NextAnnoy) + continue; + + _glimmer.Glimmer += _random.Next(1, 5); // Increase glimmer by a random amount. + + var msg = GetSeverityMessage(psionic, out var messageSize, out var minWait); + // Prompt the user to use the power. + Popup.PopupEntity(Loc.GetString(msg, ("user", psionic)), psionic, psionic, messageSize); + comp.NextAnnoy = curTime + minWait + TimeSpan.FromSeconds(_random.Next(0, 10)); // Add a random delay to the next annoyance. + } + } + + private string GetSeverityMessage(EntityUid psionic, out PopupType messageSize, out TimeSpan minWait) + { + string message; + switch (_glimmer.GetGlimmerTier(_glimmer.Glimmer)) + { + case GlimmerTier.Minimal: + default: + message = "psionic-eruption-annoy-minimal"; + minWait = TimeSpan.FromSeconds(60); + messageSize = PopupType.Small; + break; + case GlimmerTier.Low: + message = "psionic-eruption-annoy-low"; + minWait = TimeSpan.FromSeconds(45); + messageSize = PopupType.Small; + break; + case GlimmerTier.Moderate: + message = "psionic-eruption-annoy-moderate"; + minWait = TimeSpan.FromSeconds(30); + messageSize = PopupType.Small; + break; + case GlimmerTier.High: + message = "psionic-eruption-annoy-high"; + minWait = TimeSpan.FromSeconds(25); + messageSize = PopupType.Medium; + Spawn(Sparks, Transform(psionic).Coordinates); + break; + case GlimmerTier.Dangerous: + message = "psionic-eruption-annoy-dangerous"; + minWait = TimeSpan.FromSeconds(20); + messageSize = PopupType.Large; + Spawn(Sparks, Transform(psionic).Coordinates); + break; + case GlimmerTier.Critical: + message = "psionic-eruption-annoy-critical"; + minWait = TimeSpan.FromSeconds(10); + messageSize = PopupType.LargeCaution; + Spawn(Sparks, Transform(psionic).Coordinates); + break; + } + + return message; + } + + private void CauseSparks(EntityUid psionic, PsionicEruptionPowerComponent comp, TimeSpan curTime) + { + if (_glimmer.GetGlimmerTier(_glimmer.Glimmer) == GlimmerTier.Critical && _random.Prob(0.125f)) + { + _lightning.ShootRandomLightnings(psionic, 5f, _random.Next(1, 3)); + } + _jittering.DoJitter(psionic, TimeSpan.FromSeconds(5), true, 10, 32); + Spawn(Sparks, Transform(psionic).Coordinates); + + comp.NextSpark = curTime + TimeSpan.FromMilliseconds(500); + } + + private void OnDoAfter(Entity psionic, ref PsionicEruptionDoAfterEvent args) + { + if (args.Handled) + return; + args.Handled = true; + psionic.Comp.RemoveSavedDoAfterId(); + + if (args.Cancelled || !TryComp(args.User, out var body)) + return; + + var pos = _transform.GetMapCoordinates(args.User); + _gibbing.Gib(args.User, user: args.User); + + int boom = _glimmer.GetGlimmerTier(_glimmer.Glimmer) switch + { + GlimmerTier.Minimal => 4, + GlimmerTier.Low => 8, + GlimmerTier.Moderate => 12, + GlimmerTier.High => 16, + GlimmerTier.Dangerous => 32, + GlimmerTier.Critical => 64, + _ => 0 + }; + _explosion.QueueExplosion(pos, ExplosionSystem.DefaultExplosionPrototypeId, boom, 1, 5, psionic, maxTileBreak: 0); + } +} diff --git a/Content.Server/_DV/Psionics/Systems/PsionicPowers/PyrokinesisPowerSystem.cs b/Content.Server/_DV/Psionics/Systems/PsionicPowers/PyrokinesisPowerSystem.cs new file mode 100644 index 0000000000..435bdee56f --- /dev/null +++ b/Content.Server/_DV/Psionics/Systems/PsionicPowers/PyrokinesisPowerSystem.cs @@ -0,0 +1,25 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; +using Content.Shared.Atmos.Components; +using Content.Shared.Popups; + +namespace Content.Server._DV.Psionics.Systems.PsionicPowers; + +public sealed class PyrokinesisPowerSystem : SharedPyrokinesisPowerSystem +{ + [Dependency] private readonly FlammableSystem _flammableSystem = default!; + + protected override void OnPowerUsed(Entity psionic, ref PyrokinesisPowerActionEvent args) + { + if (!Psionic.CanBeTargeted(args.Target, hasAggressor: args.Performer) || !TryComp(args.Target, out var flammableComponent)) + return; + + flammableComponent.FireStacks += psionic.Comp.AddedFirestacks; + _flammableSystem.Ignite(args.Target, args.Target); + Popup.PopupEntity(Loc.GetString("pyrokinesis-power-used", ("target", args.Target)), args.Target, args.Performer, PopupType.LargeCaution); + + AfterPowerUsed(psionic, args.Performer); + } +} diff --git a/Content.Server/_DV/Psionics/Systems/PsionicPowers/TelegnosisPowerSystem.cs b/Content.Server/_DV/Psionics/Systems/PsionicPowers/TelegnosisPowerSystem.cs new file mode 100644 index 0000000000..2efd9a4fd7 --- /dev/null +++ b/Content.Server/_DV/Psionics/Systems/PsionicPowers/TelegnosisPowerSystem.cs @@ -0,0 +1,71 @@ +using System.Numerics; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Body.Systems; +using Content.Server.Disposal.Unit; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; +using Content.Shared.Atmos; +using Content.Shared.Body.Components; +using Content.Shared.Mech.Components; +using Content.Shared.Medical.Cryogenics; +using Content.Shared.Mindshield.Components; +using Content.Shared.Popups; +using Content.Shared.Storage.Components; + +namespace Content.Server._DV.Psionics.Systems.PsionicPowers; + +public sealed class TelegnosisPowerSystem : SharedTelegnosisPowerSystem +{ + [Dependency] private readonly AtmosphereSystem _atmos = default!; + [Dependency] private readonly SharedMindSwapPowerSystem _mindSwap = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInhaleLocation, after: [typeof(InsideCryoPodComponent), typeof(InternalsComponent), typeof(BeingDisposedComponent), typeof(InsideEntityStorageComponent), typeof(MechPilotComponent)]); + } + + // This needs to be here on serverside, because mindswap CANNOT be predicted. + // The logic for transferring minds is server-side only. If we don't put this here, it'll look bad for the person. + protected override void OnPowerUsed(Entity psionic, ref TelegnosisPowerActionEvent args) + { + // TODO: Fix this. MindSwapSystem cannot handle popups when called from serverside while the performer is the cause. + if (HasComp(psionic)) + { + Popup.PopupEntity(Loc.GetString("psionic-power-mindswap-own-mindshield"), psionic, psionic, PopupType.SmallCaution); + return; + } + + var projection = Spawn(psionic.Comp.Prototype, Transform(psionic).Coordinates); + + _transform.AttachToGridOrMap(projection); + if (!_mindSwap.SwapMinds(args.Performer, projection)) + { + // If swap didn't work out, delete the spawned projection. + QueueDel(projection); + return; + } + + AfterPowerUsed(psionic, args.Performer); + } + + private void OnInhaleLocation(Entity entity, ref InhaleLocationEvent args) + { + var sensorUid = GetCasterProjection(entity); + if (sensorUid == default) + return; + // Determine the distance to the sensor, this will be used to dilute the amount of air we take in. + var sensorPosition = _transform.GetWorldPosition(sensorUid); + var projectionPosition = _transform.GetWorldPosition(entity); + // A linear curve from 1.0 at 7 tiles away, to 0 at 57 tiles away + var distance = Vector2.Distance(sensorPosition, projectionPosition); + float gasMult = Math.Clamp(1f - (distance - 7f) / 50f, 0f, 1f); + args.Gas = (args.Gas ?? _atmos.GetContainingMixture(entity.Owner, excite: true))?.RemoveVolume(Atmospherics.BreathVolume * gasMult); + if (args.Gas == null) + return; + args.Gas.Volume = Math.Min(args.Gas.Volume, Atmospherics.BreathVolume); + } +} diff --git a/Content.Server/_DV/Psionics/Systems/PsionicSystem.cs b/Content.Server/_DV/Psionics/Systems/PsionicSystem.cs new file mode 100644 index 0000000000..2edd50d1e7 --- /dev/null +++ b/Content.Server/_DV/Psionics/Systems/PsionicSystem.cs @@ -0,0 +1,27 @@ +using Content.Server._DV.Psionics.UI; +using Content.Server.EUI; +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Systems; +using Content.Shared.GameTicking; + +namespace Content.Server._DV.Psionics.Systems; + +public sealed partial class PsionicSystem : SharedPsionicSystem +{ + [Dependency] private readonly EuiManager _euiManager = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPlayerSpawnComplete); + + InitializeItems(); + } + + private void OnPlayerSpawnComplete(Entity potPsionic, ref PlayerSpawnCompleteEvent args) + { + if (RollChance(potPsionic)) + _euiManager.OpenEui(new AcceptPsionicsEui(potPsionic, this), args.Player); + } +} diff --git a/Content.Server/_DV/Psionics/Systems/SubSystems/PsionicSystem.Items.cs b/Content.Server/_DV/Psionics/Systems/SubSystems/PsionicSystem.Items.cs new file mode 100644 index 0000000000..3e68fbe96b --- /dev/null +++ b/Content.Server/_DV/Psionics/Systems/SubSystems/PsionicSystem.Items.cs @@ -0,0 +1,44 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Events; +using Content.Shared.Atmos.Components; +using Content.Shared.Damage.Systems; +using Content.Shared.Inventory; +using Content.Shared.Popups; + +namespace Content.Server._DV.Psionics.Systems; + +public sealed partial class PsionicSystem +{ + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly FlammableSystem _flammable = default!; + + public void InitializeItems() + { + SubscribeLocalEvent>(OnFry); + } + + private void OnFry(Entity gear, ref InventoryRelayedEvent args) + { + if (gear.Comp.CanBeFried) + { + Popup.PopupEntity(Loc.GetString("psionic-burns-up", ("item", gear)), gear.Owner, PopupType.MediumCaution); + Audio.PlayEntity(gear.Comp.FrySound, gear, gear); + Spawn("Ash", Transform(gear).Coordinates); + QueueDel(gear); + } + else + { + Popup.PopupEntity(Loc.GetString("psionic-burn-resist", ("item", gear)), gear.Owner, PopupType.MediumCaution); + Audio.PlayEntity(gear.Comp.FrySound, gear, gear); + } + + _damageable.TryChangeDamage(args.Owner, args.Args.Damage); + + if (!TryComp(args.Owner, out var flammable)) + return; + + _flammable.AdjustFireStacks(args.Owner, args.Args.FireStacks, flammable); + _flammable.Ignite(args.Owner, gear, flammable); + } +} diff --git a/Content.Server/_DV/Psionics/UI/AcceptPsionicsEui.cs b/Content.Server/_DV/Psionics/UI/AcceptPsionicsEui.cs new file mode 100644 index 0000000000..041ed4ead1 --- /dev/null +++ b/Content.Server/_DV/Psionics/UI/AcceptPsionicsEui.cs @@ -0,0 +1,25 @@ +using Content.Shared.Psionics; +using Content.Shared.Eui; +using Content.Server.EUI; +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Systems; + +namespace Content.Server._DV.Psionics.UI; + +public sealed class AcceptPsionicsEui(Entity potPsionic, SharedPsionicSystem psionicsSystem, bool midRound = false) : BaseEui +{ + public override void HandleMessage(EuiMessageBase message) + { + base.HandleMessage(message); + + if (message is not AcceptPsionicsChoiceMessage choice || + choice.Button == AcceptPsionicsUiButton.Deny) + { + Close(); + return; + } + + psionicsSystem.AddRandomPsionicPower(potPsionic, midRound); + Close(); + } +} diff --git a/Content.Server/_DV/Psionics/EruptionWarningEui.cs b/Content.Server/_DV/Psionics/UI/EruptionWarningEui.cs similarity index 100% rename from Content.Server/_DV/Psionics/EruptionWarningEui.cs rename to Content.Server/_DV/Psionics/UI/EruptionWarningEui.cs diff --git a/Content.Server/_DV/StationEvents/Components/DebrisSpawnerRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/DebrisSpawnerRuleComponent.cs index 0f89e161fc..3f8a8b5443 100644 --- a/Content.Server/_DV/StationEvents/Components/DebrisSpawnerRuleComponent.cs +++ b/Content.Server/_DV/StationEvents/Components/DebrisSpawnerRuleComponent.cs @@ -4,13 +4,13 @@ * See AGPLv3.txt for details. */ -using Content.Server.StationEvents.Events; +using Content.Server._DV.StationEvents.GameRules; -namespace Content.Server.StationEvents.Components; +namespace Content.Server._DV.StationEvents.Components; /// /// Spawns random debris in space around a loaded grid. -/// Requires . +/// Requires . /// [RegisterComponent, Access(typeof(DebrisSpawnerRule))] public sealed partial class DebrisSpawnerRuleComponent : Component diff --git a/Content.Server/_DV/StationEvents/Components/FugitiveRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/FugitiveRuleComponent.cs index ad3feeb012..741250067a 100644 --- a/Content.Server/_DV/StationEvents/Components/FugitiveRuleComponent.cs +++ b/Content.Server/_DV/StationEvents/Components/FugitiveRuleComponent.cs @@ -1,10 +1,9 @@ +using Content.Server._DV.StationEvents.GameRules; using Content.Shared.Dataset; -using Content.Server.StationEvents.Events; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -using Robust.Shared.Utility; -namespace Content.Server.StationEvents.Components; +namespace Content.Server._DV.StationEvents.Components; /// /// Makes a GALPOL announcement and creates a report some time after an antag spawns. diff --git a/Content.Server/_DV/StationEvents/Components/GlimmerMobRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/GlimmerMobRuleComponent.cs index ac008322bc..73a65219ac 100644 --- a/Content.Server/_DV/StationEvents/Components/GlimmerMobRuleComponent.cs +++ b/Content.Server/_DV/StationEvents/Components/GlimmerMobRuleComponent.cs @@ -1,4 +1,4 @@ -using Content.Server._DV.StationEvents.Events; +using Content.Server._DV.StationEvents.GameRules; using Content.Shared.Psionics.Glimmer; using Robust.Shared.Prototypes; diff --git a/Content.Server/_DV/StationEvents/Components/LoadFarGridRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/LoadFarGridRuleComponent.cs index 2c67e491f1..68bbffdc21 100644 --- a/Content.Server/_DV/StationEvents/Components/LoadFarGridRuleComponent.cs +++ b/Content.Server/_DV/StationEvents/Components/LoadFarGridRuleComponent.cs @@ -1,7 +1,7 @@ -using Content.Server.StationEvents.Events; +using Content.Server._DV.StationEvents.GameRules; using Robust.Shared.Utility; -namespace Content.Server.StationEvents.Components; +namespace Content.Server._DV.StationEvents.Components; /// /// Loads a grid far away from a random station. diff --git a/Content.Server/_DV/StationEvents/Components/LockProbersRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/LockProbersRuleComponent.cs index 86aee762af..3334fec697 100644 --- a/Content.Server/_DV/StationEvents/Components/LockProbersRuleComponent.cs +++ b/Content.Server/_DV/StationEvents/Components/LockProbersRuleComponent.cs @@ -1,6 +1,4 @@ -using Content.Server._DV.StationEvents.Events; -using Content.Shared.Psionics.Glimmer; -using Robust.Shared.Prototypes; +using Content.Server._DV.StationEvents.GameRules; namespace Content.Server._DV.StationEvents.Components; @@ -8,4 +6,4 @@ namespace Content.Server._DV.StationEvents.Components; /// Locks all the probers on the station and turns them into bombs. /// [RegisterComponent, Access(typeof(GlimmerMobRule))] -public sealed partial class LockProbersRuleComponent : Component { } +public sealed partial class LockProbersRuleComponent : Component; diff --git a/Content.Server/_DV/StationEvents/Components/MeteorSwarmRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/MeteorSwarmRuleComponent.cs index e08d7a1a8a..d08209dc55 100644 --- a/Content.Server/_DV/StationEvents/Components/MeteorSwarmRuleComponent.cs +++ b/Content.Server/_DV/StationEvents/Components/MeteorSwarmRuleComponent.cs @@ -1,6 +1,6 @@ -using Content.Server.StationEvents.Events; +using Content.Server._DV.StationEvents.GameRules; -namespace Content.Server.StationEvents.Components; +namespace Content.Server._DV.StationEvents.Components; [RegisterComponent, Access(typeof(MeteorSwarmRule))] public sealed partial class MeteorSwarmRuleComponent : Component diff --git a/Content.Server/_DV/StationEvents/Components/MinorMassMindSwapRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/MinorMassMindSwapRuleComponent.cs index 9b7f57fc93..fc4a0a089d 100644 --- a/Content.Server/_DV/StationEvents/Components/MinorMassMindSwapRuleComponent.cs +++ b/Content.Server/_DV/StationEvents/Components/MinorMassMindSwapRuleComponent.cs @@ -1,4 +1,4 @@ -using Content.Server._DV.StationEvents.Events; +using Content.Server._DV.StationEvents.GameRules; using Robust.Shared.Audio; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; diff --git a/Content.Server/_DV/StationEvents/Components/NoosphericFryRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/NoosphericFryRuleComponent.cs new file mode 100644 index 0000000000..4c8d13701b --- /dev/null +++ b/Content.Server/_DV/StationEvents/Components/NoosphericFryRuleComponent.cs @@ -0,0 +1,25 @@ +using Content.Server._DV.StationEvents.GameRules; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; + +namespace Content.Server._DV.StationEvents.Components; + +[RegisterComponent, Access(typeof(NoosphericFryRule))] +public sealed partial class NoosphericFryRuleComponent : Component +{ + /// + /// The damage dealt to everyone wearing insulative gear. + /// + [DataField] + public DamageSpecifier Damage = new() + { + DamageDict = new Dictionary + { + {"Heat", 10}, + {"Shock", 10}, + } + }; + + [DataField] + public int FireStacks = 2; +} diff --git a/Content.Server/_DV/StationEvents/Components/NoosphericSilenceRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/NoosphericSilenceRuleComponent.cs new file mode 100644 index 0000000000..157ef75bd9 --- /dev/null +++ b/Content.Server/_DV/StationEvents/Components/NoosphericSilenceRuleComponent.cs @@ -0,0 +1,13 @@ +using Content.Server._DV.StationEvents.GameRules; + +namespace Content.Server._DV.StationEvents.Components; + +[RegisterComponent, Access(typeof(NoosphericSilenceRule))] +public sealed partial class NoosphericSilenceRuleComponent : Component +{ + [DataField] + public TimeSpan MinDuration = TimeSpan.FromSeconds(20); + + [DataField] + public TimeSpan MaxDuration = TimeSpan.FromSeconds(80); +} diff --git a/Content.Server/_DV/StationEvents/Components/NoosphericStormRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/NoosphericStormRuleComponent.cs new file mode 100644 index 0000000000..4826e018a8 --- /dev/null +++ b/Content.Server/_DV/StationEvents/Components/NoosphericStormRuleComponent.cs @@ -0,0 +1,44 @@ +using Content.Server._DV.StationEvents.GameRules; + +namespace Content.Server._DV.StationEvents.Components; + +[RegisterComponent, Access(typeof(NoosphericStormRule))] +public sealed partial class NoosphericStormRuleComponent : Component +{ + /// + /// The minimum amount of psionics that are created with each storm. + /// + [DataField] + public int MinAwaken = 1; + + /// + /// The maximum amount of psionics that are created with each storm. + /// + [DataField] + public int MaxAwaken = 3; + + /// + /// The coefficient for additional psionic awakenings. + /// It's calculated via glimmer / coefficient, rounded down. + /// + [DataField] + public float AdditionalAwokenPerGlimmer = 100f; + + /// + /// The minimum amount of glimmer added. + /// + [DataField] + public int BaseGlimmerAddMin = 65; + + /// + /// The maximum amount of glimmer added. + /// + [DataField] + public int BaseGlimmerAddMax = 85; + + /// + /// Multiply the EventSeverityModifier by this to determine how much extra glimmer to add. + /// + [DataField] + public float GlimmerSeverityCoefficient = 0.25f; +} diff --git a/Content.Server/_DV/StationEvents/Components/NoosphericZapRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/NoosphericZapRuleComponent.cs new file mode 100644 index 0000000000..17c043944f --- /dev/null +++ b/Content.Server/_DV/StationEvents/Components/NoosphericZapRuleComponent.cs @@ -0,0 +1,6 @@ +using Content.Server._DV.StationEvents.GameRules; + +namespace Content.Server._DV.StationEvents.Components; + +[RegisterComponent, Access(typeof(NoosphericZapRule))] +public sealed partial class NoosphericZapRuleComponent : Component; diff --git a/Content.Server/_DV/StationEvents/Components/PsionicNosebleedRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/PsionicNosebleedRuleComponent.cs index 7f1438f47d..45c767ba68 100644 --- a/Content.Server/_DV/StationEvents/Components/PsionicNosebleedRuleComponent.cs +++ b/Content.Server/_DV/StationEvents/Components/PsionicNosebleedRuleComponent.cs @@ -1,4 +1,4 @@ -using Content.Server._DV.StationEvents.Events; +using Content.Server._DV.StationEvents.GameRules; namespace Content.Server._DV.StationEvents.Components; diff --git a/Content.Server/_DV/StationEvents/Components/RandomAnimationRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/RandomAnimationRuleComponent.cs index a3aa6d089b..279659da8e 100644 --- a/Content.Server/_DV/StationEvents/Components/RandomAnimationRuleComponent.cs +++ b/Content.Server/_DV/StationEvents/Components/RandomAnimationRuleComponent.cs @@ -1,4 +1,4 @@ -using Content.Server._DV.StationEvents.Events; +using Content.Server._DV.StationEvents.GameRules; namespace Content.Server._DV.StationEvents.Components; diff --git a/Content.Server/_DV/StationEvents/Components/ThavenMoodUpsetRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/ThavenMoodUpsetRuleComponent.cs index dca0b3db67..07b6df08fc 100644 --- a/Content.Server/_DV/StationEvents/Components/ThavenMoodUpsetRuleComponent.cs +++ b/Content.Server/_DV/StationEvents/Components/ThavenMoodUpsetRuleComponent.cs @@ -1,4 +1,4 @@ -using Content.Server._DV.StationEvents.Events; +using Content.Server._DV.StationEvents.GameRules; namespace Content.Server._DV.StationEvents.Components; diff --git a/Content.Server/_DV/StationEvents/Events/DebrisSpawnerRule.cs b/Content.Server/_DV/StationEvents/GameRules/DebrisSpawnerRule.cs similarity index 93% rename from Content.Server/_DV/StationEvents/Events/DebrisSpawnerRule.cs rename to Content.Server/_DV/StationEvents/GameRules/DebrisSpawnerRule.cs index 772ed6ce12..2a526e4d67 100644 --- a/Content.Server/_DV/StationEvents/Events/DebrisSpawnerRule.cs +++ b/Content.Server/_DV/StationEvents/GameRules/DebrisSpawnerRule.cs @@ -4,20 +4,19 @@ * See AGPLv3.txt for details. */ +using System.Linq; +using Content.Server._DV.StationEvents.Components; using Content.Server.GameTicking.Rules; -using Content.Server.Station.Components; -using Content.Server.StationEvents.Components; +using Content.Server.StationEvents.Events; using Content.Shared.CCVar; using Content.Shared.Salvage; using Robust.Shared.Configuration; using Robust.Shared.EntitySerialization.Systems; -using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using System.Linq; -namespace Content.Server.StationEvents.Events; +namespace Content.Server._DV.StationEvents.GameRules; public sealed class DebrisSpawnerRule : StationEventSystem { diff --git a/Content.Server/_DV/StationEvents/Events/EpsilonEventRule.cs b/Content.Server/_DV/StationEvents/GameRules/EpsilonEventRule.cs similarity index 100% rename from Content.Server/_DV/StationEvents/Events/EpsilonEventRule.cs rename to Content.Server/_DV/StationEvents/GameRules/EpsilonEventRule.cs diff --git a/Content.Server/_DV/StationEvents/Events/FugitiveRule.cs b/Content.Server/_DV/StationEvents/GameRules/FugitiveRule.cs similarity index 97% rename from Content.Server/_DV/StationEvents/Events/FugitiveRule.cs rename to Content.Server/_DV/StationEvents/GameRules/FugitiveRule.cs index 7bfebbe8d5..200f8c8f33 100644 --- a/Content.Server/_DV/StationEvents/Events/FugitiveRule.cs +++ b/Content.Server/_DV/StationEvents/GameRules/FugitiveRule.cs @@ -1,14 +1,13 @@ +using Content.Server._DV.StationEvents.Components; using Content.Server.Antag; using Content.Server.Communications; -using Content.Server.Forensics; -using Content.Server.StationEvents.Components; +using Content.Server.StationEvents.Events; +using Content.Shared.Forensics.Components; using Content.Shared.GameTicking.Components; using Content.Shared.Ghost; using Content.Shared.Hands.EntitySystems; using Content.Shared.Humanoid; -using Content.Shared.Humanoid.Prototypes; using Content.Shared.Inventory; -using Content.Shared.Forensics.Components; using Content.Shared.Paper; using Content.Shared.Popups; using Content.Shared.Random.Helpers; @@ -17,7 +16,7 @@ using Content.Shared.Storage.EntitySystems; using Robust.Shared.Physics.Components; using Robust.Shared.Utility; -namespace Content.Server.StationEvents.Events; +namespace Content.Server._DV.StationEvents.GameRules; public sealed class FugitiveRule : StationEventSystem { diff --git a/Content.Server/_DV/StationEvents/Events/GlimmerFoxfireSpawnRule.cs b/Content.Server/_DV/StationEvents/GameRules/GlimmerFoxfireSpawnRule.cs similarity index 100% rename from Content.Server/_DV/StationEvents/Events/GlimmerFoxfireSpawnRule.cs rename to Content.Server/_DV/StationEvents/GameRules/GlimmerFoxfireSpawnRule.cs diff --git a/Content.Server/_DV/StationEvents/Events/GlimmerMobSpawnRule.cs b/Content.Server/_DV/StationEvents/GameRules/GlimmerMobSpawnRule.cs similarity index 96% rename from Content.Server/_DV/StationEvents/Events/GlimmerMobSpawnRule.cs rename to Content.Server/_DV/StationEvents/GameRules/GlimmerMobSpawnRule.cs index 4ca2d4e044..cb28033bc9 100644 --- a/Content.Server/_DV/StationEvents/Events/GlimmerMobSpawnRule.cs +++ b/Content.Server/_DV/StationEvents/GameRules/GlimmerMobSpawnRule.cs @@ -4,14 +4,14 @@ using Content.Server.Station.Systems; using Content.Server.StationEvents; using Content.Server.StationEvents.Components; using Content.Server.StationEvents.Events; -using Content.Shared.Abilities.Psionics; +using Content.Shared._DV.Psionics.Components; using Content.Shared.GameTicking.Components; using Content.Shared.NPC.Components; using Content.Shared.Psionics.Glimmer; -using Robust.Shared.Random; using Robust.Shared.Map; +using Robust.Shared.Random; -namespace Content.Server._DV.StationEvents.Events; +namespace Content.Server._DV.StationEvents.GameRules; public sealed class GlimmerMobRule : StationEventSystem { diff --git a/Content.Server/_DV/StationEvents/Events/GlimmerRestyleRule.cs b/Content.Server/_DV/StationEvents/GameRules/GlimmerRestyleRule.cs similarity index 88% rename from Content.Server/_DV/StationEvents/Events/GlimmerRestyleRule.cs rename to Content.Server/_DV/StationEvents/GameRules/GlimmerRestyleRule.cs index 46448957b1..73a48d47e6 100644 --- a/Content.Server/_DV/StationEvents/Events/GlimmerRestyleRule.cs +++ b/Content.Server/_DV/StationEvents/GameRules/GlimmerRestyleRule.cs @@ -2,6 +2,9 @@ using System.Linq; using Content.Server._DV.StationEvents.Components; using Content.Server.Psionics; using Content.Server.StationEvents.Events; +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Events; +using Content.Shared._DV.Psionics.Systems; using Content.Shared.Abilities.Psionics; using Content.Shared.GameTicking.Components; using Content.Shared.Humanoid; @@ -16,10 +19,11 @@ namespace Content.Server._DV.StationEvents.Events; public sealed class GlimmerRestyleRule : StationEventSystem { - [Dependency] private readonly MobStateSystem _mob = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly MobStateSystem _mob = default!; [Dependency] private readonly MarkingManager _markingManager = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPsionicSystem _psionic = default!; protected override void Started(EntityUid uid, GlimmerRestyleRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args) { @@ -30,9 +34,11 @@ public sealed class GlimmerRestyleRule : StationEventSystem(entity) || !HasComp(entity)) + if (!_mob.IsAlive(entity, mobState) || !HasComp(entity)) continue; - potentialTargets.Add((entity, humanoid)); + + if (_psionic.CanBeTargeted(entity)) + potentialTargets.Add((entity, humanoid)); } _random.Shuffle(potentialTargets); diff --git a/Content.Server/_DV/StationEvents/Events/LoadFarGridRule.cs b/Content.Server/_DV/StationEvents/GameRules/LoadFarGridRule.cs similarity index 94% rename from Content.Server/_DV/StationEvents/Events/LoadFarGridRule.cs rename to Content.Server/_DV/StationEvents/GameRules/LoadFarGridRule.cs index 30bb5babd0..42feae8220 100644 --- a/Content.Server/_DV/StationEvents/Events/LoadFarGridRule.cs +++ b/Content.Server/_DV/StationEvents/GameRules/LoadFarGridRule.cs @@ -1,13 +1,14 @@ +using Content.Server._DV.StationEvents.Components; using Content.Server.GameTicking.Rules; using Content.Server.StationEvents.Components; +using Content.Server.StationEvents.Events; using Content.Shared.GameTicking.Components; using Content.Shared.Station.Components; using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; using Robust.Shared.Map.Components; -using Robust.Shared.Random; -namespace Content.Server.StationEvents.Events; +namespace Content.Server._DV.StationEvents.GameRules; public sealed class LoadFarGridRule : StationEventSystem { diff --git a/Content.Server/_DV/StationEvents/Events/LockProbersRule.cs b/Content.Server/_DV/StationEvents/GameRules/LockProbersRule.cs similarity index 97% rename from Content.Server/_DV/StationEvents/Events/LockProbersRule.cs rename to Content.Server/_DV/StationEvents/GameRules/LockProbersRule.cs index d5c1d04e17..c3136c2b04 100644 --- a/Content.Server/_DV/StationEvents/Events/LockProbersRule.cs +++ b/Content.Server/_DV/StationEvents/GameRules/LockProbersRule.cs @@ -1,12 +1,12 @@ +using System.Linq; using Content.Server._DV.StationEvents.Components; using Content.Server.Psionics.Glimmer; using Content.Server.Station.Systems; using Content.Server.StationEvents.Events; using Content.Shared.GameTicking.Components; using Content.Shared.Psionics.Glimmer; -using System.Linq; -namespace Content.Server._DV.StationEvents.Events; +namespace Content.Server._DV.StationEvents.GameRules; public sealed class LockProbersRule : StationEventSystem { diff --git a/Content.Server/_DV/StationEvents/Events/MeteorSwarmRule.cs b/Content.Server/_DV/StationEvents/GameRules/MeteorSwarmRule.cs similarity index 98% rename from Content.Server/_DV/StationEvents/Events/MeteorSwarmRule.cs rename to Content.Server/_DV/StationEvents/GameRules/MeteorSwarmRule.cs index e4f39807d6..e1cadbba5b 100644 --- a/Content.Server/_DV/StationEvents/Events/MeteorSwarmRule.cs +++ b/Content.Server/_DV/StationEvents/GameRules/MeteorSwarmRule.cs @@ -1,8 +1,9 @@ using System.Linq; using System.Numerics; +using Content.Server._DV.StationEvents.Components; using Content.Server.Atmos.Components; -using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; +using Content.Server.StationEvents.Events; using Content.Shared.GameTicking.Components; using Robust.Shared.Map; using Robust.Shared.Map.Components; @@ -11,7 +12,7 @@ using Robust.Shared.Physics.Systems; using Robust.Shared.Random; using Robust.Shared.Spawners; -namespace Content.Server.StationEvents.Events +namespace Content.Server._DV.StationEvents.GameRules { public sealed class MeteorSwarmRule : StationEventSystem { diff --git a/Content.Server/_DV/StationEvents/Events/MinorMassMindSwapRule.cs b/Content.Server/_DV/StationEvents/GameRules/MinorMassMindSwapRule.cs similarity index 80% rename from Content.Server/_DV/StationEvents/Events/MinorMassMindSwapRule.cs rename to Content.Server/_DV/StationEvents/GameRules/MinorMassMindSwapRule.cs index 8f3d578575..816af06b24 100644 --- a/Content.Server/_DV/StationEvents/Events/MinorMassMindSwapRule.cs +++ b/Content.Server/_DV/StationEvents/GameRules/MinorMassMindSwapRule.cs @@ -1,9 +1,9 @@ -using Content.Server.Abilities.Psionics; -using Content.Server.Chat.Systems; +using Content.Server._DV.Psionics.Systems; using Content.Server._DV.StationEvents.Components; -using Content.Server.Psionics; +using Content.Server.Chat.Systems; using Content.Server.StationEvents.Events; -using Content.Shared.Abilities.Psionics; +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; using Content.Shared.GameTicking.Components; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; @@ -12,18 +12,19 @@ using Robust.Shared.Audio; using Robust.Shared.Player; using Robust.Shared.Random; -namespace Content.Server._DV.StationEvents.Events; +namespace Content.Server._DV.StationEvents.GameRules; /// /// Forces a mind swap on a small amount of non-insulated psionic entities. /// internal sealed class MinorMassMindSwapRule : StationEventSystem { - [Dependency] private readonly AudioSystem _audio = default!; - [Dependency] private readonly MindSwapPowerSystem _mindSwap = default!; - [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly SharedMindSwapPowerSystem _mindSwap = default!; [Dependency] private readonly MobStateSystem _mobstateSystem = default!; + [Dependency] private readonly PsionicSystem _psionic = default!; private TimeSpan _warningSoundLength; private ResolvedSoundSpecifier _resolvedWarningSound = string.Empty; @@ -75,19 +76,18 @@ internal sealed class MinorMassMindSwapRule : StationEventSystem psionicActors = new(); + List psionicActors = []; var query = EntityQueryEnumerator(); - while (query.MoveNext(out var psion, out _, out _)) + while (query.MoveNext(out var psion, out _, out var mobState)) { - if (_mobstateSystem.IsAlive(psion) && !HasComp(psion) - && HasComp(psion)) + if (_mobstateSystem.IsAlive(psion, mobState) && HasComp(psion) && _psionic.CanBeTargeted(psion)) // Only a list of Players psionicActors.Add(psion); } // We go with 4 pairs for now - List actorsToSwap = new(); + List actorsToSwap = []; var swapPairCount = _random.Next(1, component.MaxNumberOfPairs); for (; swapPairCount > 0 && psionicActors.Count > 1; swapPairCount--) @@ -95,13 +95,7 @@ internal sealed class MinorMassMindSwapRule : StationEventSystem +/// Fries tinfoil hats and cages +/// +internal sealed class NoosphericFryRule : StationEventSystem +{ + [Dependency] private readonly AnchorableSystem _anchorableSystem = default!; + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + [Dependency] private readonly GlimmerReactiveSystem _glimmerReactiveSystem = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = 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); + + var damage = component.Damage; + var fireStacks = component.FireStacks; + + switch (_glimmerSystem.Glimmer) + { + case > 500 and < 750: + damage *= 2; + fireStacks += 1; + break; + case > 750: + damage *= 3; + fireStacks += 2; + break; + } + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var psion, out var _)) + { + if (!_mobStateSystem.IsAlive(psion)) + continue; + + var ev = new NoosphericFryEvent(damage, fireStacks); + RaiseLocalEvent(psion, ref ev); + } + + // for probers: + var queryReactive = EntityQueryEnumerator(); + while (queryReactive.MoveNext(out var reactive, out _, out var xform, out var physics)) + { + // shoot out one bolt of lighting... + _glimmerReactiveSystem.BeamRandomNearProber(reactive, 1, 12); + + // try to anchor if we can + if (!xform.Anchored) + { + var coordinates = xform.Coordinates; + var gridUid = xform.GridUid; + if (!TryComp(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/_DV/StationEvents/GameRules/NoosphericSilenceRule.cs b/Content.Server/_DV/StationEvents/GameRules/NoosphericSilenceRule.cs new file mode 100644 index 0000000000..721afcc4bc --- /dev/null +++ b/Content.Server/_DV/StationEvents/GameRules/NoosphericSilenceRule.cs @@ -0,0 +1,49 @@ +using Content.Server._DV.StationEvents.Components; +using Content.Server.StationEvents.Events; +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Systems; +using Content.Shared.GameTicking.Components; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.StatusEffect; +using Robust.Shared.Random; + +namespace Content.Server._DV.StationEvents.GameRules; + +/// +/// Mutes everyone for a random amount of time. +/// +internal sealed class NoosphericSilenceRule : StationEventSystem +{ + [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly SharedPsionicSystem _psionic = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; + + protected override void Started(EntityUid uid, NoosphericSilenceRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) + { + base.Started(uid, component, gameRule, args); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var potPsion, out _, out _)) + { + if (!_mobStateSystem.IsAlive(potPsion)) + continue; + + if (_psionic.CanBeTargeted(potPsion)) + Silence(potPsion, component); + } + } + + private void Silence(EntityUid potPsion, NoosphericSilenceRuleComponent ruleComp) + { + var duration = _robustRandom.Next(ruleComp.MinDuration, ruleComp.MaxDuration); + + // TODO Replace with statusEffectSystemNew when Upstream makes a muted prototype. + _statusEffectsSystem.TryAddStatusEffect(potPsion, + "Muted", + duration, + false, + "Muted"); + } +} diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericStormRule.cs b/Content.Server/_DV/StationEvents/GameRules/NoosphericStormRule.cs similarity index 51% rename from Content.Server/Nyanotrasen/StationEvents/Events/NoosphericStormRule.cs rename to Content.Server/_DV/StationEvents/GameRules/NoosphericStormRule.cs index e99aa0ebcc..0221c4ab45 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericStormRule.cs +++ b/Content.Server/_DV/StationEvents/GameRules/NoosphericStormRule.cs @@ -1,59 +1,58 @@ -using Content.Server.Abilities.Psionics; -using Content.Server.Psionics; -using Content.Server.StationEvents.Components; +using System.Linq; +using Content.Server._DV.StationEvents.Components; using Content.Server.StationEvents.Events; -using Content.Shared.Abilities.Psionics; +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Systems; using Content.Shared.GameTicking.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Psionics.Glimmer; -using Content.Shared.Zombies; using Robust.Shared.Random; -namespace Content.Server.Nyanotrasen.StationEvents.Events; +namespace Content.Server._DV.StationEvents.GameRules; internal sealed class NoosphericStormRule : StationEventSystem { - [Dependency] private readonly PsionicAbilitiesSystem _psionicAbilitiesSystem = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; - [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly SharedPsionicSystem _psionic = default!; protected override void Started(EntityUid uid, NoosphericStormRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) { base.Started(uid, component, gameRule, args); - List validList = new(); + Dictionary validList = []; var query = EntityManager.EntityQueryEnumerator(); - while (query.MoveNext(out var potentialPsionic, out var potentialPsionicComponent)) + while (query.MoveNext(out var potPsionic, out var potPsionicComp)) { - if (_mobStateSystem.IsDead(potentialPsionic)) + if (!_mobStateSystem.IsAlive(potPsionic) + || HasComp(potPsionic)) // Skip over already psionic entities. continue; - // Skip over those who are already psionic or those who are insulated, or zombies. - if (HasComp(potentialPsionic) || HasComp(potentialPsionic) || HasComp(potentialPsionic)) + if (!_psionic.CanBeTargeted(potPsionic)) // Skip over shielded entities. continue; - validList.Add(potentialPsionic); + validList.Add(potPsionic, potPsionicComp); } // Give some targets psionic abilities. - RobustRandom.Shuffle(validList); + var keyList = validList.Keys.ToList(); + _robustRandom.Shuffle(keyList); var toAwaken = RobustRandom.Next(1, component.MaxAwaken); + var additional = _glimmerSystem.Glimmer / component.AdditionalAwokenPerGlimmer; + toAwaken = (int) MathF.Round(toAwaken, 0, MidpointRounding.ToZero); - foreach (var target in validList) + foreach (var target in keyList.TakeWhile(_ => toAwaken-- != 0)) { - if (toAwaken-- == 0) - break; - - _psionicAbilitiesSystem.AddPsionics(target); + _psionic.AddRandomPsionicPower((target, validList[target]), midRound: true); } // Increase glimmer. var baseGlimmerAdd = _robustRandom.Next(component.BaseGlimmerAddMin, component.BaseGlimmerAddMax); //var glimmerSeverityMod = 1 + (component.GlimmerSeverityCoefficient * (GetSeverityModifier() - 1f)); - var glimmerAdded = (int) baseGlimmerAdd; // Math.Round(baseGlimmerAdd * glimmerSeverityMod); + var glimmerAdded = baseGlimmerAdd; // Math.Round(baseGlimmerAdd * glimmerSeverityMod); _glimmerSystem.Glimmer += glimmerAdded; } diff --git a/Content.Server/_DV/StationEvents/GameRules/NoosphericZapRule.cs b/Content.Server/_DV/StationEvents/GameRules/NoosphericZapRule.cs new file mode 100644 index 0000000000..909851d8c1 --- /dev/null +++ b/Content.Server/_DV/StationEvents/GameRules/NoosphericZapRule.cs @@ -0,0 +1,64 @@ +using Content.Server._DV.Psionics.Systems; +using Content.Server._DV.StationEvents.Components; +using Content.Server.Popups; +using Content.Server.StationEvents.Events; +using Content.Shared._DV.Psionics.Components; +using Content.Shared.GameTicking.Components; +using Content.Shared.Jittering; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Popups; +using Content.Shared.Speech.EntitySystems; +using Content.Shared.Stunnable; + +namespace Content.Server._DV.StationEvents.GameRules; + +/// +/// Zaps everyone, rolling psionics and disorienting them +/// +internal sealed class NoosphericZapRule : StationEventSystem +{ + [Dependency] private readonly SharedJitteringSystem _jittering = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly PsionicSystem _psionic = default!; + [Dependency] private readonly SharedStunSystem _stun = default!; + [Dependency] private readonly SharedStutteringSystem _stuttering = 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 potPsion, out var potPsionComponent, out var mobStateComponent)) + { + if (!_mobStateSystem.IsAlive(potPsion, mobStateComponent)) + continue; + + if (!_psionic.CanBeTargeted(potPsion)) + continue; + + // Zap non-psionics only if they spent their roll already. + if (potPsionComponent.Rolled) + Zap(potPsion, potPsionComponent); + // Then zap all other psionics regardless. + else if (HasComp(potPsion)) + Zap(potPsion, potPsionComponent); + } + } + + private void Zap(EntityUid psionic, PotentialPsionicComponent potPsionComp) + { + _stun.TryUpdateParalyzeDuration(psionic, TimeSpan.FromSeconds(5)); + _jittering.DoJitter(psionic, TimeSpan.FromSeconds(5), false); + _stuttering.DoStutter(psionic, TimeSpan.FromSeconds(15), false); + + var grantedRoll = _psionic.GrantPsionicRoll((psionic, potPsionComp)); + var message = grantedRoll + ? "gamerule-noospheric-zap-seize-potential-regained" + : "gamerule-noospheric-zap-seize"; + + _popupSystem.PopupEntity(Loc.GetString(message), psionic, psionic, PopupType.LargeCaution); + } +} diff --git a/Content.Server/_DV/StationEvents/Events/PsionicNosebleedRule.cs b/Content.Server/_DV/StationEvents/GameRules/PsionicNosebleedRule.cs similarity index 62% rename from Content.Server/_DV/StationEvents/Events/PsionicNosebleedRule.cs rename to Content.Server/_DV/StationEvents/GameRules/PsionicNosebleedRule.cs index d541585e21..5d056946ea 100644 --- a/Content.Server/_DV/StationEvents/Events/PsionicNosebleedRule.cs +++ b/Content.Server/_DV/StationEvents/GameRules/PsionicNosebleedRule.cs @@ -1,19 +1,23 @@ using Content.Server._DV.StationEvents.Components; using Content.Server.Body.Systems; using Content.Server.StationEvents.Events; +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Events; +using Content.Shared._DV.Psionics.Systems; using Content.Shared.Abilities.Psionics; using Content.Shared.GameTicking.Components; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Popups; -namespace Content.Server._DV.StationEvents.Events; +namespace Content.Server._DV.StationEvents.GameRules; public sealed class PsionicNosebleedRule : StationEventSystem { - [Dependency] private readonly MobStateSystem _mob = default!; [Dependency] private readonly BloodstreamSystem _bloodstream = default!; + [Dependency] private readonly MobStateSystem _mob = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPsionicSystem _psionic = default!; protected override void Started(EntityUid uid, PsionicNosebleedRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args) { @@ -22,11 +26,14 @@ public sealed class PsionicNosebleedRule : StationEventSystem(); while (query.MoveNext(out var psion, out _, out var mobState)) { - if (_mob.IsAlive(psion, mobState) && !HasComp(psion)) - { - _popup.PopupEntity(Loc.GetString("psionic-nosebleed-message"), psion, psion, PopupType.MediumCaution); - _bloodstream.TryModifyBleedAmount(psion, comp.BleedAmount); - } + if (!_mob.IsAlive(psion, mobState)) + continue; + + if (!_psionic.CanBeTargeted(psion)) + continue; + + _popup.PopupEntity(Loc.GetString("psionic-nosebleed-message"), psion, psion, PopupType.MediumCaution); + _bloodstream.TryModifyBleedAmount(psion, comp.BleedAmount); } } } diff --git a/Content.Server/_DV/StationEvents/Events/RandomAnimationRule.cs b/Content.Server/_DV/StationEvents/GameRules/RandomAnimationRule.cs similarity index 97% rename from Content.Server/_DV/StationEvents/Events/RandomAnimationRule.cs rename to Content.Server/_DV/StationEvents/GameRules/RandomAnimationRule.cs index 67c78ae0b8..6f4b1bf0ae 100644 --- a/Content.Server/_DV/StationEvents/Events/RandomAnimationRule.cs +++ b/Content.Server/_DV/StationEvents/GameRules/RandomAnimationRule.cs @@ -7,7 +7,7 @@ using Content.Shared.Magic.Components; using Robust.Shared.Containers; using Robust.Shared.Random; -namespace Content.Server._DV.StationEvents.Events; +namespace Content.Server._DV.StationEvents.GameRules; public sealed class RandomAnimationRule : StationEventSystem { diff --git a/Content.Server/_DV/StationEvents/Events/RandomMultipleSpawnRule.cs b/Content.Server/_DV/StationEvents/GameRules/RandomMultipleSpawnRule.cs similarity index 85% rename from Content.Server/_DV/StationEvents/Events/RandomMultipleSpawnRule.cs rename to Content.Server/_DV/StationEvents/GameRules/RandomMultipleSpawnRule.cs index e5a2ebbb88..7589c5984e 100644 --- a/Content.Server/_DV/StationEvents/Events/RandomMultipleSpawnRule.cs +++ b/Content.Server/_DV/StationEvents/GameRules/RandomMultipleSpawnRule.cs @@ -1,11 +1,9 @@ using Content.Server._DV.StationEvents.Components; -using Content.Server.GameTicking.Rules.Components; -using Content.Server.StationEvents.Components; using Content.Server.StationEvents.Events; using Content.Shared.GameTicking.Components; using Robust.Shared.Random; -namespace Content.Server._DV.StationEvents.Events; +namespace Content.Server._DV.StationEvents.GameRules; public sealed class RandomMultipleSpawnRule : StationEventSystem { diff --git a/Content.Server/_DV/StationEvents/Events/ThavenMoodUpset.cs b/Content.Server/_DV/StationEvents/GameRules/ThavenMoodUpset.cs similarity index 93% rename from Content.Server/_DV/StationEvents/Events/ThavenMoodUpset.cs rename to Content.Server/_DV/StationEvents/GameRules/ThavenMoodUpset.cs index 8e988875cf..cbd490c8a2 100644 --- a/Content.Server/_DV/StationEvents/Events/ThavenMoodUpset.cs +++ b/Content.Server/_DV/StationEvents/GameRules/ThavenMoodUpset.cs @@ -4,7 +4,7 @@ using Content.Server.StationEvents.Events; using Content.Shared._Impstation.Thaven.Components; using Content.Shared.GameTicking.Components; -namespace Content.Server._DV.StationEvents.Events; +namespace Content.Server._DV.StationEvents.GameRules; public sealed class ThavenMoodUpset : StationEventSystem { diff --git a/Content.Server/_DV/StationEvents/NextEvent/NextEventSystem.cs b/Content.Server/_DV/StationEvents/NextEvent/NextEventSystem.cs index 203b0de6d9..ceeb44e5cf 100644 --- a/Content.Server/_DV/StationEvents/NextEvent/NextEventSystem.cs +++ b/Content.Server/_DV/StationEvents/NextEvent/NextEventSystem.cs @@ -10,7 +10,7 @@ public sealed class NextEventSystem : EntitySystem /// public EntProtoId? UpdateNextEvent(NextEventComponent component, EntProtoId newEventId, TimeSpan newEventTime) { - EntProtoId? oldEventId = component.NextEventId; // Store components current NextEventId for return + var oldEventId = component.NextEventId; // Store components current NextEventId for return component.NextEventId = newEventId; component.NextEventTime = newEventTime; return oldEventId; diff --git a/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Effects/Components/PsionicProducingArtifactComponent.cs b/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Effects/Components/PsionicProducingArtifactComponent.cs deleted file mode 100644 index 374b532acc..0000000000 --- a/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Effects/Components/PsionicProducingArtifactComponent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Server._DV.Xenoarchaeology.XenoArtifacts.Effects.Systems; - -namespace Content.Server._DV.Xenoarchaeology.XenoArtifacts.Effects.Components; - -/// -/// Makes people in a radius around it psionic when triggered. -/// -[RegisterComponent, Access(typeof(PsionicProducingArtifactSystem))] -public sealed partial class PsionicProducingArtifactComponent : Component -{ - /// - /// Range to look for potential psionics in. - /// - [DataField(required: true)] - public float Range; - - /// - /// Number of times this node can trigger before it switches to doing nothing. - /// - [DataField] - public int Limit = 1; -} diff --git a/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Effects/Systems/GlimmerArtifactSystem.cs b/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Effects/Systems/GlimmerArtifactSystem.cs deleted file mode 100644 index 60b16c8c6f..0000000000 --- a/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Effects/Systems/GlimmerArtifactSystem.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Content.Server._DV.Xenoarchaeology.XenoArtifacts.Effects.Components; -using Content.Shared.Xenoarchaeology.Artifact; -using Content.Shared.Psionics.Glimmer; - -namespace Content.Server._DV.Xenoarchaeology.XenoArtifacts.Effects.Systems; - -public sealed class GlimmerArtifactSystem : EntitySystem -{ - [Dependency] private readonly GlimmerSystem _glimmer = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnActivated); - } - - private void OnActivated(Entity ent, ref XenoArtifactActivatedEvent args) - { - var range = ent.Comp.Range; - var current = _glimmer.Glimmer; - if (current < range.Min || current > range.Max) - return; - - _glimmer.Glimmer += ent.Comp.Change; - } -} diff --git a/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Effects/Systems/PsionicProducingArtifactSystem.cs b/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Effects/Systems/PsionicProducingArtifactSystem.cs deleted file mode 100644 index e5c3e9bca5..0000000000 --- a/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Effects/Systems/PsionicProducingArtifactSystem.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Linq; -using Content.Server._DV.Xenoarchaeology.XenoArtifacts.Effects.Components; -using Content.Server.Psionics; -using Content.Shared.Xenoarchaeology.Artifact; -using Content.Shared.Xenoarchaeology.Artifact.Components; - -namespace Content.Server._DV.Xenoarchaeology.XenoArtifacts.Effects.Systems; -public sealed class PsionicProducingArtifactSystem : EntitySystem -{ - [Dependency] private readonly SharedXenoArtifactSystem _artifact = default!; - [Dependency] private readonly EntityLookupSystem _lookup = default!; - [Dependency] private readonly PsionicsSystem _psionics = default!; - - public const string NodeDataPsionicAmount = "nodeDataPsionicAmount"; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnActivated); - } - - private void OnActivated(Entity ent, ref XenoArtifactActivatedEvent args) - { - var (uid, comp) = ent; - - // Resolve the artifact entity from the node - if (!TryComp(uid, out var artifactComp)) - return; - - var artifactEntity = new Entity(uid, artifactComp); - - // Pick first active node - var node = _artifact.GetActiveNodes(artifactEntity).FirstOrDefault(); - - // Track psionic usage using ConsumedResearchValue - var currentAmount = _artifact.GetResearchValue(node); - - if (currentAmount >= comp.Limit) - return; - - var coords = Transform(uid).Coordinates; - - foreach (var target in _lookup.GetEntitiesInRange(coords, comp.Range)) - { - _psionics.TryMakePsionic(target); - } - - // Update node usage - _artifact.SetConsumedResearchValue(node, currentAmount + 1); - } - -} diff --git a/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMetapsionicTriggerComponent.cs b/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMetapsionicTriggerComponent.cs deleted file mode 100644 index 64688732c1..0000000000 --- a/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMetapsionicTriggerComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Content.Server._DV.Xenoarchaeology.XenoArtifacts.Triggers.Components; - -/// -/// Triggers if a psionic power is used nearby. -/// Requires MetapsionicPowerComponent to be added too. -/// -[RegisterComponent] -public sealed partial class ArtifactMetapsionicTriggerComponent : Component; diff --git a/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMetapsionicTriggerSystem.cs b/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMetapsionicTriggerSystem.cs deleted file mode 100644 index 995c6d0ad8..0000000000 --- a/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMetapsionicTriggerSystem.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Content.Server._DV.Xenoarchaeology.XenoArtifacts.Triggers.Components; -using Content.Server.Nyanotrasen.StationEvents.Events; -using Content.Server.Xenoarchaeology.Artifact; -using Content.Shared.Abilities.Psionics; -using Content.Shared.Xenoarchaeology.Artifact.Components; -using Content.Shared.Xenoarchaeology.Artifact.XAT; - -namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; - -public sealed class ArtifactMetapsionicTriggerSystem : BaseXATSystem -{ - private EntityQuery _xenoArtifactQuery; - - public override void Initialize() - { - base.Initialize(); - - _xenoArtifactQuery = GetEntityQuery(); - - SubscribeLocalEvent(OnPowerDetected); - - SubscribeLocalEvent(OnGlimmerEventEnded); - } - - private void OnPowerDetected(Entity ent, ref PsionicPowerDetectedEvent args) - { - if (!TryComp(ent, out var node)) - return; - - if (node.Attached == null) - return; - - var artifact = _xenoArtifactQuery.Get(node.Attached.Value); - - if (!CanTrigger(artifact, (ent.Owner, node))) - return; - - Trigger(artifact, (ent.Owner, ent.Comp, node)); - } - - private void OnGlimmerEventEnded(GlimmerEventEndedEvent args) - { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var comp, out var node)) - { - if (node.Attached == null) - continue; - - var artifact = _xenoArtifactQuery.Get(node.Attached.Value); - - if (!CanTrigger(artifact, (uid, node))) - continue; - - Trigger(artifact, (uid, comp, node)); - } - } -} diff --git a/Content.Shared/Abilities/Mime/MimePowersSystem.cs b/Content.Shared/Abilities/Mime/MimePowersSystem.cs index df0c6a4a3e..aa77ccb803 100644 --- a/Content.Shared/Abilities/Mime/MimePowersSystem.cs +++ b/Content.Shared/Abilities/Mime/MimePowersSystem.cs @@ -1,5 +1,4 @@ using Content.Shared.Popups; -using Content.Shared.Abilities.Psionics; //Nyano - Summary: Makes Mime psionic. using Content.Shared.Actions; using Content.Shared.Actions.Events; using Content.Shared.Alert; @@ -20,7 +19,6 @@ public sealed class MimePowersSystem : EntitySystem [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; [Dependency] private readonly AlertsSystem _alertsSystem = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; // DeltaV [Dependency] private readonly TurfSystem _turf = default!; [Dependency] private readonly IMapManager _mapMan = default!; [Dependency] private readonly SharedContainerSystem _container = default!; @@ -71,10 +69,6 @@ public sealed class MimePowersSystem : EntitySystem _alertsSystem.ShowAlert(ent.Owner, ent.Comp.VowAlert); _actionsSystem.AddAction(ent, ref ent.Comp.InvisibleWallActionEntity, ent.Comp.InvisibleWallAction); - - //DeltaV - Summary: Add Psionic Ability to Mime. - if (TryComp(ent.Owner, out var psionic) && psionic.PsionicAbility == null) - psionic.PsionicAbility = ent.Comp.InvisibleWallActionEntity; } private void OnComponentShutdown(Entity ent, ref ComponentShutdown args) @@ -108,10 +102,6 @@ public sealed class MimePowersSystem : EntitySystem return; } - // Begin DeltaV Additions - _psionics.LogPowerUsed(ent.Owner, "invisible wall"); - // End DeltaV Additions - var messageSelf = Loc.GetString("mime-invisible-wall-popup-self", ("mime", Identity.Entity(ent.Owner, EntityManager))); var messageOthers = Loc.GetString("mime-invisible-wall-popup-others", ("mime", Identity.Entity(ent.Owner, EntityManager))); _popupSystem.PopupPredicted(messageSelf, messageOthers, ent, ent); diff --git a/Content.Shared/Anomaly/SharedAnomalySystem.cs b/Content.Shared/Anomaly/SharedAnomalySystem.cs index 4799d82b7a..92dcc860fd 100644 --- a/Content.Shared/Anomaly/SharedAnomalySystem.cs +++ b/Content.Shared/Anomaly/SharedAnomalySystem.cs @@ -24,7 +24,7 @@ using Content.Shared.Actions; namespace Content.Shared.Anomaly; -public abstract class SharedAnomalySystem : EntitySystem +public abstract partial class SharedAnomalySystem : EntitySystem // DeltaV - Made Partial { [Dependency] protected readonly IGameTiming Timing = default!; [Dependency] private readonly INetManager _net = default!; @@ -44,6 +44,8 @@ public abstract class SharedAnomalySystem : EntitySystem SubscribeLocalEvent(OnAnomalyThrowStart); SubscribeLocalEvent(OnLand); + + InitializePsionics(); // DeltaV - Introduce Dispellable behavior to Anomalies. } private void OnAnomalyThrowStart(Entity ent, ref MeleeThrowOnHitStartEvent args) diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index 431b79865f..8b5ca620e3 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -1,4 +1,5 @@ -using Content.Shared._DV.Overlays; // DeltaV +using Content.Shared._DV.Overlays; +using Content.Shared._DV.Psionics.Events; // DeltaV using Content.Shared.Armor; using Content.Shared.Atmos; using Content.Shared.Chat; @@ -80,6 +81,12 @@ public partial class InventorySystem SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); + // DeltaV Start - Psionic Events + SubscribeLocalEvent(RefRelayInventoryEvent); + SubscribeLocalEvent(RefRelayInventoryEvent); + SubscribeLocalEvent(RefRelayInventoryEvent); + SubscribeLocalEvent(RefRelayInventoryEvent); + // DeltaV End - Psionic Events // Eye/vision events SubscribeLocalEvent(RelayInventoryEvent); diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs deleted file mode 100644 index ce86111fc4..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Content.Shared.Damage; - -namespace Content.Shared.Abilities.Psionics -{ - /// - /// Takes damage when dispelled. - /// - [RegisterComponent] - public sealed partial class DamageOnDispelComponent : Component - { - [DataField("damage", required: true)] - public DamageSpecifier Damage = default!; - - [DataField("variance")] - public float Variance = 0.5f; - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispelPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispelPowerComponent.cs deleted file mode 100644 index cd88786636..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispelPowerComponent.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Content.Shared.Actions; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Abilities.Psionics -{ - [RegisterComponent] - public sealed partial class DispelPowerComponent : Component - { - [DataField("range")] - public float Range = 10f; - - [DataField("dispelActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? DispelActionId = "ActionDispel"; - - [DataField("dispelActionEntity")] - public EntityUid? DispelActionEntity; - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispellableComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispellableComponent.cs deleted file mode 100644 index 4035200418..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispellableComponent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Content.Shared.Abilities.Psionics -{ - [RegisterComponent] - public sealed partial class DispellableComponent : Component - {} -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs deleted file mode 100644 index 5858c9f211..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Content.Shared.DoAfter; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Abilities.Psionics -{ - [RegisterComponent] - public sealed partial class MassSleepPowerComponent : Component - { - public float Radius = 1.25f; - [DataField("massSleepActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? MassSleepActionId = "ActionMassSleep"; - - [DataField("massSleepActionEntity")] - public EntityUid? MassSleepActionEntity; - - [DataField] - public DoAfterId? DoAfter; - - [DataField] - public TimeSpan UseDelay = TimeSpan.FromSeconds(4); - - [DataField] - public float Duration = 5f; - - [DataField] - public float WarningRadius = 6f; - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs deleted file mode 100644 index 670eb1c1ad..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs +++ /dev/null @@ -1,105 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.DoAfter; -using Content.Shared.Bed.Sleep; -using Content.Shared.Damage.Components; -using Content.Shared.Mobs.Components; -using Robust.Shared.Prototypes; -using Content.Shared.Actions.Events; -using Content.Shared.Movement.Systems; -using Content.Shared.Popups; -using Content.Shared.Psionics.Events; -using Content.Shared.StatusEffect; -using Content.Shared.Stunnable; - -namespace Content.Shared.Abilities.Psionics -{ - public sealed class MassSleepPowerSystem : EntitySystem - { - public ProtoId StatusEffectKey = "ForcedSleep"; - [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly EntityLookupSystem _lookup = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; - [Dependency] private readonly MovementModStatusSystem _movementMod = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - SubscribeLocalEvent(OnMassSleepDoAfter); - } - - private void OnInit(Entity ent, ref ComponentInit args) - { - _actions.AddAction(ent, ref ent.Comp.MassSleepActionEntity, ent.Comp.MassSleepActionId ); - - if (_actions.GetAction(ent.Comp.MassSleepActionEntity) is not { Comp.UseDelay: not null }) - { - _actions.StartUseDelay(ent.Comp.MassSleepActionEntity); - } - - if (TryComp(ent, out var psionic) && psionic.PsionicAbility == null) - psionic.PsionicAbility = ent.Comp.MassSleepActionEntity; - } - - private void OnShutdown(Entity ent, ref ComponentShutdown args) - { - _actions.RemoveAction(ent.Owner, ent.Comp.MassSleepActionEntity); - } - - private void OnPowerUsed(Entity ent, ref MassSleepPowerActionEvent args) - { - var ev = new MassSleepDoAfterEvent(); - var doAfterArgs = new DoAfterArgs(EntityManager, ent, ent.Comp.UseDelay, ev, ent) - { - BreakOnDamage = true - }; - - foreach (var entity in _lookup.GetEntitiesInRange(args.Performer, ent.Comp.WarningRadius)) - { - if (HasComp(entity) && entity != (EntityUid)ent && !HasComp(entity)) - { - _popup.PopupEntity(Loc.GetString("psionic-power-mass-sleep-warning"), - entity, - entity, - PopupType.LargeCaution); - } - } - - _movementMod.TryUpdateMovementSpeedModDuration(ent, MovementModStatusSystem.PsionicSlowdown, ent.Comp.UseDelay, 0.5f); - - _doAfter.TryStartDoAfter(doAfterArgs, out var doAfterId); - ent.Comp.DoAfter = doAfterId; - - _psionics.LogPowerUsed(ent, "mass sleep"); - args.Handled = true; - } - - private void OnMassSleepDoAfter(Entity ent, ref MassSleepDoAfterEvent args) - { - if (args.Handled) - return; - - if (args.Cancelled) - { - _statusEffects.TryRemoveStatusEffect(ent, "SlowedDown"); - return; - } - - foreach (var entity in _lookup.GetEntitiesInRange(args.User, ent.Comp.Radius)) - { - if (HasComp(entity) && entity != (EntityUid)ent && !HasComp(entity)) - { - if (TryComp(entity, out var damageable) && damageable.DamageContainerID == "Biological") - _statusEffects.TryAddStatusEffect(entity, StatusEffectKey, TimeSpan.FromSeconds(ent.Comp.Duration), false); - } - } - - ent.Comp.DoAfter = null; - } - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs deleted file mode 100644 index 39d1b0f3a2..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Actions.Components; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Abilities.Psionics -{ - [RegisterComponent] - public sealed partial class MetapsionicPowerComponent : Component - { - [DataField("range")] - public float Range = 5f; - - public InstantActionComponent? MetapsionicPowerAction = null; - [DataField("metapsionicActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? MetapsionicActionId = "ActionMetapsionic"; - - [DataField("metapsionicActionEntity")] - public EntityUid? MetapsionicActionEntity; - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs deleted file mode 100644 index 7d07857f07..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; - -namespace Content.Shared.Abilities.Psionics; - -[RegisterComponent, NetworkedComponent] -public sealed partial class MindSwapPowerComponent : Component -{ - [DataField] - public EntProtoId? MindSwapActionId = "ActionMindSwap"; - - [DataField] - public EntityUid? MindSwapActionEntity; -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs deleted file mode 100644 index 0e91894b1d..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Content.Shared.Actions; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Abilities.Psionics -{ - [RegisterComponent] - public sealed partial class NoosphericZapPowerComponent : Component - { - [DataField("noosphericZapActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? NoosphericZapActionId = "ActionNoosphericZap"; - - [DataField("noosphericZapActionEntity")] - public EntityUid? NoosphericZapActionEntity; - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs deleted file mode 100644 index 3e198aa930..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Abilities.Psionics -{ - [RegisterComponent] - public sealed partial class PsionicInvisibilityPowerComponent : Component - { - [DataField("psionicInvisibilityActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? PsionicInvisibilityActionId = "ActionPsionicInvisibility"; - - [DataField("psionicInvisibilityActionEntity")] - public EntityUid? PsionicInvisibilityActionEntity; - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs deleted file mode 100644 index 9037b8bcdf..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Abilities.Psionics -{ - [RegisterComponent] - public sealed partial class PsionicInvisibilityUsedComponent : Component - { - [ValidatePrototypeId] - public const string PsionicInvisibilityUsedActionPrototype = "ActionPsionicInvisibilityUsed"; - [DataField("psionicInvisibilityUsedActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? PsionicInvisibilityUsedActionId = "ActionPsionicInvisibilityUsed"; - - [DataField("psionicInvisibilityUsedActionEntity")] - public EntityUid? PsionicInvisibilityUsedActionEntity; - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs deleted file mode 100644 index 4a62e84d19..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Robust.Shared.Audio; -using Content.Shared.DoAfter; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Abilities.Psionics -{ - [RegisterComponent] - public sealed partial class PsionicRegenerationPowerComponent : Component - { - [DataField("doAfter")] - public DoAfterId? DoAfter; - - [DataField("essence")] - public float EssenceAmount = 20; - - [DataField("useDelay")] - public float UseDelay = 8f; - [DataField("soundUse")] - - public SoundSpecifier SoundUse = new SoundPathSpecifier("/Audio/Nyanotrasen/Psionics/heartbeat_fast.ogg"); - - [DataField("psionicRegenerationActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? PsionicRegenerationActionId = "ActionPsionicRegeneration"; - - [DataField("psionicRegenerationActionEntity")] - public EntityUid? PsionicRegenerationActionEntity; - } -} - diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs deleted file mode 100644 index 9efd674133..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Actions.Components; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Abilities.Psionics -{ - [RegisterComponent] - public sealed partial class PyrokinesisPowerComponent : Component - { - public EntityTargetActionComponent? PyrokinesisPowerAction = null; - [DataField("pyrokinesisActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? PyrokinesisActionId = "ActionPyrokinesis"; - - [DataField("pyrokinesisActionEntity")] - public EntityUid? PyrokinesisActionEntity; - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/SharedTelegnosisPowerSystem.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/SharedTelegnosisPowerSystem.cs deleted file mode 100644 index 17bdefe8e6..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/SharedTelegnosisPowerSystem.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Content.Shared._DV.Abilities.Psionics; -using Content.Shared._DV.Mind; -using Content.Shared.Interaction.Events; - -namespace Content.Shared.Abilities.Psionics; - -public abstract class SharedTelegnosisPowerSystem : EntitySystem -{ - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnInteractionAttempt); - SubscribeLocalEvent(OnShowSSDIndicator); - } - - private void OnInteractionAttempt(Entity ent, ref InteractionAttemptEvent args) - { - // no astrally stealing someones shoes - args.Cancelled = true; - } - - private void OnShowSSDIndicator(Entity entity, ref ShowSSDIndicatorEvent args) - { - if (!TryComp(entity, out var mindSwapped) || !HasComp(mindSwapped.OriginalEntity)) - return; // Only hide if currently projecting - args.Hidden = true; - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs deleted file mode 100644 index 4d8bfbae15..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Actions.Components; -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - - -namespace Content.Shared.Abilities.Psionics -{ - [RegisterComponent, NetworkedComponent, Access(typeof(SharedTelegnosisPowerSystem))] - public sealed partial class TelegnosisPowerComponent : Component - { - [DataField("prototype")] - public string Prototype = "MobObserverTelegnostic"; - public InstantActionComponent? TelegnosisPowerAction = null; - [ValidatePrototypeId] - public const string TelegnosisActionPrototype = "ActionTelegnosis"; - [DataField("telegnosisActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? TelegnosisActionId = "ActionTelegnosis"; - - [DataField("telegnosisActionEntity")] - public EntityUid? TelegnosisActionEntity; - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs deleted file mode 100644 index 8de2b046d8..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Content.Shared.Abilities.Psionics; - -[RegisterComponent, Access(typeof(SharedTelegnosisPowerSystem))] -public sealed partial class TelegnosticProjectionComponent : Component; diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/ClothingGrantPsionicPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/ClothingGrantPsionicPowerComponent.cs deleted file mode 100644 index 4cbb05c839..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/ClothingGrantPsionicPowerComponent.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Content.Shared.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/Nyanotrasen/Abilities/Psionics/Items/PsionicItemsSystem.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/PsionicItemsSystem.cs deleted file mode 100644 index f88acf61f3..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/PsionicItemsSystem.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Content.Shared.Inventory.Events; -using Content.Shared.Clothing.Components; -using Content.Shared.StatusEffect; - -namespace Content.Shared.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/Nyanotrasen/Abilities/Psionics/Items/TinfoilHatComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/TinfoilHatComponent.cs deleted file mode 100644 index 5086b9f497..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/TinfoilHatComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Content.Shared.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/Nyanotrasen/Abilities/Psionics/PsionicComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicComponent.cs deleted file mode 100644 index 9091e03cfc..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicComponent.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Content.Shared.Actions; -using Robust.Shared.GameStates; - -namespace Content.Shared.Abilities.Psionics -{ - [RegisterComponent, NetworkedComponent] - public sealed partial class PsionicComponent : Component - { - public EntityUid? PsionicAbility = null; - - /// - /// Ifrits, revenants, etc are explicitly magical beings that shouldn't get mindbreakered. - /// - [DataField("removable")] - public bool Removable = true; - - [DataField("activePowers")] - public HashSet ActivePowers = new(); - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicInsulationComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicInsulationComponent.cs deleted file mode 100644 index 12370da5ae..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicInsulationComponent.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Content.Shared.Abilities.Psionics -{ - [RegisterComponent] - public sealed partial class PsionicInsulationComponent : Component - { - public bool Passthrough = false; - - public List SuppressedFactions = new(); - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicsDisabledComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicsDisabledComponent.cs deleted file mode 100644 index 28e7157a9d..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicsDisabledComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Abilities.Psionics -{ - /// - /// Only use this for the status effect, please. - /// - [RegisterComponent, NetworkedComponent] - public sealed partial class PsionicsDisabledComponent : Component - {} -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/SharedPsionicAbilitiesSystem.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/SharedPsionicAbilitiesSystem.cs deleted file mode 100644 index 899a49424e..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/SharedPsionicAbilitiesSystem.cs +++ /dev/null @@ -1,124 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Administration.Logs; -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; -using Content.Shared.Popups; -using Content.Shared.Psionics.Glimmer; -using Robust.Shared.Random; -using Robust.Shared.Serialization; - -namespace Content.Shared.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) - { - var ev = new PsionicPowerDetectedEvent(uid, args.Power); - var coords = Transform(uid).Coordinates; - foreach (var ent in _lookup.GetEntitiesInRange(coords, 10f)) - { - if (ent.Owner != uid && !(TryComp(ent, out var insul) && !insul.Passthrough)) - { - RaiseLocalEvent(ent, ref ev); - _popups.PopupEntity(Loc.GetString("metapsionic-pulse-power", ("power", args.Power)), ent, ent, PopupType.LargeCaution); - args.Handled = true; - } - } - } - - 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; - - if (_actions.GetAction(component.PsionicAbility) is not { } actionData) - return; - - _actions.SetEnabled(actionData.Owner, 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); - } -} - -/// -/// Event raised on a metapsionic entity when someone used a psionic power nearby. -/// -[ByRefEvent] -public record struct PsionicPowerDetectedEvent(EntityUid Psionic, string Power); - -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 NetEntity Euid; - public PsionicsChangedEvent(NetEntity euid) - { - Euid = euid; - } -} diff --git a/Content.Shared/Nyanotrasen/Actions/Events/DispelPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/DispelPowerActionEvent.cs deleted file mode 100644 index 9f2c0c2e6b..0000000000 --- a/Content.Shared/Nyanotrasen/Actions/Events/DispelPowerActionEvent.cs +++ /dev/null @@ -1,2 +0,0 @@ -namespace Content.Shared.Actions.Events; -public sealed partial class DispelPowerActionEvent : EntityTargetActionEvent { } diff --git a/Content.Shared/Nyanotrasen/Actions/Events/MassSleepPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/MassSleepPowerActionEvent.cs deleted file mode 100644 index 5d775c2cc3..0000000000 --- a/Content.Shared/Nyanotrasen/Actions/Events/MassSleepPowerActionEvent.cs +++ /dev/null @@ -1,2 +0,0 @@ -namespace Content.Shared.Actions.Events; -public sealed partial class MassSleepPowerActionEvent : InstantActionEvent {} diff --git a/Content.Shared/Nyanotrasen/Actions/Events/MetapsionicPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/MetapsionicPowerActionEvent.cs deleted file mode 100644 index b28801efe7..0000000000 --- a/Content.Shared/Nyanotrasen/Actions/Events/MetapsionicPowerActionEvent.cs +++ /dev/null @@ -1,2 +0,0 @@ -namespace Content.Shared.Actions.Events; -public sealed partial class MetapsionicPowerActionEvent : InstantActionEvent {} diff --git a/Content.Shared/Nyanotrasen/Actions/Events/MindSwapPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/MindSwapPowerActionEvent.cs deleted file mode 100644 index 85701b0c5d..0000000000 --- a/Content.Shared/Nyanotrasen/Actions/Events/MindSwapPowerActionEvent.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Content.Shared.Actions.Events; -public sealed partial class MindSwapPowerActionEvent : EntityTargetActionEvent {} -public sealed partial class MindSwapPowerReturnActionEvent : InstantActionEvent {} diff --git a/Content.Shared/Nyanotrasen/Actions/Events/NoosphericZapPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/NoosphericZapPowerActionEvent.cs deleted file mode 100644 index b725431094..0000000000 --- a/Content.Shared/Nyanotrasen/Actions/Events/NoosphericZapPowerActionEvent.cs +++ /dev/null @@ -1,2 +0,0 @@ -namespace Content.Shared.Actions.Events; -public sealed partial class NoosphericZapPowerActionEvent : EntityTargetActionEvent {} diff --git a/Content.Shared/Nyanotrasen/Actions/Events/PsionicEruptionPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/PsionicEruptionPowerActionEvent.cs deleted file mode 100644 index 2468a67126..0000000000 --- a/Content.Shared/Nyanotrasen/Actions/Events/PsionicEruptionPowerActionEvent.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Content.Shared.Actions.Events; - -public sealed partial class PsionicEruptionPowerActionEvent : InstantActionEvent { } diff --git a/Content.Shared/Nyanotrasen/Actions/Events/PsionicInvisibilityPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/PsionicInvisibilityPowerActionEvent.cs deleted file mode 100644 index cff2a5be6e..0000000000 --- a/Content.Shared/Nyanotrasen/Actions/Events/PsionicInvisibilityPowerActionEvent.cs +++ /dev/null @@ -1,2 +0,0 @@ -namespace Content.Shared.Actions.Events; -public sealed partial class PsionicInvisibilityPowerActionEvent : InstantActionEvent {} \ No newline at end of file diff --git a/Content.Shared/Nyanotrasen/Actions/Events/PsionicRegenerationPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/PsionicRegenerationPowerActionEvent.cs deleted file mode 100644 index b29cec924b..0000000000 --- a/Content.Shared/Nyanotrasen/Actions/Events/PsionicRegenerationPowerActionEvent.cs +++ /dev/null @@ -1,2 +0,0 @@ -namespace Content.Shared.Actions.Events; -public sealed partial class PsionicRegenerationPowerActionEvent : InstantActionEvent {} diff --git a/Content.Shared/Nyanotrasen/Actions/Events/PyrokinesisPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/PyrokinesisPowerActionEvent.cs deleted file mode 100644 index 896ec0bb63..0000000000 --- a/Content.Shared/Nyanotrasen/Actions/Events/PyrokinesisPowerActionEvent.cs +++ /dev/null @@ -1,2 +0,0 @@ -namespace Content.Shared.Actions.Events; -public sealed partial class PyrokinesisPowerActionEvent : EntityTargetActionEvent {} diff --git a/Content.Shared/Nyanotrasen/Actions/Events/RemovePsionicInvisibilityOffPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/RemovePsionicInvisibilityOffPowerActionEvent.cs deleted file mode 100644 index 0360ed4b04..0000000000 --- a/Content.Shared/Nyanotrasen/Actions/Events/RemovePsionicInvisibilityOffPowerActionEvent.cs +++ /dev/null @@ -1,2 +0,0 @@ -namespace Content.Shared.Actions.Events; -public sealed partial class RemovePsionicInvisibilityOffPowerActionEvent : InstantActionEvent {} diff --git a/Content.Shared/Nyanotrasen/Actions/Events/TelegnosisPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/TelegnosisPowerActionEvent.cs deleted file mode 100644 index 70ce1a4ab8..0000000000 --- a/Content.Shared/Nyanotrasen/Actions/Events/TelegnosisPowerActionEvent.cs +++ /dev/null @@ -1,2 +0,0 @@ -namespace Content.Shared.Actions.Events; -public sealed partial class TelegnosisPowerActionEvent : InstantActionEvent {} \ No newline at end of file diff --git a/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs b/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs index cafe5075c9..2522034f0a 100644 --- a/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs +++ b/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs @@ -1,3 +1,4 @@ +using Content.Shared._DV.Psionics.Events; // DeltaV - Psionics Refactor using Content.Shared.Body.Events; using Content.Shared.Damage.Events; using Content.Shared.Mobs.Events; @@ -35,6 +36,11 @@ public sealed partial class StatusEffectsSystem SubscribeLocalEvent(RelayStatusEffectEvent); SubscribeLocalEvent(RefRelayStatusEffectEvent); + + // DeltaV Start - Psionics Refactor + SubscribeLocalEvent(RefRelayStatusEffectEvent); + SubscribeLocalEvent(RefRelayStatusEffectEvent); + // DeltaV End - Psionics Refactor } private void RefRelayStatusEffectEvent(EntityUid uid, StatusEffectContainerComponent component, ref T args) where T : struct diff --git a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Node.cs b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Node.cs index bf1a0c8cea..8ca4578ff8 100644 --- a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Node.cs +++ b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Node.cs @@ -1,5 +1,5 @@ using System.Linq; -using Content.Shared._DV.Xenoarchaeology.Artifact.Components; // DeltaV +using Content.Shared._DV.Xenoarcheology.XenoArtifacts.Effects.Components; // DeltaV using Content.Shared.EntityTable; using Content.Shared.NameIdentifier; using Content.Shared.Xenoarchaeology.Artifact.Components; diff --git a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.XAT.cs b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.XAT.cs index 541602770e..ec26632d4a 100644 --- a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.XAT.cs +++ b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.XAT.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Shared._DV.Psionics.Events; // DeltaV using Content.Shared.Chemistry; using Content.Shared.Damage; using Content.Shared.Damage.Systems; @@ -24,6 +25,7 @@ public abstract partial class SharedXenoArtifactSystem XATRelayLocalEvent(); XATRelayLocalEvent(); XATRelayLocalEvent(); + XATRelayLocalEvent(); // DeltaV // special case this one because we need to order the messages SubscribeLocalEvent(OnExamined); @@ -115,7 +117,7 @@ public abstract partial class SharedXenoArtifactSystem // faster unlock effect: if ( - ent.Comp.UnlockCompleteDuration is {} completeDuration + ent.Comp.UnlockCompleteDuration is {} completeDuration && TryGetNodeFromUnlockState((ent.Owner, unlockingComp, ent.Comp), out var unlockingNode) ) { diff --git a/Content.Shared/_DV/Abilities/Psionics/FracturedFormPowerComponent.cs b/Content.Shared/_DV/Abilities/Psionics/FracturedFormPowerComponent.cs deleted file mode 100644 index d123389c00..0000000000 --- a/Content.Shared/_DV/Abilities/Psionics/FracturedFormPowerComponent.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Content.Shared.Cloning; -using Robust.Shared.Prototypes; -using Content.Shared.Roles; -using Robust.Shared.Audio; -using Robust.Shared.GameStates; -using Content.Shared.DoAfter; - -namespace Content.Shared._DV.Abilities.Psionics; - -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -public sealed partial class FracturedFormPowerComponent : Component -{ - [DataField] - public EntProtoId FracturedFormActionId = "ActionFracturedForm"; - - [DataField, AutoNetworkedField] - public EntityUid? FracturedFormActionEntity; - [DataField] - public DoAfterId? DoAfter; - - [DataField] - public ProtoId CopyNaked = "CloningPod"; - [DataField] - public ProtoId CopyClothed = "Antag"; - [DataField] - public ProtoId VisitorJob = "Passenger"; - [DataField] - public ProtoId? NakedJob = null; // Scary null, but we explicitely want no job for naked spawns. - [DataField] - public TimeSpan NextSwap = TimeSpan.MaxValue; - [DataField] - public float ManualSwapTime = 5f; - [DataField] - public bool SleepWarned = false; - [DataField] - public List Bodies { get; set; } = new(); - [DataField] - public SoundSpecifier SwapSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg") - { - Params = AudioParams.Default.WithVariation(0.05f) - }; -} diff --git a/Content.Shared/_DV/Abilities/Psionics/MindSwappedComponent.cs b/Content.Shared/_DV/Abilities/Psionics/MindSwappedComponent.cs deleted file mode 100644 index 6341cbdd72..0000000000 --- a/Content.Shared/_DV/Abilities/Psionics/MindSwappedComponent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; - -namespace Content.Shared._DV.Abilities.Psionics; - -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -public sealed partial class MindSwappedComponent : Component -{ - [DataField, AutoNetworkedField] - public EntityUid OriginalEntity = default!; - [DataField] - public EntProtoId? MindSwapReturnActionId = "ActionMindSwapReturn"; - - [DataField] - public EntityUid? MindSwapReturnActionEntity; -} diff --git a/Content.Shared/_DV/Abilities/Psionics/PrecognitionPowerComponent.cs b/Content.Shared/_DV/Abilities/Psionics/PrecognitionPowerComponent.cs deleted file mode 100644 index 83051bd788..0000000000 --- a/Content.Shared/_DV/Abilities/Psionics/PrecognitionPowerComponent.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Content.Shared.DoAfter; -using Robust.Shared.Audio; -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; - -namespace Content.Shared.Abilities.Psionics; - -[RegisterComponent, NetworkedComponent] -public sealed partial class PrecognitionPowerComponent : Component -{ - [DataField] - public float RandomResultChance = 0.2f; - - [DataField] - public SoundSpecifier VisionSound = new SoundPathSpecifier("/Audio/_DV/Effects/clang2.ogg"); - - [DataField] - public EntityUid? SoundStream; - - [DataField] - public DoAfterId? DoAfter; - - [DataField] - public TimeSpan UseDelay = TimeSpan.FromSeconds(8.35); // The length of the sound effect - - [DataField] - public EntProtoId PrecognitionActionId = "ActionPrecognition"; - - [DataField] - public EntityUid? PrecognitionActionEntity; -} diff --git a/Content.Shared/_DV/Abilities/Psionics/SharedFracturedFormPowerSystem.cs b/Content.Shared/_DV/Abilities/Psionics/SharedFracturedFormPowerSystem.cs deleted file mode 100644 index 4ee3105430..0000000000 --- a/Content.Shared/_DV/Abilities/Psionics/SharedFracturedFormPowerSystem.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Shared._DV.Mind; - -namespace Content.Shared._DV.Abilities.Psionics; - -public abstract class SharedFracturedFormPowerSystem : EntitySystem -{ - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnShowSSDIndicator); - } - - private void OnShowSSDIndicator(Entity entity, ref ShowSSDIndicatorEvent args) - { - if (HasComp(entity)) - return; - args.Hidden = true; - } -} diff --git a/Content.Shared/_DV/Abilities/ShatterLightsAbilityComponent.cs b/Content.Shared/_DV/Abilities/ShatterLightsAbilityComponent.cs deleted file mode 100644 index aa26a2e1e5..0000000000 --- a/Content.Shared/_DV/Abilities/ShatterLightsAbilityComponent.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Content.Shared.Actions; -using Robust.Shared.Audio; -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; - -namespace Content.Shared._DV.Abilities; - -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -public sealed partial class ShatterLightsAbilityComponent : Component -{ - /// - /// The radius in which lights will be broken. - /// - [DataField] - public float Radius = 10f; - - /// - /// If true, lights will only be broken if the entity has line of sight to them. - /// - [DataField] - public bool LineOfSight = false; - - /// - /// The radius to ignore line of sight restrictions. - /// - [DataField] - public float PenetratingRadius = 0f; - - /// - /// The action that triggers the shatter lights ability. - /// - [DataField] - public EntProtoId ShatterLightsActionId = "ActionShatterLights"; - - /// - /// Standing reference to the action entity, if it exists. - /// - [DataField, AutoNetworkedField] - public EntityUid? ShatterLightsActionEntity; - - /// - /// The sound to play when the ability is used. - /// - [DataField] - public SoundSpecifier AbilitySound = new SoundPathSpecifier("/Audio/_DV/Effects/creepyshriek.ogg"); - - /// - /// The effect to spawn when the ability is used. - /// - [DataField] - public EntProtoId Effect = "CMEffectScreech"; -} - -public sealed partial class ShatterLightsActionEvent : InstantActionEvent; diff --git a/Content.Shared/_DV/Actions/Events/FracturedFormPowerActionEvent.cs b/Content.Shared/_DV/Actions/Events/FracturedFormPowerActionEvent.cs deleted file mode 100644 index e64939e0a7..0000000000 --- a/Content.Shared/_DV/Actions/Events/FracturedFormPowerActionEvent.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Content.Shared.Actions.Events; - -public sealed partial class FracturedFormPowerActionEvent : InstantActionEvent; diff --git a/Content.Shared/_DV/Anomaly/SharedAnomalySystem.Psionics.cs b/Content.Shared/_DV/Anomaly/SharedAnomalySystem.Psionics.cs new file mode 100644 index 0000000000..bd8bad229a --- /dev/null +++ b/Content.Shared/_DV/Anomaly/SharedAnomalySystem.Psionics.cs @@ -0,0 +1,27 @@ +using Content.Shared._DV.CosmicCult; +using Content.Shared._DV.Psionics.Events; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; +using Content.Shared.Anomaly.Components; + +namespace Content.Shared.Anomaly; + +public abstract partial class SharedAnomalySystem +{ + [Dependency] private readonly SharedDispelPowerSystem _dispel = default!; + + private void InitializePsionics() + { + SubscribeLocalEvent(OnDispelled); + } + + //Nyano - Summary: gives dispellable behavior to Anomalies. + private void OnDispelled(Entity anomaly, ref DispelledEvent args) + { + if (HasComp(anomaly)) // begone nyanocode interference with cosmic cult + return; + + _dispel.DealDispelDamage(anomaly, dispeller: args.Dispeller); + ChangeAnomalyHealth(anomaly, 0 - Random.NextFloat(0.4f, 0.8f), anomaly.Comp); + args.Handled = true; + } +} diff --git a/Content.Shared/_DV/Chat/Components/TelepathicRepeaterComponent.cs b/Content.Shared/_DV/Chat/Components/TelepathicRepeaterComponent.cs new file mode 100644 index 0000000000..2fce91260e --- /dev/null +++ b/Content.Shared/_DV/Chat/Components/TelepathicRepeaterComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Chat.Components; + +/// +/// Repeats whatever is happening in telepathic chat. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class TelepathicRepeaterComponent : Component; diff --git a/Content.Shared/_DV/EntityEffects/Effects/Psionics/RerollPsionicAbilitiesEntityEffect.cs b/Content.Shared/_DV/EntityEffects/Effects/Psionics/RollPsionicAbilityEntityEffect.cs similarity index 63% rename from Content.Shared/_DV/EntityEffects/Effects/Psionics/RerollPsionicAbilitiesEntityEffect.cs rename to Content.Shared/_DV/EntityEffects/Effects/Psionics/RollPsionicAbilityEntityEffect.cs index 90365a87b7..0421682190 100644 --- a/Content.Shared/_DV/EntityEffects/Effects/Psionics/RerollPsionicAbilitiesEntityEffect.cs +++ b/Content.Shared/_DV/EntityEffects/Effects/Psionics/RollPsionicAbilityEntityEffect.cs @@ -4,14 +4,14 @@ using Robust.Shared.Prototypes; namespace Content.Shared._DV.EntityEffects.Effects.Psionics; /// -public sealed partial class RerollPsionicAbilities : EntityEffectBase +public sealed partial class RollPsionicAbility : EntityEffectBase { /// /// Reroll multiplier. /// - [DataField("bonusMultiplier")] + [DataField] public float BonusMultiplier = 1f; public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-chem-reroll-psionic", ("chance", Probability)); + => Loc.GetString("reagent-effect-guidebook-chem-roll-psionic", ("multiplier", BonusMultiplier)); } diff --git a/Content.Shared/_DV/Movement/Systems/MovementModStatusSystem.cs b/Content.Shared/_DV/Movement/Systems/MovementModStatusSystem.cs deleted file mode 100644 index 9997f1dc93..0000000000 --- a/Content.Shared/_DV/Movement/Systems/MovementModStatusSystem.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Movement.Components; -using Content.Shared.Movement.Events; -using Content.Shared.StatusEffectNew; -using Robust.Shared.Prototypes; - -namespace Content.Shared.Movement.Systems; - -public sealed partial class MovementModStatusSystem : EntitySystem -{ - public static readonly EntProtoId PsionicSlowdown = "PsionicSlowdownStatusEffect"; -} diff --git a/Content.Shared/_DV/Psionics/Components/AntiPsionicWeaponComponent.cs b/Content.Shared/_DV/Psionics/Components/AntiPsionicWeaponComponent.cs new file mode 100644 index 0000000000..b4e2ecb4bd --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/AntiPsionicWeaponComponent.cs @@ -0,0 +1,45 @@ +using Content.Shared.Damage; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Psionics.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class AntiPsionicWeaponComponent : Component +{ + /// + /// The DamageModifiers for each DamageType. + /// + [DataField(required: true)] + public DamageModifierSet Modifiers = default!; + + /// + /// The additional stamina damage dealt by anti-psionic weaponry. + /// + [DataField] + public float StaminaDamageMultiplier = 1f; + + /// + /// The chance to disable the target's psionic abilities on hit. + /// + [DataField] + public float DisableChance = 0.3f; + + /// + /// Punish the user when used against a non-psionic target. + /// + [DataField] + public bool Punish; + + /// + /// The chance for the weapon to punish the user when used against a non-psionic target. + /// + [DataField] + public float PunishChance = 0.5f; + + /// + /// The sound created when hitting a psionic user with the weapon or being punished. + /// + [DataField] + public SoundSpecifier? HitSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg"); +} diff --git a/Content.Shared/_DV/Psionics/Components/DamageOnDispelComponent.cs b/Content.Shared/_DV/Psionics/Components/DamageOnDispelComponent.cs new file mode 100644 index 0000000000..ceeea94997 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/DamageOnDispelComponent.cs @@ -0,0 +1,31 @@ +using Content.Shared.Damage; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Psionics.Components; + +/// +/// Takes damage when dispelled. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class DamageOnDispelComponent : Component +{ + /// + /// The damage dealt to them on being dispelled. + /// + [DataField(required: true)] + public DamageSpecifier Damage = default!; + + /// + /// The variance that will be multiplied with the initial value. + /// A variance of 0.5 will lead to the damage dealing either minimum half the damage, or 1.5x the damage maximum. + /// + [DataField] + public float Variance = 0.5f; + + /// + /// The sound that occurs when being dispelled. + /// + [DataField] + public SoundSpecifier DispelSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg"); +} diff --git a/Content.Shared/_DV/Psionics/Components/DispellableComponent.cs b/Content.Shared/_DV/Psionics/Components/DispellableComponent.cs new file mode 100644 index 0000000000..a40f6a669c --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/DispellableComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Psionics.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class DispellableComponent : Component +{ + /// + /// The sound that occurs when being dispelled. + /// + [DataField] + public SoundSpecifier DispelSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg"); +} diff --git a/Content.Shared/_DV/Abilities/Psionics/FracturedFormBodyComponent.cs b/Content.Shared/_DV/Psionics/Components/FracturedFormBodyComponent.cs similarity index 70% rename from Content.Shared/_DV/Abilities/Psionics/FracturedFormBodyComponent.cs rename to Content.Shared/_DV/Psionics/Components/FracturedFormBodyComponent.cs index 6082a01c12..6dfe1885f1 100644 --- a/Content.Shared/_DV/Abilities/Psionics/FracturedFormBodyComponent.cs +++ b/Content.Shared/_DV/Psionics/Components/FracturedFormBodyComponent.cs @@ -1,6 +1,6 @@ using Robust.Shared.GameStates; -namespace Content.Shared._DV.Abilities.Psionics; +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; [RegisterComponent, NetworkedComponent] public sealed partial class FracturedFormBodyComponent : Component @@ -9,5 +9,5 @@ public sealed partial class FracturedFormBodyComponent : Component /// The entity uid of the form that is currently being controlled. /// [DataField] - public EntityUid ControllingForm { get; set; } = default!; + public EntityUid ControllingForm { get; set; } } diff --git a/Content.Shared/_DV/Psionics/Components/PotentialPsionicComponent.cs b/Content.Shared/_DV/Psionics/Components/PotentialPsionicComponent.cs new file mode 100644 index 0000000000..89c02a41de --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PotentialPsionicComponent.cs @@ -0,0 +1,46 @@ +using Content.Shared.EntityTable; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Components; + +/// +/// Entities with this component can become psionics at roundstart or via rerolling. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class PotentialPsionicComponent : Component +{ + /// + /// The Base Chance for all potential psionics to become one at roundstart and gain a power. + /// + [DataField] + public float BaseChance = 0.15f; + + /// + /// Additive bonus for from your role. + /// + /// Command roles and the Chaplain get an additive bonus to being psionic. + [DataField, AutoNetworkedField] + public float JobBonusChance; + + /// + /// Additive bonus for from your species. + /// + /// Noöspheric attuned species like Kitsunes get an additive bonus. + [DataField] + public float SpeciesBonusChance; + + /// + /// Whether you've already attempted to roll a new power via other means. + /// + /// Lotophagoi Oil will attempt to roll a new power for the consumer if this is false. + /// It'll then be set true. + [DataField, AutoNetworkedField] + public bool Rolled; + + /// + /// The Prototype ID of the table containing the available psionic powers to roll. + /// + [DataField] + public ProtoId PsionicPowerTableId = "PsionicPowerTable"; +} diff --git a/Content.Shared/_DV/Abilities/Psionics/PrecognitionResultComponent.cs b/Content.Shared/_DV/Psionics/Components/PrecognitionResultComponent.cs similarity index 77% rename from Content.Shared/_DV/Abilities/Psionics/PrecognitionResultComponent.cs rename to Content.Shared/_DV/Psionics/Components/PrecognitionResultComponent.cs index 059ada567f..9f812cb8d8 100644 --- a/Content.Shared/_DV/Abilities/Psionics/PrecognitionResultComponent.cs +++ b/Content.Shared/_DV/Psionics/Components/PrecognitionResultComponent.cs @@ -6,6 +6,9 @@ namespace Content.Shared.Abilities.Psionics; [RegisterComponent] public sealed partial class PrecognitionResultComponent : Component { + /// + /// The message that will warn the psionic about the coming event. + /// [DataField(required: true)] public LocId Message; diff --git a/Content.Shared/_DV/Psionics/Components/PsionicComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicComponent.cs new file mode 100644 index 0000000000..63ab1ba5f4 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicComponent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Psionics.Components; + +/// +/// Entities with this component are psionics and can use powers. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class PsionicComponent : Component +{ + /// + /// The list of the action buttons for every power. + /// + [DataField] + public HashSet PsionicPowersActionEntities = []; + + /// + /// Whether the psionic gets stunned when a psionic power gets removed. This doesn't mean they lost all psionic powers. + /// Psionic powers themselves regulate if they can be removed. + /// + [DataField] + public bool StunOnRemoval = true; +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicInvisibilityUsedComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicInvisibilityUsedComponent.cs new file mode 100644 index 0000000000..00c641abe3 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicInvisibilityUsedComponent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Psionics.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class PsionicInvisibilityUsedComponent : Component +{ + /// + /// The sound that plays when going invisible. + /// + public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/toss.ogg"); + + /// + /// They are fully invisible. This only makes their sprite appear shaded so they know they're invisible. + /// + public float InvisibilityStrength = 0.66f; + + /// + /// How long the user will be stunned when leaving invisibility. + /// + public TimeSpan StunDuration = TimeSpan.FromSeconds(4); +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowerDetectorComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowerDetectorComponent.cs new file mode 100644 index 0000000000..16e1ce1b6c --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowerDetectorComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Psionics.Components; + +/// +/// Entities with this component can detect psionic usage nearby. +/// This is usually paired with the metapsionic pulse power. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class PsionicPowerDetectorComponent : Component +{ + /// + /// In case the detection is provided by an item, remember the wearer. + /// + [DataField, AutoNetworkedField] + public EntityUid? Wearer; +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowers/BasePsionicPowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/BasePsionicPowerComponent.cs new file mode 100644 index 0000000000..54d91e4316 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowers/BasePsionicPowerComponent.cs @@ -0,0 +1,99 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; + +/// +/// Entities with this component are psionically insulated from a source. +/// +public abstract partial class BasePsionicPowerComponent : Component +{ + /// + /// The actual UID for the action entity. It'll be saved here when the component is initialized. + /// + [DataField, AutoNetworkedField] + public EntityUid? ActionEntity; + + /// + /// The prototype ID for the action. + /// It's set up in the component, optionally overriden via YML and then referenced via a string here. + /// + [DataField] + public abstract EntProtoId ActionProtoId { get; set; } + + /// + /// The Loc string for the name of the power. + /// + [DataField] + public abstract string PowerName { get; set; } + + /// + /// The minimum glimmer amount that will be changed upon use of the psionic power. + /// Should be lower than . + /// + [DataField] + public abstract int MinGlimmerChanged { get; set; } + + /// + /// The maximum glimmer amount that will be changed upon use of the psionic power. + /// Should be higher than . + /// + [DataField] + public abstract int MaxGlimmerChanged { get; set; } + + /// + /// Whether this ability can be removed via mindbreaking. + /// + /// Revenants shouldn't be able to lose their powers. + [DataField] + public bool CanBeRemoved = true; + + /// + /// When a power uses a DoAfter, the ID can be saved here for convenience. + /// It'll handle being dispelled automatically. + /// It'll need to be broken up into the DoAfter EntityUid and ushort index first. + /// + [DataField, AutoNetworkedField] + public EntityUid? DoAfterEntityId; + + /// + /// When a power uses a DoAfter, the index can be saved here for convenience. + /// It'll handle being dispelled automatically. + /// It'll need to be broken up into the DoAfter EntityUid and ushort index first. + /// + [DataField, AutoNetworkedField] + public ushort? DoAfterIdIndex; + + /// + /// Helper method to save a DoAfterId as DoAfterIds are not serializable and therefore cannot be networked. + /// It's parts can be though, and can be rebuilt. + /// + /// The DoAfterId to save. If null, it'll remove the saved DoAfterId. + public void SaveDoAfterId(DoAfterId doAfterId) + { + DoAfterEntityId = doAfterId.Uid; + DoAfterIdIndex = doAfterId.Index; + } + + /// + /// Helper method to remove the saved DoAfterId. + /// + public void RemoveSavedDoAfterId() + { + DoAfterEntityId = null; + DoAfterIdIndex = null; + } + + /// + /// A helper method to get a saved DoAfterId. + /// + /// Returns a DoAfterId if one is present, null if not. + public DoAfterId? GetDoAfterId() + { + if (DoAfterEntityId is not { } doAfterId + || DoAfterIdIndex is not { } doAfterIndex) + return null; + + return new DoAfterId(doAfterId, doAfterIndex); + } +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowers/DispelPowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/DispelPowerComponent.cs new file mode 100644 index 0000000000..9b656c8963 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowers/DispelPowerComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class DispelPowerComponent : BasePsionicPowerComponent +{ + public override EntProtoId ActionProtoId { get; set; } = "ActionDispel"; + + public override string PowerName { get; set; } = "psionic-power-name-dispel"; + + public override int MinGlimmerChanged { get; set; } = 5; + + public override int MaxGlimmerChanged { get; set; } = 10; +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowers/FracturedFormPowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/FracturedFormPowerComponent.cs new file mode 100644 index 0000000000..6dc9539e49 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowers/FracturedFormPowerComponent.cs @@ -0,0 +1,124 @@ +using Content.Shared.Cloning; +using Content.Shared.Roles; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] +public sealed partial class FracturedFormPowerComponent : BasePsionicPowerComponent +{ + public override EntProtoId ActionProtoId { get; set; } = "ActionFracturedForm"; + + public override string PowerName { get; set; } = "psionic-power-name-fractured-form"; + + public override int MinGlimmerChanged { get; set; } = 5; + + public override int MaxGlimmerChanged { get; set; } = 30; + + /// + /// The chance for the clone to have the starting gear of the psionic. + /// + [DataField] + public float HasGearChance = 0.4f; + + /// + /// The chance for the clone to have a different species than the psionic. + /// + [DataField] + public float DifferentSpeciesChance = 0.6f; + + /// + /// These are settings for creating the second body. + /// + [DataField] + public ProtoId CopyNaked = "CloningPod"; + + /// + /// These are settings for creating the second body. + /// + [DataField] + public ProtoId CopyClothed = "Antag"; + + /// + /// These are settings for creating the second body. + /// + [DataField] + public ProtoId VisitorJob = "Passenger"; + + /// + /// These are settings for creating the second body. + /// + [DataField] + public ProtoId? NakedJob; // Scary null, but we explicitly want no job for naked spawns. + + /// + /// The minimum time between forced swaps. + /// + [DataField] + public TimeSpan NextSwapMinTime = TimeSpan.FromMinutes(5); + + /// + /// The maximum time between swaps. + /// + [DataField] + public TimeSpan NextSwapMaxTime = TimeSpan.FromMinutes(20); + + /// + /// The current timer for when the next swap is forced. + /// + [DataField, AutoPausedField] + public TimeSpan NextSwap = TimeSpan.FromMinutes(5); + + /// + /// The minimum time between voluntary swaps by sleeping. + /// + [DataField] + public TimeSpan VoluntarySwapCooldown = TimeSpan.FromSeconds(10); + + /// + /// The minimum time between voluntary swaps by sleeping. + /// + [DataField, AutoPausedField] + public TimeSpan NextVoluntarySwap = TimeSpan.FromSeconds(10); + + /// + /// The length of the DoAfter for swapping bodies. + /// + [DataField] + public float ManualSwapTime = 5f; + + /// + /// The time the psionic needs to be asleep to swap bodies. + /// + [DataField] + public TimeSpan SwapTimeAsleep = TimeSpan.FromSeconds(3); + + /// + /// The time length where the warning is sent before the forced swap. + /// + [DataField] + public TimeSpan WarningTimeBeforeSleep = TimeSpan.FromSeconds(5); + + /// + /// A boolean check for when the person has been warned. + /// + [DataField] + public bool SleepWarned; + + /// + /// The bodies that the psionic user can swap into. + /// + [DataField, AutoNetworkedField] + public List Bodies { get; set; } = []; + + /// + /// The sound that plays when the swap happens. + /// + [DataField] + public SoundSpecifier SwapSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg") + { + Params = AudioParams.Default.WithVariation(0.05f), + }; +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowers/MassSleepPowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/MassSleepPowerComponent.cs new file mode 100644 index 0000000000..a5998c95ce --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowers/MassSleepPowerComponent.cs @@ -0,0 +1,40 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class MassSleepPowerComponent : BasePsionicPowerComponent +{ + public override EntProtoId ActionProtoId { get; set; } = "ActionMassSleep"; + + public override string PowerName { get; set; } = "psionic-power-name-mass-sleep"; + + public override int MinGlimmerChanged { get; set; } = 10; + + public override int MaxGlimmerChanged { get; set; } = 20; + + /// + /// The radius to where people will fall asleep. + /// + [DataField] + public float Radius = 2f; + + /// + /// The duration of the DoAfter. Casting time, per say. + /// + [DataField] + public TimeSpan UseDelay = TimeSpan.FromSeconds(4); + + /// + /// How long the victims will be asleep. + /// + [DataField] + public TimeSpan Duration = TimeSpan.FromSeconds(5); + + /// + /// The radius for where people will be warned about being mass slept. + /// + [DataField] + public float WarningRadius = 6f; +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowers/MetapsionicPulsePowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/MetapsionicPulsePowerComponent.cs new file mode 100644 index 0000000000..559336c11c --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowers/MetapsionicPulsePowerComponent.cs @@ -0,0 +1,22 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class MetapsionicPulsePowerComponent : BasePsionicPowerComponent +{ + public override EntProtoId ActionProtoId { get; set; } = "ActionMetapsionicPulse"; + + public override string PowerName { get; set; } = "psionic-power-name-metapsionic"; + + public override int MinGlimmerChanged { get; set; } = 1; + + public override int MaxGlimmerChanged { get; set; } = 10; + + /// + /// The radius of the pulse. + /// + [DataField] + public float Range = 1.5f; +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowers/MindSwapPowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/MindSwapPowerComponent.cs new file mode 100644 index 0000000000..7215d19cd3 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowers/MindSwapPowerComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class MindSwapPowerComponent : BasePsionicPowerComponent +{ + public override EntProtoId ActionProtoId { get; set; } = "ActionMindSwapPsionic"; + + public override string PowerName { get; set; } = "psionic-power-name-mindswap"; + + public override int MinGlimmerChanged { get; set; } = 5; + + public override int MaxGlimmerChanged { get; set; } = 30; +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowers/MindSwappedReturnPowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/MindSwappedReturnPowerComponent.cs new file mode 100644 index 0000000000..84d40b780d --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowers/MindSwappedReturnPowerComponent.cs @@ -0,0 +1,19 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class MindSwappedReturnPowerComponent : BasePsionicPowerComponent +{ + public override EntProtoId ActionProtoId { get; set; } = "ActionMindSwapReturn"; + + public override string PowerName { get; set; } = "psionic-power-name-mindswap-return"; + + public override int MinGlimmerChanged { get; set; } = 0; + + public override int MaxGlimmerChanged { get; set; } = 0; + + [DataField, AutoNetworkedField] + public EntityUid OriginalEntity; +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowers/NoosphericZapPowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/NoosphericZapPowerComponent.cs new file mode 100644 index 0000000000..8273d8e3b0 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowers/NoosphericZapPowerComponent.cs @@ -0,0 +1,34 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class NoosphericZapPowerComponent : BasePsionicPowerComponent +{ + public override EntProtoId ActionProtoId { get; set; } = "ActionNoosphericZap"; + + public override string PowerName { get; set; } = "psionic-power-name-noospheric-zap"; + + public override int MinGlimmerChanged { get; set; } = 10; + + public override int MaxGlimmerChanged { get; set; } = 50; + + /// + /// The prototype for the lightning. + /// + [DataField] + public EntProtoId LightningPrototpyeId = "PsionicLightning"; + + /// + /// How much damage the lightning will do if the target isn't insulated. + /// + [DataField] + public int ShockDamage = 15; + + /// + /// How long the target will be stunned. + /// + [DataField] + public TimeSpan StunDuration = TimeSpan.FromSeconds(3); +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowers/PrecognitionPowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/PrecognitionPowerComponent.cs new file mode 100644 index 0000000000..1fc51c403e --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowers/PrecognitionPowerComponent.cs @@ -0,0 +1,59 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class PrecognitionPowerComponent : BasePsionicPowerComponent +{ + public override EntProtoId ActionProtoId { get; set; } = "ActionPrecognition"; + + public override string PowerName { get; set; } = "psionic-power-name-precognition"; + + public override int MinGlimmerChanged { get; set; } = 5; + + public override int MaxGlimmerChanged { get; set; } = 10; + + /// + /// This dictates the chance that it'll return a wrong message, seeding unreliance. + /// + [DataField] + public float RandomResultChance = 0.2f; + + /// + /// The time it takes for the precognition to finish. Casting time, so to speak. + /// + [DataField] + public TimeSpan UseDelay = TimeSpan.FromSeconds(8.35); // The length of the sound effect + + /// + /// It'll have a shorter cooldown if it was cancelled. + /// + [DataField] + public TimeSpan CancellationCooldown = TimeSpan.FromSeconds(15); + + /// + /// The minimum time distance to the next event to be considered as a result for the precognition. + /// + [DataField] + public TimeSpan MinEventTimeDistance = TimeSpan.FromSeconds(30); + + /// + /// The maximum time distance to the next event to be considered as a result for the precognition. + /// + [DataField] + public TimeSpan MaxEventTimeDistance = TimeSpan.FromMinutes(10); + + /// + /// The sound that plays when you start the DoAfter. + /// + [DataField] + public SoundSpecifier VisionSound = new SoundPathSpecifier("/Audio/_DV/Effects/clang2.ogg"); + + /// + /// Cached SoundStream, so it can be stopped if the DoAfter is prematurely cancelled. + /// + [DataField] + public EntityUid? SoundStream; +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowers/PsionicEruptionPowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/PsionicEruptionPowerComponent.cs new file mode 100644 index 0000000000..19b08bd33b --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowers/PsionicEruptionPowerComponent.cs @@ -0,0 +1,49 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] +public sealed partial class PsionicEruptionPowerComponent : BasePsionicPowerComponent +{ + public override EntProtoId ActionProtoId { get; set; } = "ActionEruption"; + + public override string PowerName { get; set; } = "psionic-power-name-eruption"; + + public override int MinGlimmerChanged { get; set; } = -200; + + public override int MaxGlimmerChanged { get; set; } = -100; + + /// + /// Minimum time for the Detonation DoAfter to take effect. + /// Half of it is the time for every spark. + /// + [DataField] + public TimeSpan MinDetonateDelay = TimeSpan.FromSeconds(10); + + /// + /// Maximum time for the Detonation DoAfter to take effect. + /// Half of it is the time for every spark. + /// + [DataField] + public TimeSpan MaxDetonateDelay = TimeSpan.FromSeconds(30); + + /// + /// The sound that appears when the action is pressed. + /// + [DataField] + public SoundSpecifier SoundUse = new SoundPathSpecifier("/Audio/Nyanotrasen/Psionics/heartbeat_fast.ogg"); + + /// + /// The timespan for the next annoy popup. This will be refreshed depending on the glimmer amount. + /// + [DataField, AutoPausedField] + public TimeSpan NextAnnoy = TimeSpan.FromSeconds(5); + + /// + /// The timespan for the next spark to appear. + /// + [DataField, AutoPausedField] + public TimeSpan? NextSpark; +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowers/PsionicInvisibilityPowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/PsionicInvisibilityPowerComponent.cs new file mode 100644 index 0000000000..5a9276441a --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowers/PsionicInvisibilityPowerComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class PsionicInvisibilityPowerComponent : BasePsionicPowerComponent +{ + public override EntProtoId ActionProtoId { get; set; } = "ActionPsionicInvisibility"; + + public override string PowerName { get; set; } = "psionic-power-name-psionic-invisibility"; + + public override int MinGlimmerChanged { get; set; } = 10; + + public override int MaxGlimmerChanged { get; set; } = 25; +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowers/PsionicRegenerationPowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/PsionicRegenerationPowerComponent.cs new file mode 100644 index 0000000000..d203554e08 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowers/PsionicRegenerationPowerComponent.cs @@ -0,0 +1,41 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class PsionicRegenerationPowerComponent : BasePsionicPowerComponent +{ + public override EntProtoId ActionProtoId { get; set; } = "ActionPsionicRegeneration"; + + public override string PowerName { get; set; } = "psionic-power-name-psionic-regeneration"; + + public override int MinGlimmerChanged { get; set; } = 20; + + public override int MaxGlimmerChanged { get; set; } = 30; + + /// + /// How much prometheum essence will be injected into the psionic on full completion. + /// + [DataField] + public float EssenceAmount = 20; + + /// + /// The reagent ID that will be inserted into the bloodstream. + /// + [DataField] + public string ReagentId = "Prometheum"; + + /// + /// How long the DoAfter lasts. + /// + [DataField] + public float UseDelay = 8f; + + /// + /// The sound that plays when activating the ability. + /// + [DataField] + public SoundSpecifier SoundUse = new SoundPathSpecifier("/Audio/Nyanotrasen/Psionics/heartbeat_fast.ogg"); +} diff --git a/Content.Shared/_DV/Abilities/Psionics/PsychokineticScreamPowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/PsychokineticScreamPowerComponent.cs similarity index 63% rename from Content.Shared/_DV/Abilities/Psionics/PsychokineticScreamPowerComponent.cs rename to Content.Shared/_DV/Psionics/Components/PsionicPowers/PsychokineticScreamPowerComponent.cs index 6def2b2a86..3906e6d9b2 100644 --- a/Content.Shared/_DV/Abilities/Psionics/PsychokineticScreamPowerComponent.cs +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowers/PsychokineticScreamPowerComponent.cs @@ -1,13 +1,20 @@ -using Content.Shared.Actions; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -namespace Content.Shared._DV.Abilities; +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -public sealed partial class PsychokineticScreamPowerComponent : Component +public sealed partial class PsychokineticScreamPowerComponent : BasePsionicPowerComponent { + public override EntProtoId ActionProtoId { get; set; } = "ActionPsychokineticScream"; + + public override string PowerName { get; set; } = "psionic-power-name-psychokinetic"; + + public override int MinGlimmerChanged { get; set; } = 20; + + public override int MaxGlimmerChanged { get; set; } = 30; + /// /// The radius in which lights will be broken. /// @@ -18,19 +25,13 @@ public sealed partial class PsychokineticScreamPowerComponent : Component /// If true, lights will only be broken if the entity has line of sight to them. /// [DataField] - public bool LineOfSight = false; + public bool LineOfSight = true; /// - /// The action that triggers the psychokinetic scream ability. + /// The radius to ignore line of sight restrictions. /// [DataField] - public EntProtoId ShatterLightsActionId = "ActionPsychokineticScream"; - - /// - /// Standing reference to the action entity, if it exists. - /// - [DataField, AutoNetworkedField] - public EntityUid? PsychokineticScreamActionEntity; + public float PenetratingRadius; /// /// The sound to play when the ability is used. @@ -44,5 +45,3 @@ public sealed partial class PsychokineticScreamPowerComponent : Component [DataField] public EntProtoId Effect = "CMEffectScreech"; } - -public sealed partial class ShatterLightsActionEvent : InstantActionEvent; diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowers/PyrokinesisPowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/PyrokinesisPowerComponent.cs new file mode 100644 index 0000000000..34ba801d0b --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowers/PyrokinesisPowerComponent.cs @@ -0,0 +1,22 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class PyrokinesisPowerComponent : BasePsionicPowerComponent +{ + public override EntProtoId ActionProtoId { get; set; } = "ActionPyrokinesis"; + + public override string PowerName { get; set; } = "psionic-power-name-Pyrokinesis"; + + public override int MinGlimmerChanged { get; set; } = 10; + + public override int MaxGlimmerChanged { get; set; } = 30; + + /// + /// How many firestacks will be added on the target. + /// + [DataField] + public int AddedFirestacks = 5; +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowers/TelegnosisPowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/TelegnosisPowerComponent.cs new file mode 100644 index 0000000000..1abec39512 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicPowers/TelegnosisPowerComponent.cs @@ -0,0 +1,23 @@ +using Content.Shared._DV.Psionics.Systems.PsionicPowers; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Components.PsionicPowers; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedTelegnosisPowerSystem))] +public sealed partial class TelegnosisPowerComponent : BasePsionicPowerComponent +{ + public override EntProtoId ActionProtoId { get; set; } = "ActionTelegnosis"; + + public override string PowerName { get; set; } = "psionic-power-name-telegnosis"; + + public override int MinGlimmerChanged { get; set; } = 10; + + public override int MaxGlimmerChanged { get; set; } = 50; + + /// + /// The mob prototype that will be used to telegnosis. + /// + [DataField] + public EntProtoId Prototype = "MobObserverTelegnostic"; +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicallyInsulativeComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicallyInsulativeComponent.cs new file mode 100644 index 0000000000..c720034fa1 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicallyInsulativeComponent.cs @@ -0,0 +1,43 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Psionics.Components; + +/// +/// Clothing with this component insulates the wearer upon being worn. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class PsionicallyInsulativeComponent : Component +{ + /// + /// Whether the wearer can still use their own psionics. + /// + /// The mantis' jacket is insulated and allows passthrough, which protects them from psionics while allowing them to use their own. + [DataField] + public bool AllowsPsionicUsage; + + /// + /// Whether the wearer is shielded from psionic influences. + /// + /// The security headcage stops the wearer from using their own psionics, but can still be affected by others. + [DataField] + public bool ShieldsFromPsionics = true; + + /// + /// Whether it's active when it's equipped in pockets. + /// + [DataField] + public bool ActiveInPocket; + + /// + /// If yes, it'll be destroyed in a noöspheric fry event. + /// + [DataField] + public bool CanBeFried; + + /// + /// What sound it makes when a noöspheric fry event happens. + /// + [DataField] + public SoundSpecifier? FrySound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg"); +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicallyInvisibleComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicallyInvisibleComponent.cs new file mode 100644 index 0000000000..0bad0ef659 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicallyInvisibleComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Psionics.Components; + +/// +/// This is not the power, this makes you just invisible to anyone potentially psionic. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class PsionicallyInvisibleComponent : Component +{ + /// + /// Whether the invisibility is currently active. + /// This can be toggled via psionically surpressing gear. + /// + [DataField] + public bool Active = true; +} diff --git a/Content.Shared/_DV/Psionics/Components/PsionicsDisabledComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicsDisabledComponent.cs new file mode 100644 index 0000000000..37ef745609 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/PsionicsDisabledComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Psionics.Components; + +/// +/// Entities with this component cannot use psionic powers. +/// +/// This should solely be used for StatusEffects. For insulative gear, see +[RegisterComponent, NetworkedComponent] +public sealed partial class PsionicsDisabledComponent : Component; diff --git a/Content.Shared/_DV/Psionics/Components/ShieldedFromPsionicsComponent.cs b/Content.Shared/_DV/Psionics/Components/ShieldedFromPsionicsComponent.cs new file mode 100644 index 0000000000..8ed0345d43 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/ShieldedFromPsionicsComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Psionics.Components; + +/// +/// Entities with this component are shielded from psionic powers. +/// +/// This should solely be used for StatusEffects. For insulative gear, see +[RegisterComponent, NetworkedComponent] +public sealed partial class ShieldedFromPsionicsComponent : Component; diff --git a/Content.Shared/_DV/Psionics/Components/TelegnosticProjectionComponent.cs b/Content.Shared/_DV/Psionics/Components/TelegnosticProjectionComponent.cs new file mode 100644 index 0000000000..5341577e2b --- /dev/null +++ b/Content.Shared/_DV/Psionics/Components/TelegnosticProjectionComponent.cs @@ -0,0 +1,7 @@ +using Content.Shared._DV.Psionics.Systems.PsionicPowers; +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Psionics.Components; + +[RegisterComponent, NetworkedComponent, Access(typeof(SharedTelegnosisPowerSystem))] +public sealed partial class TelegnosticProjectionComponent : Component; diff --git a/Content.Shared/_DV/Psionics/Events.cs b/Content.Shared/_DV/Psionics/Events.cs index 9b54efd125..8c64c4ad43 100644 --- a/Content.Shared/_DV/Psionics/Events.cs +++ b/Content.Shared/_DV/Psionics/Events.cs @@ -3,17 +3,5 @@ using Robust.Shared.Serialization; namespace Content.Shared.Psionics.Events; -[Serializable, NetSerializable] -public sealed partial class PrecognitionDoAfterEvent : SimpleDoAfterEvent; - [Serializable, NetSerializable] public sealed partial class GlimmerWispDrainDoAfterEvent : SimpleDoAfterEvent; - -[Serializable, NetSerializable] -public sealed partial class MassSleepDoAfterEvent : SimpleDoAfterEvent; - -[Serializable, NetSerializable] -public sealed partial class PsionicEruptionDoAfterEvent : SimpleDoAfterEvent; - -[Serializable, NetSerializable] -public sealed partial class FracturedFormDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/_DV/Psionics/Events/DispelledEvent.cs b/Content.Shared/_DV/Psionics/Events/DispelledEvent.cs new file mode 100644 index 0000000000..ec7e782859 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/DispelledEvent.cs @@ -0,0 +1,18 @@ +using Content.Shared.Inventory; + +namespace Content.Shared._DV.Psionics.Events; + +public sealed class DispelledEvent(EntityUid dispeller, EntityUid target) : HandledEntityEventArgs, IInventoryRelayEvent +{ + /// + /// The person who used dispel on the target. + /// + public EntityUid Dispeller = dispeller; + + /// + /// The entity being dispelled. + /// + public EntityUid Target = target; + + public SlotFlags TargetSlots => SlotFlags.WITHOUT_POCKET; +} diff --git a/Content.Shared/_DV/Psionics/Events/NoosphericFryEvent.cs b/Content.Shared/_DV/Psionics/Events/NoosphericFryEvent.cs new file mode 100644 index 0000000000..1de34da17e --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/NoosphericFryEvent.cs @@ -0,0 +1,18 @@ +using Content.Shared.Damage; +using Content.Shared.Inventory; + +namespace Content.Shared._DV.Psionics.Events; + +/// +/// Event raised on an entity when a noöspheric fry gamerule happens. +/// +/// The damage dealt to every entity wearing insulative gear. +/// The firestacks added to each +[ByRefEvent] +public readonly struct NoosphericFryEvent(DamageSpecifier damage, int fireStacks) : IInventoryRelayEvent +{ + public readonly DamageSpecifier Damage = damage; + public readonly int FireStacks = fireStacks; + + SlotFlags IInventoryRelayEvent.TargetSlots => SlotFlags.WITHOUT_POCKET; +}; diff --git a/Content.Shared/_DV/Psionics/Events/PowerActionEvents/DispelPowerActionEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/DispelPowerActionEvent.cs new file mode 100644 index 0000000000..20407dfe9e --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/DispelPowerActionEvent.cs @@ -0,0 +1,5 @@ +using Content.Shared.Actions; + +namespace Content.Shared._DV.Psionics.Events.PowerActionEvents; + +public sealed partial class DispelPowerActionEvent : EntityTargetActionEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PowerActionEvents/FracturedFormPowerActionEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/FracturedFormPowerActionEvent.cs new file mode 100644 index 0000000000..81f5acba1a --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/FracturedFormPowerActionEvent.cs @@ -0,0 +1,5 @@ +using Content.Shared.Actions; + +namespace Content.Shared._DV.Psionics.Events.PowerActionEvents; + +public sealed partial class FracturedFormPowerActionEvent : InstantActionEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PowerActionEvents/MassSleepPowerActionEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/MassSleepPowerActionEvent.cs new file mode 100644 index 0000000000..8db7659fb1 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/MassSleepPowerActionEvent.cs @@ -0,0 +1,5 @@ +using Content.Shared.Actions; + +namespace Content.Shared._DV.Psionics.Events.PowerActionEvents; + +public sealed partial class MassSleepPowerActionEvent : InstantActionEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PowerActionEvents/MetapsionicPulsePowerActionEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/MetapsionicPulsePowerActionEvent.cs new file mode 100644 index 0000000000..d7b2ec7074 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/MetapsionicPulsePowerActionEvent.cs @@ -0,0 +1,9 @@ +using Content.Shared.Actions; + +namespace Content.Shared._DV.Psionics.Events.PowerActionEvents; + +/// +/// This gets fired when someone uses the MetapsionicPulse action. +/// +[ByRefEvent] +public sealed partial class MetapsionicPulsePowerActionEvent : WorldTargetActionEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PowerActionEvents/MindSwapPowerActionEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/MindSwapPowerActionEvent.cs new file mode 100644 index 0000000000..dd1fead352 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/MindSwapPowerActionEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.Actions; + +namespace Content.Shared._DV.Psionics.Events.PowerActionEvents; + +public sealed partial class MindSwapPowerActionEvent : EntityTargetActionEvent; + +public sealed partial class MindSwappedReturnPowerActionEvent : InstantActionEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PowerActionEvents/NoosphericZapPowerActionEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/NoosphericZapPowerActionEvent.cs new file mode 100644 index 0000000000..70d82a260c --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/NoosphericZapPowerActionEvent.cs @@ -0,0 +1,5 @@ +using Content.Shared.Actions; + +namespace Content.Shared._DV.Psionics.Events.PowerActionEvents; + +public sealed partial class NoosphericZapPowerActionEvent : EntityTargetActionEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PowerActionEvents/PsionicEruptionPowerActionEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/PsionicEruptionPowerActionEvent.cs new file mode 100644 index 0000000000..267c83cc1f --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/PsionicEruptionPowerActionEvent.cs @@ -0,0 +1,9 @@ +using Content.Shared.Actions; + +namespace Content.Shared._DV.Psionics.Events.PowerActionEvents; + +/// +/// This gets fired when someone uses the PsionicEruption action. +/// +[ByRefEvent] +public sealed partial class PsionicEruptionPowerActionEvent : InstantActionEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PowerActionEvents/PsionicInvisibilityPowerActionEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/PsionicInvisibilityPowerActionEvent.cs new file mode 100644 index 0000000000..243272965f --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/PsionicInvisibilityPowerActionEvent.cs @@ -0,0 +1,5 @@ +using Content.Shared.Actions; + +namespace Content.Shared._DV.Psionics.Events.PowerActionEvents; + +public sealed partial class PsionicInvisibilityPowerActionEvent : InstantActionEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PowerActionEvents/PsionicRegenerationPowerActionEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/PsionicRegenerationPowerActionEvent.cs new file mode 100644 index 0000000000..15f894389d --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/PsionicRegenerationPowerActionEvent.cs @@ -0,0 +1,5 @@ +using Content.Shared.Actions; + +namespace Content.Shared._DV.Psionics.Events.PowerActionEvents; + +public sealed partial class PsionicRegenerationPowerActionEvent : InstantActionEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PowerActionEvents/PsychokineticScreamPowerActionEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/PsychokineticScreamPowerActionEvent.cs new file mode 100644 index 0000000000..7d53f9e8be --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/PsychokineticScreamPowerActionEvent.cs @@ -0,0 +1,9 @@ +using Content.Shared.Actions; + +namespace Content.Shared._DV.Psionics.Events.PowerActionEvents; + +/// +/// This gets fired when someone uses the ShatterLightsPower action. +/// +[ByRefEvent] +public sealed partial class PsychokineticScreamPowerActionEvent : InstantActionEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PowerActionEvents/PyrokinesisPowerActionEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/PyrokinesisPowerActionEvent.cs new file mode 100644 index 0000000000..c50725bc4b --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/PyrokinesisPowerActionEvent.cs @@ -0,0 +1,5 @@ +using Content.Shared.Actions; + +namespace Content.Shared._DV.Psionics.Events.PowerActionEvents; + +public sealed partial class PyrokinesisPowerActionEvent : EntityTargetActionEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PowerActionEvents/TelegnosisPowerActionEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/TelegnosisPowerActionEvent.cs new file mode 100644 index 0000000000..c381c1e032 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/TelegnosisPowerActionEvent.cs @@ -0,0 +1,5 @@ +using Content.Shared.Actions; + +namespace Content.Shared._DV.Psionics.Events.PowerActionEvents; + +public sealed partial class TelegnosisPowerActionEvent : InstantActionEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PowerDoAfterEvents/FracturedFormDoAfterEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerDoAfterEvents/FracturedFormDoAfterEvent.cs new file mode 100644 index 0000000000..d8230270f1 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerDoAfterEvents/FracturedFormDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared._DV.Psionics.Events.PowerDoAfterEvents; + +[Serializable, NetSerializable] +public sealed partial class FracturedFormDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PowerDoAfterEvents/MassSleepDoAfterEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerDoAfterEvents/MassSleepDoAfterEvent.cs new file mode 100644 index 0000000000..a6c863f908 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerDoAfterEvents/MassSleepDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared._DV.Psionics.Events.PowerDoAfterEvents; + +[Serializable, NetSerializable] +public sealed partial class MassSleepDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PowerDoAfterEvents/PrecognitionDoAfterEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerDoAfterEvents/PrecognitionDoAfterEvent.cs new file mode 100644 index 0000000000..8e7701684a --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerDoAfterEvents/PrecognitionDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared._DV.Psionics.Events.PowerDoAfterEvents; + +[Serializable, NetSerializable] +public sealed partial class PrecognitionDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PowerDoAfterEvents/PsionicEruptionDoAfterEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerDoAfterEvents/PsionicEruptionDoAfterEvent.cs new file mode 100644 index 0000000000..83ef936e05 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PowerDoAfterEvents/PsionicEruptionDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared._DV.Psionics.Events.PowerDoAfterEvents; + +[Serializable, NetSerializable] +public sealed partial class PsionicEruptionDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/_DV/Psionics/Events/PsionicMindBrokenEvent.cs b/Content.Shared/_DV/Psionics/Events/PsionicMindBrokenEvent.cs new file mode 100644 index 0000000000..4a037a460a --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PsionicMindBrokenEvent.cs @@ -0,0 +1,23 @@ +namespace Content.Shared._DV.Psionics.Events; + +/// +/// Event raised on an entity that is being mindbroken. +/// +[ByRefEvent] +public sealed class PsionicMindBrokenEvent(bool force) +{ + /// + /// Whether even unremovable abilities should be removed. + /// + public readonly bool Force = force; + + /// + /// This is true if at least one ability got removed. + /// + public bool Success; + + /// + /// This remains true if ALL abilities were removed. + /// + public bool AllRemoved = true; +}; diff --git a/Content.Shared/_DV/Psionics/Events/PsionicPowerDetectedEvent.cs b/Content.Shared/_DV/Psionics/Events/PsionicPowerDetectedEvent.cs new file mode 100644 index 0000000000..b4bcff02b8 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PsionicPowerDetectedEvent.cs @@ -0,0 +1,9 @@ +namespace Content.Shared._DV.Psionics.Events; + +/// +/// Event raised on an entity that can detect when someone used a psionic power nearby. +/// +/// The psionic that detected the psionic usage. +/// The psionic power that was used. +[ByRefEvent] +public readonly record struct PsionicPowerDetectedEvent(EntityUid Psionic, string Power); diff --git a/Content.Shared/_DV/Psionics/Events/PsionicPowerUseAttemptEvent.cs b/Content.Shared/_DV/Psionics/Events/PsionicPowerUseAttemptEvent.cs new file mode 100644 index 0000000000..e355718f13 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PsionicPowerUseAttemptEvent.cs @@ -0,0 +1,15 @@ +using Content.Shared.Inventory; + +namespace Content.Shared._DV.Psionics.Events; + +/// +/// Event raised on an entity that is attempting to use a psionic power. +/// +/// returns true if able to use psionic powers, false if not. +[ByRefEvent] +public record struct PsionicPowerUseAttemptEvent() : IInventoryRelayEvent +{ + public bool CanUsePower = true; + + SlotFlags IInventoryRelayEvent.TargetSlots => SlotFlags.WITHOUT_POCKET; +}; diff --git a/Content.Shared/_DV/Psionics/Events/PsionicPowerUsedEvent.cs b/Content.Shared/_DV/Psionics/Events/PsionicPowerUsedEvent.cs new file mode 100644 index 0000000000..84956a8f97 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PsionicPowerUsedEvent.cs @@ -0,0 +1,14 @@ +namespace Content.Shared._DV.Psionics.Events; + +/// +/// Event that gets raised whenever someone uses a psionic power. +/// +/// The performer who used the power. +/// The source of the psionic power. +/// The psionic power used. +public sealed class PsionicPowerUsedEvent(EntityUid user, EntityUid psionicSource, string power) : HandledEntityEventArgs +{ + public EntityUid User = user; + public EntityUid PsionicSource = psionicSource; + public string Power = power; +} diff --git a/Content.Shared/_DV/Psionics/Events/PsionicShieldedEvent.cs b/Content.Shared/_DV/Psionics/Events/PsionicShieldedEvent.cs new file mode 100644 index 0000000000..185d057f94 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PsionicShieldedEvent.cs @@ -0,0 +1,8 @@ +namespace Content.Shared._DV.Psionics.Events; + +/// +/// This is raised on an entity when they are psionically shielded. +/// +/// The entity that is psionically shielded. +[ByRefEvent] +public record struct PsionicShieldedEvent(EntityUid Shielded); diff --git a/Content.Shared/_DV/Psionics/Events/PsionicStoppedShieldedEvent.cs b/Content.Shared/_DV/Psionics/Events/PsionicStoppedShieldedEvent.cs new file mode 100644 index 0000000000..21dfb6e7b2 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PsionicStoppedShieldedEvent.cs @@ -0,0 +1,8 @@ +namespace Content.Shared._DV.Psionics.Events; + +/// +/// This is raised on an entity when they are no longer psionically shielded anymore. +/// +/// The entity who is no longer psionically shielded. +[ByRefEvent] +public record struct PsionicStoppedShieldedEvent(EntityUid Shielded); diff --git a/Content.Shared/_DV/Psionics/Events/PsionicStoppedSuppressedEvent.cs b/Content.Shared/_DV/Psionics/Events/PsionicStoppedSuppressedEvent.cs new file mode 100644 index 0000000000..0914097c76 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PsionicStoppedSuppressedEvent.cs @@ -0,0 +1,8 @@ +namespace Content.Shared._DV.Psionics.Events; + +/// +/// This is raised on an entity that has been psionically suppressed, stopping them from using psionics. +/// +/// The entity that is no longer psionically suppressed. +[ByRefEvent] +public record struct PsionicStoppedSuppressedEvent(EntityUid Victim); diff --git a/Content.Shared/_DV/Psionics/Events/PsionicSuppressedEvent.cs b/Content.Shared/_DV/Psionics/Events/PsionicSuppressedEvent.cs new file mode 100644 index 0000000000..5188fc3cf0 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/PsionicSuppressedEvent.cs @@ -0,0 +1,8 @@ +namespace Content.Shared._DV.Psionics.Events; + +/// +/// Raised on an entity when they are no longer psionically suppressed from using psionic abilities. +/// +/// The entity who is psionically suppressed +[ByRefEvent] +public record struct PsionicSuppressedEvent(EntityUid Victim); diff --git a/Content.Shared/_DV/Psionics/Events/TargetedByPsionicPowerEvent.cs b/Content.Shared/_DV/Psionics/Events/TargetedByPsionicPowerEvent.cs new file mode 100644 index 0000000000..86a7e1ae5d --- /dev/null +++ b/Content.Shared/_DV/Psionics/Events/TargetedByPsionicPowerEvent.cs @@ -0,0 +1,15 @@ +using Content.Shared.Inventory; + +namespace Content.Shared._DV.Psionics.Events; + +/// +/// Event raised on an entity that is being targetted by a psionic power. +/// +/// returns true if able to use psionic powers, false if not. +[ByRefEvent] +public record struct TargetedByPsionicPowerEvent() : IInventoryRelayEvent +{ + public bool IsShielded; + + SlotFlags IInventoryRelayEvent.TargetSlots => SlotFlags.WITHOUT_POCKET; +}; diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/BasePsionicPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/BasePsionicPowerSystem.cs new file mode 100644 index 0000000000..e3bec3b65c --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/BasePsionicPowerSystem.cs @@ -0,0 +1,208 @@ +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events; +using Content.Shared.Actions; +using Content.Shared.Administration.Logs; +using Content.Shared.DoAfter; +using Content.Shared.Inventory; +using Content.Shared.Popups; +using Content.Shared.Psionics.Glimmer; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Shared._DV.Psionics.Systems.PsionicPowers; + +/// +/// This is a base psionic power system that handles being mindbroken and checks for being able to use psionic powers automagically! +/// You WILL NEED to parent of this. +/// +public abstract class BasePsionicPowerSystem : EntitySystem where T : BasePsionicPowerComponent where T1 : BaseActionEvent +{ + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] protected readonly IRobustRandom Random = default!; + [Dependency] protected readonly IGameTiming Timing = default!; + [Dependency] protected readonly SharedActionsSystem Action = default!; + [Dependency] protected readonly SharedDoAfterSystem DoAfter = default!; + [Dependency] private readonly GlimmerSystem _glimmer = default!; + [Dependency] protected readonly SharedPopupSystem Popup = default!; + [Dependency] protected readonly SharedPsionicSystem Psionic = default!; + + /// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPowerInit); + + SubscribeLocalEvent(OnPowerActionUsed); + SubscribeLocalEvent(OnDispelled); + SubscribeLocalEvent(OnMindBroken); + SubscribeLocalEvent(OnGrantingClothingEquipped); + SubscribeLocalEvent(OnPsionicallySuppressed); + SubscribeLocalEvent(OnStoppedPsionicallySuppressed); + } + + /// + /// This is called on every power upon initialization, so the action gets put into the action container. + /// The only exemption is powers that don't have just one action or require special actions at initialize. + /// + /// The psionic power whose action is put into the container. + /// The args from the event. + protected virtual void OnPowerInit(Entity power, ref MapInitEvent args) + { + Action.AddAction(power, ref power.Comp.ActionEntity, power.Comp.ActionProtoId); + + var psionicComp = EnsureComp(power); + psionicComp.PsionicPowersActionEntities.Add(power.Comp.ActionEntity); + Dirty(power); + } + + /// + /// This is called whenever an entity pushes the psionic power action button. + /// + /// The psionic who attempts to use a psionic power. + /// The action event for said power. + private void OnPowerActionUsed(Entity psionic, ref T1 args) + { + if (Timing.ApplyingState || args.Handled) + return; + + args.Handled = true; + + if (Psionic.CanUsePsionicAbility(psionic)) + { + OnPowerUsed(psionic, ref args); + return; + } + + Popup.PopupClient(Loc.GetString("psionic-cannot-use-psionics"), args.Performer, args.Performer); + } + + /// + /// This is the creme of the system. If you add a new power, you will want to call this. + /// This is where the actual power takes place that follows after the button press. + /// You will need to call LogPowerUsed() at the end. + /// You will need to call Psionic.CanBeTargeted(target) if you add a power that can target things. + /// IMPORTANT! The psionic entity DOES NOT HAVE TO BE THE PLAYER! It can be a clothing! + /// If you want something to affect the player everytime, use args.Performer! + /// psionic.Owner => Source of the power. args.Performer => The user of the power. + /// + /// The source of the power. + /// The event. + protected abstract void OnPowerUsed(Entity psionic, ref T1 args); + + /// + /// This handles being dispelled while having an active DoAfter. + /// It'll be enough for most usages. + /// But you can overwrite it if you have multiple DoAfters or require a specific interaction with being dispelled. + /// + /// The psionic who has been dispelled. + /// The DispelledEvent. + protected virtual void OnDispelled(Entity psionic, ref DispelledEvent args) + { + if (args.Handled + || psionic.Comp.GetDoAfterId() is not { } doAfterId) + return; + + DoAfter.Cancel(doAfterId); + Popup.PopupClient(Loc.GetString("psionic-dispelled"), args.Target, args.Target, PopupType.MediumCaution); + psionic.Comp.RemoveSavedDoAfterId(); + + args.Handled = true; + Dirty(psionic); + } + + /// + /// This handles when the psionic gets mindbroken via chemicals or other. + /// + /// The psionic who is being mindbroken. + /// The mindbreaking event. + protected virtual void OnMindBroken(Entity psionic, ref PsionicMindBrokenEvent args) + { + if (psionic.Comp.CanBeRemoved || args.Force) + { + args.Success = true; + TryStopDoAfter(psionic, psionic.Owner); // Mindbreaking already has a popup, so no popup for breaking doAfters. + Action.RemoveAction(psionic.Comp.ActionEntity); + RemComp(psionic); + return; + } + + args.AllRemoved = false; + } + + /// + /// This handles equipping gear that has psionic abilities. These will be automatically relayed to the user. + /// + /// The clothing that grants the power and has the component. + /// The event. + private void OnGrantingClothingEquipped(Entity psionicClothing, ref GetItemActionsEvent args) + { + if (args.SlotFlags is null or SlotFlags.POCKET + || !HasComp(args.User)) // IPCs and non-player organics shouldn't be able to use abilities. + return; + + args.AddAction(psionicClothing.Comp.ActionEntity); + } + + /// + /// This interrupts DoAfters when being suppressed psionically. + /// No putting on skullcaps AFTER casting your DoAfter to be secure from Dispels. + /// + /// The power source. + /// The event. + protected virtual void OnPsionicallySuppressed(Entity power, ref PsionicSuppressedEvent args) + { + if (Timing.ApplyingState) + return; + + TryStopDoAfter(power, args.Victim, "psionic-equipped-shielded-in-doafter"); + } + + /// + /// This is raised when the suppression stops. + /// This allows powers to handle special behavior. + /// + /// The psionic who is no longer suppressed. + /// The event. + protected virtual void OnStoppedPsionicallySuppressed(Entity psionic, ref PsionicStoppedSuppressedEvent args) + { + } + + /// + /// This will log the power usage and increase glimmer, as well as making sure metapsionics hear it. + /// + /// The SOURCE of the psionic power. + /// The entity that PERFORMED the power. + protected void AfterPowerUsed(Entity psionicSource, EntityUid performer) + { + var power = Loc.GetString(psionicSource.Comp.PowerName); + _adminLogger.Add(Database.LogType.Psionics, Database.LogImpact.Medium, $"{ToPrettyString(psionicSource):player} used {power}"); + + var ev = new PsionicPowerUsedEvent(performer, psionicSource, power); + RaiseLocalEvent(psionicSource, ev); + + _glimmer.Glimmer += Random.Next(psionicSource.Comp.MinGlimmerChanged, psionicSource.Comp.MaxGlimmerChanged); + } + + /// + /// Stops the current DoAfter of the psionic user. + /// + /// The source of the power. + /// The entity performing the power. + /// The FTL string for the popup. If null, no popup. + /// Returns true if an doAfter was interrupted, otherwise false. + private bool TryStopDoAfter(Entity psionic, EntityUid performer, string? popup = null) + { + if (psionic.Comp.GetDoAfterId() is not { } doAfterId) + return false; + + DoAfter.Cancel(doAfterId); + if (popup != null) + Popup.PopupClient(Loc.GetString(popup), performer, performer, PopupType.MediumCaution); + + psionic.Comp.RemoveSavedDoAfterId(); + Dirty(psionic); + return true; + } +} diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/MetapsionicPulsePowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/MetapsionicPulsePowerSystem.cs new file mode 100644 index 0000000000..e376d7cc76 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/MetapsionicPulsePowerSystem.cs @@ -0,0 +1,55 @@ +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared.Popups; + +namespace Content.Shared._DV.Psionics.Systems.PsionicPowers; + +/// +/// This system allows a psionic user to spot other psionic entities via a pulse. +/// +public sealed class MetapsionicPulsePowerSystem : BasePsionicPowerSystem +{ + [Dependency] private readonly EntityLookupSystem _lookupSystem = default!; + + protected override void OnPowerInit(Entity power, ref MapInitEvent args) + { + base.OnPowerInit(power, ref args); + + EnsureComp(power); + } + + protected override void OnPowerUsed(Entity psionic, ref MetapsionicPulsePowerActionEvent args) + { + foreach (var target in _lookupSystem.GetEntitiesInRange(args.Target, psionic.Comp.Range)) + { + if (target.Owner == args.Performer + || Transform(target).ParentUid == args.Performer) + continue; + + if (!Psionic.CanBeTargeted(target)) // Cannot detect shielded psionics. + continue; + + Popup.PopupClient(Loc.GetString("psionic-power-metapsionic-success"), args.Performer, args.Performer, PopupType.LargeCaution); + AfterPowerUsed(psionic, args.Performer); + + args.Handled = true; + return; + } + Popup.PopupClient(Loc.GetString("psionic-power-metapsionic-failure"), args.Performer, args.Performer, PopupType.Large); + AfterPowerUsed(psionic, args.Performer); + + args.Handled = true; + } + + protected override void OnMindBroken(Entity psionic, ref PsionicMindBrokenEvent args) + { + base.OnMindBroken(psionic, ref args); + // If the mindbreak was successful, remove the detector component too. + if (!psionic.Comp.Deleted) + return; + + RemComp(psionic); + } +} diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/MindSwappedReturnPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/MindSwappedReturnPowerSystem.cs new file mode 100644 index 0000000000..50a16ae8ea --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/MindSwappedReturnPowerSystem.cs @@ -0,0 +1,76 @@ +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared.Popups; +using Content.Shared.Speech; +using Content.Shared.Stealth.Components; + +namespace Content.Shared._DV.Psionics.Systems.PsionicPowers; + +/// +/// Sorry if this is shitcode, but the return power actually should behave like a normal power - So it gets its own system. +/// That way, we have automatical power inits, dispelled and mindbreaking, as well as checks for if someone can use a power. +/// This is NOT its own power. It can ONLY exist if someone used a mindswap or a minor mass mind swap happened. +/// +public sealed class MindSwappedReturnPowerSystem : BasePsionicPowerSystem +{ + [Dependency] private readonly SharedMindSwapPowerSystem _mindSwap = default!; + [Dependency] private readonly MetaDataSystem _metaDataSystem = default!; + + private EntityQuery _mindSwappedQuery; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnShutDown); + + _mindSwappedQuery = GetEntityQuery(); + } + + private void OnShutDown(Entity psionic, ref ComponentShutdown args) + { + // If the person is gibbed or otherwise deleted, it'll remove the links. + if (Timing.ApplyingState + || !TerminatingOrDeleted(psionic) + || !_mindSwappedQuery.TryComp(psionic.Comp.OriginalEntity, out var targetComp)) + return; + + RemoveLink((psionic.Comp.OriginalEntity, targetComp)); + } + + protected override void OnPowerUsed(Entity psionic, ref MindSwappedReturnPowerActionEvent args) + { + _mindSwap.SwapMinds(psionic, psionic.Comp.OriginalEntity); + AfterPowerUsed(psionic, args.Performer); + } + + protected override void OnDispelled(Entity psionic, ref DispelledEvent args) + { + _mindSwap.SwapMinds(psionic, psionic.Comp.OriginalEntity, false); + } + + public void RemoveLink(Entity victim, bool showPopup = true) + { + // Sometimes people lose their link without having the component - MassMindSwap for example is a situation like that. + if (showPopup) + Popup.PopupEntity(Loc.GetString("psionic-power-mindswap-original-lost"), victim, victim, PopupType.MediumCaution); + + if (!Resolve(victim, ref victim.Comp, false)) + return; + // Remove the first action and link. + Action.RemoveAction(victim.Comp.ActionEntity); + RemCompDeferred(victim, victim.Comp); + + if (!HasComp(victim)) + return; + + RemComp(victim); + RemComp(victim); + EnsureComp(victim); + EnsureComp(victim); + _metaDataSystem.SetEntityName(victim, Loc.GetString("telegnostic-trapped-entity-name")); + _metaDataSystem.SetEntityDescription(victim, Loc.GetString("telegnostic-trapped-entity-desc")); + } +} diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/PsionicInvisibilityPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/PsionicInvisibilityPowerSystem.cs new file mode 100644 index 0000000000..4bb8f3864f --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/PsionicInvisibilityPowerSystem.cs @@ -0,0 +1,87 @@ +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared.CombatMode.Pacification; +using Content.Shared.Damage.Systems; +using Content.Shared.Stealth; +using Content.Shared.Stealth.Components; +using Content.Shared.Stunnable; +using Robust.Shared.Audio.Systems; + +namespace Content.Shared._DV.Psionics.Systems.PsionicPowers; + +public sealed class PsionicInvisibilityPowerSystem : BasePsionicPowerSystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedStealthSystem _stealth = default!; + [Dependency] private readonly SharedStunSystem _stunSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnDamageChanged); + SubscribeLocalEvent(OnDispelled); + } + + protected override void OnPowerUsed(Entity psionic, ref PsionicInvisibilityPowerActionEvent args) + { + ToggleInvisibility(args.Performer); + AfterPowerUsed(psionic, args.Performer); + } + + private void OnInit(Entity invisible, ref MapInitEvent args) + { + EnsureComp(invisible); + EnsureComp(invisible); + var stealth = EnsureComp(invisible); + _stealth.SetVisibility(invisible, invisible.Comp.InvisibilityStrength, stealth); + _audio.PlayPredicted(invisible.Comp.Sound, invisible, invisible); + Psionic.SetCanSeePsionicInvisiblity(invisible, true); + } + + private void OnShutdown(Entity invisible, ref ComponentShutdown args) + { + if (Terminating(invisible)) + return; + + RemComp(invisible); + RemComp(invisible); + RemComp(invisible); + _audio.PlayPredicted(invisible.Comp.Sound, invisible, invisible); + Psionic.SetCanSeePsionicInvisiblity(invisible, false); + + _stunSystem.TryAddParalyzeDuration(invisible, invisible.Comp.StunDuration); + } + + private void OnDamageChanged(Entity invisible, ref DamageChangedEvent args) + { + if (!args.DamageIncreased) + return; + + ToggleInvisibility(invisible); + } + + protected override void OnMindBroken(Entity psionic, ref PsionicMindBrokenEvent args) + { + base.OnMindBroken(psionic, ref args); + // If the mindbreak was successful, remove the invisibility component too. + if (!psionic.Comp.Deleted) + return; + + RemComp(psionic); + } + + private void OnDispelled(Entity invisible, ref DispelledEvent args) + { + ToggleInvisibility(invisible); + } + + public void ToggleInvisibility(EntityUid invisible) + { + if (!RemComp(invisible)) + EnsureComp(invisible); + } +} diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/PsionicRegenerationPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/PsionicRegenerationPowerSystem.cs new file mode 100644 index 0000000000..43cd31ccf9 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/PsionicRegenerationPowerSystem.cs @@ -0,0 +1,69 @@ +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared.Body.Components; +using Content.Shared.Body.Systems; +using Content.Shared.Chemistry.Components; +using Content.Shared.DoAfter; +using Content.Shared.FixedPoint; +using Content.Shared.Popups; +using Content.Shared.Psionics.Events; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; + +namespace Content.Shared._DV.Psionics.Systems.PsionicPowers; + +public sealed class PsionicRegenerationPowerSystem : BasePsionicPowerSystem +{ + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly SharedBloodstreamSystem _bloodstreamSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDoAfter); + } + + protected override void OnPowerUsed(Entity psionic, ref PsionicRegenerationPowerActionEvent args) + { + var ev = new PsionicRegenerationDoAfterEvent(Timing.CurTime); + var performer = args.Performer; + var doAfterArgs = new DoAfterArgs(EntityManager, performer, psionic.Comp.UseDelay, ev, psionic); + + if (!DoAfter.TryStartDoAfter(doAfterArgs, out var doAfterId)) + return; + + psionic.Comp.SaveDoAfterId(doAfterId.Value); + + Popup.PopupPredicted(Loc.GetString("psionic-regeneration-begin", ("entity", performer)), + performer, + performer, + PopupType.Medium); + + _audioSystem.PlayPredicted(psionic.Comp.SoundUse, args.Performer, args.Performer, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f)); + AfterPowerUsed(psionic, args.Performer); + } + + private void OnDoAfter(Entity psionic, ref PsionicRegenerationDoAfterEvent args) + { + if (args.Handled) + return; + + args.Handled = true; + psionic.Comp.RemoveSavedDoAfterId(); + Dirty(psionic); + + if (!TryComp(psionic, 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, (Timing.CurTime - args.StartedAt).TotalSeconds / psionic.Comp.UseDelay); + + var solution = new Solution(); + solution.AddReagent(psionic.Comp.ReagentId, FixedPoint2.New(psionic.Comp.EssenceAmount * percentageComplete)); + _bloodstreamSystem.TryAddToBloodstream((psionic, stream), solution); + } +} diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/PsychokineticScreamPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/PsychokineticScreamPowerSystem.cs new file mode 100644 index 0000000000..08c43e2456 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/PsychokineticScreamPowerSystem.cs @@ -0,0 +1,75 @@ +using System.Linq; +using System.Numerics; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared.Coordinates; +using Content.Shared.Light.Components; +using Content.Shared.Light.EntitySystems; +using Content.Shared.Physics; +using JetBrains.Annotations; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Systems; + +namespace Content.Shared._DV.Psionics.Systems.PsionicPowers; + +/// +/// This system enables a psionic being to break lights around them. +/// +public sealed class PsychokineticScreamPowerSystem : BasePsionicPowerSystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPoweredLightSystem _light = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + protected override void OnPowerUsed(Entity psionic, ref PsychokineticScreamPowerActionEvent args) + { + if (psionic.Comp.AbilitySound != null) + _audio.PlayPredicted(psionic.Comp.AbilitySound, psionic, psionic); + + ShatterLightsAround(psionic.Owner, psionic.Comp.Radius, psionic.Comp.LineOfSight, psionic.Comp.PenetratingRadius); + + SpawnAttachedTo(psionic.Comp.Effect, psionic.Owner.ToCoordinates()); + args.Handled = true; + } + + /// + /// Shatter lights around an entity. + /// This is public because other systems require it. + /// + /// The entity that caused it. + /// The range where the lights are broken within. + /// Whether line of sight is required. + /// How far it ignores the line of sight. + [PublicAPI] + public void ShatterLightsAround(EntityUid source, float range, bool lineOfSight, float penetratingRadius = 0f) + { + var pos = _transform.GetWorldPosition(source); + + // Get all light entities within the specified radius + HashSet> lightsInRange = []; + _lookup.GetEntitiesInRange(Transform(source).Coordinates, range, lightsInRange); + + foreach (var light in lightsInRange) + { + if (lineOfSight) // If LoS is required, test it. + { + var lightPos = _transform.GetWorldPosition(light); + var sqrDistance = Vector2.DistanceSquared(pos, lightPos); + if (sqrDistance > penetratingRadius * penetratingRadius) + { + // If the light is outside the penetrating radius, do a LoS check. + var ray = new CollisionRay(pos, (lightPos - pos).Normalized(), (int)CollisionGroup.Opaque); + var hit = _physics.IntersectRay(_transform.GetMapId(source), ray, MathF.Sqrt(sqrDistance) - 0.5f, returnOnFirstHit: true); + if (hit.Any() && hit.First().Distance != 0) + continue; + } + } + + // If we reach here, the light is unobstructed and within range, break it. + _light.TryDestroyBulb(light, light.Comp, source); + } + } +} diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedDispelPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedDispelPowerSystem.cs new file mode 100644 index 0000000000..27ea004660 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedDispelPowerSystem.cs @@ -0,0 +1,139 @@ +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared.Damage; +using Content.Shared.Damage.Systems; +using Content.Shared.Popups; +using Content.Shared.Revenant.Components; +using Content.Shared.StatusEffect; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; + +namespace Content.Shared._DV.Psionics.Systems.PsionicPowers; + +/// +/// This enables psionic users to dispel noospheric beings and actions. +/// +public abstract class SharedDispelPowerSystem : BasePsionicPowerSystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; + [Dependency] private readonly StatusEffectNew.StatusEffectsSystem _statusEffectsNew = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDispelled); + SubscribeLocalEvent(OnDmgDispelled); + SubscribeLocalEvent(OnPsionicShieldingStopped, null,[typeof(SharedPsionicSystem)]); + // Upstream stuff we're just gonna handle here + SubscribeLocalEvent(OnRevenantDispelled); + } + + protected override void OnPowerInit(Entity power, ref MapInitEvent args) + { + base.OnPowerInit(power, ref args); + // Dispell psionics can now see invisible entities to dispell them. + Psionic.SetCanSeePsionicInvisiblity(power.Owner, true); + } + + protected override void OnPowerUsed(Entity psionic, ref DispelPowerActionEvent args) + { + if (!Psionic.CanBeTargeted(args.Target, hasAggressor: args.Performer)) + return; + + var ev = new DispelledEvent(args.Performer, args.Target); + RaiseLocalEvent(args.Target, ev); + + if (ev.Handled) + AfterPowerUsed(psionic, args.Performer); + } + + protected override void OnPsionicallySuppressed(Entity power, ref PsionicSuppressedEvent args) + { + if (Timing.ApplyingState) + return; + // Don't let them see if they're suppressed. + Psionic.SetCanSeePsionicInvisiblity(args.Victim, false); + } + + protected override void OnStoppedPsionicallySuppressed(Entity psionic, ref PsionicStoppedSuppressedEvent args) + { + if (Timing.ApplyingState) + return; + // Let mah people SEE + Psionic.SetCanSeePsionicInvisiblity(args.Victim, true); + } + + /// + /// This is necessary to avoid dispel users to lose their invisibility sight. + /// This fires after the System where losing psionic shielding makes you unable to see invisible entities. + /// + /// The psionic who lost their shielding. + /// The event. + private void OnPsionicShieldingStopped(Entity psionic, ref PsionicStoppedShieldedEvent args) + { + if (_statusEffectsNew.HasStatusEffect(psionic, "StatusEffectPsionicsDisabled")) + return; + // Losing the shielding causes you to lose vision of the invisible. This is to prevent that. + Psionic.SetCanSeePsionicInvisiblity(args.Shielded, true); + } + + protected override void OnMindBroken(Entity psionic, ref PsionicMindBrokenEvent args) + { + base.OnMindBroken(psionic, ref args); + // If the mindbreak was successful, make 'em blind. + if (!psionic.Comp.Deleted) + return; + + Psionic.SetCanSeePsionicInvisiblity(psionic, false); + } + + private void OnDispelled(Entity dispellable, ref DispelledEvent args) + { + QueueDel(dispellable); + Spawn("Ash", Transform(dispellable).Coordinates); + Popup.PopupPredicted(Loc.GetString("psionic-burns-up", ("item", dispellable)), dispellable, args.Dispeller, PopupType.MediumCaution); + _audio.PlayPredicted(dispellable.Comp.DispelSound, dispellable.Owner, args.Dispeller); + args.Handled = true; + } + + private void OnDmgDispelled(Entity damaged, ref DispelledEvent args) + { + var damage = damaged.Comp.Damage; + var modifier = Random.NextFloat(damaged.Comp.Variance, 1 + damaged.Comp.Variance); + + damage *= modifier; + DealDispelDamage(damaged, damage, args.Dispeller, damaged.Comp.DispelSound); + args.Handled = true; + } + + private void OnRevenantDispelled(Entity revenant, ref DispelledEvent args) + { + DealDispelDamage(revenant, dispeller: args.Dispeller); + // TODO: Port over the new StatusEffectSystem when upstream ports over the Corporeal status effect to the new system. + _statusEffects.TryAddStatusEffect(revenant, "Corporeal", TimeSpan.FromSeconds(30), false, "Corporeal"); + args.Handled = true; + } + + public void DealDispelDamage(EntityUid dispelled, DamageSpecifier? damage = null, EntityUid? dispeller = null, SoundSpecifier? sound = null) + { + if (Deleted(dispelled)) + return; + + Popup.PopupPredicted(Loc.GetString("psionic-burn-resist", ("item", dispelled)), dispelled, dispeller, PopupType.SmallCaution); + _audio.PlayPredicted(sound, dispelled, dispeller); + + if (damage == null) + { + damage = new DamageSpecifier(); + damage.DamageDict.Add("Blunt", 100); + } + + _damageable.TryChangeDamage(dispelled, damage, ignoreResistances: true); + } +} diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedFracturedFormPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedFracturedFormPowerSystem.cs new file mode 100644 index 0000000000..4ddf11ed79 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedFracturedFormPowerSystem.cs @@ -0,0 +1,150 @@ +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared._DV.Psionics.Events.PowerDoAfterEvents; +using Content.Shared.Bed.Sleep; +using Content.Shared.Chat; +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Shared.Mind.Components; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Popups; +using Content.Shared.SSDIndicator; +using Robust.Shared.Player; + +namespace Content.Shared._DV.Psionics.Systems.PsionicPowers; + +public abstract class SharedFracturedFormPowerSystem : BasePsionicPowerSystem +{ + [Dependency] protected readonly SharedChatSystem Chat = default!; + [Dependency] protected readonly MobStateSystem MobState = default!; + [Dependency] protected readonly SleepingSystem Sleeping = default!; + + protected EntityQuery FracturedQuery; + private EntityQuery _forcedSleepQuery; + protected EntityQuery MindContainerQuery; + private EntityQuery _mobStateQuery; + protected EntityQuery SsdQuery; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnPlayerDetached, after: [typeof(SSDIndicatorSystem)]); + SubscribeLocalEvent(OnExamine); + + FracturedQuery = GetEntityQuery(); + _forcedSleepQuery = GetEntityQuery(); + MindContainerQuery = GetEntityQuery(); + _mobStateQuery = GetEntityQuery(); + SsdQuery = GetEntityQuery(); + } + + protected override void OnPowerUsed(Entity psionic, ref FracturedFormPowerActionEvent args) + { + if (!CanSwap(psionic)) + { + Popup.PopupClient(Loc.GetString("psionic-power-fractured-form-nobodies"), psionic, args.Performer, PopupType.Large); + return; + } + + Chat.TryEmoteWithChat(psionic.Owner, "Yawn"); + Popup.PopupClient(Loc.GetString("psionic-power-fractured-form-sleepy"), psionic, args.Performer, PopupType.LargeCaution); + + var doAfterArgs = new DoAfterArgs(EntityManager, args.Performer, psionic.Comp.ManualSwapTime, new FracturedFormDoAfterEvent(), psionic); + + if (!DoAfter.TryStartDoAfter(doAfterArgs, out var doAfterId)) + return; + + psionic.Comp.SaveDoAfterId(doAfterId.Value); + Dirty(psionic); + AfterPowerUsed(psionic, args.Performer); + } + + private void OnDoAfter(Entity psionic, ref FracturedFormDoAfterEvent args) + { + if (args.Cancelled || args.Handled) + return; + args.Handled = true; + psionic.Comp.RemoveSavedDoAfterId(); + + Dirty(psionic); + Sleeping.TrySleeping(psionic.Owner); + } + + private void OnPlayerDetached(Entity body, ref PlayerDetachedEvent args) + { + if (!SsdQuery.TryComp(body, out var comp) + || FracturedQuery.HasComp(body)) + return; + + comp.IsSSD = false; + } + + private void OnExamine(Entity psionic, ref ExaminedEvent args) + { + if (HasComp(psionic)) + return; + + if (TryComp(args.Examiner, out var fracturedHost) && fracturedHost.Bodies.Contains(psionic.Owner)) + args.PushMarkup($"[color=yellow]{Loc.GetString("psionic-power-fractured-form-examine-self", ("ent", psionic))}[/color]"); + else + args.PushMarkup($"[color=yellow]{Loc.GetString("psionic-power-fractured-form-ssd", ("ent", psionic))}[/color]"); + } + + /// + /// We're overriding the dispelled method to introduce some special behavior. + /// + protected override void OnDispelled(Entity psionic, ref DispelledEvent args) + { + if (args.Handled) + return; + args.Handled = true; + + // This resets the swap timer. + var randomTime = Random.Next(psionic.Comp.NextSwapMinTime, psionic.Comp.NextSwapMaxTime); + psionic.Comp.NextSwap = Timing.CurTime + randomTime; + + if (psionic.Comp.GetDoAfterId() is not { } doAfterId) + { + Popup.PopupClient(Loc.GetString("psionic-power-fractured-form-dispelled"), psionic, PopupType.MediumCaution); + return; + } + + DoAfter.Cancel(doAfterId); + Popup.PopupClient(Loc.GetString("psionic-dispelled"), psionic, PopupType.MediumCaution); + psionic.Comp.RemoveSavedDoAfterId(); + } + + protected bool IsValidBody(Entity psionic, EntityUid otherBody) + { + if (otherBody == psionic.Owner) + return false; + if (!psionic.Comp.Bodies.Contains(otherBody)) + return false; + if (!MindContainerQuery.TryComp(otherBody, out var cmind)) + return false; + if (cmind.HasMind) + return false; + if (_forcedSleepQuery.HasComp(otherBody)) + return false; + if (!_mobStateQuery.TryComp(otherBody, out var mobState)) + return false; + if (MobState.IsIncapacitated(otherBody, mobState)) + return false; + + return true; + } + + public bool CanSwap(Entity psionic) + { + foreach (var body in psionic.Comp.Bodies) + { + if (IsValidBody(psionic, body)) + return true; + } + return false; + } +} diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedMassSleepPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedMassSleepPowerSystem.cs new file mode 100644 index 0000000000..d30151ebc4 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedMassSleepPowerSystem.cs @@ -0,0 +1,6 @@ +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; + +namespace Content.Shared._DV.Psionics.Systems.PsionicPowers; + +public abstract class SharedMassSleepPowerSystem : BasePsionicPowerSystem; diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedMindSwapPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedMindSwapPowerSystem.cs new file mode 100644 index 0000000000..bfd9116326 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedMindSwapPowerSystem.cs @@ -0,0 +1,167 @@ +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared.Mind; +using Content.Shared.Mindshield.Components; +using Content.Shared.Popups; +using JetBrains.Annotations; + +namespace Content.Shared._DV.Psionics.Systems.PsionicPowers; + +public abstract class SharedMindSwapPowerSystem : BasePsionicPowerSystem +{ + [Dependency] private readonly SharedMindSystem _mindSystem = default!; + [Dependency] private readonly MindSwappedReturnPowerSystem _mindSwapped = default!; + + private EntityQuery _mindSwappedQuery; + private EntityQuery _mindshieldQuery; + + public override void Initialize() + { + base.Initialize(); + + _mindSwappedQuery = GetEntityQuery(); + _mindshieldQuery = GetEntityQuery(); + } + + protected override void OnPowerUsed(Entity psionic, ref MindSwapPowerActionEvent args) + { + SwapMinds(args.Performer, args.Target); + AfterPowerUsed(psionic, args.Performer); + } + + // TODO: Fix the prediction issue when calling from server while the performer is the cause. + // PopupClient() only shows up on client - But Telegnosis can't be put into shared. + // So Telegnosis calls from server side - Leaving no PopupClient(). + // Using PopupEntity() will cause it to be called 12x times client-side. + /// + /// Checks whether the two entities can swap their minds. + /// This handles whether the performer caused the mindswap, and whether they have mindshields. + /// This also handles popups. + /// + /// The entity performing or causing the swap. + /// The entity being targeted. + /// Whether the check should ignore mindshields. + /// Whether the check should ignore psionic shielding. + /// Whether the performer actively caused or performed the swap. + /// Returns true if possible, false if not. + public bool CanSwap(EntityUid performer, EntityUid target, bool ignoreMindshields = false, bool ignorePsionicShielding = false, bool performerIsCause = true) + { + EntityUid? aggressor = performerIsCause ? performer : null; + + if (performerIsCause && !ignorePsionicShielding && !Psionic.CanUsePsionicAbility(performer)) + { + // client-side prediction. + Popup.PopupClient(Loc.GetString("psionic-cannot-use-psionics"), performer, performer); + return false; + } + // If the performer isn't the cause, like mindswap events, they can be shielded. + if (!performerIsCause && !ignorePsionicShielding && !Psionic.CanBeTargeted(performer)) + { + // Popup is handled in CanBeTargeted(). + return false; + } + // Mindshields actually shielding the mind?!?! Unplayable. + if (_mindshieldQuery.HasComp(performer) && !ignoreMindshields) + { + // Different messages whether they're the cause or not. + if (!performerIsCause) + { + Popup.PopupEntity(Loc.GetString("psionic-power-mindswap-target-mindshielded"), performer, performer, PopupType.MediumCaution); + return false; + } + Popup.PopupClient(Loc.GetString("psionic-power-mindswap-own-mindshield"), performer, performer, PopupType.SmallCaution); + return false; + } + if (_mindshieldQuery.HasComponent(target) && !ignoreMindshields) + { + // Performer should only be notified if they're the cause of the attempt. + if (performerIsCause) + Popup.PopupClient(Loc.GetString("psionic-cannot-target-shielded"), performer, performer, PopupType.SmallCaution); + Popup.PopupEntity(Loc.GetString("psionic-power-mindswap-target-mindshielded"), target, target, PopupType.MediumCaution); + return false; + } + if (!Psionic.CanBeTargeted(target, hasAggressor: aggressor) && !ignorePsionicShielding) + { + // Popup is handled in CanBeTargeted(). + return false; + } + + return true; + } + + /// + /// Swaps two minds. + /// + /// The entity performing or causing the swap/being targeted. + /// The entity being targeted. + /// Whether the performer is actively causing the swap. + /// Whether the swap is reversible via the return power. + /// Whether the swap should ignore mindshields. + /// Whether the swap should ignore psionic shielding. + /// True if the two were swapped, false if otherwise. + [PublicAPI] + public bool SwapMinds(EntityUid performer, EntityUid target, bool performerIsCause = true, bool reversible = true, bool ignoreMindshields = false, bool ignorePsionicShielding = false) + { + if (!CanSwap(performer, target, ignoreMindshields, ignorePsionicShielding, performerIsCause)) + return false; + + // Get the minds first. On transfer, they'll be gone. + // This is here to prevent missing MindContainerComponent Resolve errors. + if (!_mindSystem.TryGetMind(performer, out var performerMindId, out var performerMind)) + performerMind = null; + + if (!_mindSystem.TryGetMind(target, out var targetMindId, out var targetMind)) + targetMind = null; + + switch (performerMind) + { + // If no mind can be swapped, return. + case null when targetMind == null: + return false; + // If performer has no mind, but target does, switch places. + case null: + (performer, target) = (target, performer); + (performerMind, targetMind) = (targetMind, performerMind); + (performerMindId, targetMindId) = (targetMindId, performerMindId); + break; + } + + //This is a terrible way to 'unattach' minds. I wanted to use UnVisit but in TransferTo's code they say + //To unnatch the minds, do it like this. + //Have to unnattach the minds before we reattach them via transfer. Still feels weird, but seems to work well. + _mindSystem.TransferTo(performerMindId, null); + // Do the transfer. + if (targetMind != null) + _mindSystem.TransferTo(targetMindId, performer, ghostCheckOverride: true, false, targetMind); + + _mindSystem.TransferTo(performerMindId, target, ghostCheckOverride: true, false, performerMind); + + if (_mindSwappedQuery.TryComp(performer, out var performerSwapped) && _mindSwappedQuery.TryComp(target, out var targetSwapped)) + { + // Sanity check + if (performerSwapped.OriginalEntity == target && targetSwapped.OriginalEntity == performer) + { + _mindSwapped.RemoveLink((performer, performerSwapped), false); + _mindSwapped.RemoveLink((target, targetSwapped), false); + return true; + } + } + + if (!reversible) + { + _mindSwapped.RemoveLink(performer); + _mindSwapped.RemoveLink(target); + return true; + } + + var perfComp = EnsureComp(performer); + var targetComp = EnsureComp(target); + + perfComp.OriginalEntity = target; + targetComp.OriginalEntity = performer; + + Dirty(performer, perfComp); + Dirty(target, targetComp); + return true; + } +} diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedNoosphericZapPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedNoosphericZapPowerSystem.cs new file mode 100644 index 0000000000..d8a1c6f990 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedNoosphericZapPowerSystem.cs @@ -0,0 +1,9 @@ +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; + +namespace Content.Shared._DV.Psionics.Systems.PsionicPowers; + +/// +/// This is solely for prediction. +/// +public abstract class SharedNoosphericZapPowerSystem : BasePsionicPowerSystem; diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedPrecognitionPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedPrecognitionPowerSystem.cs new file mode 100644 index 0000000000..c00deaf0f7 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedPrecognitionPowerSystem.cs @@ -0,0 +1,51 @@ +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerDoAfterEvents; +using Content.Shared.Actions.Events; +using Content.Shared.DoAfter; +using Content.Shared.Eye.Blinding.Components; +using Content.Shared.Movement.Systems; +using Content.Shared.StatusEffect; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Systems.PsionicPowers; + +/// +/// This solely exist for client-side action handling prediction. +/// +public abstract class SharedPrecognitionPowerSystem : BasePsionicPowerSystem +{ + [Dependency] protected readonly SharedAudioSystem Audio = default!; + [Dependency] protected readonly StatusEffectsSystem StatusEffects = default!; + [Dependency] protected readonly MovementModStatusSystem Movement = default!; + + public static readonly EntProtoId PrecognitionSlowdown = "PrecognitionSlowdownStatusEffect"; + + protected override void OnPowerUsed(Entity psionic, ref PrecognitionPowerActionEvent args) + { + var ev = new PrecognitionDoAfterEvent(); + var doAfterArgs = new DoAfterArgs(EntityManager, args.Performer, psionic.Comp.UseDelay, ev, psionic) + { + BreakOnDamage = true, + }; + + if (!DoAfter.TryStartDoAfter(doAfterArgs, out var doAfterId)) + return; + + // A custom shader for seeing visions would be nice but this will do for now. + // TODO: Port over the TemporaryBlindness effect to the new StatusEffectSystem. + // When Upstream ports it over, replace this with it. + StatusEffects.TryAddStatusEffect(psionic, "TemporaryBlindness", psionic.Comp.UseDelay, true); + Movement.TryUpdateMovementSpeedModDuration(args.Performer, PrecognitionSlowdown, psionic.Comp.UseDelay, 0.5f); + + psionic.Comp.SaveDoAfterId(doAfterId.Value); + + var player = Audio.PlayGlobal(psionic.Comp.VisionSound, Filter.Entities(args.Performer), true); + if (player != null) + psionic.Comp.SoundStream = player.Value.Entity; + + Dirty(psionic); + AfterPowerUsed(psionic, args.Performer); + } +} diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedPyrokinesisPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedPyrokinesisPowerSystem.cs new file mode 100644 index 0000000000..dce385c570 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedPyrokinesisPowerSystem.cs @@ -0,0 +1,6 @@ +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; + +namespace Content.Shared._DV.Psionics.Systems.PsionicPowers; + +public abstract class SharedPyrokinesisPowerSystem : BasePsionicPowerSystem; diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedTelegnosisPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedTelegnosisPowerSystem.cs new file mode 100644 index 0000000000..86a51373ce --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedTelegnosisPowerSystem.cs @@ -0,0 +1,59 @@ +using Content.Shared._DV.Mind; +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Components.PsionicPowers; +using Content.Shared._DV.Psionics.Events.PowerActionEvents; +using Content.Shared.Examine; +using Content.Shared.Interaction.Events; +using Content.Shared.Mind.Components; + +namespace Content.Shared._DV.Psionics.Systems.PsionicPowers; + +public abstract class SharedTelegnosisPowerSystem : BasePsionicPowerSystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMindRemoved); + SubscribeLocalEvent(OnInteractionAttempt); + SubscribeLocalEvent(OnShowSSDIndicator); + SubscribeLocalEvent(OnExamine); + } + + private void OnMindRemoved(Entity projection, ref MindRemovedMessage args) + { + QueueDel(projection); + } + + private void OnInteractionAttempt(Entity projection, ref InteractionAttemptEvent args) + { + // no astrally stealing someone's shoes + args.Cancelled = true; + } + + private void OnShowSSDIndicator(Entity psionic, ref ShowSSDIndicatorEvent args) + { + if (!TryComp(psionic, out var mindSwapped) || !HasComp(mindSwapped.OriginalEntity)) + return; + // Only hide if currently projecting + args.Hidden = true; + } + + private void OnExamine(Entity entity, ref ExaminedEvent args) + { + if (GetCasterProjection(entity) == default) + return; + + args.PushMarkup($"[color=yellow]{Loc.GetString("telegnosis-power-ssd", ("ent", entity))}[/color]"); + } + + public EntityUid GetCasterProjection(Entity entity) + { + if (!TryComp(entity, out var mindSwapped) || + !HasComp(mindSwapped.OriginalEntity)) + { + return default; + } + return mindSwapped.OriginalEntity; + } +} diff --git a/Content.Shared/_DV/Psionics/Systems/SharedPsionicSystem.cs b/Content.Shared/_DV/Psionics/Systems/SharedPsionicSystem.cs new file mode 100644 index 0000000000..65e2469c66 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/SharedPsionicSystem.cs @@ -0,0 +1,130 @@ +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Events; +using Content.Shared.Jittering; +using Content.Shared.Popups; +using Content.Shared.Psionics.Glimmer; +using Content.Shared.Speech.EntitySystems; +using Content.Shared.Stunnable; +using JetBrains.Annotations; +using Robust.Shared.Random; + +namespace Content.Shared._DV.Psionics.Systems; + +/// +/// The system to deal with all psionics. Each part of the System is in a subsystem. +/// +public abstract partial class SharedPsionicSystem : EntitySystem +{ + [Dependency] protected readonly IRobustRandom Random = default!; + [Dependency] protected readonly GlimmerSystem Glimmer = default!; + [Dependency] private readonly SharedJitteringSystem _jittering = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] protected readonly SharedPopupSystem Popup = default!; + [Dependency] private readonly SharedStunSystem _stun = default!; + [Dependency] private readonly SharedStutteringSystem _stuttering = default!; + + protected EntityQuery PotentialQuery; + protected EntityQuery PsionicQuery; + + public override void Initialize() + { + base.Initialize(); + + PotentialQuery = GetEntityQuery(); + PsionicQuery = GetEntityQuery(); + + InitializeDetection(); + InitializeStatusEffects(); + InitializeInvisibility(); + InitializeItems(); + } + + /// + /// Call this when you want to mindbreak an entity, causing them to lose all psionic abilities. + /// + /// The entity to be mindbroken. + /// Whether this should stun the entity. + /// Whether this should remove unremovable psionics too. + /// Zombies shouldn't be stunned on zombification, but also lose ALL psionic abilities.> + [PublicAPI] + public void MindBreakEntity(Entity psionic, bool stun = true, bool force = false) + { + if (!Resolve(psionic, ref psionic.Comp)) + return; + + var ev = new PsionicMindBrokenEvent(force); + RaiseLocalEvent(psionic, ref ev); + + // If no abilities got removed, refrain from doing anything further. Nothing ever happens. + if (!ev.Success) + return; + // Reduce glimmer a bit. + Glimmer.Glimmer -= Random.Next(50, 70); + // Stun if stun is desired. Zombies shouldn't get stunned on zombification. + if (stun && psionic.Comp.StunOnRemoval) + { + _stuttering.DoStutter(psionic, TimeSpan.FromMinutes(1), false); + _stun.TryKnockdown(psionic.Owner, TimeSpan.FromSeconds(5), false, drop: false); + _jittering.DoJitter(psionic, TimeSpan.FromSeconds(5), false); + } + // If all were removed, remove the psionic component. + if (!ev.AllRemoved) + { + Popup.PopupClient(Loc.GetString("psionic-partly-mindbrokenpsionic-partly-mindbroken"), psionic, PopupType.Medium); + return; + } + + RemComp(psionic); + GrantPsionicRoll(psionic.Owner); + + Popup.PopupClient(Loc.GetString("psionic-mindbroken"), psionic, PopupType.Medium); + } + + // TODO: Fix prediction issues. When calling from a server side, there is no popups. + // Introducing server-side popups will cause them to be spammed client-side. + // PopupPredicted() cannot be used or it'll not show any popups clientside when called serverside. + /// + /// Whether the target can be targeted by psionic influence. + /// + /// The target of the psionic ability. + /// Whether the target needs to be a potential psionic to be eligible. + /// Whether it should show popups. + /// Whether the psionic influence originates from an entity. + /// Returns true if targetable, false otherwise. Will handle popups itself. + [PublicAPI] + public bool CanBeTargeted(EntityUid target, bool ignorePsionicRequirement = false, bool showPopup = true, EntityUid? hasAggressor = null) + { + // Normal abilities cannot target Borgs and Simplemobs that aren't psionic. + if (!PotentialQuery.HasComp(target) && !PsionicQuery.HasComp(target) && !ignorePsionicRequirement) + return false; + + var ev = new TargetedByPsionicPowerEvent(); + RaiseLocalEvent(target, ref ev); + + if (!showPopup || !ev.IsShielded) + return !ev.IsShielded; + + Popup.PopupEntity(Loc.GetString("psionic-shielded-from-attempt"), target, target, PopupType.MediumCaution); + + if (hasAggressor is { } aggressor) + { + var message = Loc.GetString("psionic-cannot-target-shielded"); + Popup.PopupClient(message, aggressor, aggressor, PopupType.SmallCaution); + } + + return !ev.IsShielded; + } + + /// + /// Whether the entity can use psionic abilities. + /// + /// The entity attempting to use psionic abilities. + /// Returns true if yes, false otherwise. + public bool CanUsePsionicAbility(EntityUid psionic) + { + var ev = new PsionicPowerUseAttemptEvent(); + RaiseLocalEvent(psionic, ref ev); + + return ev.CanUsePower; + } +} diff --git a/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Detection.cs b/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Detection.cs new file mode 100644 index 0000000000..c36a0689f3 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Detection.cs @@ -0,0 +1,54 @@ +using System.Linq; +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Events; +using Content.Shared.Clothing; +using Content.Shared.Popups; + +namespace Content.Shared._DV.Psionics.Systems; + +public abstract partial class SharedPsionicSystem +{ + private void InitializeDetection() + { + SubscribeLocalEvent(OnPowerUsed); + + SubscribeLocalEvent(OnGrantingClothingEquipped); + SubscribeLocalEvent(OnGrantingClothingUnequipped); + } + + private void OnGrantingClothingEquipped(Entity detector, ref ClothingGotEquippedEvent args) + { + if (!HasComp(args.Wearer)) // IPCs and non-player organics shouldn't be able to use abilities. + return; + + detector.Comp.Wearer = args.Wearer; + Dirty(detector); + } + + private void OnGrantingClothingUnequipped(Entity detector, ref ClothingGotUnequippedEvent args) + { + detector.Comp.Wearer = null; + Dirty(detector); + } + + private void OnPowerUsed(Entity psionic, ref PsionicPowerUsedEvent args) + { + var coords = Transform(args.User).Coordinates; + + foreach (var detector in _lookup.GetEntitiesInRange(coords, 10f).Select(detectorPower => detectorPower.Comp.Wearer ?? detectorPower.Owner)) + { + if (detector == args.User) + continue; + + if (!CanUsePsionicAbility(detector)) + continue; + // This is for artifacts. + var detectEv = new PsionicPowerDetectedEvent(args.User, args.Power); + RaiseLocalEvent(detector, ref detectEv); + + Popup.PopupEntity(Loc.GetString("psionic-power-metapsionic-power-detected", ("power", args.Power)), detector, detector, PopupType.LargeCaution); + } + + args.Handled = true; + } +} diff --git a/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Effects.cs b/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Effects.cs new file mode 100644 index 0000000000..dd0aae8f33 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Effects.cs @@ -0,0 +1,65 @@ +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Events; +using Content.Shared.StatusEffectNew; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DV.Psionics.Systems; + +public abstract partial class SharedPsionicSystem +{ + public static readonly EntProtoId PsionicsDisabledProtoId = "StatusEffectPsionicsDisabled"; + public void InitializeStatusEffects() + { + SubscribeLocalEvent>(OnPowerUseAttempt); + SubscribeLocalEvent>(OnTargetedByPsionicPower); + + SubscribeLocalEvent(OnStatusEffectApplied); + SubscribeLocalEvent(OnStatusEffectRemoved); + SubscribeLocalEvent(OnStatusEffectApplied); + SubscribeLocalEvent(OnStatusEffectRemoved); + } + + private void OnPowerUseAttempt(Entity psionic, ref StatusEffectRelayedEvent args) + { + var ev = args.Args; + ev.CanUsePower = false; + args.Args = ev; + } + + private void OnTargetedByPsionicPower(Entity psionic, ref StatusEffectRelayedEvent args) + { + var ev = args.Args; + ev.IsShielded = true; + args.Args = ev; + } + + private void OnStatusEffectApplied(Entity statusEffect, ref StatusEffectAppliedEvent args) + { + var ev = new PsionicSuppressedEvent(args.Target); + RaiseLocalEvent(args.Target, ref ev); + } + + private void OnStatusEffectRemoved(Entity statusEffect, ref StatusEffectRemovedEvent args) + { + if (!CanUsePsionicAbility(args.Target)) + return; + + var ev = new PsionicStoppedSuppressedEvent(args.Target); + RaiseLocalEvent(args.Target, ref ev); + } + + private void OnStatusEffectApplied(Entity statusEffect, ref StatusEffectAppliedEvent args) + { + var ev = new PsionicShieldedEvent(args.Target); + RaiseLocalEvent(args.Target, ref ev); + } + + private void OnStatusEffectRemoved(Entity statusEffect, ref StatusEffectRemovedEvent args) + { + if (!CanBeTargeted(args.Target, showPopup: false)) + return; + + var ev = new PsionicStoppedShieldedEvent(args.Target); + RaiseLocalEvent(args.Target, ref ev); + } +} diff --git a/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Invisibility.cs b/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Invisibility.cs new file mode 100644 index 0000000000..6854665a5a --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Invisibility.cs @@ -0,0 +1,103 @@ +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Events; +using Content.Shared.Eye; + +namespace Content.Shared._DV.Psionics.Systems; + +public abstract partial class SharedPsionicSystem +{ + [Dependency] private readonly SharedEyeSystem _eye = default!; + [Dependency] private readonly SharedVisibilitySystem _visibility = default!; + + private void InitializeInvisibility() + { + SubscribeLocalEvent(OnInvisInit); + SubscribeLocalEvent(OnInvisShutdown); + + SubscribeLocalEvent(OnInit); + + SubscribeLocalEvent(OnSuppression); + SubscribeLocalEvent(OnSuppressionStop); + SubscribeLocalEvent(OnShielded); + SubscribeLocalEvent(OnShieldedStop); + } + + private void OnInvisInit(Entity invisible, ref MapInitEvent args) + { + if (!CanUsePsionicAbility(invisible)) + invisible.Comp.Active = false; + + SetPsionicInvisibility(invisible.Owner, invisible.Comp.Active); + } + + private void OnInvisShutdown(Entity invisible, ref ComponentShutdown args) + { + SetPsionicInvisibility(invisible.Owner, false); + } + + private void OnInit(Entity potPsionic, ref MapInitEvent args) + { + SetCanSeePsionicInvisiblity(potPsionic, false); + } + + private void OnSuppression(Entity invisible, ref PsionicSuppressedEvent args) + { + invisible.Comp.Active = false; + SetPsionicInvisibility(invisible.Owner, invisible.Comp.Active); + } + + private void OnSuppressionStop(Entity invisible, ref PsionicStoppedSuppressedEvent args) + { + // This event only raises when they can use psionic abilities again, so no need for a check. + invisible.Comp.Active = true; + SetPsionicInvisibility(invisible.Owner, invisible.Comp.Active); + } + + private void OnShielded(Entity potPsionic, ref PsionicShieldedEvent args) + { + SetCanSeePsionicInvisiblity(potPsionic, true); + } + + private void OnShieldedStop(Entity potPsionic, ref PsionicStoppedShieldedEvent args) + { + //This only gets raised when they are no longer shielded, so no need for a check. + SetCanSeePsionicInvisiblity(potPsionic, false); + } + + /// + /// Enables or disables an entity to see a psionically invisible entity. + /// + /// The entity whose ability to see psionically invisible entities is being changed. + /// Whether they can see psionically invisible entities. + public void SetCanSeePsionicInvisiblity(EntityUid potPsionic, bool canSee) + { + if (!TryComp(potPsionic, out var eye)) + return; + + if (canSee) + _eye.SetVisibilityMask(potPsionic, eye.VisibilityMask | (int) VisibilityFlags.PsionicInvisibility, eye); + else + _eye.SetVisibilityMask(potPsionic, eye.VisibilityMask & ~ (int) VisibilityFlags.PsionicInvisibility, eye); + } + + /// + /// Set their psionic invisibility. + /// + /// The entity that attempts to toggle their psionic invisibility. + /// Whether they're visible or invisible. + public void SetPsionicInvisibility(Entity visible, bool invisible) + { + if (invisible) + { + // Remove them from the normal layer and add them to the psionic layer. + _visibility.AddLayer(visible, (int) VisibilityFlags.PsionicInvisibility, false); + _visibility.RemoveLayer(visible, (int) VisibilityFlags.Normal); + } + else + { + // Remove them from the psionic layer and add them to the normal layer. + _visibility.RemoveLayer(visible, (int) VisibilityFlags.PsionicInvisibility, false); + _visibility.AddLayer(visible, (int) VisibilityFlags.Normal); + } + } +} diff --git a/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Items.cs b/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Items.cs new file mode 100644 index 0000000000..b736a88731 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Items.cs @@ -0,0 +1,124 @@ +using System.Linq; +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Events; +using Content.Shared._DV.Psionics.Systems.PsionicPowers; +using Content.Shared.Damage.Events; +using Content.Shared.Inventory; +using Content.Shared.Inventory.Events; +using Content.Shared.StatusEffectNew; +using Content.Shared.Weapons.Melee.Events; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Shared._DV.Psionics.Systems; + +public abstract partial class SharedPsionicSystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] protected readonly SharedAudioSystem Audio = default!; + [Dependency] private readonly SharedMindSwapPowerSystem _mindSwapPowerSystem = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; + + private void InitializeItems() + { + SubscribeLocalEvent(OnInsulativeGearEquipped); + SubscribeLocalEvent(OnInsulativeGearUnequipped); + + + SubscribeLocalEvent>(OnPowerUseAttempt); + SubscribeLocalEvent>(OnTargetedByPsionicPower); + + SubscribeLocalEvent(OnAntiPsionicMeleeHit); + SubscribeLocalEvent(OnAntiPsionicStamHit); + } + + private void OnInsulativeGearEquipped(Entity gear, ref GotEquippedEvent args) + { + if (_timing.ApplyingState) + return; + + if (!gear.Comp.AllowsPsionicUsage) + { + var ev = new PsionicSuppressedEvent(args.Equipee); + RaiseLocalEvent(args.Equipee, ref ev); + } + if (gear.Comp.ShieldsFromPsionics) + { + var ev = new PsionicShieldedEvent(args.Equipee); + RaiseLocalEvent(args.Equipee, ref ev); + } + } + + private void OnInsulativeGearUnequipped(Entity gear, ref GotUnequippedEvent args) + { + if (_timing.ApplyingState) + return; + + if (!gear.Comp.AllowsPsionicUsage && CanUsePsionicAbility(args.Equipee)) + { + var ev = new PsionicStoppedSuppressedEvent(args.Equipee); + RaiseLocalEvent(args.Equipee, ref ev); + } + if (gear.Comp.ShieldsFromPsionics && CanBeTargeted(args.Equipee, showPopup: false)) + { + var ev = new PsionicStoppedShieldedEvent(args.Equipee); + RaiseLocalEvent(args.Equipee, ref ev); + } + } + + #region EventHandling + private void OnPowerUseAttempt(Entity gear, ref InventoryRelayedEvent args) + { + // If one gear blocks psionic usage, psionics cannot be used. + args.Args.CanUsePower &= gear.Comp.AllowsPsionicUsage; + } + + private void OnTargetedByPsionicPower(Entity gear, ref InventoryRelayedEvent args) + { + // If one gear shields from psionics, they're shielded. + args.Args.IsShielded |= gear.Comp.ShieldsFromPsionics; + } + #endregion + + #region AntiPsionicWeaponry + private void OnAntiPsionicMeleeHit(Entity weapon, ref MeleeHitEvent args) + { + foreach (var target in args.HitEntities) + { + if (HasComp(target)) + { + Audio.PlayPredicted(weapon.Comp.HitSound, target, args.User); + args.ModifiersList.Add(weapon.Comp.Modifiers); + + if (Random.Prob(weapon.Comp.DisableChance)) + _statusEffects.TryUpdateStatusEffectDuration(target, PsionicsDisabledProtoId, TimeSpan.FromSeconds(10)); + } + + if (TryComp(target, out var swapped)) + { + _mindSwapPowerSystem.SwapMinds(target, swapped.OriginalEntity, false); + return; + } + + if (!weapon.Comp.Punish + || !HasComp(target) + || HasComp(target) + || !Random.Prob(weapon.Comp.PunishChance)) + continue; + + _stuttering.DoStutter(args.User, TimeSpan.FromMinutes(5), false); + _stun.TryKnockdown(args.User, TimeSpan.FromSeconds(5), false, drop: false); + _jittering.DoJitter(args.User, TimeSpan.FromSeconds(5), false); + } + } + + private void OnAntiPsionicStamHit(Entity weapon, ref StaminaMeleeHitEvent args) + { + if (args.HitList.Any(targetStamina => HasComp(targetStamina.Entity))) + { + args.Multiplier *= weapon.Comp.StaminaDamageMultiplier; + } + } + #endregion +} diff --git a/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Rolling.cs b/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Rolling.cs new file mode 100644 index 0000000000..2f535f9655 --- /dev/null +++ b/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Rolling.cs @@ -0,0 +1,101 @@ +using System.Linq; +using Content.Shared._DV.Psionics.Components; +using Content.Shared.EntityTable; +using Content.Shared.Popups; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Shared._DV.Psionics.Systems; + +public abstract partial class SharedPsionicSystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly EntityTableSystem _entityTable = default!; + + public bool TryRollPsionic(Entity potPsionic, float multiplier = 1.0f) + { + if (potPsionic.Comp.Rolled) + return false; + + potPsionic.Comp.Rolled = true; + + if (!RollChance(potPsionic, multiplier)) + { + Popup.PopupEntity(Loc.GetString("psionic-roll-failed"), potPsionic, potPsionic, PopupType.Medium); + return false; + } + + AddRandomPsionicPower(potPsionic, true); + Dirty(potPsionic); + return true; + } + + protected bool RollChance(Entity potPsionic, float multiplier = 1.0f) + { + var chance = potPsionic.Comp.BaseChance; + // Jobs like Command and Chaplains get a bonus on their roll. + chance += potPsionic.Comp.JobBonusChance; + // Species like Kitsunes get a bonus on their roll. + chance += potPsionic.Comp.SpeciesBonusChance; + + // Rolling with chemicals can have multipliers. + chance *= multiplier; + + chance = Math.Clamp(chance, 0, 1); + return Random.Prob(chance); + } + + public void AddRandomPsionicPower(Entity psionic, bool midRound) + { + if (!_prototypeManager.Resolve(psionic.Comp.PsionicPowerTableId, out var powerTable)) + return; + + var random = Random.GetRandom(); // This is called in GetSpawns(). We simply call it once to avoid calling it multiple times. + var attempts = 0; + while (attempts < 20) // 20 attempts to get a unique psionic power. + { + var spawns = _entityTable.GetSpawns(powerTable, random); + + foreach (var entProtoId in spawns) + { + if (TryAddPsionicPower(psionic, midRound, entProtoId)) + return; + + attempts++; + } + } + + Popup.PopupEntity(Loc.GetString("psionic-roll-failed"), psionic, psionic, PopupType.Medium); + } + + private bool TryAddPsionicPower(Entity psionic, bool midRound, EntProtoId entProtoId) + { + if (!_prototypeManager.Resolve(entProtoId, out var powerEntity)) + return false; + // If the psionic already has that power, do not add it again. + if (powerEntity.Components.Any(psionicComponent => + EntityManager.HasComponent(psionic, psionicComponent.Value.Component.GetType()))) + return false; + // If they don't have it already, add it. + EntityManager.AddComponents(psionic, powerEntity); + + if (!midRound) + return true; + // For alternative means of getting psionics that aren't via spawning in, cause them to suffer. + _stuttering.DoStutter(psionic, TimeSpan.FromMinutes(1), false); + _stun.TryKnockdown(psionic.Owner, TimeSpan.FromSeconds(3), false, drop: false); + _jittering.DoJitter(psionic, TimeSpan.FromSeconds(3), false); + + return true; + } + + public bool GrantPsionicRoll(Entity potPsionic) + { + if (!Resolve(potPsionic, ref potPsionic.Comp) || !potPsionic.Comp.Rolled) + return false; + + potPsionic.Comp.Rolled = false; + Dirty(potPsionic); + return true; + } +} diff --git a/Content.Shared/_DV/Roles/ModifyPsionicChanceSpecial.cs b/Content.Shared/_DV/Roles/ModifyPsionicChanceSpecial.cs new file mode 100644 index 0000000000..be3fde0c4e --- /dev/null +++ b/Content.Shared/_DV/Roles/ModifyPsionicChanceSpecial.cs @@ -0,0 +1,30 @@ +using Content.Shared._DV.Psionics.Components; +using Content.Shared.Roles; + +namespace Content.Shared._DV.Roles; + +public sealed partial class ModifyPsionicChanceSpecial : JobSpecial +{ + /// + /// The value to replace the JobChance with. + /// + [DataField(required: true)] + public float JobBonusChance; + + /// + /// If not null, it'll replace species bonus too. + /// + [DataField] + public float? SpeciesBonusChance; + + public override void AfterEquip(EntityUid mob) + { + var entityManager = IoCManager.Resolve(); + if (!entityManager.TryGetComponent(mob, out PotentialPsionicComponent? psionic)) + return; + + psionic.JobBonusChance = JobBonusChance; + if (SpeciesBonusChance.HasValue) + psionic.SpeciesBonusChance = SpeciesBonusChance.Value; + } +} diff --git a/Content.Shared/_DV/StationEvents/Events/GlimmerEventEndedEvent.cs b/Content.Shared/_DV/StationEvents/Events/GlimmerEventEndedEvent.cs new file mode 100644 index 0000000000..80956ebf19 --- /dev/null +++ b/Content.Shared/_DV/StationEvents/Events/GlimmerEventEndedEvent.cs @@ -0,0 +1,14 @@ +namespace Content.Shared._DV.StationEvents.Events; + +/// +/// After every glimmer event, it'll raise this event. +/// It's used for the sophic scribe to report glimmer changes. +/// +/// The message for the Sophic scribe to repeat. +/// How much glimmer was burned through the event. +[ByRefEvent] +public readonly struct GlimmerEventEndedEvent(string message, int glimmerBurned) +{ + public readonly string Message = message; + public readonly int GlimmerBurned = glimmerBurned; +} diff --git a/Content.Shared/_DV/Xenoarcheology/Artifact/Components/XAEDetailsComponent.cs b/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Effects/Components/XAEDetailsComponent.cs similarity index 82% rename from Content.Shared/_DV/Xenoarcheology/Artifact/Components/XAEDetailsComponent.cs rename to Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Effects/Components/XAEDetailsComponent.cs index 9d9375709c..1bc511a6e0 100644 --- a/Content.Shared/_DV/Xenoarcheology/Artifact/Components/XAEDetailsComponent.cs +++ b/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Effects/Components/XAEDetailsComponent.cs @@ -1,7 +1,4 @@ -using Content.Shared.Chemistry.Reagent; -using Robust.Shared.Prototypes; - -namespace Content.Shared._DV.Xenoarchaeology.Artifact.Components; +namespace Content.Shared._DV.Xenoarcheology.XenoArtifacts.Effects.Components; /// /// Contains description / tips about the effect. diff --git a/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Effects/Components/GlimmerArtifactComponent.cs b/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Effects/Components/XAEModifyGlimmerComponent.cs similarity index 64% rename from Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Effects/Components/GlimmerArtifactComponent.cs rename to Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Effects/Components/XAEModifyGlimmerComponent.cs index 1d205f67bf..57340cf364 100644 --- a/Content.Server/_DV/Xenoarchaeology/XenoArtifacts/Effects/Components/GlimmerArtifactComponent.cs +++ b/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Effects/Components/XAEModifyGlimmerComponent.cs @@ -1,13 +1,13 @@ -using Content.Server._DV.Xenoarchaeology.XenoArtifacts.Effects.Systems; +using Content.Shared._DV.Xenoarcheology.XenoArtifacts.Effects.Systems; using Content.Shared.Destructible.Thresholds; -namespace Content.Server._DV.Xenoarchaeology.XenoArtifacts.Effects.Components; +namespace Content.Shared._DV.Xenoarcheology.XenoArtifacts.Effects.Components; /// /// Raises or lowers glimmer when this artifact is triggered. /// -[RegisterComponent, Access(typeof(GlimmerArtifactSystem))] -public sealed partial class GlimmerArtifactComponent : Component +[RegisterComponent, Access(typeof(XAEModifyGlimmerSystem))] +public sealed partial class XAEModifyGlimmerComponent : Component { /// /// If glimmer is not in this range it won't do anything. diff --git a/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Effects/Components/XAEPsionicInducerComponent.cs b/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Effects/Components/XAEPsionicInducerComponent.cs new file mode 100644 index 0000000000..9419fa7b68 --- /dev/null +++ b/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Effects/Components/XAEPsionicInducerComponent.cs @@ -0,0 +1,16 @@ +using Content.Shared._DV.Xenoarcheology.XenoArtifacts.Effects.Systems; + +namespace Content.Shared._DV.Xenoarcheology.XenoArtifacts.Effects.Components; + +/// +/// Makes people in a radius around it psionic when triggered. +/// +[RegisterComponent, Access(typeof(XAEPsionicInducerSystem))] +public sealed partial class XAEPsionicInducerComponent : Component +{ + /// + /// Range to look for potential psionics in. + /// + [DataField(required: true)] + public float Range; +} diff --git a/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Effects/Systems/XAEModifyGlimmerSystem.cs b/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Effects/Systems/XAEModifyGlimmerSystem.cs new file mode 100644 index 0000000000..529337ce90 --- /dev/null +++ b/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Effects/Systems/XAEModifyGlimmerSystem.cs @@ -0,0 +1,27 @@ +using Content.Shared._DV.Xenoarcheology.XenoArtifacts.Effects.Components; +using Content.Shared.Psionics.Glimmer; +using Content.Shared.Xenoarchaeology.Artifact; + +namespace Content.Shared._DV.Xenoarcheology.XenoArtifacts.Effects.Systems; + +public sealed class XAEModifyGlimmerSystem : EntitySystem +{ + [Dependency] private readonly GlimmerSystem _glimmer = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnActivated); + } + + private void OnActivated(Entity arti, ref XenoArtifactNodeActivatedEvent args) + { + var range = arti.Comp.Range; + var current = _glimmer.Glimmer; + if (range.Min > current || current > range.Max) + return; + + _glimmer.Glimmer += arti.Comp.Change; + } +} diff --git a/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Effects/Systems/XAEPsionicInducerSystem.cs b/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Effects/Systems/XAEPsionicInducerSystem.cs new file mode 100644 index 0000000000..3a82fc438e --- /dev/null +++ b/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Effects/Systems/XAEPsionicInducerSystem.cs @@ -0,0 +1,33 @@ +using Content.Shared._DV.Psionics.Components; +using Content.Shared._DV.Psionics.Systems; +using Content.Shared._DV.Xenoarcheology.XenoArtifacts.Effects.Components; +using Content.Shared.Xenoarchaeology.Artifact; + +namespace Content.Shared._DV.Xenoarcheology.XenoArtifacts.Effects.Systems; + +public sealed class XAEPsionicInducerSystem : EntitySystem +{ + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedPsionicSystem _psionic = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnActivated); + } + + private void OnActivated(Entity arti, ref XenoArtifactNodeActivatedEvent args) + { + var coords = Transform(arti).Coordinates; + foreach (var target in _lookup.GetEntitiesInRange(coords, arti.Comp.Range)) + { + // No additional ability for already psionic beings. + if (HasComp(target)) + continue; + + if (_psionic.CanBeTargeted(target)) + _psionic.AddRandomPsionicPower(target, true); + } + } +} diff --git a/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Triggers/Components/XATPsionicUsageComponent.cs b/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Triggers/Components/XATPsionicUsageComponent.cs new file mode 100644 index 0000000000..37b223f157 --- /dev/null +++ b/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Triggers/Components/XATPsionicUsageComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Shared._DV.Xenoarcheology.XenoArtifacts.Triggers.Components; + +/// +/// Triggers if a psionic power is used nearby. +/// Requires to be added too. +/// +[RegisterComponent] +public sealed partial class XATPsionicUsageComponent : Component; diff --git a/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Triggers/Systems/XATPsionicUsageSystem.cs b/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Triggers/Systems/XATPsionicUsageSystem.cs new file mode 100644 index 0000000000..02272642f9 --- /dev/null +++ b/Content.Shared/_DV/Xenoarcheology/XenoArtifacts/Triggers/Systems/XATPsionicUsageSystem.cs @@ -0,0 +1,44 @@ +using Content.Shared._DV.Psionics.Events; +using Content.Shared._DV.StationEvents.Events; +using Content.Shared._DV.Xenoarcheology.XenoArtifacts.Triggers.Components; +using Content.Shared.Xenoarchaeology.Artifact.Components; +using Content.Shared.Xenoarchaeology.Artifact.XAT; + +namespace Content.Shared._DV.Xenoarcheology.XenoArtifacts.Triggers.Systems; + +public sealed class XATPsionicUsageSystem : BaseXATSystem +{ + private EntityQuery _xenoArtifactQuery; + + public override void Initialize() + { + base.Initialize(); + + _xenoArtifactQuery = GetEntityQuery(); + + XATSubscribeDirectEvent(OnPowerDetected); + SubscribeLocalEvent(OnGlimmerEventEnded); + } + + private void OnPowerDetected(Entity artifact, Entity node, ref PsionicPowerDetectedEvent args) + { + Trigger(artifact, node); + } + + private void OnGlimmerEventEnded(ref GlimmerEventEndedEvent args) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp, out var node)) + { + if (node.Attached == null) + continue; + + var artifact = _xenoArtifactQuery.Get(node.Attached.Value); + + if (!CanTrigger(artifact, (uid, node))) + continue; + + Trigger(artifact, (uid, comp, node)); + } + } +} diff --git a/Resources/Locale/en-US/_DV/abilities/psionic.ftl b/Resources/Locale/en-US/_DV/abilities/psionic.ftl index 30f23aa7ad..2429afe1d0 100644 --- a/Resources/Locale/en-US/_DV/abilities/psionic.ftl +++ b/Resources/Locale/en-US/_DV/abilities/psionic.ftl @@ -1,15 +1,50 @@ -psionic-nosebleed-message = Your nose starts gushing blood! +## Names of Psionic Powers +psionic-power-name-dispel = Dispel +psionic-power-name-eruption = Psionic Eruption +psionic-power-name-fractured-form = Fractured Form +psionic-power-name-mass-sleep = Mass Sleep +psionic-power-name-mindswap = Mind Swap +psionic-power-name-mindswap-return = Mind Swap Return +psionic-power-name-metapsionic = Metapsionic Pulse +psionic-power-name-noospheric-zap = Noospheric Zap +psionic-power-name-precognition = Precognition +psionic-power-name-psychokinetic = Psychokinetic Scream +psionic-power-name-psionic-invisibility = Psionic Invisibility +psionic-power-name-psionic-regeneration = Psionic Regeneration +psionic-power-name-pyrokinesis = Pyrokinesis +psionic-power-name-telegnosis = Telegnosis -mass-mind-swap-event-announcement = Warning: abnormal glimmer discharge detected. Mass consciousness transfer event imminent, T-{$time} seconds. Please equip psionically-insulating headwear immediately. +## Psionic Rolling & Mindbreaking Messages +psionic-roll-failed = The noöspheric influence leaves no mark on your mind... +psionic-partly-mindbroken = The psionic influence on your mind weakens.. +psionic-mindbroken = Your mind retreats from abstraction to reality. -mass-mind-swap-event-sender = Sophic Grammateus +## Base Psionic messages +psionic-cannot-use-psionics = Your psionic energy can't escape your mind! +psionic-shielded-from-attempt = A psionic influence faltered against your shield! +psionic-cannot-target-shielded = They remain steadfast against your psionic grasp! +psionic-equipped-shielded-in-doafter = The insulative gear broke your concentration.. +psionic-dispelled = Someone dispelled your psionic concentration! -minor-mass-mind-swap-event-announcement = Warning: abnormal glimmer discharge detected. Minor Mass Consciousness transfer event imminent, T-{$time} seconds. Please equip psionically-insulating headwear immedieately. +## Specific Psionic messages +# Fractured Form +psionic-power-fractured-form-nobodies = You have no alternate forms to switch to! +psionic-power-fractured-form-sleepy = You feel very sleepy... You should find somewhere to rest. +psionic-power-fractured-form-ssd = { CAPITALIZE(SUBJECT($ent)) } { CONJUGATE-BE($ent) } in a deep sleep. { CAPITALIZE(POSS-ADJ($ent)) } eyes seem to be darting around as if dreaming. +psionic-power-fractured-form-examine-self = You feel a strange connection to { OBJECT($ent) }. +psionic-power-fractured-form-dispelled = Someone dispelled your sleepiness.. -minor-mass-mind-swap-event-sender = Sophic Grammateus +# Metapsionic Pulse +psionic-power-metapsionic-success = You detect psychic presence there. +psionic-power-metapsionic-failure = You don't detect any psychic presence there. +psionic-power-metapsionic-power-detected = You detect that {$power} was used nearby. -psionic-power-mass-sleep-warning = Your eyelids begin to droop... +# Mindswap +psionic-power-mindswap-target-mindshielded = Your mindshield.. surprisingly shielded your mind from an psionic influence. +psionic-power-mindswap-own-mindshield = Your mindshield.. stops your mind from leaving your body. +psionic-power-mindswap-original-lost = The psionic tether to your original body was severed! +# Precognition psionic-power-precognition-failure-by-damage = Your concentration was broken! You fail to decipher anything of use. psionic-power-precognition-no-event-result-message = You see a vision of an undisturbed lake. @@ -62,6 +97,7 @@ psionic-power-precognition-syndicate-armsdealer-result-message = You see a visio psionic-power-precognition-rift-spawn-result-message = You see a small spark of energy, quickly expanding as it tears reality apart, twisting everything around it. psionic-power-precognition-asakim-spawn-result-message = You smell stale air from a cryopod opening, and the faint echo of an intelligence far away but very near. +# Psionic Eruption psionic-eruption-begin = {CAPITALIZE(THE($user))} is being consumed by a psionic energy! psionic-eruption-annoy-minimal = You feel a pressure building up in your mind. psionic-eruption-annoy-low = Your head aches from the psionic energy. @@ -86,11 +122,20 @@ eruption-warning-window-prompt-text-part = You feel a strong pressure building u Do you understand? eruption-warning-window-acknowledge-button = I Understand +## Psionic Gamerule Messages +gamerule-noospheric-zap-seize = An external eruption overwhelms your mind! +gamerule-noospheric-zap-seize-potential-regained = Your mind restructures.. it demands knowledge... + +psionic-nosebleed-message = Your nose starts gushing blood! + +mass-mind-swap-event-announcement = Warning: abnormal glimmer discharge detected. Mass consciousness transfer event imminent, T-{$time} seconds. Please equip psionically-insulating headwear immediately. +mass-mind-swap-event-sender = Sophic Grammateus + +minor-mass-mind-swap-event-announcement = Warning: abnormal glimmer discharge detected. Minor Mass Consciousness transfer event imminent, T-{$time} seconds. Please equip psionically-insulating headwear immedieately. +minor-mass-mind-swap-event-sender = Sophic Grammateus + +psionic-power-mass-sleep-warning = Your eyelids begin to droop... + telegnosis-power-ssd = { CAPITALIZE(POSS-ADJ($ent)) } eyes are unfocused and darting around, as if trying to see something that isn't there. -fractured-form-nobodies = You have no alternate forms to switch to! -fractured-form-sleepy = You feel very sleepy... You should find somewhere to rest. -fractured-form-ssd = { CAPITALIZE(SUBJECT($ent)) } { CONJUGATE-BE($ent) } in a deep sleep. { CAPITALIZE(POSS-ADJ($ent)) } eyes seem to be darting around as if dreaming. -fractured-form-examine-self = You feel a strange connection to { OBJECT($ent) }. - glimmer-restyle-event = You feel like something changed about your looks... diff --git a/Resources/Locale/en-US/_DV/chemistry/effects.ftl b/Resources/Locale/en-US/_DV/chemistry/effects.ftl new file mode 100644 index 0000000000..0db9a6a009 --- /dev/null +++ b/Resources/Locale/en-US/_DV/chemistry/effects.ftl @@ -0,0 +1,12 @@ +reagent-effect-guidebook-chem-remove-psionic = + { $chance -> + [1] Removes + *[other] {$chance} to remove + } all psionic powers + +reagent-effect-guidebook-chem-roll-psionic = + Has a chance to grant another, different psionic power + { $multiplier -> + [1] {""} + *[other] with a chance multiplier of {$multiplier} + } diff --git a/Resources/Locale/en-US/_DV/reagents/meta/biological.ftl b/Resources/Locale/en-US/_DV/reagents/meta/biological.ftl index d3fcceb245..221c2a18e9 100644 --- a/Resources/Locale/en-US/_DV/reagents/meta/biological.ftl +++ b/Resources/Locale/en-US/_DV/reagents/meta/biological.ftl @@ -1,2 +1,8 @@ reagent-name-sentient-grey-matter = sentient grey matter reagent-desc-sentient-grey-matter = Thought juice, the stuff that leaks out of your ears. It seems to be sparkling when you look at it in the right light. That's odd... + +reagent-name-nectar = nectar +reagent-desc-nectar = A drink from the gods of old, a rare delicacy amongst the spiritual. It temporarily shields the mind from external psionic influences and quenches the bodily thirst. + +reagent-name-ambrosia = ambrosia +reagent-desc-ambrosia = A food from the gods of old, a rare delicacy amongst the spiritual. It temporarily strengthens the body, gifting speed and stamina, while quenching it's hunger. diff --git a/Resources/Locale/en-US/_DV/reagents/meta/medicine.ftl b/Resources/Locale/en-US/_DV/reagents/meta/medicine.ftl index 3534a5b6f9..f31662de28 100644 --- a/Resources/Locale/en-US/_DV/reagents/meta/medicine.ftl +++ b/Resources/Locale/en-US/_DV/reagents/meta/medicine.ftl @@ -14,6 +14,9 @@ reagent-desc-anodynafil = An effective short-lasting anesthetic that doesn't int reagent-desc-dexalin-deltav = Used for treating minor oxygen deprivation and bloodloss. A required reagent for salbutamol and convermol. +reagent-name-sanctussal = sanctus sal +reagent-desc-sanctussal = A primitive mixture that wards off noöspheric influences. Historically a scam, but sourcing the holy water from a divine source yields factual results. + reagent-name-photoxadone = photoxadone reagent-desc-photoxadone = a strange, faintly glowing cryogenics chemical. Seems to have some effect on damages deeper than physical, even affecting the dead. diff --git a/Resources/Locale/en-US/_DV/reagents/meta/narcotic.ftl b/Resources/Locale/en-US/_DV/reagents/meta/narcotic.ftl new file mode 100644 index 0000000000..46e86714c1 --- /dev/null +++ b/Resources/Locale/en-US/_DV/reagents/meta/narcotic.ftl @@ -0,0 +1,2 @@ +reagent-name-lotophagoi-oil = lotophagoi oil +reagent-desc-lotophagoi-oil = A divine drug sourced from the fruits of an ancient tree. It temporarily tears into the patient's mind, reshaping it to suit the Noösphere. diff --git a/Resources/Locale/en-US/_DV/reagents/meta/toxins.ftl b/Resources/Locale/en-US/_DV/reagents/meta/toxins.ftl index 66d1726468..191b6b8862 100644 --- a/Resources/Locale/en-US/_DV/reagents/meta/toxins.ftl +++ b/Resources/Locale/en-US/_DV/reagents/meta/toxins.ftl @@ -1 +1,4 @@ reagent-desc-heartbreaker-toxin-deltav = A hallucinogenic compound derived from mindbreaker toxin. it blocks neurological signals to the respiratory system, causing asphyxiation. Epinephrine will filter it out, however. + +reagent-name-lernaea = Lernaea +reagent-desc-lernaea = A vicious poison, speculated to originate from a river of death. It ravenously consumes noöspheric energies in the mind while it's present. diff --git a/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl b/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl index 4b9198663a..a03bb4e078 100644 --- a/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl +++ b/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl @@ -3,10 +3,6 @@ cage-resist-third-person = {CAPITALIZE(THE($user))} starts removing {POSS-ADJ($u cage-uncage-verb = Uncage -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. - 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, @@ -18,9 +14,6 @@ glimmer-event-report-generic = Noöspheric discharge detected. Glimmer level has 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 diff --git a/Resources/Locale/en-US/nyanotrasen/chemistry/effects.ftl b/Resources/Locale/en-US/nyanotrasen/chemistry/effects.ftl index a82aae768c..aa0636eac1 100644 --- a/Resources/Locale/en-US/nyanotrasen/chemistry/effects.ftl +++ b/Resources/Locale/en-US/nyanotrasen/chemistry/effects.ftl @@ -4,17 +4,7 @@ reagent-effect-guidebook-change-glimmer-reaction-effect = *[other] modify } the glimmer count by {$amount} points -reagent-effect-guidebook-chem-remove-psionic = - { $chance -> - [1] Removes - *[other] remove - } psionic powers -reagent-effect-guidebook-chem-reroll-psionic = - { $chance -> - [1] Allows - *[other] allow - } a chance to get a different psionic power ## Disease System support diff --git a/Resources/Locale/en-US/nyanotrasen/reagents/toxins.ftl b/Resources/Locale/en-US/nyanotrasen/reagents/toxins.ftl index 43e35c191c..60ae2e80df 100644 --- a/Resources/Locale/en-US/nyanotrasen/reagents/toxins.ftl +++ b/Resources/Locale/en-US/nyanotrasen/reagents/toxins.ftl @@ -1,8 +1,5 @@ reagent-name-soulbreaker-toxin = soulbreaker toxin reagent-desc-soulbreaker-toxin = An anti-psionic about 4 times as powerful as mindbreaker toxin. -reagent-name-lotophagoi-oil = lotophagoi oil -reagent-desc-lotophagoi-oil = A super potent drug that is much better at inducing psionics than normal hallucinogens, but with worse side effects. - reagent-name-ectoplasm = ectoplasm reagent-desc-ectoplasm = The physical component of semi-corporeal spirits. diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml index 20b6043207..7cc9f496c0 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml @@ -908,11 +908,9 @@ color: "#ffffff" # Begin DeltaV Additions - type: Psionic - - type: TinfoilHat - passthrough: true - destroyOnFry: false - - type: ClothingGrantPsionicPower - power: DispelPower + - type: PsionicallyInsulative + allowsPsionicUsage: true + - type: DispelPower - type: GuideHelp guides: - Psionics diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index ded670f3b4..74ca47a432 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -130,7 +130,6 @@ attributes: gender: male - type: Sharp - - type: PotentialPsionic # Nyano - type: NightVision #Delta-v - Added nightvision color: "#808080" diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml index 8f43711c98..1c61cc3f65 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml @@ -93,6 +93,7 @@ - type: Reactive groups: Acidic: [Touch] + - type: Psionic # DeltaV - Revenant is of Noospheric Origin - type: Tag tags: - SlowImmune diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 9d0adc3a4f..101baf4daf 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -125,10 +125,7 @@ - type: Speech speechVerb: LargeMob speechSounds: Xenonid # DeltaV: Use RMC's speech sounds for sentient xenos - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. - chance: -2 - type: Psionic #Nyano - Summary: makes psionic by default. - removable: false - type: NightVision #Delta-v - Added nightvision color: "#808080" diff --git a/Resources/Prototypes/Entities/Mobs/Player/familiars.yml b/Resources/Prototypes/Entities/Mobs/Player/familiars.yml index 58548c1a96..a8a2e8ca44 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/familiars.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/familiars.yml @@ -37,7 +37,6 @@ - type: Alerts - type: Familiar - type: Psionic #Nyano - Summary: Makes psionic on creation. - removable: false - type: entity name: Cerberus @@ -98,7 +97,6 @@ - type: Familiar - type: Dispellable - type: Psionic #Nyano - Summary: makes psionic on creation. - removable: false - type: Vocal sounds: Male: Cerberus diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml index 1ae214addd..50929073c6 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml @@ -82,9 +82,8 @@ components: - type: NukeOperative - type: RandomHumanoidAppearance - - type: PsionicBonusChance #Nyano - Summary: makes more likely to be psionic. - multiplier: 7 - warn: false + - type: PotentialPsionic # DeltaV - Psionic Refactor + jobBonusChance: 0.05 # Additional 5% chance to roll a psionic power. - type: entity categories: [ HideSpawnMenu ] @@ -106,9 +105,8 @@ - type: NpcFactionMember factions: - Syndicate - - type: PsionicBonusChance # DeltaV - multiplier: 7 - warn: false + - type: PotentialPsionic # DeltaV - Psionic Refactor + jobBonusChance: 0.05 # Additional 5% chance to roll a psionic power. # Wizard - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml b/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml index 4f4cf4ba23..12c5234e1d 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml @@ -648,9 +648,8 @@ - type: RandomHumanoidAppearance randomizeName: false - type: NukeOperative - - type: PsionicBonusChance #Nyano - Summary: makes more likely to be psionic. - multiplier: 7 - warn: false + - type: PotentialPsionic # DeltaV - Psionic Refactor + jobBonusChance: 0.05 # Additional 5% chance to roll a psionic power. - type: entity id: RandomHumanoidSpawnerCluwne diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 5ddb26fbe3..e00b180fa2 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -224,7 +224,6 @@ - FootstepSound - DoorBumpOpener - AnomalyHost - - type: PotentialPsionic # DeltaV - organic species can all be psionic - type: Targeting # Shitmed Change - type: SurgeryTarget # Shitmed Change - type: CosmicCenserTarget # DeltaV - Cosmic Cult @@ -302,6 +301,7 @@ alternateState: Standing #- type: StunVisuals # DeltaV - No stars - type: CanDoCPR # DeltaV - Addition of CPR + - type: PotentialPsionic # DeltaV - organic species can all be psionic - type: entity save: false diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml index 23277c3ce9..e48c62a8ff 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml @@ -44,6 +44,7 @@ ents: [] - type: Psionic # DeltaV - sentient artifacts are psionic # These components are needed for certain triggers to work. + - type: PsionicPowerDetector # DeltaV - Required for PsionicPowerUsage Trigger. - type: RadiationReceiver selfSourceMultiplier: 0.1 # DeltaV - a radiation effect triggering a radiation trigger on the same artifact is often annoying - type: Reactive @@ -64,7 +65,7 @@ useDelay: 300 - type: InstantAction event: !type:ArtifactSelfActivateEvent - + - type: entity id: SimpleXenoArtifact parent: ComplexXenoArtifact diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hardsuit-helmets.yml index 81feb2ca9b..08613c201b 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hardsuit-helmets.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hardsuit-helmets.yml @@ -11,12 +11,8 @@ sprite: _DV/Clothing/Head/Hardsuits/rd.rsi - type: PointLight color: "#d6adff" - - type: Psionic - - type: TinfoilHat - passthrough: true - destroyOnFry: false - - type: ClothingGrantPsionicPower - power: MetapsionicPower + - type: PsionicallyInsulative + allowsPsionicUsage: true - type: GuideHelp guides: - Psionics diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hats.yml b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hats.yml index ee2f5c6b75..3fa15bd3b6 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hats.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hats.yml @@ -55,7 +55,8 @@ sprite: Nyanotrasen/Clothing/Head/Hats/tinfoil.rsi - type: Clothing sprite: Nyanotrasen/Clothing/Head/Hats/tinfoil.rsi - - type: TinfoilHat + - type: PsionicallyInsulative + canBeFried: true - type: Armor modifiers: coefficients: @@ -79,7 +80,7 @@ sprite: Nyanotrasen/Clothing/Head/Hats/bellhop.rsi - type: entity - parent: ClothingHeadTinfoil + parent: ClothingHeadBase id: ClothingHeadCage name: insulative headcage suffix: SelfUnremovable @@ -92,12 +93,18 @@ equipDelay: 5 sprite: Nyanotrasen/Clothing/Head/Hats/cage.rsi - type: SelfUnremovableClothing + - type: PsionicallyInsulative + shieldsFromPsionics: false + canBeFried: true + - type: GuideHelp + guides: + - Psionics - type: Tag tags: - ForensicBeltEquip - type: entity - parent: ClothingHeadTinfoil + parent: ClothingHeadBase id: ClothingHeadHelmetInsulated name: insulative skullcap description: Psionically insulates whoevers head is inside it. It has some protection from physical damage. @@ -106,6 +113,7 @@ sprite: Nyanotrasen/Clothing/Head/Helmets/insulative_skullcap.rsi - type: Clothing sprite: Nyanotrasen/Clothing/Head/Helmets/insulative_skullcap.rsi + - type: PsionicallyInsulative - type: Armor modifiers: coefficients: diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml index 8b4e3b4f0d..e17d0d17ae 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml @@ -2,7 +2,7 @@ parent: ClothingOuterHardsuitRd id: ClothingOuterHardsuitMystagogue name: mystagogue's hardsuit - description: A heavy-duty hardsuit specialized for research that includes a built-in psionic nullification and detection system in the helmet. Modified bluespace embedded into the plating allows it to be folded into a smaller space then one would expect. + description: A heavy-duty hardsuit specialized for research that includes a built-in psionic one-way nullification system in the helmet and a detection system in the core suit. Modified bluespace embedded into the plating allows it to be folded into a smaller space than one would expect. components: - type: Sprite sprite: _DV/Clothing/OuterClothing/Hardsuits/rd.rsi @@ -10,6 +10,7 @@ sprite: _DV/Clothing/OuterClothing/Hardsuits/rd.rsi - type: ToggleableClothing clothingPrototype: ClothingHeadHelmetHardsuitMystagogue + - type: MetapsionicPulsePower # The suit gives the ability to pulse and seek psionic energies. - type: entity parent: [ClothingOuterHardsuitMystagogue, ClothingOuterStorageFoldableBaseOpened] diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/special.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/special.yml index 22b739a46c..bed7d67927 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/special.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/special.yml @@ -17,12 +17,12 @@ - type: MovementSpeedModifier baseSprintSpeed: 6 baseWalkSpeed: 4 - #- type: PsionicallyInvisible + - type: PsionicallyInvisible - type: Eye drawFov: false visMask: - Normal - - TelegnosticProjection + - PsionicInvisibility - type: Input context: "ghost" - type: TelegnosticProjection diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Medical/pills.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Medical/pills.yml index 962f535194..3aa516064b 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Medical/pills.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Medical/pills.yml @@ -11,17 +11,3 @@ reagents: - ReagentId: MindbreakerToxin Quantity: 20 - -- type: entity - name: cryptobiolin - parent: Pill - id: PillCryptobiolin - description: A long-lasting drug which causes mild confusion, but renders you psionically insulated. - components: - - type: SolutionContainerManager - solutions: - food: - maxVol: 20 - reagents: - - ReagentId: Cryptobiolin - Quantity: 20 diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Melee/knives.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Melee/knives.yml index 7ba4910d8d..9c5b2bf590 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Melee/knives.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Melee/knives.yml @@ -1,24 +1,19 @@ - type: entity parent: CombatKnife id: AntiPsychicKnife - name: anti-psychic knife - description: A special knife designed for killing psychics. + name: anti-psionic knife + description: A special knife designed for killing psionics. 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 + Slash: 2 + Holy: 2 - type: Sprite sprite: Nyanotrasen/Objects/Weapons/Melee/anti_psychic_knife.rsi state: icon diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml index 908658acaa..2d66afc981 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml @@ -24,7 +24,6 @@ channels: - Common - Science - - type: PotentialPsionic #this makes her easier to access for glimmer events, dw about it - type: Psionic - type: Grammar attributes: diff --git a/Resources/Prototypes/Nyanotrasen/Reagents/psionic.yml b/Resources/Prototypes/Nyanotrasen/Reagents/psionic.yml index 42d1e7cc3a..41197742c4 100644 --- a/Resources/Prototypes/Nyanotrasen/Reagents/psionic.yml +++ b/Resources/Prototypes/Nyanotrasen/Reagents/psionic.yml @@ -62,35 +62,6 @@ reagent: SoulbreakerToxin min: 5 -- type: reagent - id: LotophagoiOil - name: reagent-name-lotophagoi-oil - group: Narcotics - desc: reagent-desc-lotophagoi-oil - flavor: enthralling - color: "#FFBF00" - physicalDesc: reagent-physical-desc-overpowering - metabolisms: - Narcotic: - metabolismRate: 0.2 - effects: - - !type:ModifyStatusEffect - effectProto: StatusEffectSeeingRainbow - time: 5 - type: Add - - !type:RerollPsionicAbilities - bonusMultiplier: 4 - conditions: - - !type:ReagentCondition - reagent: LotophagoiOil - min: 20 - - !type:ModifyStatusEffect - effectProto: StatusEffectScrambled - type: Update - - !type:GenericStatusEffect - key: RepeatingTelepath - component: TelepathicRepeater - - type: reagent id: Ectoplasm name: reagent-name-ectoplasm diff --git a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml index dfc699bb16..6dbde7fd0e 100644 --- a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml +++ b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml @@ -22,8 +22,7 @@ special: - !type:AddComponentSpecial components: - - type: Psionic - - type: MetapsionicPower + - type: MetapsionicPulsePower - type: startingGear id: ForensicMantisGear diff --git a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml deleted file mode 100644 index c01c99520b..0000000000 --- a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml +++ /dev/null @@ -1,15 +0,0 @@ -- type: weightedRandom - id: RandomPsionicPowerPool - weights: - MetapsionicPower: 1 - DispelPower: 1 - TelegnosisPower: 1 - PsionicRegenerationPower: 1 - PrecognitionPower: 1 # DeltaV - PsychokineticScreamPower: 1 - MassSleepPower: 0.3 - NoosphericZapPower: 0.3 - FracturedFormPower: 0.3 -# PsionicInvisibilityPower: 0.15 - MindSwapPower: 0.15 - PsionicEruptionPower: 0.1 diff --git a/Resources/Prototypes/Nyanotrasen/status_effects.yml b/Resources/Prototypes/Nyanotrasen/status_effects.yml deleted file mode 100644 index 322fcc9988..0000000000 --- a/Resources/Prototypes/Nyanotrasen/status_effects.yml +++ /dev/null @@ -1,5 +0,0 @@ -- type: statusEffect - id: PsionicsDisabled - -- type: statusEffect - id: PsionicallyInsulated diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml index 78760c202a..9742cd24bd 100644 --- a/Resources/Prototypes/Reagents/medicine.yml +++ b/Resources/Prototypes/Reagents/medicine.yml @@ -10,7 +10,6 @@ color: "#081a80" metabolisms: Medicine: - metabolismRate: 0.1 effects: - !type:ModifyStatusEffect effectProto: StatusEffectScrambled @@ -18,14 +17,6 @@ - !type:ModifyStatusEffect effectProto: StatusEffectWoozy time: 10.0 # DeltaV - 20 is ridiculous - - !type:GenericStatusEffect # DeltaV - key: PsionicsDisabled - component: PsionicsDisabled - type: Add - - !type:GenericStatusEffect # DeltaV - key: PsionicallyInsulated - component: PsionicInsulation - type: Add - type: reagent id: Dylovene diff --git a/Resources/Prototypes/Reagents/narcotics.yml b/Resources/Prototypes/Reagents/narcotics.yml index 106a3025fe..e28fd79334 100644 --- a/Resources/Prototypes/Reagents/narcotics.yml +++ b/Resources/Prototypes/Reagents/narcotics.yml @@ -332,7 +332,7 @@ type: Add time: 5 # DeltaV Additions - - !type:RerollPsionicAbilities # DeltaV - Roll for psionics + - !type:RollPsionicAbility # DeltaV - Roll for psionics conditions: - !type:ReagentCondition reagent: SpaceDrugs diff --git a/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml b/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml index 4cee1fe9be..09e97e5482 100644 --- a/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml +++ b/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml @@ -38,6 +38,10 @@ - !type:AddComponentSpecial components: - type: CommandStaff + # Start DeltaV Additions + - !type:ModifyPsionicChanceSpecial # Command are more likely to be psionic. + jobBonusChance: 0.05 # Additional 5% chance. + # End DeltaV Additions - type: startingGear id: QuartermasterGear diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml b/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml index 156f348319..bc37d8c00b 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml @@ -17,8 +17,8 @@ - type: BibleUser #Lets them heal with bibles - type: Condemned soulOwnedNotDevil: true # This just means they won't suffer the effects of being condemned, while still not being able to sell their soul. - Goobstation - - type: PsionicBonusChance # DeltaV - makes it more likely to become psionic. - multiplier: 3 + - !type:ModifyPsionicChanceSpecial # DeltaV - Chaplains are much more likely to be psionic. + jobBonusChance: 0.15 # Additional 15% chance. - type: startingGear id: ChaplainGear diff --git a/Resources/Prototypes/Roles/Jobs/Command/captain.yml b/Resources/Prototypes/Roles/Jobs/Command/captain.yml index 23ef3f418e..d1c4570bc2 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/captain.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/captain.yml @@ -35,10 +35,10 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - type: PsionicBonusChance # DeltaV - makes it more likely to become psionic. - flatBonus: 0.025 - type: Condemned # Goobstation - Nanotrasen owns your soul pal. soulOwnedNotDevil: true + - !type:ModifyPsionicChanceSpecial # DeltaV - Command Roles are more likely to be psionic. + jobBonusChance: 0.05 # Additional 5% chance. - type: startingGear id: CaptainGear diff --git a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml index 192d42cc7e..580b9706eb 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml @@ -69,8 +69,8 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - type: PsionicBonusChance # DeltaV: makes it more likely to become psionic. - flatBonus: 0.025 + - !type:ModifyPsionicChanceSpecial # DeltaV - Command Roles are more likely to be psionic. + jobBonusChance: 0.05 # Additional 5% chance. - type: startingGear id: HoPGear diff --git a/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml b/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml index 03391edd34..2291221d05 100644 --- a/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml +++ b/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml @@ -34,8 +34,8 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - type: PsionicBonusChance # DeltaV - makes it more likely to become psionic. - flatBonus: 0.025 + - !type:ModifyPsionicChanceSpecial # DeltaV - Command Roles are more likely to be psionic. + jobBonusChance: 0.05 # Additional 5% chance. - type: startingGear id: ChiefEngineerGear diff --git a/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml b/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml index bf54d8f8f2..f9d63d48ea 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml @@ -42,8 +42,8 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - type: PsionicBonusChance # DeltaV - makes it more likely to become psionic. - flatBonus: 0.025 + - !type:ModifyPsionicChanceSpecial # DeltaV - Command Roles are more likely to be psionic. + jobBonusChance: 0.05 # Additional 5% chance. - type: startingGear id: CMOGear diff --git a/Resources/Prototypes/Roles/Jobs/Science/scientist.yml b/Resources/Prototypes/Roles/Jobs/Science/scientist.yml index 8835909ccc..09e4c87342 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/scientist.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/scientist.yml @@ -13,8 +13,13 @@ access: - Research - Maintenance - extendedAccess: # DeltaV - Scientists get robotics access on lowpop + # Begin DeltaV Additions + extendedAccess: - Robotics + special: + - !type:ModifyPsionicChanceSpecial # Scientists are much more likely to be psionic. + jobBonusChance: 0.10 # Additional 10% chance. + # End DeltaV Additions - type: startingGear id: ScientistGear diff --git a/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml b/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml index b031f77456..68fb8b425c 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml @@ -42,8 +42,8 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - type: PsionicBonusChance # DeltaV - makes it more likely to become psionic. - flatBonus: 0.025 + - !type:ModifyPsionicChanceSpecial # DeltaV - Command Roles are more likely to be psionic. + jobBonusChance: 0.05 # Additional 5% chance. - type: startingGear id: HoSGear diff --git a/Resources/Prototypes/XenoArch/triggers.yml b/Resources/Prototypes/XenoArch/triggers.yml index 9c8886ffe6..d82396fc7b 100644 --- a/Resources/Prototypes/XenoArch/triggers.yml +++ b/Resources/Prototypes/XenoArch/triggers.yml @@ -1,7 +1,7 @@ - type: weightedRandomXenoArchTrigger id: DefaultTriggers weights: - TriggerMetapsionic: 1 # DeltaV + TriggerPsionicUsage: 1 # DeltaV TriggerMusic: 1 # DeltaV - make less frequent #TriggerHeat: 1 diff --git a/Resources/Prototypes/_DV/Actions/psionic.yml b/Resources/Prototypes/_DV/Actions/psionic.yml index 29e14f3617..5bd9fed95a 100644 --- a/Resources/Prototypes/_DV/Actions/psionic.yml +++ b/Resources/Prototypes/_DV/Actions/psionic.yml @@ -5,6 +5,7 @@ components: - type: Action itemIconStyle: BigAction + startDelay: true - type: entity parent: BasePsionicAction @@ -71,7 +72,7 @@ useDelay: 20 checkCanInteract: false - type: InstantAction - event: !type:MindSwapPowerReturnActionEvent + event: !type:MindSwappedReturnPowerActionEvent - type: entity parent: BasePsionicAction @@ -109,9 +110,9 @@ - type: entity parent: BasePsionicAction - id: ActionMetapsionic + id: ActionMetapsionicPulse name: Metapsionic Pulse - description: Send a mental pulse through the area to see if there are any psychics nearby. + description: Send a mental pulse through a specified area to see if there are any psychics. components: - type: Action icon: @@ -119,8 +120,11 @@ state: metapsionic useDelay: 45 checkCanInteract: false - - type: InstantAction - event: !type:MetapsionicPowerActionEvent + - type: TargetAction + checkCanAccess: false + range: -1 # You can cast it anywhere on your screen. + - type: WorldTargetAction + event: !type:MetapsionicPulsePowerActionEvent - type: entity parent: BasePsionicAction @@ -182,20 +186,6 @@ - type: InstantAction event: !type:PsionicInvisibilityPowerActionEvent -- type: entity - parent: BasePsionicAction - id: ActionPsionicInvisibilityUsed - name: Turn Off Psionic Invisibility - description: Return to visibility, and receive a stun. - components: - - type: Action - icon: - sprite: _DV/Interface/Actions/actions_psionics.rsi - state: invisibility_off - checkCanInteract: false - - type: InstantAction - event: !type:RemovePsionicInvisibilityOffPowerActionEvent - - type: entity parent: BasePsionicAction id: ActionPrecognition @@ -213,6 +203,7 @@ event: !type:PrecognitionPowerActionEvent - type: entity + parent: BasePsionicAction id: ActionPsychokineticScream name: Psychokinetic Scream description: Emit a blood-curdling scream that shatters all lights in the area. @@ -221,9 +212,10 @@ icon: { sprite: _DV/Actions/shatterlights.rsi, state: shatter-lights } useDelay: 60 - type: InstantAction - event: !type:ShatterLightsActionEvent + event: !type:PsychokineticScreamPowerActionEvent - type: entity + parent: BasePsionicAction id: ActionFracturedForm name: Swap Form description: Switch to one of your alternate forms, if you have one. This can also be done by sleeping. diff --git a/Resources/Prototypes/_DV/Actions/skia.yml b/Resources/Prototypes/_DV/Actions/skia.yml index 06fdd429fe..8406681e9f 100644 --- a/Resources/Prototypes/_DV/Actions/skia.yml +++ b/Resources/Prototypes/_DV/Actions/skia.yml @@ -1,18 +1,17 @@ - type: entity - id: ActionShatterLights - name: Shatter Lights - description: Emit a blood-curdling scream that shatters all lights in the area. + parent: ActionPsychokineticScream + id: ActionPsychokineticScreamSkia # This one has a smaller cooldown components: - type: Action icon: { sprite: _DV/Actions/shatterlights.rsi, state: shatter-lights } useDelay: 30 - type: InstantAction - event: !type:ShatterLightsActionEvent + event: !type:PsychokineticScreamPowerActionEvent - type: entity id: ActionTechnokineticPulse name: Technokinetic Pulse - description: Unleash a burst of Technokinetic energy, disabling and destroying nearby electronics. + description: Unleash a burst of Technokinetic energy, disabling or destroying nearby electronics. components: - type: Action icon: { sprite: _DV/Actions/technokineticpulse.rsi, state: technokinetic-pulse } diff --git a/Resources/Prototypes/_DV/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/_DV/Entities/Clothing/Head/hardsuit-helmets.yml index c99ae61647..042326443c 100644 --- a/Resources/Prototypes/_DV/Entities/Clothing/Head/hardsuit-helmets.yml +++ b/Resources/Prototypes/_DV/Entities/Clothing/Head/hardsuit-helmets.yml @@ -163,8 +163,7 @@ heldPrefix: off - type: PointLight color: red - - type: TinfoilHat - destroyOnFry: false + - type: PsionicallyInsulative - type: GuideHelp guides: - Psionics @@ -205,11 +204,9 @@ - type: PointLight color: "#ffffff" - type: Psionic - - type: TinfoilHat - passthrough: true - destroyOnFry: false - - type: ClothingGrantPsionicPower - power: DispelPower + - type: PsionicallyInsulative + allowsPsionicUsage: true + - type: DispelPower - type: GuideHelp guides: - Psionics diff --git a/Resources/Prototypes/_DV/Entities/Clothing/Psionic/psionic_clothing.yml b/Resources/Prototypes/_DV/Entities/Clothing/Psionic/psionic_clothing.yml index a0336593ae..c2fa3f7a62 100644 --- a/Resources/Prototypes/_DV/Entities/Clothing/Psionic/psionic_clothing.yml +++ b/Resources/Prototypes/_DV/Entities/Clothing/Psionic/psionic_clothing.yml @@ -21,8 +21,7 @@ - type: Clothing sprite: Clothing/Eyes/Glasses/glasses.rsi - type: VisionCorrection - - type: ClothingGrantPsionicPower - power: TelegnosisPower + - type: TelegnosisPower - type: entity id: BedsheetInvisibilityCloak @@ -36,8 +35,7 @@ state: sheetblack - type: Clothing sprite: Clothing/Neck/Bedsheets/black.rsi - - type: ClothingGrantPsionicPower - power: PsionicInvisibilityPower + - type: PsionicInvisibilityPower - type: entity parent: @@ -55,8 +53,7 @@ fiberMaterial: fibers-leather fiberColor: fibers-brown - type: FingerprintMask - - type: ClothingGrantPsionicPower - power: DispelPower + - type: DispelPower - type: entity parent: @@ -74,5 +71,4 @@ fiberMaterial: fibers-insulative fiberColor: fibers-yellow - type: FingerprintMask - - type: ClothingGrantPsionicPower - power: NoosphericZapPower + - type: NoosphericZapPower diff --git a/Resources/Prototypes/_DV/Entities/Effects/psionic.yml b/Resources/Prototypes/_DV/Entities/Effects/psionic.yml new file mode 100644 index 0000000000..31be9d0e67 --- /dev/null +++ b/Resources/Prototypes/_DV/Entities/Effects/psionic.yml @@ -0,0 +1,29 @@ +# We need to copy paste the entire thing because we don't want Electrified component on that thang. +- type: entity + name: lightning + id: PsionicLightning + categories: [ HideSpawnMenu ] + components: + - type: Sprite + sprite: /Textures/Effects/lightning.rsi + drawdepth: Effects + layers: + - state: "lightning_1" + shader: unshaded + - type: Physics + canCollide: false + - type: Lightning + - type: PointLight + enabled: true + color: "#4080FF" + radius: 3.5 + softness: 1 + autoRot: true + castShadows: false + - type: Beam + sound: /Audio/Effects/Lightning/lightningshock.ogg + - type: TimedDespawn + lifetime: 3 + - type: Tag + tags: + - HideContextMenu diff --git a/Resources/Prototypes/_DV/Entities/Effects/puddles.yml b/Resources/Prototypes/_DV/Entities/Effects/puddles.yml index 1a34855d46..5ecfa546c1 100644 --- a/Resources/Prototypes/_DV/Entities/Effects/puddles.yml +++ b/Resources/Prototypes/_DV/Entities/Effects/puddles.yml @@ -176,6 +176,8 @@ - ToxinTrash - Nausium - LotophagoiOil + - Ambrosia + - Nectar - type: entity parent: MarkerBase diff --git a/Resources/Prototypes/_DV/Entities/Mobs/NPCs/familiars.yml b/Resources/Prototypes/_DV/Entities/Mobs/NPCs/familiars.yml index 5040ba22e3..7d23d16cea 100644 --- a/Resources/Prototypes/_DV/Entities/Mobs/NPCs/familiars.yml +++ b/Resources/Prototypes/_DV/Entities/Mobs/NPCs/familiars.yml @@ -88,10 +88,9 @@ - type: ComplexInteraction - type: RandomMetadata nameSegments: [NamesGolem] - - type: PotentialPsionic - type: Psionic - removable: false - type: PyrokinesisPower + canBeRemoved: false - type: Grammar attributes: proper: true diff --git a/Resources/Prototypes/_DV/Entities/Mobs/NPCs/fun.yml b/Resources/Prototypes/_DV/Entities/Mobs/NPCs/fun.yml index 07d534c139..f515ba09a7 100644 --- a/Resources/Prototypes/_DV/Entities/Mobs/NPCs/fun.yml +++ b/Resources/Prototypes/_DV/Entities/Mobs/NPCs/fun.yml @@ -58,8 +58,8 @@ - type: MindExaminable - type: PotentialPsionic - type: Psionic - removable: false - type: PyrokinesisPower + canBeRemoved: false - type: NpcFactionMember factions: - Passive diff --git a/Resources/Prototypes/_DV/Entities/Mobs/NPCs/glimmer_creatures.yml b/Resources/Prototypes/_DV/Entities/Mobs/NPCs/glimmer_creatures.yml index 8a2dd6b6bf..2b04099286 100644 --- a/Resources/Prototypes/_DV/Entities/Mobs/NPCs/glimmer_creatures.yml +++ b/Resources/Prototypes/_DV/Entities/Mobs/NPCs/glimmer_creatures.yml @@ -21,7 +21,6 @@ reagents: - ReagentId: Ectoplasm Quantity: 15 - - type: PotentialPsionic - type: Psionic - type: GlimmerSource - type: AmbientSound @@ -72,10 +71,9 @@ - type: MovementIgnoreGravity - type: Speech # powers - - type: PotentialPsionic - type: Psionic - removable: false - type: NoosphericZapPower + canBeRemoved: false - type: LifeDrainer damage: types: diff --git a/Resources/Prototypes/_DV/Entities/Mobs/NPCs/skia.yml b/Resources/Prototypes/_DV/Entities/Mobs/NPCs/skia.yml index 7bd86ae3ab..7ef1c710f6 100644 --- a/Resources/Prototypes/_DV/Entities/Mobs/NPCs/skia.yml +++ b/Resources/Prototypes/_DV/Entities/Mobs/NPCs/skia.yml @@ -102,10 +102,11 @@ resurrectDesc: skia-resurrecting-desc - type: Bloodstream maxBleedAmount: 0 - - type: ShatterLightsAbility - lineOfSight: true + - type: PsychokineticScreamPower radius: 7.0 penetratingRadius: 2.0 + actionProtoId: ActionPsychokineticScreamSkia + canBeRemoved: false - type: TechnokineticPulseAbility range: 5.0 energyConsumption: 20000 diff --git a/Resources/Prototypes/_DV/Entities/Mobs/Player/kitsune.yml b/Resources/Prototypes/_DV/Entities/Mobs/Player/kitsune.yml index a848cf217a..3929c4626b 100644 --- a/Resources/Prototypes/_DV/Entities/Mobs/Player/kitsune.yml +++ b/Resources/Prototypes/_DV/Entities/Mobs/Player/kitsune.yml @@ -13,3 +13,5 @@ damage: types: Heat: 5 + - type: PotentialPsionic + speciesBonusChance: 0.05 # Additional 5% chance to roll a psionic power. diff --git a/Resources/Prototypes/_DV/Entities/Mobs/Species/kitsune.yml b/Resources/Prototypes/_DV/Entities/Mobs/Species/kitsune.yml index 3d71dcffc5..8255939a30 100644 --- a/Resources/Prototypes/_DV/Entities/Mobs/Species/kitsune.yml +++ b/Resources/Prototypes/_DV/Entities/Mobs/Species/kitsune.yml @@ -37,8 +37,6 @@ interactSuccessSound: path: /Audio/Animals/fox_squeak.ogg - type: Kitsune - - type: PsionicBonusChance - flatBonus: 0.5 - type: LightweightDrunk boozeStrengthMultiplier: 3 - type: PseudoItem diff --git a/Resources/Prototypes/_DV/Entities/Objects/Specific/Medical/pills.yml b/Resources/Prototypes/_DV/Entities/Objects/Specific/Medical/pills.yml new file mode 100644 index 0000000000..6d9adb204f --- /dev/null +++ b/Resources/Prototypes/_DV/Entities/Objects/Specific/Medical/pills.yml @@ -0,0 +1,13 @@ +- type: entity + name: sanctus sal + parent: Pill + id: PillSanctusSal + description: A long-lasting drug which prevents and shields from psionic usage. + components: + - type: SolutionContainerManager + solutions: + food: + maxVol: 20 + reagents: + - ReagentId: SanctusSal + Quantity: 20 diff --git a/Resources/Prototypes/_DV/Entities/StatusEffects/movement.yml b/Resources/Prototypes/_DV/Entities/StatusEffects/movement.yml deleted file mode 100644 index 5718d99b8a..0000000000 --- a/Resources/Prototypes/_DV/Entities/StatusEffects/movement.yml +++ /dev/null @@ -1,4 +0,0 @@ -- type: entity - parent: StatusEffectSlowdown - id: PsionicSlowdownStatusEffect - name: affected by slowdown from your powers diff --git a/Resources/Prototypes/_DV/Entities/StatusEffects/psionics.yml b/Resources/Prototypes/_DV/Entities/StatusEffects/psionics.yml new file mode 100644 index 0000000000..eb003d1a3d --- /dev/null +++ b/Resources/Prototypes/_DV/Entities/StatusEffects/psionics.yml @@ -0,0 +1,35 @@ +# Prevents the affected entity from using psionic powers. +- type: entity + parent: MobStatusEffectDebuff + id: StatusEffectPsionicsDisabled + name: psionics disabled + components: + - type: PsionicsDisabled + +# Shields the affected entity from external psionic powers. +- type: entity + parent: MobStatusEffectBase + id: StatusEffectShieldedFromPsionics + name: shielded from psionics + components: + - type: ShieldedFromPsionics + +# Causes the entity to repeat what is said in telepathic chat. +- type: entity + parent: MobStatusEffectDebuff + id: StatusEffectTelepathicRepeater + name: telepathic repeater + components: + - type: TelepathicRepeater + +# Slows down the user when using the precognition ability. +- type: entity + parent: [ StatusEffectSlowdown, MobStatusEffectDebuff ] + id: PrecognitionSlowdownStatusEffect + name: slowed by precognition power + +# Slows down the user when using the mass sleep ability. +- type: entity + parent: [ StatusEffectSlowdown, MobStatusEffectDebuff ] + id: MassSleepSlowdownStatusEffect + name: slowed by mass sleep power diff --git a/Resources/Prototypes/_DV/GameRules/glimmer_events.yml b/Resources/Prototypes/_DV/GameRules/glimmer_events.yml index 8120eb75e2..10c49da5ac 100644 --- a/Resources/Prototypes/_DV/GameRules/glimmer_events.yml +++ b/Resources/Prototypes/_DV/GameRules/glimmer_events.yml @@ -5,7 +5,7 @@ - id: MundaneDischarge - id: NoosphericZap - id: NoosphericFry - - id: PsionicCatGotYourTongue + - id: NoosphericSilence - id: MassMindSwap - id: GlimmerWispSpawn - id: FreeProber @@ -71,7 +71,7 @@ - type: NoosphericFryRule - type: entity - id: PsionicCatGotYourTongue + id: NoosphericSilence parent: BaseGlimmerEvent components: - type: GlimmerEvent @@ -79,7 +79,7 @@ maximumGlimmer: 900 glimmerBurnLower: 18 glimmerBurnUpper: 40 - - type: PsionicCatGotYourTongueRule + - type: NoosphericSilenceRule - type: entity id: MassMindSwap diff --git a/Resources/Prototypes/_DV/Psionics/psionicPowers.yml b/Resources/Prototypes/_DV/Psionics/psionicPowers.yml new file mode 100644 index 0000000000..98474df482 --- /dev/null +++ b/Resources/Prototypes/_DV/Psionics/psionicPowers.yml @@ -0,0 +1,94 @@ +# Table +- type: entityTable + id: PsionicPowerTable + table: !type:GroupSelector + children: + - id: DispelPowerEntity + weight: 1 + - id: PrecognitionPowerEntity + weight: 1 + - id: PsionicRegenerationPowerEntity + weight: 1 + - id: PsychokineticScreamPowerEntity + weight: 1 + - id: TelegnosisPowerEntity + weight: 1 + - id: FracturedFormPowerEntity + weight: 0.3 + - id: MassSleepPowerEntity + weight: 0.3 + - id: MetapsionicPulsePowerEntity + weight: 0.5 + - id: NoosphericZapPowerEntity + weight: 0.3 + - id: MindSwapPowerEntity + weight: 0.15 + - id: PsionicEruptionPowerEntity + weight: 0.1 + +# Powers +- type: entity + id: DispelPowerEntity + categories: [ HideSpawnMenu ] + components: + - type: DispelPower + +- type: entity + id: MetapsionicPulsePowerEntity + categories: [ HideSpawnMenu ] + components: + - type: MetapsionicPulsePower + +- type: entity + id: PsionicEruptionPowerEntity + categories: [ HideSpawnMenu ] + components: + - type: PsionicEruptionPower + +- type: entity + id: FracturedFormPowerEntity + categories: [ HideSpawnMenu ] + components: + - type: FracturedFormPower + +- type: entity + id: PsychokineticScreamPowerEntity + categories: [ HideSpawnMenu ] + components: + - type: PsychokineticScreamPower + +- type: entity + id: PrecognitionPowerEntity + categories: [ HideSpawnMenu ] + components: + - type: PrecognitionPower + +- type: entity + id: MassSleepPowerEntity + categories: [ HideSpawnMenu ] + components: + - type: MassSleepPower + +- type: entity + id: NoosphericZapPowerEntity + categories: [ HideSpawnMenu ] + components: + - type: NoosphericZapPower + +- type: entity + id: MindSwapPowerEntity + categories: [ HideSpawnMenu ] + components: + - type: MindSwapPower + +- type: entity + id: PsionicRegenerationPowerEntity + categories: [ HideSpawnMenu ] + components: + - type: PsionicRegenerationPower + +- type: entity + id: TelegnosisPowerEntity + categories: [ HideSpawnMenu ] + components: + - type: TelegnosisPower diff --git a/Resources/Prototypes/_DV/Reagents/biological.yml b/Resources/Prototypes/_DV/Reagents/biological.yml index b45bf8a435..a693a9804d 100644 --- a/Resources/Prototypes/_DV/Reagents/biological.yml +++ b/Resources/Prototypes/_DV/Reagents/biological.yml @@ -3,3 +3,39 @@ parent: GreyMatter name: reagent-name-sentient-grey-matter desc: reagent-desc-sentient-grey-matter + +- type: reagent + id: Nectar + name: reagent-name-nectar + group: Biological + desc: reagent-desc-nectar + flavor: enthralling + color: "#FFBF00" + physicalDesc: reagent-physical-desc-refreshing + metabolisms: + Drink: + effects: + - !type:ModifyStatusEffect + effectProto: StatusEffectShieldedFromPsionics + type: Add + time: 20 + - !type:SatiateThirst + factor: 10 # A drink for the Gods after all. + +- type: reagent + id: Ambrosia + name: reagent-name-ambrosia + group: Biological + desc: reagent-desc-ambrosia + flavor: enthralling + color: "#FFBF00" + physicalDesc: reagent-physical-desc-thick + metabolisms: + Narcotic: + effects: + - !type:Jitter + - !type:MovementSpeedModifier + walkSpeedModifier: 1.3 + sprintSpeedModifier: 1.3 + - !type:SatiateHunger + factor: 10 # A food for the Gods after all. diff --git a/Resources/Prototypes/_DV/Reagents/medicine.yml b/Resources/Prototypes/_DV/Reagents/medicine.yml index 56f69c365c..216a33b036 100644 --- a/Resources/Prototypes/_DV/Reagents/medicine.yml +++ b/Resources/Prototypes/_DV/Reagents/medicine.yml @@ -129,3 +129,24 @@ - !type:TemperatureCondition min: 293.15 amount: -10000 + +- type: reagent + id: SanctusSal + name: reagent-name-sanctussal + group: Medicine + desc: reagent-desc-sanctussal + physicalDesc: reagent-physical-desc-salty + flavor: medicine + color: "#91C3F7" + metabolisms: + Medicine: + metabolismRate: 0.5 + effects: + - !type:ModifyStatusEffect + effectProto: StatusEffectShieldedFromPsionics + type: Add + time: 60 + - !type:ModifyStatusEffect + effectProto: StatusEffectPsionicsDisabled + type: Add + time: 60 diff --git a/Resources/Prototypes/_DV/Reagents/narcotics.yml b/Resources/Prototypes/_DV/Reagents/narcotics.yml new file mode 100644 index 0000000000..4f88ab9f3c --- /dev/null +++ b/Resources/Prototypes/_DV/Reagents/narcotics.yml @@ -0,0 +1,28 @@ +- type: reagent + id: LotophagoiOil + name: reagent-name-lotophagoi-oil + group: Narcotics + desc: reagent-desc-lotophagoi-oil + flavor: enthralling + color: "#FFBF00" + physicalDesc: reagent-physical-desc-overpowering + metabolisms: + Narcotic: + metabolismRate: 0.2 + effects: + - !type:ModifyStatusEffect + effectProto: StatusEffectSeeingRainbow + type: Add + time: 5 + - !type:ModifyStatusEffect + effectProto: StatusEffectScrambled + type: Update + - !type:ModifyStatusEffect + effectProto: StatusEffectTelepathicRepeater + type: Add + - !type:RollPsionicAbility + bonusMultiplier: 4 + conditions: + - !type:ReagentCondition + reagent: LotophagoiOil + min: 20 diff --git a/Resources/Prototypes/_DV/Reagents/toxins.yml b/Resources/Prototypes/_DV/Reagents/toxins.yml new file mode 100644 index 0000000000..969dfde5e4 --- /dev/null +++ b/Resources/Prototypes/_DV/Reagents/toxins.yml @@ -0,0 +1,14 @@ +- type: reagent + id: Lernaea + name: reagent-name-lernaea + group: Toxins + desc: reagent-desc-lernaea + color: "#2E3E64" + physicalDesc: reagent-physical-desc-sickly + metabolisms: + Poison: + effects: + - !type:ModifyStatusEffect + effectProto: StatusEffectPsionicsDisabled + type: Add + time: 5 diff --git a/Resources/Prototypes/_DV/Recipes/Reactions/medicine.yml b/Resources/Prototypes/_DV/Recipes/Reactions/medicine.yml index 7926f41977..0129bf909d 100644 --- a/Resources/Prototypes/_DV/Recipes/Reactions/medicine.yml +++ b/Resources/Prototypes/_DV/Recipes/Reactions/medicine.yml @@ -71,3 +71,13 @@ amount: 1 products: Photoxadone: 3 + +- type: reaction + id: SanctusSal + reactants: + Holywater: + amount: 4 + TableSalt: + amount: 1 + products: + SanctusSal: 5 diff --git a/Resources/Prototypes/_DV/Roles/Jobs/Justice/chief_justice.yml b/Resources/Prototypes/_DV/Roles/Jobs/Justice/chief_justice.yml index d8ca590424..b831289f9b 100644 --- a/Resources/Prototypes/_DV/Roles/Jobs/Justice/chief_justice.yml +++ b/Resources/Prototypes/_DV/Roles/Jobs/Justice/chief_justice.yml @@ -38,10 +38,8 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - !type:AddComponentSpecial - components: - - type: PsionicBonusChance #Nyano - Summary: makes it more likely to become psionic. - flatBonus: 0.025 + - !type:ModifyPsionicChanceSpecial # Command Roles are more likely to be psionic. + jobBonusChance: 0.05 # Additional 5% chance. - type: startingGear id: CJGear diff --git a/Resources/Prototypes/_DV/XenoArch/effects.yml b/Resources/Prototypes/_DV/XenoArch/effects.yml index 742596bb8c..d407b2618c 100644 --- a/Resources/Prototypes/_DV/XenoArch/effects.yml +++ b/Resources/Prototypes/_DV/XenoArch/effects.yml @@ -20,7 +20,7 @@ - type: XAEDetails specificTip: xenoarch-effect-tip-specific-glimmer vagueTip: xenoarch-effect-tip-vague-energy-manipulation - - type: GlimmerArtifact + - type: XAEModifyGlimmer range: min: 0 max: 450 # cant instantly get ended by a looping artifact @@ -31,7 +31,7 @@ id: XenoArtifactRaiseGlimmerLarge categories: [ HideSpawnMenu ] components: - - type: GlimmerArtifact + - type: XAEModifyGlimmer range: min: 0 max: 700 # cant get to 1000 from a looping artifact @@ -42,7 +42,7 @@ id: XenoArtifactLowerGlimmer categories: [ HideSpawnMenu ] components: - - type: GlimmerArtifact + - type: XAEModifyGlimmer range: min: 400 # for recovering from serious glimmer emergencies, not a full drain replacement max: 1000 @@ -56,5 +56,5 @@ - type: XAEDetails specificTip: xenoarch-effect-tip-specific-psionic vagueTip: xenoarch-effect-tip-vague-energy-manipulation - - type: PsionicProducingArtifact + - type: XAEPsionicInducer range: 5 diff --git a/Resources/Prototypes/_DV/XenoArch/triggers.yml b/Resources/Prototypes/_DV/XenoArch/triggers.yml index e286c74a22..293ec9150d 100644 --- a/Resources/Prototypes/_DV/XenoArch/triggers.yml +++ b/Resources/Prototypes/_DV/XenoArch/triggers.yml @@ -1,8 +1,7 @@ - type: xenoArchTrigger - id: TriggerMetapsionic + id: TriggerPsionicUsage tip: xenoarch-trigger-tip-psionic components: - - type: ArtifactMetapsionicTrigger - - type: MetapsionicPower # so the trigger receives the event from people using psionics + - type: XATPsionicUsage # TODO: when golemization is ported, add a trigger for golemizing someone, target 7 or something