Files
wwdpublic/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs
VMSolidus 6c8adaeb52 Various Misc Psi Power Fixes (#2272)
# Description

The bug with the traits menu related to psionics is a significantly more
complicated issue, so I'm fixing that separately, it notably involves
touching a different system entirely than anything in this PR. The
biggest ones this fixes are Psi-Invis and Anoigo. Others are a variety
of other powers that were supposed to be checking "Is the target
insulated?" but weren't. To make it easier to check, I added an overload
for the OnAttemptPowerUse function that accepts a Target EntityUid,
which extends the insulation check to a second entity, which all of the
pre-existing targetted powers now use. Which simplifies the need to
constantly write the checks over and over again.

The biggest fixes this patch from to Psi Invisibility, and to an
underlying secondary problem that I discovered as a result of more
closely examining the cause of the Invis bugs. So now the client gets
networked their casting stats. Both Psi-invis and Anoigo have had their
audio fixed (it was a typo in both cases). Anoigo now also checks if the
target has insulation.

This last point is relevant because certain "High Security" doors have
had their components psifoil insulated to protect from people with a
rare and obnoxious power from forcing their way in.

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

https://github.com/user-attachments/assets/ba53df07-5ed9-4b08-b787-a6f6b347c8a3

</p>
</details>

# Changelog

🆑
- add: NT has had its station's "High Security" doors such as Command,
Security, and Vault doors shielded with psifoil around their components.
They can no longer be forced open by the Anoigo power.
- tweak: Anoigo, Dispel, Mindswap, Healing Word, Breath Of Life,
Pyrokinesis, and Noospheric Zap now all don't work if the target has
psionic insulation.
- fix: Psionic Invisibility now actually works as advertised.
- fix: Psionic Invisibility and Anoigo now actually produce a sound, as
well as properly enforce their cooldowns.
- tweak: Anoigo reclassified as a "Dangerous" power, it now also costs
10 points for Elementalists.
- remove: Prisoners can no longer be Psionic.
- fix: Mantis can no longer summon his black blade if his hands are
full.

---------

Signed-off-by: VMSolidus <evilexecutive@gmail.com>

(cherry picked from commit a4b8cf17f0a6cb62a499cb004fd2f5512c31ef18)
2025-04-19 17:05:42 +03:00

169 lines
6.4 KiB
C#

using Content.Shared.Administration.Logs;
using Content.Shared.Contests;
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 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!;
[Dependency] private readonly ContestsSystem _contests = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PsionicComponent, PsionicPowerUsedEvent>(OnPowerUsed);
}
public bool OnAttemptPowerUse(EntityUid uid, string power, bool checkInsulation = true)
{
if (!TryComp<PsionicComponent>(uid, out var component)
|| HasComp<MindbrokenComponent>(uid)
|| checkInsulation
&& TryComp(uid, out PsionicInsulationComponent? insul) && !insul.Passthrough)
return false;
var tev = new OnAttemptPowerUseEvent(uid, power);
RaiseLocalEvent(uid, tev);
if (tev.Cancelled)
return false;
if (component.DoAfter is not null)
{
_popups.PopupEntity(Loc.GetString(component.AlreadyCasting), uid, uid, PopupType.LargeCaution);
return false;
}
return true;
}
public bool OnAttemptPowerUse(EntityUid uid, EntityUid target, string power, bool checkInsulation = true)
{
if (!TryComp<PsionicComponent>(uid, out var component)
|| HasComp<MindbrokenComponent>(uid) || HasComp<MindbrokenComponent>(target)
|| checkInsulation
&& (TryComp(uid, out PsionicInsulationComponent? insul) && !insul.Passthrough || HasComp<PsionicInsulationComponent>(target)))
return false;
var tev = new OnAttemptPowerUseEvent(uid, power);
RaiseLocalEvent(uid, tev);
if (tev.Cancelled)
return false;
if (component.DoAfter is not null)
{
_popups.PopupEntity(Loc.GetString(component.AlreadyCasting), uid, uid, PopupType.LargeCaution);
return false;
}
return true;
}
private void OnPowerUsed(EntityUid uid, PsionicComponent component, PsionicPowerUsedEvent args)
{
foreach (var entity in _lookup.GetEntitiesInRange(uid, 10f))
{
if (HasComp<MetapsionicPowerComponent>(entity) && entity != uid && !(TryComp<PsionicInsulationComponent>(entity, out var insul) && !insul.Passthrough))
{
_popups.PopupEntity(Loc.GetString("metapsionic-pulse-power", ("power", args.Power)), entity, entity, PopupType.LargeCaution);
args.Handled = true;
return;
}
}
}
public void LogPowerUsed(EntityUid uid, string power, float minGlimmer = 8, float 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.DeltaGlimmerInput(_robustRandom.NextFloat(minGlimmer, maxGlimmer));
}
/// <summary>
/// Returns the CurrentAmplification of a given Entity, multiplied by the result of that Entity's MoodContest.
/// Higher mood means more Amplification, Lower mood means less Amplification.
/// </summary>
public float ModifiedAmplification(EntityUid uid)
{
if (!TryComp<PsionicComponent>(uid, out var psionicComponent))
return 1;
return ModifiedAmplification(uid, psionicComponent);
}
/// <summary>
/// Returns the CurrentAmplification of a given Entity, multiplied by the result of that Entity's MoodContest.
/// Higher mood means more Amplification, Lower mood means less Amplification.
/// </summary>
public float ModifiedAmplification(EntityUid uid, PsionicComponent component)
{
return component.CurrentAmplification * _contests.MoodContest(uid, true);
}
/// <summary>
/// Returns the CurrentDampening of a given Entity, multiplied by the result of that Entity's MoodContest.
/// Lower mood means more Dampening, higher mood means less Dampening.
/// </summary>
public float ModifiedDampening(EntityUid uid)
{
if (!TryComp<PsionicComponent>(uid, out var psionicComponent))
return 1;
return ModifiedDampening(uid, psionicComponent);
}
/// <summary>
/// Returns the CurrentDampening of a given Entity, multiplied by the result of that Entity's MoodContest.
/// Lower mood means more Dampening, higher mood means less Dampening.
/// </summary>
public float ModifiedDampening(EntityUid uid, PsionicComponent component) =>
component.CurrentDampening / _contests.MoodContest(uid, true);
}
public sealed class PsionicPowerUsedEvent : HandledEntityEventArgs
{
public EntityUid User { get; }
public string Power;
public PsionicPowerUsedEvent(EntityUid user, string power)
{
User = user;
Power = power;
}
}
public sealed class OnAttemptPowerUseEvent : CancellableEntityEventArgs
{
public EntityUid User { get; }
public string Power = string.Empty;
public OnAttemptPowerUseEvent(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;
}
}
}