This commit is contained in:
Sir Warock 2026-08-14 01:46:09 +00:00 committed by GitHub
commit 116d8b39b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 488 additions and 562 deletions

View File

@ -1,8 +0,0 @@
using Content.Shared._DV.Psionics.Systems.PsionicPowers;
namespace Content.Client._DV.Psionics.Systems.PsionicPowers;
/// <summary>
/// This solely exists for prediction.
/// </summary>
public sealed class MindSwapPowerSystem : SharedMindSwapPowerSystem;

View File

@ -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;
/// <summary>
/// This is solely for prediction. Everything is in the server-side file.
/// </summary>
public sealed class MindSwappedReturnPowerSystem : SharedMindSwappedReturnPowerSystem
{
protected override void OnPowerUsed(Entity<MindSwappedReturnPowerComponent> psionic, ref MindSwappedReturnPowerActionEvent args)
{
}
}

View File

@ -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
{
/// <summary>
/// The mind swap is only temporary if true.
/// </summary>
[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;
}

View File

@ -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<GhostAttemptHandleEvent>(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<MindSwappedReturnPowerComponent>(args.Mind.CurrentEntity, out var component)
&& Action.GetAction(component.ActionEntity) is { } action
&& action.Comp.AttachedEntity is not null)
{
args.Result = false;
args.Handled = true;
}
}
}

View File

@ -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<MindSwappedReturnPowerComponent> _mindSwappedQuery;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MindSwappedReturnPowerComponent, ComponentShutdown>(OnShutDown);
SubscribeLocalEvent<MindSwappedReturnPowerComponent, AntiPsionicWeaponHitEvent>(OnAntiPsionicHit);
_mindSwappedQuery = GetEntityQuery<MindSwappedReturnPowerComponent>();
}
private void OnShutDown(Entity<MindSwappedReturnPowerComponent> 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<MindSwappedReturnPowerComponent> psionic, ref MindSwappedReturnPowerActionEvent args)
{
_psionic.SwapMinds(psionic, psionic.Comp.OriginalEntity);
AfterPowerUsed(psionic, args.Performer);
}
protected override void OnDispelled(Entity<MindSwappedReturnPowerComponent> psionic, ref DispelledEvent args)
{
_psionic.SwapMinds(psionic, psionic.Comp.OriginalEntity, false);
}
private void OnAntiPsionicHit(Entity<MindSwappedReturnPowerComponent> psionic, ref AntiPsionicWeaponHitEvent args)
{
_psionic.SwapMinds(psionic, psionic.Comp.OriginalEntity, false);
}
}

View File

@ -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<TelegnosisPowerComponent> psionic, ref TelegnosisPowerActionEvent args)
{
// TODO: Fix this. MindSwapSystem cannot handle popups when called from serverside while the performer is the cause.
if (HasComp<MindShieldComponent>(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);

View File

@ -17,6 +17,7 @@ public sealed partial class PsionicSystem : SharedPsionicSystem
SubscribeLocalEvent<PotentialPsionicComponent, PlayerSpawnCompleteEvent>(OnPlayerSpawnComplete);
InitializeItems();
InitializeMindSwap();
}
private void OnPlayerSpawnComplete(Entity<PotentialPsionicComponent> potPsionic, ref PlayerSpawnCompleteEvent args)

View File

@ -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<PsionicallyInsulativeComponent, InventoryRelayedEvent<NoosphericFryEvent>>(OnFry);
}

View File

