Skip to content

Commit

Permalink
Shadeskip Power (Simple-Station#987)
Browse files Browse the repository at this point in the history
# Description

This PR adds the first of the "Anomalist" powers, that of Shadeskip.
Upon activation, Shadeskip summons a small amount of living shadows
around the caster, functioning almost exactly like a Shadow Anomaly,
only smaller.

<details><summary><h1>Media</h1></summary>
<p>

A very early rendition of Shadeskip:


https://github.com/user-attachments/assets/2425aca8-4d09-48a2-80f5-572b3aa864b1

</p>
</details>

# Changelog

:cl:
- add: Added Shadeskip as a new psionic power.

---------

Signed-off-by: VMSolidus <[email protected]>
Co-authored-by: DEATHB4DEFEAT <[email protected]>
  • Loading branch information
VMSolidus and DEATHB4DEFEAT authored Sep 30, 2024
1 parent ce9d9aa commit 13f52f7
Show file tree
Hide file tree
Showing 9 changed files with 298 additions and 2 deletions.
146 changes: 146 additions & 0 deletions Content.Server/Abilities/Psionics/AnomalyPowerSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using Content.Shared.Abilities.Psionics;
using Content.Shared.Actions.Events;
using Content.Shared.Psionics.Glimmer;
using Content.Shared.Random.Helpers;
using Robust.Shared.Random;
using Content.Shared.Anomaly.Effects.Components;
using Robust.Shared.Map.Components;
using Content.Shared.Anomaly;
using Robust.Shared.Audio.Systems;
using Content.Shared.Actions;
using Content.Shared.Damage;
using Content.Server.Popups;

namespace Content.Server.Abilities.Psionics;

public sealed class AnomalyPowerSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!;
[Dependency] private readonly SharedAnomalySystem _anomalySystem = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly PopupSystem _popup = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PsionicComponent, AnomalyPowerActionEvent>(OnPowerUsed);
}

private void OnPowerUsed(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args)
{
if (HasComp<PsionicInsulationComponent>(uid)
|| HasComp<MindbrokenComponent>(uid))
return;

var overcharged = _glimmerSystem.Glimmer * component.CurrentAmplification
> Math.Min(args.SupercriticalThreshold * component.CurrentDampening, args.MaxSupercriticalThreshold);

// I already hate this, so much.
//DoBluespaceAnomalyEffects(uid, component, args, overcharged);
//DoElectricityAnomalyEffects(uid, component, args, overcharged);
DoEntityAnomalyEffects(uid, component, args, overcharged);
//DoExplosionAnomalyEffects(uid, component, args, overcharged);
//DoGasProducerAnomalyEffects(uid, component, args, overcharged);
//DoGravityAnomalyEffects(uid, component, args, overcharged);
//DoInjectionAnomalyEffects(uid, component, args, overcharged);
//DoPuddleCreateAnomalyEffects(uid, component, args, overcharged);
//DoPyroclasticAnomalyEffects(uid, component, args, overcharged);
//DoTemperatureAnomalyEffects(uid, component, args, overcharged);

DoAnomalySounds(uid, component, args, overcharged);
DoGlimmerEffects(uid, component, args, overcharged);

if (overcharged)
DoOverchargedEffects(uid, component, args);

args.Handled = true;
}

public void DoEntityAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged)
{
if (args.EntitySpawnEntries is null)
return;

if (overcharged)
foreach (var entry in args.EntitySpawnEntries)
{
if (!entry.Settings.SpawnOnSuperCritical)
continue;

SpawnEntities(uid, component, entry);
}
else foreach (var entry in args.EntitySpawnEntries)
{
if (!entry.Settings.SpawnOnPulse)
continue;

SpawnEntities(uid, component, entry);
}
}

public void SpawnEntities(EntityUid uid, PsionicComponent component, EntitySpawnSettingsEntry entry)
{
if (!TryComp<MapGridComponent>(Transform(uid).GridUid, out var grid))
return;

var tiles = _anomalySystem.GetSpawningPoints(uid,
component.CurrentDampening,
component.CurrentAmplification,
entry.Settings,
_glimmerSystem.Glimmer / 1000,
component.CurrentAmplification,
component.CurrentAmplification);

if (tiles is null)
return;

foreach (var tileref in tiles)
Spawn(_random.Pick(entry.Spawns), _mapSystem.ToCenterCoordinates(tileref, grid));
}

