From 711e09d37e493e6e6c508b49ce42c223e30b0c55 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 30 Dec 2024 15:24:44 -0500 Subject: [PATCH 1/5] Psionic Refactor Version 3 --- .../PsionicAbilitiesSystem.Functions.cs | 580 ++++++++++++++ .../Psionics/PsionicAbilitiesSystem.cs | 633 +++++++--------- .../Chat/TelepathicChatSystem.Psychognomy.cs | 2 +- Content.Server/Psionics/PsionicsSystem.cs | 41 +- Content.Shared/Psionics/PsionicComponent.cs | 35 +- .../Psionics/PsionicPowerPrototype.cs | 90 +-- .../Prototypes/Nyanotrasen/psionicPowers.yml | 4 - Resources/Prototypes/Psionics/psionics.yml | 707 ++++++++++++++---- 8 files changed, 1482 insertions(+), 610 deletions(-) create mode 100644 Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.Functions.cs diff --git a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.Functions.cs b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.Functions.cs new file mode 100644 index 00000000000..3db2a2bae4e --- /dev/null +++ b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.Functions.cs @@ -0,0 +1,580 @@ +using JetBrains.Annotations; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; +using Content.Shared.Actions; +using Content.Shared.Psionics; +using Content.Shared.Abilities.Psionics; +using Content.Shared.Popups; +using Content.Shared.Chat; +using Content.Shared.Psionics.Glimmer; +using Content.Shared.Random; +using Content.Server.Chat.Managers; +using Robust.Shared.Player; + +namespace Content.Server.Abilities.Psionics; + +[UsedImplicitly] +public sealed partial class AddPsionicActions : PsionicPowerFunction +{ + /// + /// The list of each Action that this power adds in the form of ActionId and ActionEntity + /// + [DataField] + public List Actions = new(); + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + var actions = entityManager.System(); + foreach (var id in Actions) + { + EntityUid? actionId = null; + if (actions.AddAction(uid, ref actionId, id)) + { + actions.StartUseDelay(actionId); + psionicComponent.Actions.Add(proto.ID, actionId); + } + } + } +} + +[UsedImplicitly] +public sealed partial class RemovePsionicActions : PsionicPowerFunction +{ + // As a novelty, this does not require any DataFields. + // This removes all Actions directly associated with a specific power, which works with our current system of record-keeping + // for psi-powers. + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + var actions = entityManager.System(); + if (psionicComponent.Actions is null + || !psionicComponent.Actions.ContainsKey(proto.ID)) + return; + + var copy = serializationManager.CreateCopy(psionicComponent.Actions, notNullableOverride: true); + + foreach (var (id, actionUid) in copy) + { + if (id != proto.ID) + continue; + + actions.RemoveAction(uid, actionUid); + } + } +} + +[UsedImplicitly] +public sealed partial class AddPsionicPowerComponents : PsionicPowerFunction +{ + /// + /// The list of what Components this power adds. + /// + [DataField] + public ComponentRegistry Components = new(); + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + foreach (var entry in Components.Values) + { + if (entityManager.HasComponent(uid, entry.Component.GetType())) + continue; + + var comp = (Component) serializationManager.CreateCopy(entry.Component, notNullableOverride: true); + comp.Owner = uid; + entityManager.AddComponent(uid, comp); + } + } +} + +[UsedImplicitly] +public sealed partial class RemovePsionicPowerComponents : PsionicPowerFunction +{ + /// + /// The list of what Components this power removes. + /// + [DataField] + public ComponentRegistry Components = new(); + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + foreach (var (name, _) in Components) + entityManager.RemoveComponentDeferred(uid, factory.GetComponent(name).GetType()); + } +} + +[UsedImplicitly] +public sealed partial class AddPsionicStatSources : PsionicPowerFunction +{ + /// + /// How much this power will increase or decrease a user's Amplification. + /// + [DataField] + public float AmplificationModifier; + + /// + /// How much this power will increase or decrease a user's Dampening. + /// + [DataField] + public float DampeningModifier; + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + if (AmplificationModifier != 0) + psionicComponent.AmplificationSources.Add(proto.Name, AmplificationModifier); + + if (DampeningModifier != 0) + psionicComponent.DampeningSources.Add(proto.Name, DampeningModifier); + } +} + +[UsedImplicitly] +public sealed partial class RemovePsionicStatSources : PsionicPowerFunction +{ + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + psionicComponent.AmplificationSources.Remove(proto.Name); + psionicComponent.DampeningSources.Remove(proto.Name); + } +} + +[UsedImplicitly] +public sealed partial class PsionicFeedbackPopup : PsionicPowerFunction +{ + /// + /// What message will be sent to the player as a Popup. + /// If left blank, it will default to the Const "generic-power-initialization-feedback" + /// + [DataField] + public string InitializationPopup = "generic-power-initialization-feedback"; + + [DataField] + public PopupType InitPopupType = PopupType.MediumCaution; + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + var popups = entityManager.System(); + if (playerManager.TryGetSessionByEntity(uid, out var session) + || session is null + || !loc.TryGetString(InitializationPopup, out var popupString)) + return; + + popups.PopupEntity(popupString, uid, uid, InitPopupType); + } +} + +[UsedImplicitly] +public sealed partial class PsionicFeedbackSelfChat : PsionicPowerFunction +{ + /// + /// What message will be sent to the player as a Chat message. + /// If left blank, it will default to the Const "generic-power-initialization-feedback" + /// + [DataField] + public string FeedbackMessage = "generic-power-initialization-feedback"; + + /// + /// What color will the initialization feedback display in the chat window with. + /// + [DataField] + public string InitializationFeedbackColor = "#8A00C2"; + + /// + /// What font size will the initialization message use in chat. + /// + [DataField] + public int InitializationFeedbackFontSize = 12; + + + /// + /// Which chat channel will the initialization message use. + /// + [DataField] + public ChatChannel InitializationFeedbackChannel = ChatChannel.Emotes; + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + var chatManager = IoCManager.Resolve(); + if (playerManager.TryGetSessionByEntity(uid, out var session) + || session is null + || !loc.TryGetString(FeedbackMessage, out var feedback)) + return; + + var feedbackMessage = $"[font size={InitializationFeedbackFontSize}][color={InitializationFeedbackColor}]{feedback}[/color][/font]"; + chatManager.ChatMessageToOne( + InitializationFeedbackChannel, + feedbackMessage, + feedbackMessage, + EntityUid.Invalid, + false, + session.Channel); + } +} + +[UsedImplicitly] +public sealed partial class AddPsionicAssayFeedback : PsionicPowerFunction +{ + /// + /// What message will this power generate when scanned by an Assay user. + /// These are also used for the Psi-Potentiometer. + /// + [DataField] + public string AssayFeedback = ""; + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + if (AssayFeedback is "") + return; + + psionicComponent.AssayFeedback.Add(AssayFeedback); + } +} + +[UsedImplicitly] +public sealed partial class RemoveAssayFeedback : PsionicPowerFunction +{ + [DataField] + public string AssayFeedback = ""; + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + if (AssayFeedback is "" + || !psionicComponent.AssayFeedback.Contains(AssayFeedback)) + return; + + psionicComponent.AssayFeedback.Remove(AssayFeedback); + } +} + +[UsedImplicitly] +public sealed partial class AddPsionicPsychognomicDescriptors : PsionicPowerFunction +{ + [DataField] + public string PsychognomicDescriptor = ""; + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + // It is entirely intended that this doesn't include a Contains check. + // The descriptors list allows duplicates, and will only ever pick one anyway. + if (PsychognomicDescriptor is "") + return; + + psionicComponent.PsychognomicDescriptors.Add(PsychognomicDescriptor); + } +} + +[UsedImplicitly] +public sealed partial class RemovePsionicPsychognomicDescriptors : PsionicPowerFunction +{ + [DataField] + public string PsychognomicDescriptor = ""; + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + if (PsychognomicDescriptor is "" + || !psionicComponent.PsychognomicDescriptors.Contains(PsychognomicDescriptor)) + return; + + psionicComponent.PsychognomicDescriptors.Remove(PsychognomicDescriptor); + } +} + +[UsedImplicitly] +public sealed partial class PsionicModifyPowerSlots : PsionicPowerFunction +{ + [DataField] + public int PowerSlotsModifier; + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + psionicComponent.PowerSlots += PowerSlotsModifier; + } +} + +[UsedImplicitly] +public sealed partial class PsionicModifyFamiliarLimit : PsionicPowerFunction +{ + [DataField] + public int FamiliarLimitModifier; + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + psionicComponent.FamiliarLimit += FamiliarLimitModifier; + } +} + +[UsedImplicitly] +public sealed partial class PsionicModifyRemovable : PsionicPowerFunction +{ + [DataField] + public bool Removable; + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + psionicComponent.Removable = Removable; + } +} + +[UsedImplicitly] +public sealed partial class PsionicModifyMana : PsionicPowerFunction +{ + [DataField] + public float MaxManaModifier; + + [DataField] + public float ManaGainModifier; + + [DataField] + public float ManaGainMultiplierModifier; + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + psionicComponent.MaxMana += MaxManaModifier; + psionicComponent.ManaGain += ManaGainModifier; + psionicComponent.ManaGainMultiplier += ManaGainMultiplierModifier; + } +} + +[UsedImplicitly] +public sealed partial class PsionicModifyGlimmer : PsionicPowerFunction +{ + [DataField] + public int GlimmerModifier; + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + var glimmerSystem = entityManager.System(); + glimmerSystem.Glimmer += GlimmerModifier; + } +} + +[UsedImplicitly] +public sealed partial class PsionicChangePowerPool : PsionicPowerFunction +{ + [DataField] + public ProtoId PowerPool = "RandomPsionicPowerPool"; + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + psionicComponent.PowerPool = PowerPool; + } +} + +[UsedImplicitly] +public sealed partial class PsionicAddAvailablePowers : PsionicPowerFunction +{ + /// + /// I can't validate these using this method. So this is a string. + /// + [DataField] + public string PowerPrototype = ""; + + [DataField] + public float Weight = 1f; + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + var protoMan = IoCManager.Resolve(); + if (!protoMan.HasIndex(PowerPrototype) + || psionicComponent.AvailablePowers.ContainsKey(PowerPrototype)) + return; + + psionicComponent.AvailablePowers.Add(PowerPrototype, Weight); + } +} + +[UsedImplicitly] +public sealed partial class PsionicRemoveAvailablePowers : PsionicPowerFunction +{ + /// + /// I can't validate these using this method. So this is a string. + /// + [DataField] + public string PowerPrototype = ""; + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + psionicComponent.AvailablePowers.Remove(PowerPrototype); + } +} + +[UsedImplicitly] +public sealed partial class PsionicModifyRollChances : PsionicPowerFunction +{ + [DataField] + public float BaselinePowerCostModifier; + + [DataField] + public float BaselineChanceModifier; + + public override void OnAddPsionic( + EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto) + { + psionicComponent.BaselinePowerCost += BaselinePowerCostModifier; + psionicComponent.Chance += BaselineChanceModifier; + } +} diff --git a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs index a657af150f4..9865b0aa881 100644 --- a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs +++ b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs @@ -1,17 +1,13 @@ using Content.Shared.Abilities.Psionics; -using Content.Shared.Actions; using Content.Shared.Popups; using Content.Shared.Chat; -using Content.Shared.Psionics.Glimmer; -using Content.Shared.Random; +using Content.Shared.Psionics; using Content.Shared.Random.Helpers; using Content.Shared.StatusEffect; using Robust.Shared.Random; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager; -using Content.Shared.Psionics; -using System.Linq; -using Robust.Server.Player; +using Robust.Shared.Player; using Content.Server.Chat.Managers; using Robust.Shared.Configuration; using Content.Shared.CCVar; @@ -19,413 +15,302 @@ using Content.Server.NPC.HTN; using Content.Server.Ghost; using Content.Server.Mind; -namespace Content.Server.Abilities.Psionics -{ - public sealed class PsionicAbilitiesSystem : EntitySystem - { - [Dependency] private readonly IComponentFactory _componentFactory = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; - [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly SharedPopupSystem _popups = default!; - [Dependency] private readonly ISerializationManager _serialization = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly IChatManager _chatManager = default!; - [Dependency] private readonly PsionicFamiliarSystem _psionicFamiliar = default!; - [Dependency] private readonly IConfigurationManager _config = default!; - [Dependency] private readonly NpcFactionSystem _npcFaction = default!; - [Dependency] private readonly GhostSystem _ghost = default!; - [Dependency] private readonly MindSystem _mind = default!; - - private ProtoId _pool = "RandomPsionicPowerPool"; - private const string GenericInitializationMessage = "generic-power-initialization-feedback"; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(InnatePowerStartup); - SubscribeLocalEvent(OnPsionicShutdown); - } - - /// - /// Special use-case for a InnatePsionicPowers, which allows an entity to start with any number of Psionic Powers. - /// - private void InnatePowerStartup(EntityUid uid, InnatePsionicPowersComponent comp, MapInitEvent args) - { - // Any entity with InnatePowers should also be psionic, but in case they aren't already... - EnsureComp(uid, out var psionic); - - foreach (var proto in comp.PowersToAdd) - if (!psionic.ActivePowers.Contains(_prototypeManager.Index(proto))) - InitializePsionicPower(uid, _prototypeManager.Index(proto), psionic, false); - } - - private void OnPsionicShutdown(EntityUid uid, PsionicComponent component, ComponentShutdown args) - { - if (!EntityManager.EntityExists(uid) - || HasComp(uid)) - return; - - KillFamiliars(component); - RemoveAllPsionicPowers(uid); - } - - /// - /// The most shorthand route to creating a Psion. If an entity is not already psionic, it becomes one. This also adds a random new PsionicPower. - /// To create a "Latent Psychic"(Psion with no powers) just add or ensure the PsionicComponent normally. - /// - public void AddPsionics(EntityUid uid) - { - if (Deleted(uid)) - return; - - AddRandomPsionicPower(uid); - } - - /// - /// Pretty straightforward, adds a random psionic power to a given Entity. If that Entity is not already Psychic, it will be made one. - /// If an entity already has all possible powers, this will not add any new ones. - /// - public void AddRandomPsionicPower(EntityUid uid) - { - // We need to EnsureComp here to make sure that we aren't iterating over a component that: - // A: Isn't fully initialized - // B: Is in the process of being shutdown/deleted - // Imagine my surprise when I found out Resolve doesn't check for that. - // TODO: This EnsureComp will be 1984'd in a separate PR, when I rework how you get psionics in the first place. - EnsureComp(uid, out var psionic); - - if (!_prototypeManager.TryIndex(_pool.Id, out var pool)) - return; - - var newPool = pool.Weights.Keys.ToList(); - newPool.RemoveAll(s => - _prototypeManager.TryIndex(s, out var p) && - psionic.ActivePowers.Contains(p)); - if (newPool.Count == 0) - return; +namespace Content.Server.Abilities.Psionics; - var newProto = _random.Pick(newPool); - if (!_prototypeManager.TryIndex(newProto, out var newPower)) - return; - - InitializePsionicPower(uid, newPower); - - _glimmerSystem.Glimmer += _random.Next(1, (int) Math.Round(1 + psionic.CurrentAmplification + psionic.CurrentDampening)); - } +public sealed class PsionicAbilitiesSystem : EntitySystem +{ + [Dependency] private readonly IComponentFactory _componentFactory = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedPopupSystem _popups = default!; + [Dependency] private readonly ISerializationManager _serialization = default!; + [Dependency] private readonly ISharedPlayerManager _playerManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly PsionicFamiliarSystem _psionicFamiliar = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly NpcFactionSystem _npcFaction = default!; + [Dependency] private readonly GhostSystem _ghost = default!; + [Dependency] private readonly MindSystem _mind = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(InnatePowerStartup); + SubscribeLocalEvent(OnPsionicShutdown); + } - /// - /// Initializes a new Psionic Power on a given entity, assuming the entity does not already have said power initialized. - /// - public void InitializePsionicPower(EntityUid uid, PsionicPowerPrototype proto, PsionicComponent psionic, bool playFeedback = true) - { - if (!_prototypeManager.HasIndex(proto.ID) - || psionic.ActivePowers.Contains(proto)) - return; - - psionic.ActivePowers.Add(proto); - - AddPsionicActions(uid, proto, psionic); - AddPsionicPowerComponents(uid, proto); - AddPsionicStatSources(proto, psionic); - RefreshPsionicModifiers(uid, psionic); - SendFeedbackMessage(uid, proto, playFeedback); - UpdatePowerSlots(psionic); - //UpdatePsionicDanger(uid, psionic); // TODO: After Glimmer Refactor - //SendFeedbackAudio(uid, proto, playPopup); // TODO: This one is coming next! - } + /// + /// Special use-case for a InnatePsionicPowers, which allows an entity to start with any number of Psionic Powers. + /// + private void InnatePowerStartup(EntityUid uid, InnatePsionicPowersComponent comp, MapInitEvent args) + { + // Any entity with InnatePowers should also be psionic, but in case they aren't already... + EnsureComp(uid, out var psionic); - /// - /// Initializes a new Psionic Power on a given entity, assuming the entity does not already have said power initialized. - /// - public void InitializePsionicPower(EntityUid uid, PsionicPowerPrototype proto, bool playFeedback = true) - { - EnsureComp(uid, out var psionic); + foreach (var proto in comp.PowersToAdd) + if (!psionic.ActivePowers.Contains(_prototypeManager.Index(proto))) + InitializePsionicPower(uid, _prototypeManager.Index(proto), psionic, false); + } - InitializePsionicPower(uid, proto, psionic, playFeedback); - } + private void OnPsionicShutdown(EntityUid uid, PsionicComponent component, ComponentShutdown args) + { + if (!EntityManager.EntityExists(uid) + || HasComp(uid)) + return; - /// - /// Updates a Psion's casting stats, call this anytime a system adds a new source of Amp or Damp. - /// - public void RefreshPsionicModifiers(EntityUid uid, PsionicComponent comp) - { - var ampModifier = 0f; - var dampModifier = 0f; - foreach (var (_, source) in comp.AmplificationSources) - ampModifier += source; - foreach (var (_, source) in comp.DampeningSources) - dampModifier += source; - - var ev = new OnSetPsionicStatsEvent(ampModifier, dampModifier); - RaiseLocalEvent(uid, ref ev); - ampModifier = ev.AmplificationChangedAmount; - dampModifier = ev.DampeningChangedAmount; - - comp.CurrentAmplification = ampModifier; - comp.CurrentDampening = dampModifier; - } + KillFamiliars(component); + RemoveAllPsionicPowers(uid); + } - /// - /// Updates a Psion's casting stats, call this anytime a system adds a new source of Amp or Damp. - /// Variant function for systems that didn't already have the PsionicComponent. - /// - public void RefreshPsionicModifiers(EntityUid uid) - { - if (!TryComp(uid, out var comp)) - return; + /// + /// The most shorthand route to creating a Psion. If an entity is not already psionic, it becomes one. This also adds a random new PsionicPower. + /// To create a "Latent Psychic"(Psion with no powers) just add or ensure the PsionicComponent normally. + /// + public void AddPsionics(EntityUid uid) + { + if (Deleted(uid)) + return; - RefreshPsionicModifiers(uid, comp); - } + AddRandomPsionicPower(uid); + } - /// - /// A more advanced form of removing powers. Mindbreaking not only removes all psionic powers, - /// it also disables the possibility of obtaining new ones. - /// - public void MindBreak(EntityUid uid) + /// + /// Pretty straightforward, adds a random psionic power to a given Entity. If that Entity is not already Psychic, it will be made one. + /// If an entity already has all possible powers, this will not add any new ones. + /// + public void AddRandomPsionicPower(EntityUid uid, bool forced = false) + { + // We need to EnsureComp here to make sure that we aren't iterating over a component that: + // A: Isn't fully initialized + // B: Is in the process of being shutdown/deleted + // Imagine my surprise when I found out Resolve doesn't check for that. + // TODO: This EnsureComp will be 1984'd in a separate PR, when I rework how you get psionics in the first place. + EnsureComp(uid, out var psionic); + if (!psionic.Roller && !forced) + return; + + // Since this can be called by systems other than the original roundstart initialization, we need to check that the available powers list + // doesn't contain duplicates of powers we already have. + var copy = _serialization.CreateCopy(psionic.AvailablePowers, notNullableOverride: true); + foreach (var weight in copy) { - if (!HasComp(uid)) - return; - - RemoveAllPsionicPowers(uid, true); - if (_config.GetCVar(CCVars.ScarierMindbreaking)) - ScarierMindbreak(uid); - } + if (!_prototypeManager.TryIndex(weight.Key, out var copyPower) + || !psionic.ActivePowers.Contains(copyPower)) + continue; - /// - /// An even more advanced form of Mindbreaking. Turn the victim into an NPC. - /// For the people who somehow didn't intuit from the absolutely horrifying text that mindbreaking people is very fucking bad. - /// - public void ScarierMindbreak(EntityUid uid) - { - if (!_playerManager.TryGetSessionByEntity(uid, out var session) || session is null) - return; - - var feedbackMessage = $"[font size=24][color=#ff0000]{"Your characters personhood has been obliterated. If you wish to continue playing, consider respawning as a new character."}[/color][/font]"; - _chatManager.ChatMessageToOne( - ChatChannel.Emotes, - feedbackMessage, - feedbackMessage, - EntityUid.Invalid, - false, - session.Channel); - - if (!_mind.TryGetMind(session, out var mindId, out var mind)) - return; - - _ghost.SpawnGhost((mindId, mind), Transform(uid).Coordinates, false); - _npcFaction.AddFaction(uid, "SimpleNeutral"); - var htn = EnsureComp(uid); - htn.RootTask = new HTNCompoundTask() { Task = "IdleCompound" }; + psionic.AvailablePowers.Remove(copyPower.ID); } - /// - /// Remove all Psionic powers, with accompanying actions, components, and casting stat sources, from a given Psion. - /// Optionally, the Psion can also be rendered permanently non-Psionic. - /// - public void RemoveAllPsionicPowers(EntityUid uid, bool mindbreak = false) - { - if (!TryComp(uid, out var psionic) - || !psionic.Removable) - return; - - RemovePsionicActions(uid, psionic); + if (psionic.AvailablePowers.Count <= 0) + return; + var proto = _random.Pick(psionic.AvailablePowers); + if (!_prototypeManager.TryIndex(proto, out var newPower)) + return; - var newPsionic = psionic.ActivePowers.ToList(); - foreach (var proto in newPsionic) - { - if (!_prototypeManager.TryIndex(proto.ID, out var power)) - continue; + InitializePsionicPower(uid, newPower); + } - RemovePsionicPowerComponents(uid, proto); + /// + /// Initializes a new Psionic Power on a given entity, assuming the entity does not already have said power initialized. + /// + public void InitializePsionicPower(EntityUid uid, PsionicPowerPrototype proto, PsionicComponent psionic, bool playFeedback = true) + { + if (!_prototypeManager.HasIndex(proto.ID) + || psionic.ActivePowers.Contains(proto)) + return; + + psionic.ActivePowers.Add(proto); + + foreach (var function in proto.InitializeFunctions) + function.OnAddPsionic(uid, + _componentFactory, + EntityManager, + _serialization, + _playerManager, + Loc, + psionic, + proto); + + RefreshPsionicModifiers(uid, psionic); + UpdatePowerSlots(psionic); + } - // If we're mindbreaking, we can skip the casting stats since the PsionicComponent is getting 1984'd. - if (!mindbreak) - RemovePsionicStatSources(uid, power, psionic); - } + /// + /// Initializes a new Psionic Power on a given entity, assuming the entity does not already have said power initialized. + /// + public void InitializePsionicPower(EntityUid uid, PsionicPowerPrototype proto, bool playFeedback = true) + { + EnsureComp(uid, out var psionic); - if (mindbreak) - { - EnsureComp(uid); - _statusEffectsSystem.TryAddStatusEffect(uid, psionic.MindbreakingStutterCondition, - TimeSpan.FromMinutes(psionic.MindbreakingStutterTime * psionic.CurrentAmplification * psionic.CurrentDampening), - false, - psionic.MindbreakingStutterAccent); + InitializePsionicPower(uid, proto, psionic, playFeedback); + } - _popups.PopupEntity(Loc.GetString(psionic.MindbreakingFeedback, ("entity", MetaData(uid).EntityName)), uid, uid, PopupType.MediumCaution); + /// + /// Updates a Psion's casting stats, call this anytime a system adds a new source of Amp or Damp. + /// + public void RefreshPsionicModifiers(EntityUid uid, PsionicComponent comp) + { + var ampModifier = 0f; + var dampModifier = 0f; + foreach (var (_, source) in comp.AmplificationSources) + ampModifier += source; + foreach (var (_, source) in comp.DampeningSources) + dampModifier += source; + + var ev = new OnSetPsionicStatsEvent(ampModifier, dampModifier); + RaiseLocalEvent(uid, ref ev); + ampModifier = ev.AmplificationChangedAmount; + dampModifier = ev.DampeningChangedAmount; + + comp.CurrentAmplification = ampModifier; + comp.CurrentDampening = dampModifier; + } - KillFamiliars(psionic); - RemComp(uid); - RemComp(uid); + /// + /// Updates a Psion's casting stats, call this anytime a system adds a new source of Amp or Damp. + /// Variant function for systems that didn't already have the PsionicComponent. + /// + public void RefreshPsionicModifiers(EntityUid uid) + { + if (!TryComp(uid, out var comp)) + return; - var ev = new OnMindbreakEvent(); - RaiseLocalEvent(uid, ref ev); + RefreshPsionicModifiers(uid, comp); + } - return; - } - RefreshPsionicModifiers(uid, psionic); - } + /// + /// A more advanced form of removing powers. Mindbreaking not only removes all psionic powers, + /// it also disables the possibility of obtaining new ones. + /// + public void MindBreak(EntityUid uid) + { + if (!TryComp(uid, out var psionic)) + return; - /// - /// Add all actions associated with a specific Psionic Power - /// - private void AddPsionicActions(EntityUid uid, PsionicPowerPrototype proto, PsionicComponent psionic) - { - foreach (var id in proto.Actions) - { - EntityUid? actionId = null; - if (_actions.AddAction(uid, ref actionId, id)) - { - _actions.StartUseDelay(actionId); - psionic.Actions.Add(id, actionId); - } - } - } + RemoveAllPsionicPowers(uid, true); + EnsureComp(uid); + _statusEffectsSystem.TryAddStatusEffect(uid, psionic.MindbreakingStutterCondition, + TimeSpan.FromMinutes(psionic.MindbreakingStutterTime * psionic.CurrentAmplification * psionic.CurrentDampening), + false, + psionic.MindbreakingStutterAccent); - /// - /// Add all components associated with a specific Psionic power. - /// - private void AddPsionicPowerComponents(EntityUid uid, PsionicPowerPrototype proto) - { - if (proto.Components is null) - return; - - foreach (var entry in proto.Components.Values) - { - if (HasComp(uid, entry.Component.GetType())) - continue; - - var comp = (Component) _serialization.CreateCopy(entry.Component, notNullableOverride: true); - comp.Owner = uid; - EntityManager.AddComponent(uid, comp); - } - } + _popups.PopupEntity(Loc.GetString(psionic.MindbreakingFeedback, ("entity", MetaData(uid).EntityName)), uid, uid, PopupType.MediumCaution); - /// - /// Update the Amplification and Dampening sources of a Psion to include a new Power. - /// - private void AddPsionicStatSources(PsionicPowerPrototype proto, PsionicComponent psionic) - { - if (proto.AmplificationModifier != 0) - psionic.AmplificationSources.Add(proto.Name, proto.AmplificationModifier); + KillFamiliars(psionic); + RemComp(uid); + RemComp(uid); - if (proto.DampeningModifier != 0) - psionic.DampeningSources.Add(proto.Name, proto.DampeningModifier); - } + var ev = new OnMindbreakEvent(); + RaiseLocalEvent(uid, ref ev); - /// - /// Displays a message to alert the player when they have obtained a new psionic power. These generally will not play for Innate powers. - /// Chat messages of this nature should be written in the first-person. - /// Popup feedback should be no more than a sentence, while the full Initialization Feedback can be as much as a paragraph of text. - /// - private void SendFeedbackMessage(EntityUid uid, PsionicPowerPrototype proto, bool playFeedback = true) - { - if (!playFeedback - || !_playerManager.TryGetSessionByEntity(uid, out var session) - || session is null) - return; - - if (proto.InitializationPopup is null) - _popups.PopupEntity(Loc.GetString(GenericInitializationMessage), uid, uid, PopupType.MediumCaution); - else _popups.PopupEntity(Loc.GetString(proto.InitializationPopup), uid, uid, PopupType.MediumCaution); - - if (proto.InitializationFeedback is null) - return; - - if (!Loc.TryGetString(proto.InitializationFeedback, out var feedback)) - return; - var feedbackMessage = $"[font size={proto.InitializationFeedbackFontSize}][color={proto.InitializationFeedbackColor}]{feedback}[/color][/font]"; - _chatManager.ChatMessageToOne( - proto.InitializationFeedbackChannel, - feedbackMessage, - feedbackMessage, - EntityUid.Invalid, - false, - session.Channel); - } + if (_config.GetCVar(CCVars.ScarierMindbreaking)) + ScarierMindbreak(uid); + } - private void UpdatePowerSlots(PsionicComponent psionic) - { - var slotsUsed = 0; - foreach (var power in psionic.ActivePowers) - slotsUsed += power.PowerSlotCost; + /// + /// An even more advanced form of Mindbreaking. Turn the victim into an NPC. + /// For the people who somehow didn't intuit from the absolutely horrifying text that mindbreaking people is very fucking bad. + /// + public void ScarierMindbreak(EntityUid uid) + { + if (!_playerManager.TryGetSessionByEntity(uid, out var session) || session is null) + return; + + var feedbackMessage = $"[font size=24][color=#ff0000]{"Your characters personhood has been obliterated. If you wish to continue playing, consider respawning as a new character."}[/color][/font]"; + _chatManager.ChatMessageToOne( + ChatChannel.Emotes, + feedbackMessage, + feedbackMessage, + EntityUid.Invalid, + false, + session.Channel); + + if (!_mind.TryGetMind(session, out var mindId, out var mind)) + return; + + _ghost.SpawnGhost((mindId, mind), Transform(uid).Coordinates, false); + _npcFaction.AddFaction(uid, "SimpleNeutral"); + var htn = EnsureComp(uid); + htn.RootTask = new HTNCompoundTask() { Task = "IdleCompound" }; + } - psionic.PowerSlotsTaken = slotsUsed; - } + /// + /// Remove all Psionic powers, with accompanying actions, components, and casting stat sources, from a given Psion. + /// Optionally, the Psion can also be rendered permanently non-Psionic. + /// + public void RemoveAllPsionicPowers(EntityUid uid, bool mindbreak = false) + { + if (!TryComp(uid, out var psionic) + || !psionic.Removable) + return; - /// - /// Psions over a certain power threshold become a glimmer source. This cannot be fully implemented until after I rework Glimmer - /// - //private void UpdatePsionicDanger(EntityUid uid, PsionicComponent psionic) - //{ - // if (psionic.PowerSlotsTaken <= psionic.PowerSlots) - // return; - // - // EnsureComp(uid, out var glimmerSource); - // glimmerSource.SecondsPerGlimmer = 10 / (psionic.PowerSlotsTaken - psionic.PowerSlots); - //} - - /// - /// Remove all Psychic Actions listed in an entity's Psionic Component. Unfortunately, removing actions associated with a specific Power Prototype is not supported. - /// - private void RemovePsionicActions(EntityUid uid, PsionicComponent psionic) - { - if (psionic.Actions is null) - return; + foreach (var proto in psionic.ActivePowers) + RemovePsionicPower(uid, psionic, proto, mindbreak); - foreach (var action in psionic.Actions) - _actionsSystem.RemoveAction(uid, action.Value); - } + if (mindbreak) + return; - /// - /// Remove all Components associated with a specific Psionic Power. - /// - private void RemovePsionicPowerComponents(EntityUid uid, PsionicPowerPrototype proto) - { - if (proto.Components is null) - return; + RefreshPsionicModifiers(uid, psionic); + } - foreach (var comp in proto.Components) - { - var powerComp = (Component) _componentFactory.GetComponent(comp.Key); - if (!EntityManager.HasComponent(uid, powerComp.GetType())) - continue; + public void RemovePsionicPower(EntityUid uid, PsionicComponent psionicComponent, PsionicPowerPrototype psionicPower, bool forced = false) + { + if (!psionicComponent.ActivePowers.Contains(psionicPower) + || !psionicComponent.Removable && !forced) + return; + + foreach (var function in psionicPower.RemovalFunctions) + function.OnAddPsionic(uid, + _componentFactory, + EntityManager, + _serialization, + _playerManager, + Loc, + psionicComponent, + psionicPower); + } - EntityManager.RemoveComponent(uid, powerComp.GetType()); - } - } + public void RemovePsionicPower(EntityUid uid, PsionicPowerPrototype psionicPower, bool forced = false) + { + if (!TryComp(uid, out var psionicComponent) + || !psionicComponent.ActivePowers.Contains(psionicPower) + || !psionicComponent.Removable && !forced) + return; + + foreach (var function in psionicPower.RemovalFunctions) + function.OnAddPsionic(uid, + _componentFactory, + EntityManager, + _serialization, + _playerManager, + Loc, + psionicComponent, + psionicPower); + } - /// - /// Remove all stat sources associated with a specific Psionic Power. - /// - private void RemovePsionicStatSources(EntityUid uid, PsionicPowerPrototype proto, PsionicComponent psionic) - { - if (proto.AmplificationModifier != 0) - psionic.AmplificationSources.Remove(proto.Name); + private void UpdatePowerSlots(PsionicComponent psionic) + { + var slotsUsed = 0; + foreach (var power in psionic.ActivePowers) + slotsUsed += power.PowerSlotCost; - if (proto.DampeningModifier != 0) - psionic.DampeningSources.Remove(proto.Name); + psionic.PowerSlotsTaken = slotsUsed; + } - RefreshPsionicModifiers(uid, psionic); - } + private void KillFamiliars(PsionicComponent component) + { + if (component.Familiars.Count <= 0) + return; - private void KillFamiliars(PsionicComponent component) + foreach (var familiar in component.Familiars) { - if (component.Familiars.Count <= 0) - return; - - foreach (var familiar in component.Familiars) - { - if (!TryComp(familiar, out var familiarComponent) - || !familiarComponent.DespawnOnMasterDeath) - continue; + if (!TryComp(familiar, out var familiarComponent) + || !familiarComponent.DespawnOnMasterDeath) + continue; - _psionicFamiliar.DespawnFamiliar(familiar, familiarComponent); - } + _psionicFamiliar.DespawnFamiliar(familiar, familiarComponent); } } } diff --git a/Content.Server/Chat/TelepathicChatSystem.Psychognomy.cs b/Content.Server/Chat/TelepathicChatSystem.Psychognomy.cs index 4ba990466c6..383096c03d6 100644 --- a/Content.Server/Chat/TelepathicChatSystem.Psychognomy.cs +++ b/Content.Server/Chat/TelepathicChatSystem.Psychognomy.cs @@ -130,7 +130,7 @@ private void DescribeGlimmerSource(EntityUid uid, GlimmerSourceComponent compone // This one's also a bit of a catch-all for "lacks component" private void DescribePsion(EntityUid uid, PsionicComponent component, GetPsychognomicDescriptorEvent ev) { - if (component.PsychognomicDescriptors != null) + if (component.PsychognomicDescriptors.Count > 0) { foreach (var descriptor in component.PsychognomicDescriptors) { diff --git a/Content.Server/Psionics/PsionicsSystem.cs b/Content.Server/Psionics/PsionicsSystem.cs index 9685334daba..fab1d6d5073 100644 --- a/Content.Server/Psionics/PsionicsSystem.cs +++ b/Content.Server/Psionics/PsionicsSystem.cs @@ -1,6 +1,8 @@ using Content.Shared.Abilities.Psionics; using Content.Shared.StatusEffect; +using Content.Shared.Psionics; using Content.Shared.Psionics.Glimmer; +using Content.Shared.Random; using Content.Shared.Weapons.Melee.Events; using Content.Shared.Damage.Events; using Content.Shared.CCVar; @@ -19,6 +21,7 @@ using Content.Shared.Mobs; using Content.Shared.Damage; using Content.Shared.Interaction.Events; +using Timer = Robust.Shared.Timing.Timer; namespace Content.Server.Psionics; @@ -58,6 +61,9 @@ public sealed class PsionicsSystem : EntitySystem public override void Update(float frameTime) { base.Update(frameTime); + if (!_cfg.GetCVar(CCVars.PsionicRollsEnabled)) + return; + foreach (var roller in _rollers) RollPsionics(roller.uid, roller.component, true); _rollers.Clear(); @@ -82,7 +88,22 @@ private void OnStartup(EntityUid uid, PsionicComponent component, MapInitEvent a || !component.CanReroll) return; + Timer.Spawn(TimeSpan.FromSeconds(30), () => DeferRollers(uid)); + + } + + /// + /// We wait a short time before starting up the rolled powers, so that other systems have a chance to modify the list first. + /// This is primarily for the sake of TraitSystem and AddJobSpecial. + /// + private void DeferRollers(EntityUid uid) + { + if (!Exists(uid) + || !TryComp(uid, out PsionicComponent? component)) + return; + CheckPowerCost(uid, component); + GenerateAvailablePowers(component); _rollers.Enqueue((component, uid)); } @@ -103,6 +124,24 @@ private void CheckPowerCost(EntityUid uid, PsionicComponent component) component.NextPowerCost = 100 * MathF.Pow(2, powerCount); } + /// + /// The power pool is itself a DataField, and things like Traits/Antags are allowed to modify or replace the pool. + /// + private void GenerateAvailablePowers(PsionicComponent component) + { + if (!_protoMan.TryIndex(component.PowerPool.Id, out var pool)) + return; + + foreach (var id in pool.Weights) + { + if (!_protoMan.TryIndex(id.Key, out var power) + || component.ActivePowers.Contains(power)) + continue; + + component.AvailablePowers.Add(id.Key, id.Value); + } + } + private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, MeleeHitEvent args) { foreach (var entity in args.HitEntities) @@ -180,7 +219,7 @@ private bool HandlePotentiaCalculations(EntityUid uid, PsionicComponent componen component.Potentia -= component.NextPowerCost; _psionicAbilitiesSystem.AddPsionics(uid); - component.NextPowerCost = 100 * MathF.Pow(2, component.PowerSlotsTaken); + component.NextPowerCost = component.BaselinePowerCost * MathF.Pow(2, component.PowerSlotsTaken); return true; } diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs index 16e0f028de0..d927671cf0a 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.DoAfter; using Content.Shared.Psionics; +using Content.Shared.Random; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -44,6 +45,12 @@ public sealed partial class PsionicComponent : Component [DataField] public float Potentia; + /// + /// The base cost for new powers. + /// + [DataField] + public float BaselinePowerCost = 100; + /// /// Each time a Psion rolls for a new power, they roll a number between 0 and 100, adding any relevant modifiers. This number is then added to Potentia, /// meaning that it carries over between rolls. When a character has an amount of potentia equal to at least 100 * 2^(total powers), the potentia is then spent, and a power is generated. @@ -138,6 +145,12 @@ private set } } + /// + /// Whether this entity is capable of randomly rolling for powers. + /// + [DataField] + public bool Roller = true; + /// /// Ifrits, revenants, etc are explicitly magical beings that shouldn't get mindbroken /// @@ -152,10 +165,10 @@ private set public HashSet ActivePowers = new(); /// - /// The list of each Psionic Power by action with entityUid. + /// The list of each Psionic Power by prototype with entityUid. /// [ViewVariables(VVAccess.ReadOnly)] - public Dictionary Actions = new(); + public Dictionary Actions = new(); /// /// What sources of Amplification does this Psion have? @@ -201,7 +214,7 @@ private set /// unneccesary subs for unique psionic entities like e.g. Oracle. /// [DataField] - public List? PsychognomicDescriptors = null; + public List PsychognomicDescriptors = new(); /// Used for tracking what spell a Psion is actively casting [DataField] @@ -225,5 +238,21 @@ private set /// [DataField] public int FamiliarLimit = 1; + + /// + /// The list of all potential Assay messages that can be obtained from this Psion. + /// + [DataField] + public List AssayFeedback = new(); + + /// + /// The list of powers that this Psion is eligible to roll new abilities from. + /// This generates the initial ability pool, but can also be modified by other systems. + /// + [DataField] + public ProtoId PowerPool = "RandomPsionicPowerPool"; + + [DataField] + public Dictionary AvailablePowers = new(); } } diff --git a/Content.Shared/Psionics/PsionicPowerPrototype.cs b/Content.Shared/Psionics/PsionicPowerPrototype.cs index d81ae05be23..ecc988d5d67 100644 --- a/Content.Shared/Psionics/PsionicPowerPrototype.cs +++ b/Content.Shared/Psionics/PsionicPowerPrototype.cs @@ -1,5 +1,7 @@ -using Content.Shared.Chat; +using Content.Shared.Abilities.Psionics; +using Robust.Shared.Player; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; namespace Content.Shared.Psionics; @@ -19,77 +21,43 @@ public sealed partial class PsionicPowerPrototype : IPrototype public string Name = default!; /// - /// The description of a power in yml, used for player notifications. - /// - [DataField(required: true)] - public string Description = default!; - - /// - /// The list of each Action that this power adds in the form of ActionId and ActionEntity - /// - [DataField] - public List Actions = new(); - - /// - /// The list of what Components this power adds. - /// - [DataField] - public ComponentRegistry Components = new(); - - /// - /// What message will be sent to the player as a Popup. - /// If left blank, it will default to the Const "generic-power-initialization-feedback" - /// - [DataField] - public string? InitializationPopup; - - /// - /// What message will be sent to the chat window when the power is initialized. Leave it blank to send no message. - /// Initialization messages won't play for powers that are Innate, only powers obtained during the round. - /// These should generally also be written in the first person, and can be far lengthier than popups. - /// - [DataField] - public string? InitializationFeedback; - - /// - /// What color will the initialization feedback display in the chat window with. - /// - [DataField] - public string InitializationFeedbackColor = "#8A00C2"; - - /// - /// What font size will the initialization message use in chat. - /// - [DataField] - public int InitializationFeedbackFontSize = 12; - - /// - /// Which chat channel will the initialization message use. + /// What category of psionics does this power come from. + /// EG: Mentalics, Anomalists, Blood Cults, Heretics, etc. /// [DataField] - public ChatChannel InitializationFeedbackChannel = ChatChannel.Emotes; + public List PowerCategories = new(); /// - /// What message will this power generate when scanned by a Metempsionic Focused Pulse. + /// These functions are called when a Psionic Power is added to a Psion. /// - [DataField] - public string MetapsionicFeedback = "psionic-metapsionic-feedback-default"; + [DataField(serverOnly: true)] + public PsionicPowerFunction[] InitializeFunctions { get; private set; } = Array.Empty(); /// - /// How much this power will increase or decrease a user's Amplification. + /// These functions are called when a Psionic Power is removed from a Psion, + /// as a rule of thumb these should do the exact opposite of most of a power's init functions. /// - [DataField] - public float AmplificationModifier = 0; - - /// - /// How much this power will increase or decrease a user's Dampening. - /// - [DataField] - public float DampeningModifier = 0; + [DataField(serverOnly: true)] + public PsionicPowerFunction[] RemovalFunctions { get; private set; } = Array.Empty(); /// /// How many "Power Slots" this power occupies. /// [DataField] public int PowerSlotCost = 1; -} \ No newline at end of file +} + +/// This serves as a hook for psionic powers to modify the psionic component. +[ImplicitDataDefinitionForInheritors] +public abstract partial class PsionicPowerFunction +{ + public abstract void OnAddPsionic( + EntityUid mob, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager, + ISharedPlayerManager playerManager, + ILocalizationManager loc, + PsionicComponent psionicComponent, + PsionicPowerPrototype proto); +} diff --git a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml index 8b2911018f9..f1d4f69b05a 100644 --- a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml +++ b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml @@ -5,15 +5,11 @@ DispelPower: 1 #TelegnosisPower: 1 PsionicRegenerationPower: 1 - XenoglossyPower: 0.75 - PsychognomyPower: 0.75 MassSleepPower: 0.3 # PsionicInvisibilityPower: 0.15 MindSwapPower: 0.15 TelepathyPower: 1 HealingWordPower: 0.85 - RevivifyPower: 0.1 ShadeskipPower: 0.15 TelekineticPulsePower: 0.15 PyrokineticFlare: 0.3 - SummonImpPower: 0.15 diff --git a/Resources/Prototypes/Psionics/psionics.yml b/Resources/Prototypes/Psionics/psionics.yml index 7ee6e193e37..ad32424778d 100644 --- a/Resources/Prototypes/Psionics/psionics.yml +++ b/Resources/Prototypes/Psionics/psionics.yml @@ -1,275 +1,650 @@ - type: psionicPower id: DispelPower name: Dispel - description: dispel-power-description - actions: - - ActionDispel - components: - - type: DispelPower - initializationFeedback: dispel-power-initialization-feedback - metapsionicFeedback: dispel-power-metapsionic-feedback - dampeningModifier: 1 + powerCategories: + - Mentalic + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionDispel + - !type:AddPsionicPowerComponents + components: + - type: DispelPower + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: dispel-power-initialization-feedback + - !type:PsionicModifyGlimmer + glimmerModifier: -4 + - !type:AddPsionicAssayFeedback + assayFeedback: dispel-power-metapsionic-feedback + - !type:AddPsionicStatSources + dampeningModifier: 1 + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicPowerComponents + components: + - type: DispelPower + - !type:RemovePsionicStatSources + - !type:RemoveAssayFeedback + assayFeedback: dispel-power-metapsionic-feedback - type: psionicPower id: MassSleepPower name: Mass Sleep - description: mass-sleep-power-description - actions: - - ActionMassSleep - components: - - type: MassSleepPower - # initializationFeedback: mass-sleep-power-initialization-feedback # I apologize, I don't feel like writing a paragraph of feedback for a power that's getting replaced with a new one. - metapsionicFeedback: mass-sleep-power-metapsionic-feedback - amplificationModifier: 0.5 - dampeningModifier: 0.5 + powerCategories: + - Mentalic + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionMassSleep + - !type:AddPsionicPowerComponents + components: + - type: MassSleepPower + - !type:PsionicFeedbackPopup + - !type:PsionicModifyGlimmer + glimmerModifier: 5 + - !type:AddPsionicAssayFeedback + assayFeedback: mass-sleep-power-metapsionic-feedback + - !type:AddPsionicStatSources + amplificationModifier: 0.5 + dampeningModifier: 0.5 + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicPowerComponents + components: + - type: MassSleepPower + - !type:RemovePsionicStatSources + - !type:RemoveAssayFeedback + assayFeedback: mass-sleep-power-metapsionic-feedback - type: psionicPower id: MindSwapPower name: Mind Swap - description: mind-swap-power-description - actions: - - ActionMindSwap - components: - - type: MindSwapPower - initializationFeedback: mind-swap-power-initialization-feedback - metapsionicFeedback: mind-swap-power-metapsionic-feedback - amplificationModifier: 1 + powerCategories: + - Mentalic + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionMindSwap + - !type:AddPsionicPowerComponents + components: + - type: MindSwapPower + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: mind-swap-power-initialization-feedback + - !type:PsionicModifyGlimmer + glimmerModifier: 4 + - !type:AddPsionicAssayFeedback + assayFeedback: mind-swap-power-metapsionic-feedback + - !type:AddPsionicStatSources + amplificationModifier: 1 + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicPowerComponents + components: + - type: MindSwapPower + - !type:RemovePsionicStatSources + - !type:RemoveAssayFeedback + assayFeedback: mind-swap-power-metapsionic-feedback - type: psionicPower id: NoosphericZapPower name: Noospheric Zap - description: noospheric-zap-power-description - actions: - - ActionNoosphericZap - components: - - type: NoosphericZapPower - initializationFeedback: noospheric-zap-power-initialization-feedback - metapsionicFeedback: noospheric-zap-power-metapsionic-feedback - amplificationModifier: 1 + powerCategories: + - Anomalist + - Electrokinesis + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionNoosphericZap + - !type:AddPsionicPowerComponents + components: + - type: NoosphericZapPower + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: noospheric-zap-power-initialization-feedback + - !type:PsionicModifyGlimmer + glimmerModifier: 4 + - !type:AddPsionicAssayFeedback + assayFeedback: noospheric-zap-power-metapsionic-feedback + - !type:AddPsionicStatSources + amplificationModifier: 1 + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicPowerComponents + components: + - type: NoosphericZapPower + - !type:RemovePsionicStatSources + - !type:RemoveAssayFeedback + assayFeedback: noospheric-zap-power-metapsionic-feedback - type: psionicPower id: PyrokinesisPower name: Pyrokinesis - description: pyrokinesis-power-description - actions: - - ActionPyrokinesis - components: - - type: PyrokinesisPower - initializationFeedback: pyrokinesis-power-initialization-feedback - metapsionicFeedback: pyrokinesis-power-metapsionic-feedback - amplificationModifier: 1 + powerCategories: + - Anomalist + - Pyrokinesis + - Dangerous + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionPyrokinesis + - !type:AddPsionicPowerComponents + components: + - type: PyrokinesisPower + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: pyrokinesis-power-initialization-feedback + - !type:PsionicModifyGlimmer + glimmerModifier: 4 + - !type:AddPsionicAssayFeedback + assayFeedback: pyrokinesis-power-metapsionic-feedback + - !type:AddPsionicStatSources + amplificationModifier: 1 + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicPowerComponents + components: + - type: PyrokinesisPower + - !type:RemovePsionicStatSources + - !type:RemoveAssayFeedback + assayFeedback: pyrokinesis-power-metapsionic-feedback - type: psionicPower id: MetapsionicPower name: Metapsionic Pulse - description: metapsionic-power-description - actions: - - ActionMetapsionic - components: - - type: MetapsionicPower - initializationFeedback: metapsionic-power-initialization-feedback - metapsionicFeedback: metapsionic-power-metapsionic-feedback - amplificationModifier: 0.5 - dampeningModifier: 0.5 + powerCategories: + - Mentalic + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionMetapsionic + - !type:AddPsionicPowerComponents + components: + - type: MetapsionicPower + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: metapsionic-power-initialization-feedback + - !type:AddPsionicAssayFeedback + assayFeedback: metapsionic-power-metapsionic-feedback + - !type:AddPsionicStatSources + amplificationModifier: 0.5 + dampeningModifier: 0.5 + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicPowerComponents + components: + - type: MetapsionicPower + - !type:RemovePsionicStatSources + - !type:RemoveAssayFeedback + assayFeedback: metapsionic-power-metapsionic-feedback - type: psionicPower id: PsionicRegenerationPower name: Psionic Regeneration - description: psionic-regeneration-power-description - actions: - - ActionPsionicRegeneration - components: - - type: PsionicRegenerationPower - initializationFeedback: psionic-regeneration-power-initialization-feedback - metapsionicFeedback: psionic-regeneration-power-metapsionic-feedback - amplificationModifier: 0.5 - dampeningModifier: 0.5 + powerCategories: + - Mentalic + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionPsionicRegeneration + - !type:AddPsionicPowerComponents + components: + - type: PsionicRegenerationPower + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: psionic-regeneration-power-initialization-feedback + - !type:PsionicModifyGlimmer + glimmerModifier: 4 + - !type:AddPsionicAssayFeedback + assayFeedback: psionic-regeneration-power-metapsionic-feedback + - !type:AddPsionicStatSources + amplificationModifier: 0.5 + dampeningModifier: 0.5 + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicPowerComponents + components: + - type: PsionicRegenerationPower + - !type:RemovePsionicStatSources + - !type:RemoveAssayFeedback + assayFeedback: psionic-regeneration-power-metapsionic-feedback - type: psionicPower id: TelegnosisPower name: Telegnosis - description: telegnosis-power-description - actions: - - ActionTelegnosis - components: - - type: TelegnosisPower - initializationFeedback: telegnosis-power-initialization-feedback - metapsionicFeedback: telegnosis-power-metapsionic-feedback - amplificationModifier: 0.5 - dampeningModifier: 0.5 + powerCategories: + - Mentalic + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionTelegnosis + - !type:AddPsionicPowerComponents + components: + - type: TelegnosisPower + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: telegnosis-power-initialization-feedback + - !type:PsionicModifyGlimmer + glimmerModifier: 4 + - !type:AddPsionicAssayFeedback + assayFeedback: telegnosis-power-metapsionic-feedback + - !type:AddPsionicStatSources + amplificationModifier: 0.5 + dampeningModifier: 0.5 + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicPowerComponents + components: + - type: TelegnosisPower + - !type:RemovePsionicStatSources + - !type:RemoveAssayFeedback + assayFeedback: telegnosis-power-metapsionic-feedback - type: psionicPower id: PsionicInvisibilityPower name: Psionic Invisibility - description: psionic-invisibility-power-description - actions: - - ActionDispel - components: - - type: PsionicInvisibilityPower - initializationFeedback: psionic-invisibility-power-initialization-feedback - metapsionicFeedback: psionic-invisibility-power-metapsionic-feedback - amplificationModifier: 0.5 - dampeningModifier: 0.5 + powerCategories: + - Mentalic + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionPsionicInvisibility + - !type:AddPsionicPowerComponents + components: + - type: PsionicInvisibilityPower + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: psionic-invisibility-power-initialization-feedback + - !type:PsionicModifyGlimmer + glimmerModifier: 4 + - !type:AddPsionicAssayFeedback + assayFeedback: psionic-invisibility-power-metapsionic-feedback + - !type:AddPsionicStatSources + amplificationModifier: 0.5 + dampeningModifier: 0.5 + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicPowerComponents + components: + - type: PsionicInvisibilityPower + - !type:RemovePsionicStatSources + - !type:RemoveAssayFeedback + assayFeedback: psionic-invisibility-power-metapsionic-feedback - type: psionicPower id: XenoglossyPower name: Xenoglossy - description: xenoglossy-power-description - components: - - type: UniversalLanguageSpeaker - initializationFeedback: xenoglossy-power-initialization-feedback - metapsionicFeedback: psionic-language-power-feedback # Reuse for telepathy, clairaudience, etc + powerCategories: + - Mentalic + initializeFunctions: + - !type:AddPsionicPowerComponents + components: + - type: UniversalLanguageSpeaker + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: xenoglossy-power-initialization-feedback + - !type:AddPsionicAssayFeedback + assayFeedback: psionic-language-power-feedback + removalFunctions: + - !type:RemovePsionicPowerComponents + components: + - type: Telepathy + - !type:RemoveAssayFeedback + assayFeedback: psionic-language-power-feedback powerSlotCost: 0 - type: psionicPower id: PsychognomyPower #i.e. reverse physiognomy name: Psychognomy #psycho- + -gnomy. I reccomend starting with your language's equilvalent of "physiognomy" and working backwards. i.e. психо(г)номика - description: psychognomy-power-description - components: - - type: Psychognomist - initializationFeedback: psychognomy-power-initialization-feedback - metapsionicFeedback: psionic-language-power-feedback + powerCategories: + - Mentalic + initializeFunctions: + - !type:AddPsionicPowerComponents + components: + - type: Psychognomist + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: psychognomy-power-initialization-feedback + - !type:AddPsionicAssayFeedback + assayFeedback: psionic-language-power-feedback + removalFunctions: + - !type:RemovePsionicPowerComponents + components: + - type: Psychognomist + - !type:RemoveAssayFeedback + assayFeedback: psionic-language-power-feedback powerSlotCost: 0 - type: psionicPower id: TelepathyPower name: Telepathy - description: telepathy-power-description - components: - - type: Telepathy - initializationFeedback: telepathy-power-initialization-feedback - metapsionicFeedback: psionic-language-power-feedback # Reuse for telepathy, clairaudience, etc + powerCategories: + - Mentalic + initializeFunctions: + - !type:AddPsionicPowerComponents + components: + - type: Telepathy + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: telepathy-power-initialization-feedback + - !type:AddPsionicAssayFeedback + assayFeedback: psionic-language-power-feedback + - !type:PsionicAddAvailablePowers + powerPrototype: XenoglossyPower + weight: 0.75 + - !type:PsionicAddAvailablePowers + powerPrototype: PsychognomyPower + weight: 0.75 + removalFunctions: + - !type:RemovePsionicPowerComponents + components: + - type: Telepathy + - !type:RemoveAssayFeedback + assayFeedback: psionic-language-power-feedback + - !type:PsionicRemoveAvailablePowers + powerPrototype: XenoglossyPower + - !type:PsionicRemoveAvailablePowers + powerPrototype: PsychognomyPower powerSlotCost: 0 - type: psionicPower id: HealingWordPower name: HealingWord - description: healing-word-power-description - actions: - - ActionHealingWord - initializationFeedback: healing-word-power-initialization-feedback - metapsionicFeedback: healing-word-power-feedback - amplificationModifier: 0.5 - dampeningModifier: 0.5 + powerCategories: + - Anomalist + - Life + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionHealingWord + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: healing-word-power-initialization-feedback + - !type:PsionicModifyGlimmer + glimmerModifier: 4 + - !type:AddPsionicAssayFeedback + assayFeedback: healing-word-power-feedback + - !type:AddPsionicStatSources + amplificationModifier: 0.5 + dampeningModifier: 0.5 + - !type:PsionicAddAvailablePowers + powerPrototype: RevivifyPower + weight: 0.1 + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicStatSources + - !type:RemoveAssayFeedback + assayFeedback: healing-word-power-feedback + - !type:PsionicRemoveAvailablePowers + powerPrototype: RevivifyPower - type: psionicPower id: RevivifyPower name: Revivify - description: revivify-power-description - actions: - - ActionRevivify - initializationFeedback: revivify-power-initialization-feedback - metapsionicFeedback: revivify-power-feedback - amplificationModifier: 2.5 # An extremely rare and dangerous power - powerSlotCost: 2 + powerCategories: + - Anomalist + - Life + - Dangerous + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionRevivify + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: revivify-power-initialization-feedback + - !type:PsionicModifyGlimmer + glimmerModifier: 15 + - !type:AddPsionicAssayFeedback + assayFeedback: revivify-power-feedback + - !type:AddPsionicStatSources + amplificationModifier: 2.5 + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicStatSources + - !type:RemoveAssayFeedback + assayFeedback: revivify-power-feedback - type: psionicPower id: LowAmplification name: LowAmplification - description: low-amplification-power-description - amplificationModifier: -0.25 + powerCategories: + - Passive + initializeFunctions: + - !type:AddPsionicStatSources + amplificationModifier: -0.25 + removalFunctions: + - !type:RemovePsionicStatSources powerSlotCost: 0 - type: psionicPower id: HighAmplification name: HighAmplification - description: high-amplification-power-description - amplificationModifier: 0.25 + powerCategories: + - Passive + initializeFunctions: + - !type:AddPsionicStatSources + amplificationModifier: 0.25 + removalFunctions: + - !type:RemovePsionicStatSources powerSlotCost: 0 - type: psionicPower id: PowerOverwhelming name: PowerOverwhelming - description: power-overwhelming-power-description - metapsionicFeedback: power-overwhelming-power-feedback - amplificationModifier: 2 + powerCategories: + - Passive + - Dangerous + initializeFunctions: + - !type:AddPsionicStatSources + amplificationModifier: 2 + - !type:AddPsionicAssayFeedback + assayFeedback: power-overwhelming-power-feedback + - !type:AddPsionicPsychognomicDescriptors + psychognomicDescriptor: OVERWHELMING + removalFunctions: + - !type:RemovePsionicStatSources + - !type:RemoveAssayFeedback + assayFeedback: power-overwhelming-power-feedback + - !type:AddPsionicPsychognomicDescriptors + psychognomicDescriptor: OVERWHELMING powerSlotCost: 2 - type: psionicPower id: LowDampening name: LowDampening - description: low-dampening-power-description - dampeningModifier: -0.25 + powerCategories: + - Passive + initializeFunctions: + - !type:AddPsionicStatSources + dampeningModifier: -0.25 + removalFunctions: + - !type:RemovePsionicStatSources powerSlotCost: 0 - type: psionicPower id: HighDampening name: HighDampening - description: high-dampening-power-description - dampeningModifier: 0.25 + powerCategories: + - Passive + initializeFunctions: + - !type:AddPsionicStatSources + dampeningModifier: 0.25 + removalFunctions: + - !type:RemovePsionicStatSources powerSlotCost: 0 - type: psionicPower id: ShadeskipPower name: Shadeskip - description: shadeskip-power-description - actions: - - ActionShadeskip - initializationFeedback: shadeskip-power-initialization-feedback - metapsionicFeedback: shadeskip-power-metapsionic-feedback - amplificationModifier: 1 + powerCategories: + - Anomalist + - Shadow + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionShadeskip + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: shadeskip-power-initialization-feedback + - !type:PsionicModifyGlimmer + glimmerModifier: 4 + - !type:AddPsionicAssayFeedback + assayFeedback: shadeskip-power-metapsionic-feedback + - !type:AddPsionicStatSources + amplificationModifier: 1 + - !type:AddPsionicPsychognomicDescriptors + psychognomicDescriptor: tenebrous + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicStatSources + - !type:RemoveAssayFeedback + assayFeedback: shadeskip-power-metapsionic-feedback + - !type:RemovePsionicPsychognomicDescriptors + psychognomicDescriptor: tenebrous - type: psionicPower id: TelekineticPulsePower name: Telekinetic Pulse - description: telekinetic-pulse-power-description - actions: - - ActionTelekineticPulse - initializationFeedback: telekinetic-pulse-power-initialization-feedback - metapsionicFeedback: telekinetic-pulse-power-metapsionic-feedback - amplificationModifier: 1 + powerCategories: + - Anomalist + - Kinetic + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionTelekineticPulse + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: telekinetic-pulse-power-initialization-feedback + - !type:PsionicModifyGlimmer + glimmerModifier: 10 + - !type:AddPsionicAssayFeedback + assayFeedback: telekinetic-pulse-power-metapsionic-feedback + - !type:AddPsionicStatSources + amplificationModifier: 1 + - !type:AddPsionicPsychognomicDescriptors + psychognomicDescriptor: kinetic + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicStatSources + - !type:RemoveAssayFeedback + assayFeedback: telekinetic-pulse-power-metapsionic-feedback + - !type:RemovePsionicPsychognomicDescriptors + psychognomicDescriptor: kinetic -- type: psionicPower - id: ShadowkinPowers - name: Shadowkin Powers - description: shadowkin-powers-description - actions: - - ActionShadowkinShadeskip - - ActionDarkSwap - powerSlotCost: 1 +# - type: psionicPower +# id: ShadowkinPowers +# name: Shadowkin Powers +# description: shadowkin-powers-description +# actions: +# - ActionShadowkinShadeskip +# - ActionDarkSwap +# powerSlotCost: 1 -- type: psionicPower - id: EtherealVisionPower - name: Ethereal Vision - description: ethereal-vision-powers-description - components: - - type: ShowEthereal - powerSlotCost: 0 +# - type: psionicPower +# id: EtherealVisionPower +# name: Ethereal Vision +# description: ethereal-vision-powers-description +# components: +# - type: ShowEthereal +# powerSlotCost: 0 -- type: psionicPower - id: DarkSwapPower - name: DarkSwap - description: darkswap-power-description - actions: - - ActionDarkSwap - powerSlotCost: 1 +# - type: psionicPower +# id: DarkSwapPower +# name: DarkSwap +# description: darkswap-power-description +# actions: +# - ActionDarkSwap +# powerSlotCost: 1 - type: psionicPower id: PyrokineticFlare name: Pyrokinetic Flare - description: pyrokinetic-flare-power-description - actions: - - ActionPyrokineticFlare + powerCategories: + - Anomalist + - Pyrokinetic + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionPyrokineticFlare + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: pyrokinetic-flare-power-initialization-feedback + - !type:PsionicModifyGlimmer + glimmerModifier: 4 + - !type:AddPsionicAssayFeedback + assayFeedback: pyrokinetic-flare-power-metapsionic-feedback + - !type:AddPsionicStatSources + amplificationModifier: 0.25 + - !type:PsionicAddAvailablePowers + powerPrototype: SummonImpPower + weight: 0.3 + - !type:PsionicAddAvailablePowers + powerPrototype: PyrokinesisPower + weight: 0.1 + - !type:AddPsionicPsychognomicDescriptors + psychognomicDescriptor: pyre + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicStatSources + - !type:RemoveAssayFeedback + assayFeedback: pyrokinetic-flare-power-metapsionic-feedback + - !type:PsionicRemoveAvailablePowers + powerPrototype: SummonImpPower + - !type:PsionicRemoveAvailablePowers + powerPrototype: PyrokinesisPower + - !type:RemovePsionicPsychognomicDescriptors + psychognomicDescriptor: pyre powerSlotCost: 1 - initializationFeedback: pyrokinetic-flare-power-initialization-feedback - metapsionicFeedback: pyrokinetic-flare-power-metapsionic-feedback - amplificationModifier: 0.25 - type: psionicPower id: SummonImpPower name: Summon Imp - description: summon-imp-power-description - actions: - - ActionSummonImp + powerCategories: + - Anomalist + - Pyrokinetic + - Dangerous + - Summoning + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionSummonImp + - !type:PsionicFeedbackPopup + - !type:PsionicFeedbackSelfChat + feedbackMessage: summon-imp-power-initialization-feedback + - !type:PsionicModifyGlimmer + glimmerModifier: 10 + - !type:AddPsionicStatSources + amplificationModifier: 0.5 + dampeningModifier: 0.5 + - !type:AddPsionicPsychognomicDescriptors + psychognomicDescriptor: pyre + - !type:AddPsionicPsychognomicDescriptors + psychognomicDescriptor: calling + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicStatSources + - !type:RemovePsionicPsychognomicDescriptors + psychognomicDescriptor: pyre + - !type:RemovePsionicPsychognomicDescriptors + psychognomicDescriptor: calling powerSlotCost: 1 - initializationFeedback: summon-imp-power-initialization-feedback - amplificationModifier: 0.5 - dampeningModifier: 0.5 - type: psionicPower id: SummonRemiliaPower name: Summon Remilia - description: summon-imp-power-description - actions: - - ActionSummonRemilia + powerCategories: + - Mentalic + - Unique + - Summoning + initializeFunctions: + - !type:AddPsionicActions + actions: + - ActionSummonRemilia + - !type:AddPsionicPsychognomicDescriptors + psychognomicDescriptor: calling + removalFunctions: + - !type:RemovePsionicActions + - !type:RemovePsionicPsychognomicDescriptors + psychognomicDescriptor: calling powerSlotCost: 0 From e9799ce7a232766266ce0e5a5ce4a1203f3b85db Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 30 Dec 2024 15:46:20 -0500 Subject: [PATCH 2/5] GO --- Resources/Prototypes/Entities/Mobs/Species/shadowkin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Mobs/Species/shadowkin.yml b/Resources/Prototypes/Entities/Mobs/Species/shadowkin.yml index a0694465b55..3dd7b2c651c 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/shadowkin.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/shadowkin.yml @@ -229,7 +229,7 @@ noMana: shadowkin-tired - type: InnatePsionicPowers powersToAdd: - - ShadowkinPowers + - DarkSwapPower - type: LanguageKnowledge speaks: - TauCetiBasic From 4684ed12cb784f781a9c7720b15b72ab0a9d4c39 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 30 Dec 2024 16:00:58 -0500 Subject: [PATCH 3/5] comment unused Nyanotrasen Shitcode --- .../Psionics/NPC/PsionicNpcCombatSystem.cs | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/Content.Server/Nyanotrasen/Psionics/NPC/PsionicNpcCombatSystem.cs b/Content.Server/Nyanotrasen/Psionics/NPC/PsionicNpcCombatSystem.cs index 9caef36a752..1f18176fac6 100644 --- a/Content.Server/Nyanotrasen/Psionics/NPC/PsionicNpcCombatSystem.cs +++ b/Content.Server/Nyanotrasen/Psionics/NPC/PsionicNpcCombatSystem.cs @@ -27,25 +27,24 @@ public override void Initialize() SubscribeLocalEvent(ZapCombat); NoosphericZap = _protoMan.Index(NoosphericZapProto); - DebugTools.Assert(NoosphericZap.Actions.Count == 1, "I can't account for this, so it's your problem now"); } private void ZapCombat(Entity ent, ref NPCSteeringEvent args) { - PsionicComponent? psionics = null; - if (!Resolve(ent, ref psionics, logMissing: true) - || !psionics.Actions.TryGetValue(NoosphericZap.Actions[0], out var action) - || action is null) - return; - - var actionTarget = Comp(action.Value); - if (actionTarget.Cooldown is {} cooldown && cooldown.End > _timing.CurTime - || !TryComp(ent, out var combat) - || !_actions.ValidateEntityTarget(ent, combat.Target, (action.Value, actionTarget)) - || actionTarget.Event is not {} ev) - return; - - ev.Target = combat.Target; - _actions.PerformAction(ent, null, action.Value, actionTarget, ev, _timing.CurTime, predicted: false); + // Nothing uses this anyway, what the hell it's pure shitcode? + // PsionicComponent? psionics = null; + // if (!Resolve(ent, ref psionics, logMissing: true) + // || !psionics.ActivePowers.Contains(NoosphericZap)) + // return; + + // var actionTarget = Comp(action.Value); + // if (actionTarget.Cooldown is {} cooldown && cooldown.End > _timing.CurTime + // || !TryComp(ent, out var combat) + // || !_actions.ValidateEntityTarget(ent, combat.Target, (action.Value, actionTarget)) + // || actionTarget.Event is not {} ev) + // return; + + // ev.Target = combat.Target; + // _actions.PerformAction(ent, null, action.Value, actionTarget, ev, _timing.CurTime, predicted: false); } } From e7e372f3a5e6b42332e76dae3081fed69366cb85 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 1 Jan 2025 16:30:47 -0500 Subject: [PATCH 4/5] Localizing --- .../Abilities/Psionics/PsionicAbilitiesSystem.cs | 7 ++++--- Content.Shared/Psionics/PsionicComponent.cs | 5 +++++ Resources/Locale/en-US/psionics/psionic-powers.ftl | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs index 9865b0aa881..ff32809a5a4 100644 --- a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs +++ b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs @@ -206,19 +206,20 @@ public void MindBreak(EntityUid uid) RaiseLocalEvent(uid, ref ev); if (_config.GetCVar(CCVars.ScarierMindbreaking)) - ScarierMindbreak(uid); + ScarierMindbreak(uid, psionic); } /// /// An even more advanced form of Mindbreaking. Turn the victim into an NPC. /// For the people who somehow didn't intuit from the absolutely horrifying text that mindbreaking people is very fucking bad. /// - public void ScarierMindbreak(EntityUid uid) + public void ScarierMindbreak(EntityUid uid, PsionicComponent component) { if (!_playerManager.TryGetSessionByEntity(uid, out var session) || session is null) return; - var feedbackMessage = $"[font size=24][color=#ff0000]{"Your characters personhood has been obliterated. If you wish to continue playing, consider respawning as a new character."}[/color][/font]"; + var popup = Loc.GetString(component.HardMindbreakingFeedback); + var feedbackMessage = $"[font size=24][color=#ff0000]{popup}[/color][/font]"; _chatManager.ChatMessageToOne( ChatChannel.Emotes, feedbackMessage, diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs index 4594d9f9e85..58118dbd700 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -88,6 +88,11 @@ public sealed partial class PsionicComponent : Component [DataField] public string MindbreakingFeedback = "mindbreaking-feedback"; + /// + /// + [DataField] + public string HardMindbreakingFeedback = "hard-mindbreaking-feedback"; + /// /// How much should the odds of obtaining a Psionic Power be multiplied when rolling for one. /// diff --git a/Resources/Locale/en-US/psionics/psionic-powers.ftl b/Resources/Locale/en-US/psionics/psionic-powers.ftl index ae3cfb383ed..a0e6f1df7ac 100644 --- a/Resources/Locale/en-US/psionics/psionic-powers.ftl +++ b/Resources/Locale/en-US/psionics/psionic-powers.ftl @@ -160,6 +160,7 @@ summon-remilia-power-description = { action-description-summon-remilia } # Psionic System Messages mindbreaking-feedback = The light of life vanishes from {CAPITALIZE($entity)}'s eyes, leaving behind a husk pretending at sapience +hard-mindbreaking-feedback = Your characters personhood has been obliterated. If you wish to continue playing, consider respawning as a new character. examine-mindbroken-message = Eyes unblinking, staring deep into the horizon. {CAPITALIZE($entity)} is a sack of meat pretending it has a soul. There is nothing behind its gaze, no evidence there can be found of the divine light of creation. From 269e28ea0734a1620a1c8d150af59d1ea3f447d9 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 1 Jan 2025 16:31:21 -0500 Subject: [PATCH 5/5] Update psionic-powers.ftl --- Resources/Locale/en-US/psionics/psionic-powers.ftl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Locale/en-US/psionics/psionic-powers.ftl b/Resources/Locale/en-US/psionics/psionic-powers.ftl index a0e6f1df7ac..ab2a991e06f 100644 --- a/Resources/Locale/en-US/psionics/psionic-powers.ftl +++ b/Resources/Locale/en-US/psionics/psionic-powers.ftl @@ -160,7 +160,7 @@ summon-remilia-power-description = { action-description-summon-remilia } # Psionic System Messages mindbreaking-feedback = The light of life vanishes from {CAPITALIZE($entity)}'s eyes, leaving behind a husk pretending at sapience -hard-mindbreaking-feedback = Your characters personhood has been obliterated. If you wish to continue playing, consider respawning as a new character. +hard-mindbreaking-feedback = Your character's personhood has been obliterated. If you wish to continue playing, consider respawning as a new character. examine-mindbroken-message = Eyes unblinking, staring deep into the horizon. {CAPITALIZE($entity)} is a sack of meat pretending it has a soul. There is nothing behind its gaze, no evidence there can be found of the divine light of creation.