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