Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assay Psionic Power #1450

Merged
merged 13 commits into from
Jan 11, 2025
140 changes: 140 additions & 0 deletions Content.Server/Abilities/Psionics/Abilities/AssayPowerSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using Content.Server.Chat.Managers;
using Content.Shared.Abilities.Psionics;
using Content.Shared.Actions.Events;
using Content.Shared.Chat;
using Content.Shared.DoAfter;
using Content.Shared.Popups;
using Content.Shared.Psionics.Events;
using Robust.Server.Audio;
using Robust.Shared.Audio;
using Robust.Server.Player;
using Robust.Shared.Timing;
using Robust.Shared.Player;

namespace Content.Server.Abilities.Psionics;

public sealed class AssayPowerSystem : EntitySystem
{
[Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly AudioSystem _audioSystem = default!;
[Dependency] private readonly SharedPopupSystem _popups = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PsionicComponent, AssayPowerActionEvent>(OnPowerUsed);
SubscribeLocalEvent<PsionicComponent, AssayDoAfterEvent>(OnDoAfter);
}

/// <summary>
/// This power activates when scanning any entity, displaying to the player's chat window a variety of psionic related statistics about the target.
/// </summary>
private void OnPowerUsed(EntityUid uid, PsionicComponent psionic, AssayPowerActionEvent args)
VMSolidus marked this conversation as resolved.
Show resolved Hide resolved
{
if (!_psionics.OnAttemptPowerUse(args.Performer, "assay")
|| psionic.DoAfter is not null)
return;

var ev = new AssayDoAfterEvent(_gameTiming.CurTime, args.FontSize, args.FontColor);
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.Performer, args.UseDelay - TimeSpan.FromSeconds(psionic.CurrentAmplification), ev, args.Performer, args.Target, args.Performer)
{
BlockDuplicate = true,
BreakOnMove = true,
BreakOnDamage = true,
}, out var doAfterId);
psionic.DoAfter = doAfterId;

_popups.PopupEntity(Loc.GetString(args.PopupTarget, ("entity", args.Target)), args.Performer, PopupType.Medium);

_audioSystem.PlayPvs(args.SoundUse, args.Performer, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f));
_psionics.LogPowerUsed(args.Performer, args.PowerName, args.MinGlimmer, args.MaxGlimmer);
args.Handled = true;
}

/// <summary>
/// Assuming the DoAfter wasn't canceled, the user wasn't mindbroken, and the target still exists, prepare the scan results!
/// </summary>
private void OnDoAfter(EntityUid uid, PsionicComponent component, AssayDoAfterEvent args)
{
if (component is null)
return;
component.DoAfter = null;

var user = uid;
var target = args.Target;
if (target == null || args.Cancelled
|| !_playerManager.TryGetSessionByEntity(user, out var session))
return;

if (InspectSelf(uid, args, session))
return;

if (!TryComp<PsionicComponent>(target, out var targetPsionic))
{
var noPowers = Loc.GetString("no-powers", ("entity", target));
_popups.PopupEntity(noPowers, user, user, PopupType.LargeCaution);

// Incredibly spooky message for non-psychic targets.
var noPowersFeedback = $"[font size={args.FontSize}][color={args.FontColor}]{noPowers}[/color][/font]";
SendDescToChat(noPowersFeedback, session);
return;
}

InspectOther(targetPsionic, args, session);
}

/// <summary>
/// This is a special use-case for scanning yourself with the power. The player receives a unique feedback message if they do so.
/// It however displays significantly less information when doing so. Consider this an intriguing easter egg.
/// </summary>
private bool InspectSelf(EntityUid uid, AssayDoAfterEvent args, ICommonSession session)
{
if (args.Target != args.User)
return false;

var user = uid;
var target = args.Target;

var assaySelf = Loc.GetString("assay-self", ("entity", target!.Value));
_popups.PopupEntity(assaySelf, user, user, PopupType.LargeCaution);

var assaySelfFeedback = $"[font size=20][color=#ff0000]{assaySelf}[/color][/font]";
SendDescToChat(assaySelfFeedback, session);
return true;
}

