using Content.Shared._DV.Psionics.Components;
using Content.Shared._DV.Psionics.Components.PsionicPowers;
using Content.Shared._DV.Psionics.Events;
using Content.Shared.Actions;
using Content.Shared.Administration.Logs;
using Content.Shared.DoAfter;
using Content.Shared.Inventory;
using Content.Shared.Popups;
using Content.Shared.Psionics.Glimmer;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Shared._DV.Psionics.Systems.PsionicPowers;
///
/// This is a base psionic power system that handles being mindbroken and checks for being able to use psionic powers automagically!
/// You WILL NEED to parent of this.
///
public abstract class BasePsionicPowerSystem : EntitySystem where T : BasePsionicPowerComponent where T1 : BaseActionEvent
{
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] protected readonly IRobustRandom Random = default!;
[Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] protected readonly SharedActionsSystem Action = default!;
[Dependency] protected readonly SharedDoAfterSystem DoAfter = default!;
[Dependency] private readonly GlimmerSystem _glimmer = default!;
[Dependency] protected readonly SharedPopupSystem Popup = default!;
[Dependency] protected readonly SharedPsionicSystem Psionic = default!;
///
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnPowerInit);
SubscribeLocalEvent(OnPowerActionUsed);
SubscribeLocalEvent(OnDispelled);
SubscribeLocalEvent(OnMindBroken);
SubscribeLocalEvent(OnGrantingClothingEquipped);
SubscribeLocalEvent(OnPsionicallySuppressed);
SubscribeLocalEvent(OnStoppedPsionicallySuppressed);
}
///
/// This is called on every power upon initialization, so the action gets put into the action container.
/// The only exemption is powers that don't have just one action or require special actions at initialize.
///
/// The psionic power whose action is put into the container.
/// The args from the event.
protected virtual void OnPowerInit(Entity power, ref MapInitEvent args)
{
Action.AddAction(power, ref power.Comp.ActionEntity, power.Comp.ActionProtoId);
var psionicComp = EnsureComp(power);
psionicComp.PsionicPowersActionEntities.Add(power.Comp.ActionEntity);
Dirty(power);
}
///
/// This is called whenever an entity pushes the psionic power action button.
///
/// The psionic who attempts to use a psionic power.
/// The action event for said power.
private void OnPowerActionUsed(Entity psionic, ref T1 args)
{
if (Timing.ApplyingState || args.Handled)
return;
args.Handled = true;
if (Psionic.CanUsePsionicAbility(psionic))
{
OnPowerUsed(psionic, ref args);
return;
}
Popup.PopupClient(Loc.GetString("psionic-cannot-use-psionics"), args.Performer, args.Performer);
}
///
/// This is the creme of the system. If you add a new power, you will want to call this.
/// This is where the actual power takes place that follows after the button press.
/// You will need to call LogPowerUsed() at the end.
/// You will need to call Psionic.CanBeTargeted(target) if you add a power that can target things.
/// IMPORTANT! The psionic entity DOES NOT HAVE TO BE THE PLAYER! It can be a clothing!
/// If you want something to affect the player everytime, use args.Performer!
/// psionic.Owner => Source of the power. args.Performer => The user of the power.
///
/// The source of the power.
/// The event.
protected abstract void OnPowerUsed(Entity psionic, ref T1 args);
///
/// This handles being dispelled while having an active DoAfter.
/// It'll be enough for most usages.
/// But you can overwrite it if you have multiple DoAfters or require a specific interaction with being dispelled.
///
/// The psionic who has been dispelled.
/// The DispelledEvent.
protected virtual void OnDispelled(Entity psionic, ref DispelledEvent args)
{
if (args.Handled
|| psionic.Comp.GetDoAfterId() is not { } doAfterId)
return;
DoAfter.Cancel(doAfterId);
Popup.PopupClient(Loc.GetString("psionic-dispelled"), args.Target, args.Target, PopupType.MediumCaution);
psionic.Comp.RemoveSavedDoAfterId();
args.Handled = true;
Dirty(psionic);
}
///
/// This handles when the psionic gets mindbroken via chemicals or other.
///
/// The psionic who is being mindbroken.
/// The mindbreaking event.
protected virtual void OnMindBroken(Entity psionic, ref PsionicMindBrokenEvent args)
{
if (psionic.Comp.CanBeRemoved || args.Force)
{
args.Success = true;
TryStopDoAfter(psionic, psionic.Owner); // Mindbreaking already has a popup, so no popup for breaking doAfters.
Action.RemoveAction(psionic.Comp.ActionEntity);
RemComp(psionic);
return;
}
args.AllRemoved = false;
}
///
/// This handles equipping gear that has psionic abilities. These will be automatically relayed to the user.
///
/// The clothing that grants the power and has the component.
/// The event.
private void OnGrantingClothingEquipped(Entity psionicClothing, ref GetItemActionsEvent args)
{
if (args.SlotFlags is null or SlotFlags.POCKET
|| !HasComp(args.User)) // IPCs and non-player organics shouldn't be able to use abilities.
return;
args.AddAction(psionicClothing.Comp.ActionEntity);
}
///
/// This interrupts DoAfters when being suppressed psionically.
/// No putting on skullcaps AFTER casting your DoAfter to be secure from Dispels.
///
/// The power source.
/// The event.
protected virtual void OnPsionicallySuppressed(Entity power, ref PsionicSuppressedEvent args)
{
if (Timing.ApplyingState)
return;
TryStopDoAfter(power, args.Victim, "psionic-equipped-shielded-in-doafter");
}
///
/// This is raised when the suppression stops.
/// This allows powers to handle special behavior.
///
/// The psionic who is no longer suppressed.
/// The event.
protected virtual void OnStoppedPsionicallySuppressed(Entity psionic, ref PsionicStoppedSuppressedEvent args)
{
}
///
/// This will log the power usage and increase glimmer, as well as making sure metapsionics hear it.
///
/// The SOURCE of the psionic power.
/// The entity that PERFORMED the power.
protected void AfterPowerUsed(Entity psionicSource, EntityUid performer)
{
var power = Loc.GetString(psionicSource.Comp.PowerName);
_adminLogger.Add(Database.LogType.Psionics, Database.LogImpact.Medium, $"{ToPrettyString(psionicSource):player} used {power}");
var ev = new PsionicPowerUsedEvent(performer, psionicSource, power);
RaiseLocalEvent(psionicSource, ev);
_glimmer.Glimmer += Random.Next(psionicSource.Comp.MinGlimmerChanged, psionicSource.Comp.MaxGlimmerChanged);
}
///
/// Stops the current DoAfter of the psionic user.
///
/// The source of the power.
/// The entity performing the power.
/// The FTL string for the popup. If null, no popup.
/// Returns true if an doAfter was interrupted, otherwise false.
private bool TryStopDoAfter(Entity psionic, EntityUid performer, string? popup = null)
{
if (psionic.Comp.GetDoAfterId() is not { } doAfterId)
return false;
DoAfter.Cancel(doAfterId);
if (popup != null)
Popup.PopupClient(Loc.GetString(popup), performer, performer, PopupType.MediumCaution);
psionic.Comp.RemoveSavedDoAfterId();
Dirty(psionic);
return true;
}
}