@ -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;
/// <summary>
/// This handles swapping the minds of beings for psionic reasons, such as telegnosis or mass mindswaps.
/// </summary>
public sealed partial class PsionicSystem
{
[Dependency] private readonly ActionsSystem _action = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
private EntityQuery<MindSwappedReturnPowerComponent> _mindSwappedQuery;
private EntityQuery<MindShieldComponent> _mindshieldQuery;
private void InitializeMindSwap()
{
_mindSwappedQuery = GetEntityQuery<MindSwappedReturnPowerComponent>();
_mindshieldQuery = GetEntityQuery<MindShieldComponent>();
SubscribeLocalEvent<GhostAttemptHandleEvent>(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<MindSwappedReturnPowerComponent>(args.Mind.CurrentEntity, out var component)
&& _action.GetAction(component.ActionEntity) is { } action
&& action.Comp.AttachedEntity is not null)
{
args.Result = false;
args.Handled = true;
}
}
/// <summary>
/// Swaps two minds.
/// </summary>
/// <param name="performer">The entity performing or causing the swap/being targeted.</param>
/// <param name="target">The entity being targeted.</param>
/// <param name="performerIsCause">Whether the performer is actively causing the swap.</param>
/// <param name="reversible">Whether the swap is reversible via the return power.</param>
/// <param name="ignoreMindshields">Whether the swap should ignore mindshields.</param>
/// <param name="ignorePsionicShielding">Whether the swap should ignore psionic shielding.</param>
/// <returns>True if the two were swapped, false if otherwise.</returns>
[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<MindSwappedReturnPowerComponent>(performer);
var targetComp = EnsureComp<MindSwappedReturnPowerComponent>(target);
perfComp.OriginalEntity = target;
targetComp.OriginalEntity = performer;
Dirty(performer, perfComp);
Dirty(target, targetComp);
return true;
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>This assumes the performer caused the swap to happen.</remarks>
/// <param name="performer">The entity performing or causing the swap.</param>
/// <param name="target">The entity being targeted.</param>
/// <param name="ignoreMindshields">Whether the check should ignore mindshields.</param>
/// <param name="ignorePsionicShielding">Whether the check should ignore psionic shielding.</param>
/// <returns>Returns true if nothing blocks the swap, returns false otherwise.</returns>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// This does NOT assume the performer caused the swap to happen.
/// For that, use <see cref="CanPerformerSwapWithTarget"/>
/// </remarks>
/// <param name="firstTarget">The first entity to be mindswapped.</param>
/// <param name="secondTarget">The second entity to be mindswapped.</param>
/// <param name="ignoreMindshields">Whether the mindswap ignores mindshields.</param>
/// <param name="ignorePsionicShielding">Whether the mindswap ignores psionic shielding.</param>
/// <returns>Returns true if nothing blocks the swap, returns false otherwise.</returns>
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;
}
/// <summary>
/// This removes the mindswap link from a mindswapped entity.
/// </summary>
/// <param name="victim">The mindswapped entity.</param>
/// <param name="showPopup">Whether to show popups.</param>
public void RemoveLink(Entity<MindSwappedReturnPowerComponent?> 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);
}
}

View File

@ -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
{
/// <summary>
/// The mind swap is only temporary if true.
/// </summary>
[DataField]
public bool IsTemporary;
/// <summary>
/// Whether this will also allow players to be mindswapped into NPCs.
/// </summary>
[DataField]
public bool OnlyPlayers;
/// <summary>
/// Whether the mind swap will ignore mindshields.
/// </summary>
[DataField]
public bool IgnoreMindshields = true;
/// <summary>
/// How long it'll take to activate after making the announcement.
/// </summary>
[DataField]
public TimeSpan Delay = TimeSpan.FromSeconds(15);
/// <summary>
/// How long victims have to wait to swap back if <see cref="IsTemporary"/> is true.
/// </summary>
[DataField]
public int ReturnSwapCooldown = 120;
/// <summary>
/// The FTL reference for what will be written in the announcement.
/// </summary>
[DataField]
public string AnnouncementText = "mass-mind-swap-event-announcement";
/// <summary>
/// The FTL reference for what will the be the name of the sender.
/// </summary>
[DataField]
public string AnnouncementSender = "mass-mind-swap-event-sender";
/// <summary>
/// The sound of the announcement warning of the imminent mind swap.
/// </summary>
[DataField]
public SoundSpecifier AnnouncementSound = new SoundPathSpecifier("/Audio/Misc/notice1.ogg");
/// <summary>
///The warning sound that will play just before the mind swap.
/// </summary>
[DataField]
public SoundSpecifier SwapWarningSound = new SoundPathSpecifier("/Audio/_DV/Effects/clang2.ogg");
/// <summary>
/// The timestamp where the sound before the swap will be played.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan? SoundTime;
/// <summary>
/// The timestamp where the swap will happen.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan? SwapTime;
/// <summary>
/// How many pairs of people (2 in a pair) should be mind swapped.
/// If null, all targets will be paired up.
/// </summary>
[DataField]
public int? MaxNumberOfPairs;
}

View File

