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 ]