Merge afc31d36a9 into fd5b16abb3
This commit is contained in:
commit
116d8b39b9
|
|
@ -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;
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>;
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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...
|
||||
|
|
|
|||
|
|
@ -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...
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ]
|
||||
|
|
|
|||
Loading…
Reference in New Issue