/// <summary>
/// If the target turns out to be a psychic, display their feedback messages in chat.
/// </summary>
private void InspectOther(PsionicComponent targetPsionic, AssayDoAfterEvent args, ICommonSession session)
{
var target = args.Target;
var targetAmp = MathF.Round(targetPsionic.CurrentAmplification, 2).ToString("#.##");
var targetDamp = MathF.Round(targetPsionic.CurrentDampening, 2).ToString("#.##");
var targetPotentia = MathF.Round(targetPsionic.Potentia, 2).ToString("#.##");
var message = $"[font size={args.FontSize}][color={args.FontColor}]{Loc.GetString("assay-body", ("entity", target!.Value), ("amplification", targetAmp), ("dampening", targetDamp), ("potentia", targetPotentia))}[/color][/font]";
SendDescToChat(message, session);

foreach (var feedback in targetPsionic.AssayFeedback)
{
var locale = Loc.GetString(feedback, ("entity", target!.Value));
var feedbackMessage = $"[font size={args.FontSize}][color={args.FontColor}]{locale}[/color][/font]";
SendDescToChat(feedbackMessage, session);
}
}

private void SendDescToChat(string feedbackMessage, ICommonSession session)
{
_chatManager.ChatMessageToOne(
ChatChannel.Emotes,
feedbackMessage,
feedbackMessage,
EntityUid.Invalid,
false,
session.Channel);
}
}
30 changes: 30 additions & 0 deletions Content.Shared/Actions/Events/AssayPowerActionEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Robust.Shared.Audio;

namespace Content.Shared.Actions.Events;

public sealed partial class AssayPowerActionEvent : EntityTargetActionEvent
{
[DataField]
public TimeSpan UseDelay = TimeSpan.FromSeconds(8f);

[DataField]
public SoundSpecifier SoundUse = new SoundPathSpecifier("/Audio/Psionics/heartbeat_fast.ogg");

[DataField]
public string PopupTarget = "assay-begin";

[DataField]
public int FontSize = 12;

[DataField]
public string FontColor = "#8A00C2";

[DataField]
public int MinGlimmer = 3;

[DataField]
public int MaxGlimmer = 6;

[DataField]
public string PowerName = "assay";
}
27 changes: 27 additions & 0 deletions Content.Shared/Psionics/Events.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Robust.Shared.Serialization;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.Abilities.Psionics;

namespace Content.Shared.Psionics.Events;

Expand Down Expand Up @@ -67,3 +68,29 @@ public PsionicHealOtherDoAfterEvent(TimeSpan startedAt)

public override DoAfterEvent Clone() => this;
}