public void DoAnomalySounds(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false)
{
if (overcharged && args.SupercriticalSound is not null)
{
_audio.PlayPvs(args.SupercriticalSound, uid);
return;
}

if (args.PulseSound is null
|| _glimmerSystem.Glimmer < args.GlimmerSoundThreshold * component.CurrentDampening)
return;

_audio.PlayEntity(args.PulseSound, uid, uid);
}

public void DoGlimmerEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false)
{
var minGlimmer = (int) Math.Round(MathF.MinMagnitude(args.MinGlimmer, args.MaxGlimmer)
* (overcharged ? args.SupercriticalGlimmerMultiplier : 1)
* component.CurrentAmplification - component.CurrentDampening);
var maxGlimmer = (int) Math.Round(MathF.MaxMagnitude(args.MinGlimmer, args.MaxGlimmer)
* (overcharged ? args.SupercriticalGlimmerMultiplier : 1)
* component.CurrentAmplification - component.CurrentDampening);

_psionics.LogPowerUsed(uid, args.PowerName, minGlimmer, maxGlimmer);
}

public void DoOverchargedEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args)
{
if (args.OverchargeFeedback is not null
&& Loc.TryGetString(args.OverchargeFeedback, out var popup))
_popup.PopupEntity(popup, uid, uid);

if (args.OverchargeRecoil is not null
&& TryComp<DamageableComponent>(uid, out var damageable))
_damageable.TryChangeDamage(uid, args.OverchargeRecoil / component.CurrentDampening, true, true, damageable, uid);

if (args.OverchargeCooldown > 0)
foreach (var action in component.Actions)
_actions.SetCooldown(action.Value, TimeSpan.FromSeconds(args.OverchargeCooldown / component.CurrentDampening));
}
}
84 changes: 84 additions & 0 deletions Content.Shared/Actions/Events/AnomalyPowerActionEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using Content.Shared.Anomaly.Effects.Components;
using Content.Shared.Damage;
using Robust.Shared.Audio;

namespace Content.Shared.Actions.Events;