@ -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
{
/// <summary>
/// The mind swap is only temporary if true.
/// </summary>
[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;
}

View File

@ -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;
/// <summary>
/// Forces a mind swap on all non-insulated potential psionic entities.
@ -22,12 +21,12 @@ internal sealed class MassMindSwapRule : StationEventSystem<MassMindSwapRuleComp
[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 SharedPsionicSystem _psionic = default!;
[Dependency] private readonly PsionicSystem _psionic = default!;
private TimeSpan _warningSoundLength;
private ResolvedSoundSpecifier _resolvedWarningSound = String.Empty;
protected override void Started(EntityUid uid, MassMindSwapRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
@ -38,8 +37,8 @@ internal sealed class MassMindSwapRule : StationEventSystem<MassMindSwapRuleComp
component.SwapTime = Timing.CurTime + component.Delay;
component.SoundTime = component.SwapTime - _warningSoundLength;
var announcement = Loc.GetString("mass-mind-swap-event-announcement", ("time", component.Delay.TotalSeconds));
var sender = Loc.GetString("mass-mind-swap-event-sender");
var announcement = Loc.GetString(component.AnnouncementText, ("time", component.Delay.TotalSeconds));
var sender = Loc.GetString(component.AnnouncementSender);
_chat.DispatchGlobalAnnouncement(announcement, sender, true, component.AnnouncementSound, Color.White);
}
@ -54,6 +53,7 @@ internal sealed class MassMindSwapRule : StationEventSystem<MassMindSwapRuleComp
{
_audio.PlayGlobal(_resolvedWarningSound, Filter.Broadcast(), true);
comp.SoundTime = null;
continue;
}
if (comp.SwapTime == null || comp.SwapTime > Timing.CurTime)
@ -76,41 +76,37 @@ internal sealed class MassMindSwapRule : StationEventSystem<MassMindSwapRuleComp
if (!_mobStateSystem.IsAlive(psion, mobState) || !_psionic.CanBeTargeted(psion))
continue;
psionicPool.Add(psion);
if (HasComp<ActorComponent>(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;
}
}
}
}

View File

@ -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;
/// <summary>
/// Forces a mind swap on a small amount of non-insulated psionic entities.
/// </summary>
internal sealed class MinorMassMindSwapRule : StationEventSystem<MinorMassMindSwapRuleComponent>
{
[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<MinorMassMindSwapRuleComponent, GameRuleComponent>();
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<EntityUid> psionicActors = [];
var query = EntityQueryEnumerator<PotentialPsionicComponent, MobStateComponent>();
while (query.MoveNext(out var psion, out _, out var mobState))
{
if (_mobstateSystem.IsAlive(psion, mobState) && HasComp<ActorComponent>(psion) && _psionic.CanBeTargeted(psion))
// Only a list of Players
psionicActors.Add(psion);
}
// We go with 4 pairs for now
List<EntityUid> 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);
}
}
}

View File

@ -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;
}

View File

@ -0,0 +1,7 @@
namespace Content.Shared._DV.Psionics.Events;
/// <summary>
/// This is raised on the entity when it's struck with an anti-psionic weapon.
/// </summary>
[ByRefEvent]
public record struct AntiPsionicWeaponHitEvent;

View File

@ -0,0 +1,7 @@
namespace Content.Shared._DV.Psionics.Events;
/// <summary>
/// This is raised on the entity when their mindswap link is severed.
/// </summary>
[ByRefEvent]
public record struct MindSwapLinkSeveredEvent;

View File

@ -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;

View File

@ -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;
/// <summary>
/// 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.
/// </summary>
public sealed class MindSwappedReturnPowerSystem : BasePsionicPowerSystem<MindSwappedReturnPowerComponent, MindSwappedReturnPowerActionEvent>
{
[Dependency] private readonly SharedMindSwapPowerSystem _mindSwap = default!;
[Dependency] private readonly MetaDataSystem _metaDataSystem = default!;
private EntityQuery<MindSwappedReturnPowerComponent> _mindSwappedQuery;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MindSwappedReturnPowerComponent, ComponentShutdown>(OnShutDown);
_mindSwappedQuery = GetEntityQuery<MindSwappedReturnPowerComponent>();
}
private void OnShutDown(Entity<MindSwappedReturnPowerComponent> 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<MindSwappedReturnPowerComponent> psionic, ref MindSwappedReturnPowerActionEvent args)
{
_mindSwap.SwapMinds(psionic, psionic.Comp.OriginalEntity);
AfterPowerUsed(psionic, args.Performer);
}
protected override void OnDispelled(Entity<MindSwappedReturnPowerComponent> psionic, ref DispelledEvent args)
{
_mindSwap.SwapMinds(psionic, psionic.Comp.OriginalEntity, false);
}
public void RemoveLink(Entity<MindSwappedReturnPowerComponent?> 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<TelegnosticProjectionComponent>(victim))
return;
RemComp<PsionicallyInvisibleComponent>(victim);
RemComp<StealthComponent>(victim);
EnsureComp<SpeechComponent>(victim);
EnsureComp<DispellableComponent>(victim);
_metaDataSystem.SetEntityName(victim, Loc.GetString("telegnostic-trapped-entity-name"));
_metaDataSystem.SetEntityDescription(victim, Loc.GetString("telegnostic-trapped-entity-desc"));
}
}

