Psionic Eruption (#4078)

* Psionic Eruption

* Art Pass

* Detangles spaghetti

Removed from Nyano namespaces, moved entirely into _DV namespaces and folders where possible.

* Remove nuke behavior (Maybe next time 😢 )

* Various maintainer requested patches

* More cleanup

* I'm blind

---------

Signed-off-by: William Lemon <William.Lemon2@gmail.com>
This commit is contained in:
William Lemon 2025-07-31 21:39:48 +10:00 committed by GitHub
parent 27ceb097f6
commit 31718cfbd1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 369 additions and 1 deletions

View File

@ -0,0 +1,10 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'eruption-warning-window-title'}">
<BoxContainer Orientation="Vertical" Margin="10">
<Label Text="{Loc 'eruption-warning-window-prompt-text-part'}" Margin="5" Align="Center"/>
<BoxContainer Orientation="Horizontal" Margin="5" Align="Center">
<Button Name="ConfirmButton" Text="{Loc 'eruption-warning-window-acknowledge-button'}"/>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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<PsionicEruptionPowerComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<PsionicEruptionPowerComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<PsionicEruptionPowerComponent, PsionicEruptionPowerActionEvent>(OnPowerUsed);
SubscribeLocalEvent<PsionicEruptionPowerComponent, DispelledEvent>(OnDispelled);
SubscribeLocalEvent<PsionicEruptionPowerComponent, PsionicEruptionDoAfterEvent>(OnDoAfter);
}
private void OnInit(Entity<PsionicEruptionPowerComponent> 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<PsionicComponent>(entity, out var psionic) && psionic.PsionicAbility == null)
{
psionic.PsionicAbility = component.EruptionActionEntity;
psionic.ActivePowers.Add(component);
}
}
private void ShowWarning(Entity<PsionicEruptionPowerComponent> 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<PsionicEruptionPowerComponent> entity, ref ComponentShutdown args)
{
_actions.RemoveAction(entity, entity.Comp.EruptionActionEntity);
if (TryComp<PsionicComponent>(entity, out var psionic))
{
psionic.ActivePowers.Remove(entity.Comp);
}
}
private void OnDispelled(Entity<PsionicEruptionPowerComponent> 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<PsionicEruptionPowerComponent> 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<PsionicEruptionPowerComponent>();
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<PsionicEruptionPowerComponent> entity, ref PsionicEruptionDoAfterEvent args)
{
entity.Comp.DoAfter = null;
if (args.Cancelled || args.Handled)
return;
if (!TryComp<BodyComponent>(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);
}
}

View File

@ -0,0 +1,8 @@
using Content.Server.EUI;
namespace Content.Server._DV.Psionics;
/// <summary>
/// Does nothing on the server as this popup has no interactions
/// </summary>
public sealed class EruptionWarningEui : BaseEui;

View File

@ -0,0 +1,3 @@
namespace Content.Shared.Actions.Events;
public sealed partial class PsionicEruptionPowerActionEvent : InstantActionEvent { }

View File

@ -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;

View File

@ -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.

View File

@ -10,3 +10,4 @@
NoosphericZapPower: 0.3
# PsionicInvisibilityPower: 0.15
MindSwapPower: 0.15
PsionicEruptionPower: 0.1

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -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"
}
]
}