[Serializable, NetSerializable]
public sealed partial class AssayDoAfterEvent : DoAfterEvent
{
[DataField(required: true)]
public TimeSpan StartedAt;

[DataField]
public int FontSize = 12;

[DataField]
public string FontColor = "#8A00C2";

private AssayDoAfterEvent()
{
}

public AssayDoAfterEvent(TimeSpan startedAt, int fontSize, string fontColor)
{
StartedAt = startedAt;
FontSize = fontSize;
FontColor = fontColor;
}

public override DoAfterEvent Clone() => this;
}
3 changes: 3 additions & 0 deletions Resources/Locale/en-US/abilities/psionic.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,6 @@ action-description-psychokinesis = Bend the fabric of space to instantly move ac
action-name-rf-sensitivity = Toggle RF Sensitivity
action-desc-rf-sensitivity = Toggle your ability to interpret radio waves on and off.
action-name-assay = Assay
action-description-assay = Probe an entity at close range to glean metaphorical information about any powers they may have
30 changes: 30 additions & 0 deletions Resources/Locale/en-US/psionics/psionic-powers.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,33 @@ ghost-role-information-familiar-description = An interdimensional creature bound
ghost-role-information-familiar-rules =
Obey the one who summoned you. Do not act against the interests of your Master. You will die for your Master if it is necessary.
# Assay Power
assay-begin = The air around {CAPITALIZE($entity)} begins to shimmer faintly
assay-self = I AM.
no-powers = {CAPITALIZE($entity)} will never awaken from the dream in this life
assay-body = "My will cast upon {CAPITALIZE($entity)} divines these. Amplification: {$amplification} Dampening: {$dampening} Potentia: {$potentia}"
assay-power-initialization-feedback =
I descend into the dreamlight once more, there I drink more fully of the cup of knowledge. The touch of the noosphere upon others becomes known to me,
I can cast my will upon them, divining the inner nature of others.
assay-power-metapsionic-feedback = {CAPITALIZE($entity)} bears a spark of the divine's judgment, they have drunk deeply of the cup of knowledge.
# Entity Specific Feedback Messages
ifrit-feedback = A spirit of Gehenna, bound by the will of a powerful psychic
prober-feedback = A mirror into the end of time, the screaming of dead stars emanates from this machine
drain-feedback = A mirror into a realm where the stars sit still forever, a cold and distant malevolence stares back
sophic-grammateus-feedback = SEEKER, YOU NEED ONLY ASK FOR MY WISDOM.
oracle-feedback = WHY DO YOU BOTHER ME SEEKER? HAVE I NOT MADE MY DESIRES CLEAR?
orecrab-feedback = Heralds of the Lord of Earth, summoned to this realm from Grome's kingdom
reagent-slime-feedback = Heralds of the Lord of Water, summoned to this realm from Straasha's kingdom.
flesh-golem-feedback = Abominations pulled from dead realms, twisted amalgamations of those fallen to the influence of primordial Chaos
glimmer-mite-feedback = A semi-corporeal parasite native to the dreamlight, its presence here brings forth the screams of dead stars.
anomaly-pyroclastic-feedback = A small mirror to the plane of Gehenna, truth lies within the Secret of Fire
anomaly-gravity-feedback = Violet and crimson, blue of blue, impossibly dark yet greater than the whitest of white, a black star shines weakly at the end of it all
anomaly-electricity-feedback = A mirror to a realm tiled by silicon, the lifeblood of artificial thought flows from it
anomaly-flesh-feedback = From within it comes the suffering of damned mutants howling for all eternity
anomaly-bluespace-feedback = A bridge of dreamlight, crossing into the space between realms of the multiverse
anomaly-ice-feedback = Walls of blackened stone, ruin and famine wait for those who fall within
anomaly-rock-feedback = A vast old oak dwells high over a plane of stone, it turns to stare back
anomaly-flora-feedback = Musical notes drift around you, playfully beckoning, they wish to feast
anomaly-liquid-feedback = A realm of twisting currents. Its placidity is a lie. The eyes within stare hungrilly
anomaly-shadow-feedback = At the end of time, when all suns have set forever, there amidst the void stands a monument to past sins.
19 changes: 19 additions & 0 deletions Resources/Prototypes/Actions/psionics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,22 @@
followMaster: true
minGlimmer: 5
maxGlimmer: 10

- type: entity
id: ActionAssay
name: action-name-assay
description: action-description-assay
categories: [ HideSpawnMenu ]
components:
- type: EntityTargetAction
icon: { sprite: Interface/Actions/psionics.rsi, state: assay }
useDelay: 45
checkCanAccess: false
range: 2
itemIconStyle: BigAction
canTargetSelf: true
blacklist:
components:
- PsionicInsulation
- Mindbroken
event: !type:AssayPowerActionEvent
2 changes: 2 additions & 0 deletions Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@
nameSegments: [names_golem]
- type: Psionic
removable: false
assayFeedback:
- ifrit-feedback
psychognomicDescriptors:
- p-descriptor-bound
- p-descriptor-cyclic
Expand Down
7 changes: 7 additions & 0 deletions Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
- type: ZombieImmune
- type: ClothingRequiredStepTriggerImmune
slots: All
- type: Dispellable

- type: entity
abstract: true
Expand Down Expand Up @@ -104,6 +105,9 @@
damageContainer: StructuralInorganic
- type: Psionic
removable: false
roller: false
assayFeedback:
- orecrab-feedback
- type: InnatePsionicPowers
powersToAdd:
- TelepathyPower
Expand Down Expand Up @@ -303,6 +307,9 @@
solution: bloodstream
- type: Psionic
removable: false
roller: false
assayFeedback:
- reagent-slime-feedback
- type: InnatePsionicPowers
powersToAdd:
- TelepathyPower
Expand Down
5 changes: 5 additions & 0 deletions Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
Slash: 6
- type: ReplacementAccent
accent: genericAggressive
- type: Psionic
removable: false
roller: false
assayFeedback:
- flesh-golem-feedback

- type: entity
parent: BaseMobFlesh
Expand Down
4 changes: 4 additions & 0 deletions Resources/Prototypes/Entities/Mobs/NPCs/glimmer_creatures.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
- ReagentId: Ectoplasm
Quantity: 15
- type: Psionic
removable: false
roller: false
assayFeedback:
- glimmer-mite-feedback
- type: GlimmerSource
- type: AmbientSound
range: 6
Expand Down
Loading
Loading