View File

@ -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<MindSwapPowerComponent, MindSwapPowerActionEvent>
{
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
[Dependency] private readonly MindSwappedReturnPowerSystem _mindSwapped = default!;
private EntityQuery<MindSwappedReturnPowerComponent> _mindSwappedQuery;
private EntityQuery<MindShieldComponent> _mindshieldQuery;
public override void Initialize()
{
base.Initialize();
_mindSwappedQuery = GetEntityQuery<MindSwappedReturnPowerComponent>();
_mindshieldQuery = GetEntityQuery<MindShieldComponent>();
}
protected override void OnPowerUsed(Entity<MindSwapPowerComponent> 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.
/// <summary>
/// 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.
/// </summary>
/// <param name="performer">The entity performing or causing the swap.</param>
/// <param name="target">The entity being targeted.</param>
/// <param name="ignoreMindshields">Whether the check should ignore mindshields.</param>
/// <param name="ignorePsionicShielding">Whether the check should ignore psionic shielding.</param>
/// <param name="performerIsCause">Whether the performer actively caused or performed the swap.</param>
/// <returns>Returns true if possible, false if not.</returns>
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;
}
/// <summary>
/// Swaps two minds.
/// </summary>
/// <param name="performer">The entity performing or causing the swap/being targeted.</param>
/// <param name="target">The entity being targeted.</param>
/// <param name="performerIsCause">Whether the performer is actively causing the swap.</param>
/// <param name="reversible">Whether the swap is reversible via the return power.</param>
/// <param name="ignoreMindshields">Whether the swap should ignore mindshields.</param>
/// <param name="ignorePsionicShielding">Whether the swap should ignore psionic shielding.</param>
/// <returns>True if the two were swapped, false if otherwise.</returns>
[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<MindSwappedReturnPowerComponent>(performer);
var targetComp = EnsureComp<MindSwappedReturnPowerComponent>(target);
perfComp.OriginalEntity = target;
targetComp.OriginalEntity = performer;
Dirty(performer, perfComp);
Dirty(target, targetComp);
return true;
}
}

View File

@ -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;
/// <summary>
/// This is solely for prediction. Everything is in the server-side file.
/// </summary>
public abstract class SharedMindSwappedReturnPowerSystem : BasePsionicPowerSystem<MindSwappedReturnPowerComponent, MindSwappedReturnPowerActionEvent>;

View File

@ -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<TelegnosisPowerComponent, TelegnosisPowerActionEvent>
{
[Dependency] private readonly MetaDataSystem _metaData = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TelegnosticProjectionComponent, MindRemovedMessage>(OnMindRemoved);
SubscribeLocalEvent<TelegnosticProjectionComponent, InteractionAttemptEvent>(OnInteractionAttempt);
SubscribeLocalEvent<TelegnosticProjectionComponent, MindSwapLinkSeveredEvent>(OnLinkSevered);
SubscribeLocalEvent<TelegnosisPowerComponent, ShowSSDIndicatorEvent>(OnShowSSDIndicator);
SubscribeLocalEvent<TelegnosisPowerComponent, ExaminedEvent>(OnExamine);
}
@ -31,6 +37,16 @@ public abstract class SharedTelegnosisPowerSystem : BasePsionicPowerSystem<Teleg
args.Cancelled = true;
}
private void OnLinkSevered(Entity<TelegnosticProjectionComponent> victim, ref MindSwapLinkSeveredEvent args)
{
RemComp<PsionicallyInvisibleComponent>(victim);
RemComp<StealthComponent>(victim);
EnsureComp<SpeechComponent>(victim);
EnsureComp<DispellableComponent>(victim);
_metaData.SetEntityName(victim, Loc.GetString("telegnostic-trapped-entity-name"));
_metaData.SetEntityDescription(victim, Loc.GetString("telegnostic-trapped-entity-desc"));
}
private void OnShowSSDIndicator(Entity<TelegnosisPowerComponent> psionic, ref ShowSSDIndicatorEvent args)
{
if (!TryComp<MindSwappedReturnPowerComponent>(psionic, out var mindSwapped) || !HasComp<TelegnosticProjectionComponent>(mindSwapped.OriginalEntity))

View File

@ -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<PsionicComponent>(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<Components.PsionicPowers.MindSwappedReturnPowerComponent>(target, out var swapped))
else if (HasComp<MobStateComponent>(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<PotentialPsionicComponent>(target)
|| HasComp<PsionicComponent>(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);
}
}

View File

@ -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...

View File

@ -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...

View File

@ -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

View File

@ -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

View File

@ -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 ]