diff --git a/Content.Client/_DV/Abilities/Psionics/EruptionWarning.xaml b/Content.Client/_DV/Abilities/Psionics/EruptionWarning.xaml
new file mode 100644
index 0000000000..88f053a3d0
--- /dev/null
+++ b/Content.Client/_DV/Abilities/Psionics/EruptionWarning.xaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_DV/Abilities/Psionics/EruptionWarning.xaml.cs b/Content.Client/_DV/Abilities/Psionics/EruptionWarning.xaml.cs
new file mode 100644
index 0000000000..969e863c48
--- /dev/null
+++ b/Content.Client/_DV/Abilities/Psionics/EruptionWarning.xaml.cs
@@ -0,0 +1,16 @@
+using Content.Client.UserInterface.Controls;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._DV.Abilities.Psionics;
+
+[GenerateTypedNameReferences]
+public sealed partial class EruptionWarning : FancyWindow
+{
+ public EruptionWarning()
+ {
+ RobustXamlLoader.Load(this);
+
+ ConfirmButton.OnPressed += _ => Close();
+ }
+}
diff --git a/Content.Client/_DV/Psionics/UI/EruptionWarningEUI.cs b/Content.Client/_DV/Psionics/UI/EruptionWarningEUI.cs
new file mode 100644
index 0000000000..99badd0126
--- /dev/null
+++ b/Content.Client/_DV/Psionics/UI/EruptionWarningEUI.cs
@@ -0,0 +1,29 @@
+using Content.Client._DV.Abilities.Psionics;
+using Content.Client.Eui;
+using JetBrains.Annotations;
+
+namespace Content.Client._DV.Psionics.UI;
+
+[UsedImplicitly]
+public sealed class EruptionWarningEui : BaseEui
+{
+ private readonly EruptionWarning _window;
+
+ public EruptionWarningEui()
+ {
+ _window = new EruptionWarning();
+ }
+
+ public override void Opened()
+ {
+ _window.OpenCentered();
+ }
+
+ public override void Closed()
+ {
+ base.Closed();
+
+ _window.Close();
+ }
+
+}
diff --git a/Content.Server/_DV/Abilities/Psionics/PsionicEruption/PsionicEruptionComponent.cs b/Content.Server/_DV/Abilities/Psionics/PsionicEruption/PsionicEruptionComponent.cs
new file mode 100644
index 0000000000..c5b5ea0f78
--- /dev/null
+++ b/Content.Server/_DV/Abilities/Psionics/PsionicEruption/PsionicEruptionComponent.cs
@@ -0,0 +1,28 @@
+using Robust.Shared.Audio;
+using Content.Shared.Actions;
+using Content.Shared.DoAfter;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Shared._DV.Abilities.Psionics;
+
+[RegisterComponent]
+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]
+ public TimeSpan NextAnnoy = TimeSpan.FromSeconds(5);
+ [DataField]
+ 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
new file mode 100644
index 0000000000..c58238ffef
--- /dev/null
+++ b/Content.Server/_DV/Abilities/Psionics/PsionicEruption/PsionicEruptionSystem.cs
@@ -0,0 +1,229 @@
+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.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!;
+
+ 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);
+ _actions.TryGetActionData(component.EruptionActionEntity, out var actionData);
+ if (actionData is { UseDelay: not null })
+ _actions.StartUseDelay(component.EruptionActionEntity);
+ 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, 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))
+ {
+ ShowWarning(new(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);
+ _body.GibBody(entity, acidify: true, body, launchGibs: true);
+ 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/Psionics/EruptionWarningEui.cs b/Content.Server/_DV/Psionics/EruptionWarningEui.cs
new file mode 100644
index 0000000000..02afda445e
--- /dev/null
+++ b/Content.Server/_DV/Psionics/EruptionWarningEui.cs
@@ -0,0 +1,8 @@
+using Content.Server.EUI;
+
+namespace Content.Server._DV.Psionics;
+
+///
+/// Does nothing on the server as this popup has no interactions
+///
+public sealed class EruptionWarningEui : BaseEui;
diff --git a/Content.Shared/Nyanotrasen/Actions/Events/PsionicEruptionPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/PsionicEruptionPowerActionEvent.cs
new file mode 100644
index 0000000000..2468a67126
--- /dev/null
+++ b/Content.Shared/Nyanotrasen/Actions/Events/PsionicEruptionPowerActionEvent.cs
@@ -0,0 +1,3 @@
+namespace Content.Shared.Actions.Events;
+
+public sealed partial class PsionicEruptionPowerActionEvent : InstantActionEvent { }
diff --git a/Content.Shared/_DV/Psionics/Events.cs b/Content.Shared/_DV/Psionics/Events.cs
index ce5b680a11..1e1bcb1833 100644
--- a/Content.Shared/_DV/Psionics/Events.cs
+++ b/Content.Shared/_DV/Psionics/Events.cs
@@ -8,3 +8,6 @@ public sealed partial class PrecognitionDoAfterEvent : SimpleDoAfterEvent;
[Serializable, NetSerializable]
public sealed partial class GlimmerWispDrainDoAfterEvent : SimpleDoAfterEvent;
+
+[Serializable, NetSerializable]
+public sealed partial class PsionicEruptionDoAfterEvent : SimpleDoAfterEvent;
diff --git a/Resources/Locale/en-US/_DV/abilities/psionic.ftl b/Resources/Locale/en-US/_DV/abilities/psionic.ftl
index 6c5e3cd169..d9215e7e49 100644
--- a/Resources/Locale/en-US/_DV/abilities/psionic.ftl
+++ b/Resources/Locale/en-US/_DV/abilities/psionic.ftl
@@ -48,4 +48,28 @@ psionic-power-precognition-unknown-shuttle-cargo-lost-result-message = You see a
psionic-power-precognition-unknown-shuttle-traveling-cuisine-result-message = You see a vision of peace, a cozy meal sizzling on a warm stove. A delicious smells wafts through the air.
psionic-power-precognition-unknown-shuttle-disaster-evac-pod-result-message = You see a vision of death and blood, of a destruction so complete only few survive, drifting through the coldness of space.
+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.
+psionic-eruption-annoy-medium = You feel a strong pressure in your mind. Make it stop!
+psionic-eruption-annoy-high = Your head is pounding from the psionic energy. You need to release it!
+psionic-eruption-annoy-dangerous = Your head is about to explode from the psionic energy!
+psionic-eruption-annoy-critical = Make it stop! Make it stop! Make it stop!
+
+psionic-eruption-nuke-warning = Attention! An overwhelming psionic energy has been detected at {$location}.
+psionic-eruption-nuke-sender = ???
+
+psionic-eruption-not-enough-creatures = You need at least 3 living creatures nearby to unleash the eruption. You only have {$count}.
+
+eruption-warning-window-title = Your Brain isn't Ready!
+eruption-warning-window-prompt-text-part = You feel a strong pressure building up in your mind
+ and you need to release it before it overwhelms you.
+ When you are ready, you can unleash a psionic eruption.
+ Doing so will cause a massive psionic discharge,
+ which will destroy yourself and the station around you.
+ THIS ALONE DOES NOT MAKE YOU AN ANTAGONIST.
+ Do not detonately randomly, ensure proper buildup.
+ Do you understand?
+eruption-warning-window-acknowledge-button = I Understand
+
telegnosis-power-ssd = { CAPITALIZE(POSS-ADJ($ent)) } eyes are unfocused and darting around, as if trying to see something that isn't there.
diff --git a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml
index 145d77a245..9504d259bf 100644
--- a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml
+++ b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml
@@ -10,3 +10,4 @@
NoosphericZapPower: 0.3
# PsionicInvisibilityPower: 0.15
MindSwapPower: 0.15
+ PsionicEruptionPower: 0.1
diff --git a/Resources/Prototypes/_DV/Actions/psionic.yml b/Resources/Prototypes/_DV/Actions/psionic.yml
index 1d8c6efb74..d4c9379844 100644
--- a/Resources/Prototypes/_DV/Actions/psionic.yml
+++ b/Resources/Prototypes/_DV/Actions/psionic.yml
@@ -103,6 +103,20 @@
checkCanInteract: false
event: !type:MetapsionicPowerActionEvent
+- type: entity
+ id: ActionEruption
+ name: Psionic Eruption
+ description: Unleash a powerful psionic eruption destroying you and everything around you.
+ components:
+ - type: InstantAction
+ icon:
+ sprite: _DV/Interface/Actions/actions_psionics.rsi
+ state: eruption
+ useDelay: 45
+ checkCanInteract: false
+ event: !type:PsionicEruptionPowerActionEvent
+
+
- type: entity
id: ActionPsionicRegeneration
name: Psionic Regeneration
diff --git a/Resources/Textures/_DV/Interface/Actions/actions_psionics.rsi/eruption.png b/Resources/Textures/_DV/Interface/Actions/actions_psionics.rsi/eruption.png
new file mode 100644
index 0000000000..c1ca57ee52
Binary files /dev/null and b/Resources/Textures/_DV/Interface/Actions/actions_psionics.rsi/eruption.png differ
diff --git a/Resources/Textures/_DV/Interface/Actions/actions_psionics.rsi/meta.json b/Resources/Textures/_DV/Interface/Actions/actions_psionics.rsi/meta.json
index 6ea613e543..0f32531368 100644
--- a/Resources/Textures/_DV/Interface/Actions/actions_psionics.rsi/meta.json
+++ b/Resources/Textures/_DV/Interface/Actions/actions_psionics.rsi/meta.json
@@ -1,7 +1,7 @@
{
"version": 1,
"license": "CC-BY-SA-4.0",
- "copyright": "Created by @Vordenburg (github) for Nyanotrasen. Precognition sprited by chamomileteatime on Discord for DeltaV",
+ "copyright": "Created by @Vordenburg (github) for Nyanotrasen. Precognition sprited by chamomileteatime on Discord for DeltaV. Eruption sprited by TehFlaminTAco on Discord for DeltaV.",
"size": {
"x": 64,
"y": 64
@@ -42,6 +42,9 @@
},
{
"name": "telegnosis"
+ },
+ {
+ "name": "eruption"
}
]
}