public sealed partial class AnomalyPowerActionEvent : InstantActionEvent
{

[DataField]
public string PowerName;

/// <summary>
/// When casting above the Supercritical Threshold, if not 0, this will cause all powers to enter cooldown for the given duration.
/// </summary>
[DataField]
public float OverchargeCooldown;

/// <summary>
/// When casting above the Supercritical Threshold, if not 0, this will deal recoil damage to the caster of the specified amounts.
/// </summary>
[DataField]
public DamageSpecifier? OverchargeRecoil;

/// <summary>
/// When casting above the Supercritical Threshold, play a popup above the caster's head.
/// </summary>
[DataField]
public string? OverchargeFeedback;

/// <summary>
/// The minimum amount of glimmer generated by this power.
/// </summary>
[DataField]
public int MinGlimmer;

/// <summary>
/// The maximum amount of glimmer generated by this power.
/// </summary>
[DataField]
public int MaxGlimmer;

/// <summary>
/// The amount to multiply glimmer generation by when above the Supercritical Threshold
/// </summary>
[DataField]
public int SupercriticalGlimmerMultiplier = 1;

/// <summary>
/// The threshold of glimmer at which this power will play a sound.
/// </summary>
[DataField]
public float GlimmerSoundThreshold;

/// <summary>
/// The glimmer threshold(divided by amplification and multiplied by dampening) at which this power will act as a Supercritical Anomaly.
/// </summary>
[DataField]
public float SupercriticalThreshold = 500f;

/// <summary>
/// The maximum amount Dampening can increase the Supercritical threshold to.
/// </summary>
[DataField]
public float MaxSupercriticalThreshold = 800f;

/// <summary>
/// What entities will be spawned by this action, using the same arguments as an EntitySpawnAnomalyComponent?
/// </summary>
[DataField]
public List<EntitySpawnSettingsEntry>? EntitySpawnEntries;

/// <summary>
/// The sound to be played upon activating this power(and not Supercritically)
/// </summary>
[DataField]
public SoundSpecifier? PulseSound = new SoundCollectionSpecifier("RadiationPulse");

/// <summary>
/// The sound plays when this power is activated above a Supercritical glimmer threshold
/// </summary>
[DataField]
public SoundSpecifier? SupercriticalSound = new SoundCollectionSpecifier("Explosion");
}
4 changes: 2 additions & 2 deletions Content.Shared/Anomaly/SharedAnomalySystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -368,14 +368,14 @@ public override void Update(float frameTime)
/// <summary>
/// Gets random points around the anomaly based on the given parameters.
/// </summary>
public List<TileRef>? GetSpawningPoints(EntityUid uid, float stability, float severity, AnomalySpawnSettings settings, float powerModifier = 1f)
public List<TileRef>? GetSpawningPoints(EntityUid uid, float stability, float severity, AnomalySpawnSettings settings, float powerModifier = 1f, float minAmountOffset = 0, float maxAmountOffset = 0)
{
var xform = Transform(uid);

if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
return null;

var amount = (int) (MathHelper.Lerp(settings.MinAmount, settings.MaxAmount, severity * stability * powerModifier) + 0.5f);
var amount = (int) MathF.Round(MathHelper.Lerp(settings.MinAmount + minAmountOffset, settings.MaxAmount + maxAmountOffset, severity * stability * powerModifier) + 0.5f);

var localpos = xform.Coordinates.Position;
var tilerefs = grid.GetLocalTilesIntersecting(
Expand Down
13 changes: 13 additions & 0 deletions Resources/Locale/en-US/psionics/psionic-powers.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ telepathy-power-initialization-feedback =
The voices I've heard all my life begin to clear, yet they do not leave me. Before, they were as incoherent whispers,
now my senses broaden, I come to a realization that they are part of a communal shared hallucination. Behind every voice is a glimmering sentience.
# Shadeskip
action-name-shadeskip = Shadeskip
action-description-shadeskip =
Call upon the Lords of the End of Time, and beseech them for a fragment of true entropy.
shadeskip-power-description = { action-description-shadeskip }
shadeskip-power-initialization-feedback =
I find myself standing in a frigid land, under a sky lacking in all starlight. Cold is the void at the End of Time.
I look to the pale blue within blue horizon, and find a great eye standing at the center of it all, black and emptier than the deepest reaches of space.
My soul begins to wither under its gaze, and I find myself begging for it to look away. The eye laughs, it demands that I serve it or die.
Knowing I have no choice, I pledge myself to it, and suddenly I am back in the material realm. The eye stares behind me still.
shadeskip-power-metapsionic-feedback = {CAPITALIZE($entity)} has been claimed by the Lords of the End of Time.
shadeskip-overcharge-feedback = My body reels from shock as it is overwhelmed by the sheer force flowing through me.
# Psionic System Messages
mindbreaking-feedback = The light of life vanishes from {CAPITALIZE($entity)}'s eyes, leaving behind a husk pretending at sapience
examine-mindbroken-message =
Expand Down
39 changes: 39 additions & 0 deletions Resources/Prototypes/Actions/psionics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,42 @@
glimmerSoundThreshold: 50
glimmerPopupThreshold: 100
glimmerDoAfterVisibilityThreshold: 35

- type: entity
id: ActionShadeskip
name: action-name-shadeskip
description: action-description-shadeskip
noSpawn: true
components:
- type: InstantAction
icon: { sprite : Interface/Actions/psionics.rsi, state: shadeskip }
useDelay: 45
checkCanInteract: false
event: !type:AnomalyPowerActionEvent
powerName: "Shadeskip"
overchargeFeedback: "shadeskip-overcharge-feedback"
overchargeCooldown: 120
overchargeRecoil:
groups:
Burn: -100 #This will be divided by the caster's Dampening.
minGlimmer: 6
maxGlimmer: 8
supercriticalGlimmerMultiplier: 3
glimmerSoundThreshold: 50
supercriticalThreshold: 500
entitySpawnEntries:
- settings:
spawnOnPulse: true
spawnOnSuperCritical: true
minAmount: 5
maxAmount: 8
maxRange: 1.5
spawns:
- ShadowKudzuWeak
- settings:
spawnOnSuperCritical: true
minAmount: 30
maxAmount: 50
maxRange: 50
spawns:
- ShadowKudzu
1 change: 1 addition & 0 deletions Resources/Prototypes/Nyanotrasen/psionicPowers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
TelepathyPower: 1
HealingWordPower: 0.85
RevivifyPower: 0.1
ShadeskipPower: 0.15
10 changes: 10 additions & 0 deletions Resources/Prototypes/Psionics/psionics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,13 @@
metapsionicFeedback: revivify-power-feedback
amplificationModifier: 2.5 # An extremely rare and dangerous power
powerSlotCost: 2

- type: psionicPower
id: ShadeskipPower
name: Shadeskip
description: shadeskip-power-description
actions:
- ActionShadeskip
initializationFeedback: shadeskip-power-initialization-feedback
metapsionicFeedback: shadeskip-power-metapsionic-feedback
amplificationModifier: 1
3 changes: 3 additions & 0 deletions Resources/Textures/Interface/Actions/psionics.rsi/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
},
{
"name": "revivify"
},
{
"name": "shadeskip"
}
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 13f52f7

Please sign in to comment.