Delta-v/Content.Server/_DV/Psionics/Systems/SubSystems/PsionicSystem.Mindswap.cs

249 lines
11 KiB
C#

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