diff --git a/Content.Client/_DV/Psionics/Systems/PsionicPowers/MindSwapPowerSystem.cs b/Content.Client/_DV/Psionics/Systems/PsionicPowers/MindSwapPowerSystem.cs
deleted file mode 100644
index 234d40a88c6..00000000000
--- a/Content.Client/_DV/Psionics/Systems/PsionicPowers/MindSwapPowerSystem.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using Content.Shared._DV.Psionics.Systems.PsionicPowers;
-
-namespace Content.Client._DV.Psionics.Systems.PsionicPowers;
-
-///
-/// This solely exists for prediction.
-///
-public sealed class MindSwapPowerSystem : SharedMindSwapPowerSystem;
diff --git a/Content.Client/_DV/Psionics/Systems/PsionicPowers/MindSwappedReturnPowerSystem.cs b/Content.Client/_DV/Psionics/Systems/PsionicPowers/MindSwappedReturnPowerSystem.cs
new file mode 100644
index 00000000000..9c5dd661985
--- /dev/null
+++ b/Content.Client/_DV/Psionics/Systems/PsionicPowers/MindSwappedReturnPowerSystem.cs
@@ -0,0 +1,15 @@
+using Content.Shared._DV.Psionics.Components.PsionicPowers;
+using Content.Shared._DV.Psionics.Events.PowerActionEvents;
+using Content.Shared._DV.Psionics.Systems.PsionicPowers;
+
+namespace Content.Client._DV.Psionics.Systems.PsionicPowers;
+
+///
+/// This is solely for prediction. Everything is in the server-side file.
+///
+public sealed class MindSwappedReturnPowerSystem : SharedMindSwappedReturnPowerSystem
+{
+ protected override void OnPowerUsed(Entity psionic, ref MindSwappedReturnPowerActionEvent args)
+ {
+ }
+}
diff --git a/Content.Server/Nyanotrasen/StationEvents/Components/MassMindSwapRuleComponent.cs b/Content.Server/Nyanotrasen/StationEvents/Components/MassMindSwapRuleComponent.cs
deleted file mode 100644
index 16706665c24..00000000000
--- a/Content.Server/Nyanotrasen/StationEvents/Components/MassMindSwapRuleComponent.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Content.Server.Nyanotrasen.StationEvents.Events;
-using Robust.Shared.Audio;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-
-namespace Content.Server.StationEvents.Components;
-
-[RegisterComponent, Access(typeof(MassMindSwapRule))]
-public sealed partial class MassMindSwapRuleComponent : Component
-{
- ///
- /// The mind swap is only temporary if true.
- ///
- [DataField]
- public bool IsTemporary;
-
- [DataField]
- public TimeSpan Delay = TimeSpan.FromSeconds(15);
-
- [DataField]
- public SoundSpecifier AnnouncementSound = new SoundPathSpecifier("/Audio/Misc/notice1.ogg");
-
- [DataField]
- public SoundSpecifier SwapWarningSound = new SoundPathSpecifier("/Audio/_DV/Effects/clang2.ogg");
-
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
- public TimeSpan? SoundTime;
-
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
- public TimeSpan? SwapTime;
-}
diff --git a/Content.Server/_DV/Psionics/Systems/PsionicPowers/MindSwapPowerSystem.cs b/Content.Server/_DV/Psionics/Systems/PsionicPowers/MindSwapPowerSystem.cs
deleted file mode 100644
index 109a4cd7795..00000000000
--- a/Content.Server/_DV/Psionics/Systems/PsionicPowers/MindSwapPowerSystem.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Content.Server.Ghost;
-using Content.Shared._DV.Psionics.Components.PsionicPowers;
-using Content.Shared._DV.Psionics.Systems.PsionicPowers;
-
-namespace Content.Server._DV.Psionics.Systems.PsionicPowers;
-
-public sealed class MindSwapPowerSystem : SharedMindSwapPowerSystem
-{
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnGhostAttempt);
- }
-
- private void OnGhostAttempt(GhostAttemptHandleEvent args)
- {
- if (args.Handled)
- return;
-
- // If you're able to swap back to your original body, you should swap back before you ghost.
- if (TryComp(args.Mind.CurrentEntity, out var component)
- && Action.GetAction(component.ActionEntity) is { } action
- && action.Comp.AttachedEntity is not null)
- {
- args.Result = false;
- args.Handled = true;
- }
- }
-}
diff --git a/Content.Server/_DV/Psionics/Systems/PsionicPowers/MindSwappedReturnPowerSystem.cs b/Content.Server/_DV/Psionics/Systems/PsionicPowers/MindSwappedReturnPowerSystem.cs
new file mode 100644
index 00000000000..a8695815a83
--- /dev/null
+++ b/Content.Server/_DV/Psionics/Systems/PsionicPowers/MindSwappedReturnPowerSystem.cs
@@ -0,0 +1,50 @@
+using Content.Shared._DV.Psionics.Components.PsionicPowers;
+using Content.Shared._DV.Psionics.Events;
+using Content.Shared._DV.Psionics.Events.PowerActionEvents;
+using Content.Shared._DV.Psionics.Systems.PsionicPowers;
+
+namespace Content.Server._DV.Psionics.Systems.PsionicPowers;
+
+public sealed class MindSwappedReturnPowerSystem : SharedMindSwappedReturnPowerSystem
+{
+ [Dependency] private readonly PsionicSystem _psionic = default!;
+
+ private EntityQuery _mindSwappedQuery;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnShutDown);
+ SubscribeLocalEvent(OnAntiPsionicHit);
+
+ _mindSwappedQuery = GetEntityQuery();
+ }
+
+ private void OnShutDown(Entity psionic, ref ComponentShutdown args)
+ {
+ // If the person is gibbed or otherwise deleted, it'll remove the links.
+ if (Timing.ApplyingState
+ || !TerminatingOrDeleted(psionic)
+ || !_mindSwappedQuery.TryComp(psionic.Comp.OriginalEntity, out var targetComp))
+ return;
+
+ _psionic.RemoveLink((psionic.Comp.OriginalEntity, targetComp));
+ }
+
+ protected override void OnPowerUsed(Entity psionic, ref MindSwappedReturnPowerActionEvent args)
+ {
+ _psionic.SwapMinds(psionic, psionic.Comp.OriginalEntity);
+ AfterPowerUsed(psionic, args.Performer);
+ }
+
+ protected override void OnDispelled(Entity psionic, ref DispelledEvent args)
+ {
+ _psionic.SwapMinds(psionic, psionic.Comp.OriginalEntity, false);
+ }
+
+ private void OnAntiPsionicHit(Entity psionic, ref AntiPsionicWeaponHitEvent args)
+ {
+ _psionic.SwapMinds(psionic, psionic.Comp.OriginalEntity, false);
+ }
+}
diff --git a/Content.Server/_DV/Psionics/Systems/PsionicPowers/TelegnosisPowerSystem.cs b/Content.Server/_DV/Psionics/Systems/PsionicPowers/TelegnosisPowerSystem.cs
index 2efd9a4fd77..aee33de3845 100644
--- a/Content.Server/_DV/Psionics/Systems/PsionicPowers/TelegnosisPowerSystem.cs
+++ b/Content.Server/_DV/Psionics/Systems/PsionicPowers/TelegnosisPowerSystem.cs
@@ -9,8 +9,6 @@ using Content.Shared.Atmos;
using Content.Shared.Body.Components;
using Content.Shared.Mech.Components;
using Content.Shared.Medical.Cryogenics;
-using Content.Shared.Mindshield.Components;
-using Content.Shared.Popups;
using Content.Shared.Storage.Components;
namespace Content.Server._DV.Psionics.Systems.PsionicPowers;
@@ -18,7 +16,7 @@ namespace Content.Server._DV.Psionics.Systems.PsionicPowers;
public sealed class TelegnosisPowerSystem : SharedTelegnosisPowerSystem
{
[Dependency] private readonly AtmosphereSystem _atmos = default!;
- [Dependency] private readonly SharedMindSwapPowerSystem _mindSwap = default!;
+ [Dependency] private readonly PsionicSystem _psionic = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
public override void Initialize()
@@ -32,17 +30,10 @@ public sealed class TelegnosisPowerSystem : SharedTelegnosisPowerSystem
// The logic for transferring minds is server-side only. If we don't put this here, it'll look bad for the person.
protected override void OnPowerUsed(Entity psionic, ref TelegnosisPowerActionEvent args)
{
- // TODO: Fix this. MindSwapSystem cannot handle popups when called from serverside while the performer is the cause.
- if (HasComp(psionic))
- {
- Popup.PopupEntity(Loc.GetString("psionic-power-mindswap-own-mindshield"), psionic, psionic, PopupType.SmallCaution);
- return;
- }
-
var projection = Spawn(psionic.Comp.Prototype, Transform(psionic).Coordinates);
_transform.AttachToGridOrMap(projection);
- if (!_mindSwap.SwapMinds(args.Performer, projection))
+ if (!_psionic.SwapMinds(args.Performer, projection))
{
// If swap didn't work out, delete the spawned projection.
QueueDel(projection);
diff --git a/Content.Server/_DV/Psionics/Systems/PsionicSystem.cs b/Content.Server/_DV/Psionics/Systems/PsionicSystem.cs
index 2edd50d1e75..abda5676ba0 100644
--- a/Content.Server/_DV/Psionics/Systems/PsionicSystem.cs
+++ b/Content.Server/_DV/Psionics/Systems/PsionicSystem.cs
@@ -17,6 +17,7 @@ public sealed partial class PsionicSystem : SharedPsionicSystem
SubscribeLocalEvent(OnPlayerSpawnComplete);
InitializeItems();
+ InitializeMindSwap();
}
private void OnPlayerSpawnComplete(Entity potPsionic, ref PlayerSpawnCompleteEvent args)
diff --git a/Content.Server/_DV/Psionics/Systems/SubSystems/PsionicSystem.Items.cs b/Content.Server/_DV/Psionics/Systems/SubSystems/PsionicSystem.Items.cs
index 3e68fbe96b4..50a834ff428 100644
--- a/Content.Server/_DV/Psionics/Systems/SubSystems/PsionicSystem.Items.cs
+++ b/Content.Server/_DV/Psionics/Systems/SubSystems/PsionicSystem.Items.cs
@@ -13,7 +13,7 @@ public sealed partial class PsionicSystem
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly FlammableSystem _flammable = default!;
- public void InitializeItems()
+ private void InitializeItems()
{
SubscribeLocalEvent>(OnFry);
}
diff --git a/Content.Server/_DV/Psionics/Systems/SubSystems/PsionicSystem.Mindswap.cs b/Content.Server/_DV/Psionics/Systems/SubSystems/PsionicSystem.Mindswap.cs
new file mode 100644
index 00000000000..335b03f133e
--- /dev/null
+++ b/Content.Server/_DV/Psionics/Systems/SubSystems/PsionicSystem.Mindswap.cs
@@ -0,0 +1,248 @@
+using Content.Server.Actions;
+using Content.Server.Ghost;
+using Content.Shared._DV.Psionics.Components.PsionicPowers;
+using Content.Shared._DV.Psionics.Events;
+using Content.Shared.Mind;
+using Content.Shared.Mindshield.Components;
+using Content.Shared.Popups;
+using JetBrains.Annotations;
+
+namespace Content.Server._DV.Psionics.Systems;
+
+///
+/// This handles swapping the minds of beings for psionic reasons, such as telegnosis or mass mindswaps.
+///
+public sealed partial class PsionicSystem
+{
+ [Dependency] private readonly ActionsSystem _action = default!;
+ [Dependency] private readonly SharedMindSystem _mind = default!;
+
+ private EntityQuery _mindSwappedQuery;
+ private EntityQuery _mindshieldQuery;
+
+ private void InitializeMindSwap()
+ {
+ _mindSwappedQuery = GetEntityQuery();
+ _mindshieldQuery = GetEntityQuery();
+
+ SubscribeLocalEvent(OnGhostAttempt);
+ }
+
+ private void OnGhostAttempt(GhostAttemptHandleEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ // If you're able to swap back to your original body, you should swap back before you ghost.
+ if (TryComp(args.Mind.CurrentEntity, out var component)
+ && _action.GetAction(component.ActionEntity) is { } action
+ && action.Comp.AttachedEntity is not null)
+ {
+ args.Result = false;
+ args.Handled = true;
+ }
+ }
+
+ ///
+ /// Swaps two minds.
+ ///
+ /// The entity performing or causing the swap/being targeted.
+ /// The entity being targeted.
+ /// Whether the performer is actively causing the swap.
+ /// Whether the swap is reversible via the return power.
+ /// Whether the swap should ignore mindshields.
+ /// Whether the swap should ignore psionic shielding.
+ /// True if the two were swapped, false if otherwise.
+ [PublicAPI]
+ public bool SwapMinds(EntityUid performer, EntityUid target, bool performerIsCause = true, bool reversible = true, bool ignoreMindshields = false, bool ignorePsionicShielding = false)
+ {
+ if (performerIsCause && !CanPerformerSwapWithTarget(performer, target, ignoreMindshields, ignorePsionicShielding))
+ return false;
+ // The check is a little different if there is no person causing it.
+ if (!performerIsCause && !CanExternallySwap(performer, target, ignoreMindshields, ignorePsionicShielding))
+ return false;
+
+ // Get the minds first. On transfer, they'll be gone.
+ // This is here to prevent missing MindContainerComponent Resolve errors.
+ if (!_mind.TryGetMind(performer, out var performerMindId, out var performerMind))
+ performerMind = null;
+
+ if (!_mind.TryGetMind(target, out var targetMindId, out var targetMind))
+ targetMind = null;
+
+ switch (performerMind)
+ {
+ // If no mind can be swapped, return.
+ case null when targetMind == null:
+ return false;
+ // If performer has no mind, but target does, switch places.
+ case null:
+ (performer, target) = (target, performer);
+ (performerMind, targetMind) = (targetMind, performerMind);
+ (performerMindId, targetMindId) = (targetMindId, performerMindId);
+ break;
+ }
+
+ //This is a terrible way to 'unattach' minds. I wanted to use UnVisit but in TransferTo's code they say
+ //To unnatch the minds, do it like this.
+ //Have to unnattach the minds before we reattach them via transfer. Still feels weird, but seems to work well.
+ _mind.TransferTo(performerMindId, null);
+ // Do the transfer.
+ if (targetMind != null)
+ _mind.TransferTo(targetMindId, performer, ghostCheckOverride: true, false, targetMind);
+
+ _mind.TransferTo(performerMindId, target, ghostCheckOverride: true, false, performerMind);
+
+ if (_mindSwappedQuery.TryComp(performer, out var performerSwapped) && _mindSwappedQuery.TryComp(target, out var targetSwapped))
+ {
+ // Sanity check
+ if (performerSwapped.OriginalEntity == target && targetSwapped.OriginalEntity == performer)
+ {
+ RemoveLink((performer, performerSwapped), false);
+ RemoveLink((target, targetSwapped), false);
+ return true;
+ }
+ }
+
+ if (!reversible)
+ {
+ RemoveLink(performer);
+ RemoveLink(target);
+ return true;
+ }
+
+ var perfComp = EnsureComp(performer);
+ var targetComp = EnsureComp(target);
+
+ perfComp.OriginalEntity = target;
+ targetComp.OriginalEntity = performer;
+
+ Dirty(performer, perfComp);
+ Dirty(target, targetComp);
+ return true;
+ }
+
+ ///
+ /// Checks whether the two entities can swap their minds, but doesn't swap them yet.
+ /// This handles whether they have mindshields or psionic shielding, it'll show popups accordingly.
+ ///
+ /// This assumes the performer caused the swap to happen.
+ /// The entity performing or causing the swap.
+ /// The entity being targeted.
+ /// Whether the check should ignore mindshields.
+ /// Whether the check should ignore psionic shielding.
+ /// Returns true if nothing blocks the swap, returns false otherwise.
+ private bool CanPerformerSwapWithTarget(EntityUid performer, EntityUid target, bool ignoreMindshields = false, bool ignorePsionicShielding = false)
+ {
+ if (!ignorePsionicShielding && !CanUsePsionicAbility(performer))
+ {
+ Popup.PopupEntity(Loc.GetString("psionic-cannot-use-psionics"), performer, performer, PopupType.SmallCaution);
+ return false;
+ }
+ // Mindshields actually shielding the mind?!?! Unplayable.
+ if (!ignoreMindshields)
+ {
+ if (_mindshieldQuery.HasComp(performer))
+ {
+ Popup.PopupEntity(Loc.GetString("psionic-power-mindswap-own-mindshield"), performer, performer, PopupType.MediumCaution);
+ return false;
+ }
+ if (_mindshieldQuery.HasComponent(target))
+ {
+ Popup.PopupEntity(Loc.GetString("psionic-cannot-target-shielded"), performer, performer, PopupType.SmallCaution);
+ Popup.PopupEntity(Loc.GetString("psionic-power-mindswap-target-mindshielded"), target, target, PopupType.MediumCaution);
+ return false;
+ }
+ }
+ if (!ignorePsionicShielding && !CanBeTargeted(target, hasAggressor: performer))
+ {
+ // Popup is handled in CanBeTargeted().
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Checks whether the two entities can swap their minds, but doesn't swap them yet.
+ /// This handles whether they have mindshields or psionic shielding, it'll show popups accordingly.
+ ///
+ ///
+ /// This does NOT assume the performer caused the swap to happen.
+ /// For that, use
+ ///
+ /// The first entity to be mindswapped.
+ /// The second entity to be mindswapped.
+ /// Whether the mindswap ignores mindshields.
+ /// Whether the mindswap ignores psionic shielding.
+ /// Returns true if nothing blocks the swap, returns false otherwise.
+ private bool CanExternallySwap(EntityUid firstTarget, EntityUid secondTarget, bool ignoreMindshields = false, bool ignorePsionicShielding = false)
+ {
+ var firstTargetBlocked = false;
+ var secondTargetBlocked = false;
+
+ if (!ignorePsionicShielding)
+ {
+ // Popup is handled in CanBeTargeted().
+ if (!CanBeTargeted(firstTarget))
+ {
+ firstTargetBlocked = true;
+ }
+ if (!CanBeTargeted(secondTarget))
+ {
+ secondTargetBlocked = true;
+ }
+ }
+ // If both already blocked the attempt via shielding, stop the check.
+ if (firstTargetBlocked && secondTargetBlocked)
+ return false;
+
+ // Mindshields actually shielding the mind?!?! Unplayable.
+ if (!ignoreMindshields)
+ {
+ if (!firstTargetBlocked && _mindshieldQuery.HasComp(firstTarget))
+ {
+ Popup.PopupEntity(Loc.GetString("psionic-power-mindswap-target-mindshielded"), firstTarget, firstTarget, PopupType.MediumCaution);
+ firstTargetBlocked = true;
+ }
+ if (!secondTargetBlocked && _mindshieldQuery.HasComponent(secondTarget))
+ {
+ Popup.PopupEntity(Loc.GetString("psionic-power-mindswap-target-mindshielded"), secondTarget, secondTarget, PopupType.MediumCaution);
+ secondTargetBlocked = true;
+ }
+ }
+ // Check if any one of them blocked it. If not, they can swap.
+ if (!firstTargetBlocked && !secondTargetBlocked)
+ return true;
+ // One of them blocked it, so the other gets a different popup.
+ var message = Loc.GetString("psionic-power-mindswap-no-shield-still-fail");
+
+ if (!firstTargetBlocked)
+ Popup.PopupEntity(message, firstTarget, firstTarget, PopupType.MediumCaution);
+ else // No need to check, as the second one NEEDS to be true if the first one was false.
+ Popup.PopupEntity(message, secondTarget, secondTarget, PopupType.MediumCaution);
+
+ return false;
+ }
+
+ ///
+ /// This removes the mindswap link from a mindswapped entity.
+ ///
+ /// The mindswapped entity.
+ /// Whether to show popups.
+ public void RemoveLink(Entity victim, bool showPopup = true)
+ {
+ // Sometimes people lose their link without having the component - MassMindSwap for example is a situation like that.
+ if (showPopup)
+ Popup.PopupEntity(Loc.GetString("psionic-power-mindswap-original-lost"), victim, victim, PopupType.MediumCaution);
+
+ if (!Resolve(victim, ref victim.Comp, false))
+ return;
+ // Remove the first action and link.
+ _action.RemoveAction(victim.Comp.ActionEntity);
+ RemCompDeferred(victim, victim.Comp);
+
+ var ev = new MindSwapLinkSeveredEvent();
+ RaiseLocalEvent(victim, ref ev);
+ }
+}
diff --git a/Content.Server/_DV/StationEvents/Components/MassMindSwapRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/MassMindSwapRuleComponent.cs
new file mode 100644
index 00000000000..e2bada9003a
--- /dev/null
+++ b/Content.Server/_DV/StationEvents/Components/MassMindSwapRuleComponent.cs
@@ -0,0 +1,83 @@
+using Content.Server._DV.StationEvents.GameRules;
+using Robust.Shared.Audio;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server._DV.StationEvents.Components;
+
+[RegisterComponent, Access(typeof(MassMindSwapRule))]
+public sealed partial class MassMindSwapRuleComponent : Component
+{
+ ///
+ /// The mind swap is only temporary if true.
+ ///
+ [DataField]
+ public bool IsTemporary;
+
+ ///
+ /// Whether this will also allow players to be mindswapped into NPCs.
+ ///
+ [DataField]
+ public bool OnlyPlayers;
+
+ ///
+ /// Whether the mind swap will ignore mindshields.
+ ///
+ [DataField]
+ public bool IgnoreMindshields = true;
+
+ ///
+ /// How long it'll take to activate after making the announcement.
+ ///
+ [DataField]
+ public TimeSpan Delay = TimeSpan.FromSeconds(15);
+
+ ///
+ /// How long victims have to wait to swap back if is true.
+ ///
+ [DataField]
+ public int ReturnSwapCooldown = 120;
+
+ ///
+ /// The FTL reference for what will be written in the announcement.
+ ///
+ [DataField]
+ public string AnnouncementText = "mass-mind-swap-event-announcement";
+
+ ///
+ /// The FTL reference for what will the be the name of the sender.
+ ///
+ [DataField]
+ public string AnnouncementSender = "mass-mind-swap-event-sender";
+
+ ///
+ /// The sound of the announcement warning of the imminent mind swap.
+ ///
+ [DataField]
+ public SoundSpecifier AnnouncementSound = new SoundPathSpecifier("/Audio/Misc/notice1.ogg");
+
+
+ ///
+ ///The warning sound that will play just before the mind swap.
+ ///
+ [DataField]
+ public SoundSpecifier SwapWarningSound = new SoundPathSpecifier("/Audio/_DV/Effects/clang2.ogg");
+
+ ///
+ /// The timestamp where the sound before the swap will be played.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ public TimeSpan? SoundTime;
+
+ ///
+ /// The timestamp where the swap will happen.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ public TimeSpan? SwapTime;
+
+ ///
+ /// How many pairs of people (2 in a pair) should be mind swapped.
+ /// If null, all targets will be paired up.
+ ///
+ [DataField]
+ public int? MaxNumberOfPairs;
+}
diff --git a/Content.Server/_DV/StationEvents/Components/MinorMassMindSwapRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/MinorMassMindSwapRuleComponent.cs
deleted file mode 100644
index fc4a0a089d5..00000000000
--- a/Content.Server/_DV/StationEvents/Components/MinorMassMindSwapRuleComponent.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using Content.Server._DV.StationEvents.GameRules;
-using Robust.Shared.Audio;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-
-namespace Content.Server._DV.StationEvents.Components;
-
-[RegisterComponent, Access(typeof(MinorMassMindSwapRule))]
-public sealed partial class MinorMassMindSwapRuleComponent : Component
-{
- ///
- /// The mind swap is only temporary if true.
- ///
- [DataField]
- public bool IsTemporary = false;
-
- [DataField]
- public TimeSpan Delay = TimeSpan.FromSeconds(60);
-
- [DataField]
- public int ReturnSwapCooldown = 120;
-
- [DataField]
- public SoundSpecifier AnnouncementSound = new SoundPathSpecifier("/Audio/Misc/notice1.ogg");
-
- [DataField]
- public SoundSpecifier SwapWarningSound = new SoundPathSpecifier("/Audio/_DV/Effects/clang2.ogg");
-
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
- public TimeSpan? SoundTime;
-
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
- public TimeSpan? SwapTime;
-
- [DataField]
- public int MaxNumberOfPairs = 3;
-}
diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs b/Content.Server/_DV/StationEvents/GameRules/MassMindSwapRule.cs
similarity index 70%
rename from Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs
rename to Content.Server/_DV/StationEvents/GameRules/MassMindSwapRule.cs
index bb8d744d6be..f5019934b1a 100644
--- a/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs
+++ b/Content.Server/_DV/StationEvents/GameRules/MassMindSwapRule.cs
@@ -1,9 +1,8 @@
+using Content.Server._DV.Psionics.Systems;
+using Content.Server._DV.StationEvents.Components;
using Content.Server.Chat.Systems;
-using Content.Server.StationEvents.Components;
using Content.Server.StationEvents.Events;
using Content.Shared._DV.Psionics.Components;
-using Content.Shared._DV.Psionics.Systems;
-using Content.Shared._DV.Psionics.Systems.PsionicPowers;
using Content.Shared.GameTicking.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
@@ -12,7 +11,7 @@ using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Random;
-namespace Content.Server.Nyanotrasen.StationEvents.Events;
+namespace Content.Server._DV.StationEvents.GameRules;
///
/// Forces a mind swap on all non-insulated potential psionic entities.
@@ -22,12 +21,12 @@ internal sealed class MassMindSwapRule : StationEventSystem Timing.CurTime)
@@ -76,41 +76,37 @@ internal sealed class MassMindSwapRule : StationEventSystem(psion))
{
- // This is so we don't bother mindswapping NPCs with NPCs.
psionicActors.Add(psion);
+ psionicPool.Add(psion);
}
+ else if (!component.OnlyPlayers)
+ psionicPool.Add(psion);
}
- // Shuffle the list of candidates.
- _random.Shuffle(psionicPool);
+ var maxPairs = component.MaxNumberOfPairs;
+ if (maxPairs.HasValue)
+ _random.Next(1, maxPairs.Value);
foreach (var actor in psionicActors)
{
- do
+ while (psionicPool.Count > 0 && maxPairs is null or > 0)
{
- if (psionicPool.Count == 0)
- // We ran out of candidates. Exit early.
- return;
-
- // Pop the last entry off.
- var other = psionicPool[^1];
- psionicPool.RemoveAt(psionicPool.Count - 1);
-
+ var other = _random.PickAndTake(psionicPool);
+ // Don't be yourself. Find someone else.
if (other == actor)
- // Don't be yourself. Find someone else.
continue;
// A valid swap target has been found.
// Remove this actor from the pool of swap candidates before they go.
psionicPool.Remove(actor);
+ if (maxPairs.HasValue)
+ maxPairs--;
- // Do the swap. Also ignore mindshields, because this is the big boi swap.
- _mindSwap.SwapMinds(actor, other, false, component.IsTemporary, true);
- } while (true);
+ _psionic.SwapMinds(actor, other, false, component.IsTemporary, component.IgnoreMindshields);
+ break;
+ }
}
}
}
diff --git a/Content.Server/_DV/StationEvents/GameRules/MinorMassMindSwapRule.cs b/Content.Server/_DV/StationEvents/GameRules/MinorMassMindSwapRule.cs
deleted file mode 100644
index 816af06b247..00000000000
--- a/Content.Server/_DV/StationEvents/GameRules/MinorMassMindSwapRule.cs
+++ /dev/null
@@ -1,101 +0,0 @@
-using Content.Server._DV.Psionics.Systems;
-using Content.Server._DV.StationEvents.Components;
-using Content.Server.Chat.Systems;
-using Content.Server.StationEvents.Events;
-using Content.Shared._DV.Psionics.Components;
-using Content.Shared._DV.Psionics.Systems.PsionicPowers;
-using Content.Shared.GameTicking.Components;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Mobs.Systems;
-using Robust.Server.Audio;
-using Robust.Shared.Audio;
-using Robust.Shared.Player;
-using Robust.Shared.Random;
-
-namespace Content.Server._DV.StationEvents.GameRules;
-
-///
-/// Forces a mind swap on a small amount of non-insulated psionic entities.
-///
-internal sealed class MinorMassMindSwapRule : StationEventSystem
-{
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly AudioSystem _audio = default!;
- [Dependency] private readonly ChatSystem _chat = default!;
- [Dependency] private readonly SharedMindSwapPowerSystem _mindSwap = default!;
- [Dependency] private readonly MobStateSystem _mobstateSystem = default!;
- [Dependency] private readonly PsionicSystem _psionic = default!;
-
- private TimeSpan _warningSoundLength;
- private ResolvedSoundSpecifier _resolvedWarningSound = string.Empty;
-
- protected override void Started(EntityUid uid, MinorMassMindSwapRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
- {
- base.Started(uid, component, gameRule, args);
-
- _resolvedWarningSound = _audio.ResolveSound(component.SwapWarningSound);
- _warningSoundLength = _audio.GetAudioLength(_resolvedWarningSound);
-
- component.SwapTime = Timing.CurTime + component.Delay;
- component.SoundTime = component.SwapTime - _warningSoundLength;
-
- component.MaxNumberOfPairs = component.MaxNumberOfPairs < 1 ? 1 : component.MaxNumberOfPairs;
-
- var announcement = Loc.GetString("minor-mass-mind-swap-event-announcement", ("time", component.Delay.TotalSeconds));
- var sender = Loc.GetString("minor-mass-mind-swap-event-sender");
-
- _chat.DispatchGlobalAnnouncement(announcement, sender, true, component.AnnouncementSound, Color.White);
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- var query = EntityQueryEnumerator();
-
- while (query.MoveNext(out var uid, out var comp, out var ruleComp))
- {
- if (comp.SwapTime == null)
- continue;
-
- if (comp.SoundTime != null && comp.SoundTime <= Timing.CurTime)
- {
- _audio.PlayGlobal(_resolvedWarningSound, Filter.Broadcast(), true);
- comp.SoundTime = null;
- continue;
- }
-
- if (comp.SwapTime > Timing.CurTime)
- continue;
-
- SwapMinds(comp);
- comp.SwapTime = null;
- GameTicker.EndGameRule(uid, ruleComp);
- }
- }
-
- private void SwapMinds(MinorMassMindSwapRuleComponent component)
- {
- List psionicActors = [];
-
- var query = EntityQueryEnumerator();
- while (query.MoveNext(out var psion, out _, out var mobState))
- {
- if (_mobstateSystem.IsAlive(psion, mobState) && HasComp(psion) && _psionic.CanBeTargeted(psion))
- // Only a list of Players
- psionicActors.Add(psion);
- }
-
- // We go with 4 pairs for now
- List actorsToSwap = [];
- var swapPairCount = _random.Next(1, component.MaxNumberOfPairs);
-
- for (; swapPairCount > 0 && psionicActors.Count > 1; swapPairCount--)
- {
- var target01 = _random.PickAndTake(psionicActors);
- var target02 = _random.PickAndTake(psionicActors);
-
- _mindSwap.SwapMinds(target01, target02, false, component.IsTemporary);
- }
- }
-}
diff --git a/Content.Shared/_DV/Psionics/Components/PsionicPowers/MindSwapPowerComponent.cs b/Content.Shared/_DV/Psionics/Components/PsionicPowers/MindSwapPowerComponent.cs
deleted file mode 100644
index d8f5576201d..00000000000
--- a/Content.Shared/_DV/Psionics/Components/PsionicPowers/MindSwapPowerComponent.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Robust.Shared.GameStates;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared._DV.Psionics.Components.PsionicPowers;
-
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
-public sealed partial class MindSwapPowerComponent : BasePsionicPowerComponent
-{
- public override EntProtoId ActionProtoId { get; set; } = "ActionMindSwapPsionic";
-
- public override string PowerName { get; set; } = "psionic-power-name-mindswap";
-
- public override int MinGlimmerChanged { get; set; } = 5;
-
- public override int MaxGlimmerChanged { get; set; } = 15;
-}
diff --git a/Content.Shared/_DV/Psionics/Events/AntiPsionicWeaponHitEvent.cs b/Content.Shared/_DV/Psionics/Events/AntiPsionicWeaponHitEvent.cs
new file mode 100644
index 00000000000..db53596fa72
--- /dev/null
+++ b/Content.Shared/_DV/Psionics/Events/AntiPsionicWeaponHitEvent.cs
@@ -0,0 +1,7 @@
+namespace Content.Shared._DV.Psionics.Events;
+
+///
+/// This is raised on the entity when it's struck with an anti-psionic weapon.
+///
+[ByRefEvent]
+public record struct AntiPsionicWeaponHitEvent;
diff --git a/Content.Shared/_DV/Psionics/Events/MindSwapLinkSeveredEvent.cs b/Content.Shared/_DV/Psionics/Events/MindSwapLinkSeveredEvent.cs
new file mode 100644
index 00000000000..41fecc34549
--- /dev/null
+++ b/Content.Shared/_DV/Psionics/Events/MindSwapLinkSeveredEvent.cs
@@ -0,0 +1,7 @@
+namespace Content.Shared._DV.Psionics.Events;
+
+///
+/// This is raised on the entity when their mindswap link is severed.
+///
+[ByRefEvent]
+public record struct MindSwapLinkSeveredEvent;
diff --git a/Content.Shared/_DV/Psionics/Events/PowerActionEvents/MindSwapPowerActionEvent.cs b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/MindSwappedReturnPowerActionEvent.cs
similarity index 68%
rename from Content.Shared/_DV/Psionics/Events/PowerActionEvents/MindSwapPowerActionEvent.cs
rename to Content.Shared/_DV/Psionics/Events/PowerActionEvents/MindSwappedReturnPowerActionEvent.cs
index dd1fead3521..92e12dac9a1 100644
--- a/Content.Shared/_DV/Psionics/Events/PowerActionEvents/MindSwapPowerActionEvent.cs
+++ b/Content.Shared/_DV/Psionics/Events/PowerActionEvents/MindSwappedReturnPowerActionEvent.cs
@@ -2,6 +2,4 @@ using Content.Shared.Actions;
namespace Content.Shared._DV.Psionics.Events.PowerActionEvents;
-public sealed partial class MindSwapPowerActionEvent : EntityTargetActionEvent;
-
public sealed partial class MindSwappedReturnPowerActionEvent : InstantActionEvent;
diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/MindSwappedReturnPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/MindSwappedReturnPowerSystem.cs
deleted file mode 100644
index 50a16ae8ea8..00000000000
--- a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/MindSwappedReturnPowerSystem.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using Content.Shared._DV.Psionics.Components;
-using Content.Shared._DV.Psionics.Components.PsionicPowers;
-using Content.Shared._DV.Psionics.Events;
-using Content.Shared._DV.Psionics.Events.PowerActionEvents;
-using Content.Shared.Popups;
-using Content.Shared.Speech;
-using Content.Shared.Stealth.Components;
-
-namespace Content.Shared._DV.Psionics.Systems.PsionicPowers;
-
-///
-/// Sorry if this is shitcode, but the return power actually should behave like a normal power - So it gets its own system.
-/// That way, we have automatical power inits, dispelled and mindbreaking, as well as checks for if someone can use a power.
-/// This is NOT its own power. It can ONLY exist if someone used a mindswap or a minor mass mind swap happened.
-///
-public sealed class MindSwappedReturnPowerSystem : BasePsionicPowerSystem
-{
- [Dependency] private readonly SharedMindSwapPowerSystem _mindSwap = default!;
- [Dependency] private readonly MetaDataSystem _metaDataSystem = default!;
-
- private EntityQuery _mindSwappedQuery;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnShutDown);
-
- _mindSwappedQuery = GetEntityQuery();
- }
-
- private void OnShutDown(Entity psionic, ref ComponentShutdown args)
- {
- // If the person is gibbed or otherwise deleted, it'll remove the links.
- if (Timing.ApplyingState
- || !TerminatingOrDeleted(psionic)
- || !_mindSwappedQuery.TryComp(psionic.Comp.OriginalEntity, out var targetComp))
- return;
-
- RemoveLink((psionic.Comp.OriginalEntity, targetComp));
- }
-
- protected override void OnPowerUsed(Entity psionic, ref MindSwappedReturnPowerActionEvent args)
- {
- _mindSwap.SwapMinds(psionic, psionic.Comp.OriginalEntity);
- AfterPowerUsed(psionic, args.Performer);
- }
-
- protected override void OnDispelled(Entity psionic, ref DispelledEvent args)
- {
- _mindSwap.SwapMinds(psionic, psionic.Comp.OriginalEntity, false);
- }
-
- public void RemoveLink(Entity victim, bool showPopup = true)
- {
- // Sometimes people lose their link without having the component - MassMindSwap for example is a situation like that.
- if (showPopup)
- Popup.PopupEntity(Loc.GetString("psionic-power-mindswap-original-lost"), victim, victim, PopupType.MediumCaution);
-
- if (!Resolve(victim, ref victim.Comp, false))
- return;
- // Remove the first action and link.
- Action.RemoveAction(victim.Comp.ActionEntity);
- RemCompDeferred(victim, victim.Comp);
-
- if (!HasComp(victim))
- return;
-
- RemComp(victim);
- RemComp(victim);
- EnsureComp(victim);
- EnsureComp(victim);
- _metaDataSystem.SetEntityName(victim, Loc.GetString("telegnostic-trapped-entity-name"));
- _metaDataSystem.SetEntityDescription(victim, Loc.GetString("telegnostic-trapped-entity-desc"));
- }
-}
diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedMindSwapPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedMindSwapPowerSystem.cs
deleted file mode 100644
index bfd91163266..00000000000
--- a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedMindSwapPowerSystem.cs
+++ /dev/null
@@ -1,167 +0,0 @@
-using Content.Shared._DV.Psionics.Components.PsionicPowers;
-using Content.Shared._DV.Psionics.Events.PowerActionEvents;
-using Content.Shared.Mind;
-using Content.Shared.Mindshield.Components;
-using Content.Shared.Popups;
-using JetBrains.Annotations;
-
-namespace Content.Shared._DV.Psionics.Systems.PsionicPowers;
-
-public abstract class SharedMindSwapPowerSystem : BasePsionicPowerSystem
-{
- [Dependency] private readonly SharedMindSystem _mindSystem = default!;
- [Dependency] private readonly MindSwappedReturnPowerSystem _mindSwapped = default!;
-
- private EntityQuery _mindSwappedQuery;
- private EntityQuery _mindshieldQuery;
-
- public override void Initialize()
- {
- base.Initialize();
-
- _mindSwappedQuery = GetEntityQuery();
- _mindshieldQuery = GetEntityQuery();
- }
-
- protected override void OnPowerUsed(Entity psionic, ref MindSwapPowerActionEvent args)
- {
- SwapMinds(args.Performer, args.Target);
- AfterPowerUsed(psionic, args.Performer);
- }
-
- // TODO: Fix the prediction issue when calling from server while the performer is the cause.
- // PopupClient() only shows up on client - But Telegnosis can't be put into shared.
- // So Telegnosis calls from server side - Leaving no PopupClient().
- // Using PopupEntity() will cause it to be called 12x times client-side.
- ///
- /// Checks whether the two entities can swap their minds.
- /// This handles whether the performer caused the mindswap, and whether they have mindshields.
- /// This also handles popups.
- ///
- /// The entity performing or causing the swap.
- /// The entity being targeted.
- /// Whether the check should ignore mindshields.
- /// Whether the check should ignore psionic shielding.
- /// Whether the performer actively caused or performed the swap.
- /// Returns true if possible, false if not.
- public bool CanSwap(EntityUid performer, EntityUid target, bool ignoreMindshields = false, bool ignorePsionicShielding = false, bool performerIsCause = true)
- {
- EntityUid? aggressor = performerIsCause ? performer : null;
-
- if (performerIsCause && !ignorePsionicShielding && !Psionic.CanUsePsionicAbility(performer))
- {
- // client-side prediction.
- Popup.PopupClient(Loc.GetString("psionic-cannot-use-psionics"), performer, performer);
- return false;
- }
- // If the performer isn't the cause, like mindswap events, they can be shielded.
- if (!performerIsCause && !ignorePsionicShielding && !Psionic.CanBeTargeted(performer))
- {
- // Popup is handled in CanBeTargeted().
- return false;
- }
- // Mindshields actually shielding the mind?!?! Unplayable.
- if (_mindshieldQuery.HasComp(performer) && !ignoreMindshields)
- {
- // Different messages whether they're the cause or not.
- if (!performerIsCause)
- {
- Popup.PopupEntity(Loc.GetString("psionic-power-mindswap-target-mindshielded"), performer, performer, PopupType.MediumCaution);
- return false;
- }
- Popup.PopupClient(Loc.GetString("psionic-power-mindswap-own-mindshield"), performer, performer, PopupType.SmallCaution);
- return false;
- }
- if (_mindshieldQuery.HasComponent(target) && !ignoreMindshields)
- {
- // Performer should only be notified if they're the cause of the attempt.
- if (performerIsCause)
- Popup.PopupClient(Loc.GetString("psionic-cannot-target-shielded"), performer, performer, PopupType.SmallCaution);
- Popup.PopupEntity(Loc.GetString("psionic-power-mindswap-target-mindshielded"), target, target, PopupType.MediumCaution);
- return false;
- }
- if (!Psionic.CanBeTargeted(target, hasAggressor: aggressor) && !ignorePsionicShielding)
- {
- // Popup is handled in CanBeTargeted().
- return false;
- }
-
- return true;
- }
-
- ///
- /// Swaps two minds.
- ///
- /// The entity performing or causing the swap/being targeted.
- /// The entity being targeted.
- /// Whether the performer is actively causing the swap.
- /// Whether the swap is reversible via the return power.
- /// Whether the swap should ignore mindshields.
- /// Whether the swap should ignore psionic shielding.
- /// True if the two were swapped, false if otherwise.
- [PublicAPI]
- public bool SwapMinds(EntityUid performer, EntityUid target, bool performerIsCause = true, bool reversible = true, bool ignoreMindshields = false, bool ignorePsionicShielding = false)
- {
- if (!CanSwap(performer, target, ignoreMindshields, ignorePsionicShielding, performerIsCause))
- return false;
-
- // Get the minds first. On transfer, they'll be gone.
- // This is here to prevent missing MindContainerComponent Resolve errors.
- if (!_mindSystem.TryGetMind(performer, out var performerMindId, out var performerMind))
- performerMind = null;
-
- if (!_mindSystem.TryGetMind(target, out var targetMindId, out var targetMind))
- targetMind = null;
-
- switch (performerMind)
- {
- // If no mind can be swapped, return.
- case null when targetMind == null:
- return false;
- // If performer has no mind, but target does, switch places.
- case null:
- (performer, target) = (target, performer);
- (performerMind, targetMind) = (targetMind, performerMind);
- (performerMindId, targetMindId) = (targetMindId, performerMindId);
- break;
- }
-
- //This is a terrible way to 'unattach' minds. I wanted to use UnVisit but in TransferTo's code they say
- //To unnatch the minds, do it like this.
- //Have to unnattach the minds before we reattach them via transfer. Still feels weird, but seems to work well.
- _mindSystem.TransferTo(performerMindId, null);
- // Do the transfer.
- if (targetMind != null)
- _mindSystem.TransferTo(targetMindId, performer, ghostCheckOverride: true, false, targetMind);
-
- _mindSystem.TransferTo(performerMindId, target, ghostCheckOverride: true, false, performerMind);
-
- if (_mindSwappedQuery.TryComp(performer, out var performerSwapped) && _mindSwappedQuery.TryComp(target, out var targetSwapped))
- {
- // Sanity check
- if (performerSwapped.OriginalEntity == target && targetSwapped.OriginalEntity == performer)
- {
- _mindSwapped.RemoveLink((performer, performerSwapped), false);
- _mindSwapped.RemoveLink((target, targetSwapped), false);
- return true;
- }
- }
-
- if (!reversible)
- {
- _mindSwapped.RemoveLink(performer);
- _mindSwapped.RemoveLink(target);
- return true;
- }
-
- var perfComp = EnsureComp(performer);
- var targetComp = EnsureComp(target);
-
- perfComp.OriginalEntity = target;
- targetComp.OriginalEntity = performer;
-
- Dirty(performer, perfComp);
- Dirty(target, targetComp);
- return true;
- }
-}
diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedMindSwappedReturnPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedMindSwappedReturnPowerSystem.cs
new file mode 100644
index 00000000000..c434a4a88fa
--- /dev/null
+++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedMindSwappedReturnPowerSystem.cs
@@ -0,0 +1,9 @@
+using Content.Shared._DV.Psionics.Components.PsionicPowers;
+using Content.Shared._DV.Psionics.Events.PowerActionEvents;
+
+namespace Content.Shared._DV.Psionics.Systems.PsionicPowers;
+
+///
+/// This is solely for prediction. Everything is in the server-side file.
+///
+public abstract class SharedMindSwappedReturnPowerSystem : BasePsionicPowerSystem;
diff --git a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedTelegnosisPowerSystem.cs b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedTelegnosisPowerSystem.cs
index 86a51373cee..471a1c5d145 100644
--- a/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedTelegnosisPowerSystem.cs
+++ b/Content.Shared/_DV/Psionics/Systems/PsionicPowers/SharedTelegnosisPowerSystem.cs
@@ -1,21 +1,27 @@
using Content.Shared._DV.Mind;
using Content.Shared._DV.Psionics.Components;
using Content.Shared._DV.Psionics.Components.PsionicPowers;
+using Content.Shared._DV.Psionics.Events;
using Content.Shared._DV.Psionics.Events.PowerActionEvents;
using Content.Shared.Examine;
using Content.Shared.Interaction.Events;
using Content.Shared.Mind.Components;
+using Content.Shared.Speech;
+using Content.Shared.Stealth.Components;
namespace Content.Shared._DV.Psionics.Systems.PsionicPowers;
public abstract class SharedTelegnosisPowerSystem : BasePsionicPowerSystem
{
+ [Dependency] private readonly MetaDataSystem _metaData = default!;
+
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnMindRemoved);
SubscribeLocalEvent(OnInteractionAttempt);
+ SubscribeLocalEvent(OnLinkSevered);
SubscribeLocalEvent(OnShowSSDIndicator);
SubscribeLocalEvent(OnExamine);
}
@@ -31,6 +37,16 @@ public abstract class SharedTelegnosisPowerSystem : BasePsionicPowerSystem victim, ref MindSwapLinkSeveredEvent args)
+ {
+ RemComp(victim);
+ RemComp(victim);
+ EnsureComp(victim);
+ EnsureComp(victim);
+ _metaData.SetEntityName(victim, Loc.GetString("telegnostic-trapped-entity-name"));
+ _metaData.SetEntityDescription(victim, Loc.GetString("telegnostic-trapped-entity-desc"));
+ }
+
private void OnShowSSDIndicator(Entity psionic, ref ShowSSDIndicatorEvent args)
{
if (!TryComp(psionic, out var mindSwapped) || !HasComp(mindSwapped.OriginalEntity))
diff --git a/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Items.cs b/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Items.cs
index b736a88731e..c32ec9f052b 100644
--- a/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Items.cs
+++ b/Content.Shared/_DV/Psionics/Systems/SubSystems/SharedPsionicSystem.Items.cs
@@ -1,10 +1,10 @@
using System.Linq;
using Content.Shared._DV.Psionics.Components;
using Content.Shared._DV.Psionics.Events;
-using Content.Shared._DV.Psionics.Systems.PsionicPowers;
using Content.Shared.Damage.Events;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
+using Content.Shared.Mobs.Components;
using Content.Shared.StatusEffectNew;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Audio.Systems;
@@ -17,7 +17,6 @@ public abstract partial class SharedPsionicSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] protected readonly SharedAudioSystem Audio = default!;
- [Dependency] private readonly SharedMindSwapPowerSystem _mindSwapPowerSystem = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
private void InitializeItems()
@@ -86,6 +85,9 @@ public abstract partial class SharedPsionicSystem
{
foreach (var target in args.HitEntities)
{
+ var ev = new AntiPsionicWeaponHitEvent();
+ RaiseLocalEvent(target, ref ev);
+
if (HasComp(target))
{
Audio.PlayPredicted(weapon.Comp.HitSound, target, args.User);
@@ -94,22 +96,12 @@ public abstract partial class SharedPsionicSystem
if (Random.Prob(weapon.Comp.DisableChance))
_statusEffects.TryUpdateStatusEffectDuration(target, PsionicsDisabledProtoId, TimeSpan.FromSeconds(10));
}
-
- if (TryComp(target, out var swapped))
+ else if (HasComp(target) && weapon.Comp.Punish && Random.Prob(weapon.Comp.PunishChance))
{
- _mindSwapPowerSystem.SwapMinds(target, swapped.OriginalEntity, false);
- return;
+ _stuttering.DoStutter(args.User, TimeSpan.FromMinutes(5), false);
+ _stun.TryKnockdown(args.User, TimeSpan.FromSeconds(5), false, drop: false);
+ _jittering.DoJitter(args.User, TimeSpan.FromSeconds(5), false);
}
-
- if (!weapon.Comp.Punish
- || !HasComp(target)
- || HasComp(target)
- || !Random.Prob(weapon.Comp.PunishChance))
- continue;
-
- _stuttering.DoStutter(args.User, TimeSpan.FromMinutes(5), false);
- _stun.TryKnockdown(args.User, TimeSpan.FromSeconds(5), false, drop: false);
- _jittering.DoJitter(args.User, TimeSpan.FromSeconds(5), false);
}
}
diff --git a/Resources/Locale/en-US/_DV/abilities/psionic.ftl b/Resources/Locale/en-US/_DV/abilities/psionic.ftl
index b3acb17a50f..33940e78ca6 100644
--- a/Resources/Locale/en-US/_DV/abilities/psionic.ftl
+++ b/Resources/Locale/en-US/_DV/abilities/psionic.ftl
@@ -34,6 +34,7 @@ psionic-power-metapsionic-power-detected = You detect that {$power} was used nea
# Mindswap
psionic-power-mindswap-target-mindshielded = Your mindshield.. surprisingly shielded your mind from an psionic influence.
psionic-power-mindswap-own-mindshield = Your mindshield.. stops your mind from leaving your body.
+psionic-power-mindswap-no-shield-still-fail = Your mind seemed to wander.. but found no place to stay.
psionic-power-mindswap-original-lost = The psionic tether to your original body was severed!
# Noospheric Zap
@@ -120,20 +121,6 @@ eruption-warning-window-prompt-text-part = You feel a strong pressure building u
Do you understand?
eruption-warning-window-acknowledge-button = I Understand
-## Psionic Gamerule Messages
-gamerule-noospheric-zap-seize = An external eruption overwhelms your mind!
-gamerule-noospheric-zap-seize-potential-regained = Your mind restructures.. it demands knowledge...
-
-psionic-nosebleed-message = Your nose starts gushing blood!
-
-mass-mind-swap-event-announcement = Warning: abnormal glimmer discharge detected. Mass consciousness transfer event imminent, T-{$time} seconds. Please equip psionically-insulating headwear immediately.
-mass-mind-swap-event-sender = Sophic Grammateus
-
-minor-mass-mind-swap-event-announcement = Warning: abnormal glimmer discharge detected. Minor Mass Consciousness transfer event imminent, T-{$time} seconds. Please equip psionically-insulating headwear immedieately.
-minor-mass-mind-swap-event-sender = Sophic Grammateus
-
psionic-power-mass-sleep-warning = Your eyelids begin to droop...
telegnosis-power-ssd = { CAPITALIZE(POSS-ADJ($ent)) } eyes are unfocused and darting around, as if trying to see something that isn't there.
-
-glimmer-restyle-event = You feel like something changed about your looks...
diff --git a/Resources/Locale/en-US/_DV/game-ticking/game-rules/rule-psionic.ftl b/Resources/Locale/en-US/_DV/game-ticking/game-rules/rule-psionic.ftl
new file mode 100644
index 00000000000..b98a8bda124
--- /dev/null
+++ b/Resources/Locale/en-US/_DV/game-ticking/game-rules/rule-psionic.ftl
@@ -0,0 +1,13 @@
+## Psionic Gamerule Messages
+gamerule-noospheric-zap-seize = An external eruption overwhelms your mind!
+gamerule-noospheric-zap-seize-potential-regained = Your mind restructures.. it demands knowledge...
+
+psionic-nosebleed-message = Your nose starts gushing blood!
+
+mass-mind-swap-event-announcement = Warning: Abnormal glimmer discharge detected. Mass consciousness transfer event imminent, T-{$time} seconds. Please equip psionically-insulating headwear immediately.
+mass-mind-swap-event-sender = Sophic Grammateus
+
+minor-mass-mind-swap-event-announcement = Warning: Abnormal glimmer discharge detected. Minor Mass Consciousness transfer event imminent, T-{$time} seconds. Please equip psionically-insulating headwear immediately.
+minor-mass-mind-swap-event-sender = Sophic Grammateus
+
+glimmer-restyle-event = You feel like something changed about your looks...
diff --git a/Resources/Prototypes/_DV/Actions/psionic.yml b/Resources/Prototypes/_DV/Actions/psionic.yml
index 17019c147fc..69e2b64f966 100644
--- a/Resources/Prototypes/_DV/Actions/psionic.yml
+++ b/Resources/Prototypes/_DV/Actions/psionic.yml
@@ -40,25 +40,6 @@
- type: InstantAction
event: !type:MassSleepPowerActionEvent
-- type: entity
- parent: BasePsionicAction
- id: ActionMindSwapPsionic
- name: Mind Swap
- description: Swap minds with the target. Either can change back after 20 seconds.
- components:
- - type: Action
- icon:
- sprite: _DV/Interface/Actions/actions_psionics.rsi
- state: mind_swap
- useDelay: 240
- checkCanInteract: false
- - type: TargetAction
- checkCanAccess: false
- range: 8
- - type: EntityTargetAction
- canTargetSelf: false
- event: !type:MindSwapPowerActionEvent
-
- type: entity
parent: BasePsionicAction
id: ActionMindSwapReturn
diff --git a/Resources/Prototypes/_DV/GameRules/glimmer_events.yml b/Resources/Prototypes/_DV/GameRules/glimmer_events.yml
index 9e443aeb305..d01ec94f541 100644
--- a/Resources/Prototypes/_DV/GameRules/glimmer_events.yml
+++ b/Resources/Prototypes/_DV/GameRules/glimmer_events.yml
@@ -100,8 +100,12 @@
minimumGlimmer: 750
glimmerBurnLower: 40
glimmerBurnUpper: 80
- - type: MinorMassMindSwapRule
+ - type: MassMindSwapRule
+ delay: 60
isTemporary: true
+ onlyPlayers: true
+ ignoreMindshields: false
+ maxNumberOfPairs: 3
- type: entity
abstract: true
diff --git a/Resources/Prototypes/_DV/Psionics/psionicPowers.yml b/Resources/Prototypes/_DV/Psionics/psionicPowers.yml
index a3c4583b85d..b692d27c3df 100644
--- a/Resources/Prototypes/_DV/Psionics/psionicPowers.yml
+++ b/Resources/Prototypes/_DV/Psionics/psionicPowers.yml
@@ -19,8 +19,6 @@
weight: 0.5
- id: NoosphericZapPowerEntity
weight: 0.3
- - id: MindSwapPowerEntity
- weight: 0.15
#- id: PsionicEruptionPowerEntity
# weight: 0.1
@@ -67,12 +65,6 @@
components:
- type: NoosphericZapPower
-- type: entity
- id: MindSwapPowerEntity
- categories: [ HideSpawnMenu ]
- components:
- - type: MindSwapPower
-
- type: entity
id: PsionicRegenerationPowerEntity
categories: [ HideSpawnMenu ]