From 9e293ea531cac81779457e90a6841784c2c96cc6 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Tue, 17 Sep 2024 19:41:27 -0400 Subject: [PATCH] Psionic Power Rolling Rework (#830) # Description This PR implements a long overdue rework of the underlying system for actually giving Psions their powers, by making it a semi-deterministic system that favors people having a small handful of powers, unless said person has inherent modifiers that let them generate them more consistently. A common complaint I received from the new Psionic System is that stations tended to become "Space Hogwarts" given enough time, since people could very easily game the system to consistently get new powers every 20 minutes, until a majority of the station was throwing fireballs at each other. Now, obtaining new powers gets more difficult the more powers you have, exponentially so. Powers can also declare in their prototype how many "Slots" they occupy, meaning that certain powers can make this even more difficult, or not at all, as desired. # Changelog :cl: VMSolidus & Rane - add: Psionic Power "Rerolls" have been reworked. Psions now have a stat called Potentia, which is increased by sources of Psionic Rerolls such as Space Drugs, Loto Oil, and Random Events. Instead of being an all-or-nothing "D100 with modifiers" roll, whatever you roll is added to your Potentia stat, and when Potentia reaches a certain threshold, it is automatically "Spent" to buy a random new power. Your first power costs 100 Potentia, your 2nd power costs 200, 3rd costs 400, and so on. The more powers you have, the more difficult it is to obtain new ones. - tweak: Some powers, such as Telepathy and Xenoglossy, do not count as a power for the purpose of Potentia Cost --------- Signed-off-by: VMSolidus --- .../Psionics/PsionicAbilitiesSystem.cs | 24 ++ Content.Server/Psionics/PsionicsSystem.cs | 325 ++++++++++++------ .../StationEvents/Events/NoosphericZapRule.cs | 4 +- Content.Shared/Psionics/PsionicComponent.cs | 28 +- .../Psionics/PsionicPowerPrototype.cs | 6 + .../Locale/en-US/psionics/psionic-powers.ftl | 4 +- .../Roles/Jobs/Epistemics/forensicmantis.yml | 1 + Resources/Prototypes/Psionics/psionics.yml | 3 + .../Roles/Jobs/Science/research_director.yml | 1 + 9 files changed, 278 insertions(+), 118 deletions(-) diff --git a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs index 32e51d3c101..536235b6d63 100644 --- a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs +++ b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs @@ -12,6 +12,7 @@ using System.Linq; using Robust.Server.Player; using Content.Server.Chat.Managers; +using Content.Server.Psionics.Glimmer; namespace Content.Server.Abilities.Psionics { @@ -122,6 +123,8 @@ public void InitializePsionicPower(EntityUid uid, PsionicPowerPrototype proto, P 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! } @@ -297,6 +300,27 @@ private void SendFeedbackMessage(EntityUid uid, PsionicPowerPrototype proto, boo session.Channel); } + private void UpdatePowerSlots(PsionicComponent psionic) + { + var slotsUsed = 0; + foreach (var power in psionic.ActivePowers) + slotsUsed += power.PowerSlotCost; + + psionic.PowerSlotsTaken = slotsUsed; + } + + /// + /// 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. /// diff --git a/Content.Server/Psionics/PsionicsSystem.cs b/Content.Server/Psionics/PsionicsSystem.cs index 23cf6aeb80b..0d05000a3cd 100644 --- a/Content.Server/Psionics/PsionicsSystem.cs +++ b/Content.Server/Psionics/PsionicsSystem.cs @@ -11,139 +11,242 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.Random; +using Content.Shared.Popups; +using Content.Shared.Chat; +using Robust.Server.Player; +using Content.Server.Chat.Managers; +using Robust.Shared.Prototypes; +using Content.Shared.Psionics; -namespace Content.Server.Psionics +namespace Content.Server.Psionics; + +public sealed class PsionicsSystem : EntitySystem { - public sealed class PsionicsSystem : EntitySystem + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly PsionicAbilitiesSystem _psionicAbilitiesSystem = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; + [Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!; + [Dependency] private readonly MindSwapPowerSystem _mindSwapPowerSystem = default!; + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + [Dependency] private readonly NpcFactionSystem _npcFactonSystem = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPopupSystem _popups = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; + + private const string BaselineAmplification = "Baseline Amplification"; + private const string BaselineDampening = "Baseline Dampening"; + + // Yes these are a mirror of what's normally default datafields on the PsionicPowerPrototype. + // We haven't generated a prototype yet, and I'm not going to duplicate them on the PsionicComponent. + private const string PsionicRollFailedMessage = "psionic-roll-failed"; + private const string PsionicRollFailedColor = "#8A00C2"; + private const int PsionicRollFailedFontSize = 12; + private const ChatChannel PsionicRollFailedChatChannel = ChatChannel.Emotes; + + /// + /// Unfortunately, since spawning as a normal role and anything else is so different, + /// this is the only way to unify them, for now at least. + /// + Queue<(PsionicComponent component, EntityUid uid)> _rollers = new(); + public override void Update(float frameTime) { - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly PsionicAbilitiesSystem _psionicAbilitiesSystem = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; - [Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!; - [Dependency] private readonly MindSwapPowerSystem _mindSwapPowerSystem = default!; - [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; - [Dependency] private readonly NpcFactionSystem _npcFactonSystem = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - - private const string BaselineAmplification = "Baseline Amplification"; - private const string BaselineDampening = "Baseline Dampening"; - - /// - /// Unfortunately, since spawning as a normal role and anything else is so different, - /// this is the only way to unify them, for now at least. - /// - Queue<(PsionicComponent component, EntityUid uid)> _rollers = new(); - public override void Update(float frameTime) - { - base.Update(frameTime); - foreach (var roller in _rollers) - RollPsionics(roller.uid, roller.component, false); - _rollers.Clear(); - } - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnStartup); - SubscribeLocalEvent(OnMeleeHit); - SubscribeLocalEvent(OnStamHit); + base.Update(frameTime); + foreach (var roller in _rollers) + RollPsionics(roller.uid, roller.component, false); + _rollers.Clear(); + } + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnMeleeHit); + SubscribeLocalEvent(OnStamHit); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnRemove); - } + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnRemove); + } - private void OnStartup(EntityUid uid, PsionicComponent component, MapInitEvent args) - { - _rollers.Enqueue((component, uid)); - } + private void OnStartup(EntityUid uid, PsionicComponent component, MapInitEvent args) + { + if (!component.Removable + || !component.CanReroll) + return; - private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, MeleeHitEvent args) - { - foreach (var entity in args.HitEntities) - { - if (HasComp(entity)) - { - _audio.PlayPvs("/Audio/Effects/lightburn.ogg", entity); - args.ModifiersList.Add(component.Modifiers); - if (_random.Prob(component.DisableChance)) - _statusEffects.TryAddStatusEffect(entity, component.DisableStatus, TimeSpan.FromSeconds(component.DisableDuration), true, component.DisableStatus); - } - - if (TryComp(entity, out var swapped)) - { - _mindSwapPowerSystem.Swap(entity, swapped.OriginalEntity, true); - return; - } - - if (component.Punish && !HasComp(entity) && _random.Prob(component.PunishChances)) - _electrocutionSystem.TryDoElectrocution(args.User, null, component.PunishSelfDamage, TimeSpan.FromSeconds(component.PunishStunDuration), false); - } - } + CheckPowerCost(uid, component); + _rollers.Enqueue((component, uid)); + } - private void OnInit(EntityUid uid, PsionicComponent component, ComponentStartup args) - { - component.AmplificationSources.Add(BaselineAmplification, _random.NextFloat(component.BaselineAmplification.Item1, component.BaselineAmplification.Item2)); - component.DampeningSources.Add(BaselineDampening, _random.NextFloat(component.BaselineDampening.Item1, component.BaselineDampening.Item2)); + /// + /// On MapInit, PsionicComponent isn't going to contain any powers. + /// So before we send a Latent Psychic into the roundstart roll queue, we need to calculate their power cost in advance. + /// + private void CheckPowerCost(EntityUid uid, PsionicComponent component) + { + if (!TryComp(uid, out var innate)) + return; - if (!component.Removable - || !TryComp(uid, out var factions) - || _npcFactonSystem.ContainsFaction(uid, "GlimmerMonster", factions)) - return; + var powerCount = 0; + foreach (var powerId in innate.PowersToAdd) + if (_protoMan.TryIndex(powerId, out var power)) + powerCount += power.PowerSlotCost; - _npcFactonSystem.AddFaction(uid, "PsionicInterloper"); - } + component.NextPowerCost = 100 * MathF.Pow(2, powerCount); + } - private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove args) + private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, MeleeHitEvent args) + { + foreach (var entity in args.HitEntities) + CheckAntiPsionic(entity, component, args); + } + + private void CheckAntiPsionic(EntityUid entity, AntiPsionicWeaponComponent component, MeleeHitEvent args) + { + if (HasComp(entity)) { - if (!HasComp(uid)) + _audio.PlayPvs("/Audio/Effects/lightburn.ogg", entity); + args.ModifiersList.Add(component.Modifiers); + + if (!_random.Prob(component.DisableChance)) return; - _npcFactonSystem.RemoveFaction(uid, "PsionicInterloper"); + _statusEffects.TryAddStatusEffect(entity, component.DisableStatus, TimeSpan.FromSeconds(component.DisableDuration), true, component.DisableStatus); } - private void OnStamHit(EntityUid uid, AntiPsionicWeaponComponent component, TakeStaminaDamageEvent args) - { - if (HasComp(args.Target)) - args.FlatModifier += component.PsychicStaminaDamage; - } + if (TryComp(entity, out var swapped)) + _mindSwapPowerSystem.Swap(entity, swapped.OriginalEntity, true); - public void RollPsionics(EntityUid uid, PsionicComponent component, bool applyGlimmer = true, float rollEventMultiplier = 1f) - { - if (!_cfg.GetCVar(CCVars.PsionicRollsEnabled) - || !component.Removable) - return; + if (!component.Punish + || HasComp(entity) + || !_random.Prob(component.PunishChances)) + return; + + _electrocutionSystem.TryDoElectrocution(args.User, null, component.PunishSelfDamage, TimeSpan.FromSeconds(component.PunishStunDuration), false); + } - // Calculate the initial odds based on the innate potential - var baselineChance = component.Chance - * component.PowerRollMultiplier - + component.PowerRollFlatBonus; + private void OnInit(EntityUid uid, PsionicComponent component, ComponentStartup args) + { + component.AmplificationSources.Add(BaselineAmplification, _random.NextFloat(component.BaselineAmplification.Item1, component.BaselineAmplification.Item2)); + component.DampeningSources.Add(BaselineDampening, _random.NextFloat(component.BaselineDampening.Item1, component.BaselineDampening.Item2)); - // Increase the initial odds based on Glimmer. - // TODO: Change this equation when I do my Glimmer Refactor - baselineChance += applyGlimmer - ? (float) _glimmerSystem.Glimmer / 1000 //Convert from Glimmer to %chance - : 0; + if (!component.Removable + || !TryComp(uid, out var factions) + || _npcFactonSystem.ContainsFaction(uid, "GlimmerMonster", factions)) + return; - // Certain sources of power rolls provide their own multiplier. - baselineChance *= rollEventMultiplier; + _npcFactonSystem.AddFaction(uid, "PsionicInterloper"); + } - // Ask if the Roller has any other effects to contribute, such as Traits. - var ev = new OnRollPsionicsEvent(uid, baselineChance); - RaiseLocalEvent(uid, ref ev); + private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove args) + { + if (!HasComp(uid)) + return; - if (_random.Prob(Math.Clamp(ev.BaselineChance, 0, 1))) - _psionicAbilitiesSystem.AddPsionics(uid); - } + _npcFactonSystem.RemoveFaction(uid, "PsionicInterloper"); + } - public void RerollPsionics(EntityUid uid, PsionicComponent? psionic = null, float bonusMuliplier = 1f) - { - if (!Resolve(uid, ref psionic, false) - || !psionic.Removable - || psionic.CanReroll) - return; + private void OnStamHit(EntityUid uid, AntiPsionicWeaponComponent component, TakeStaminaDamageEvent args) + { + if (!HasComp(args.Target)) + return; - RollPsionics(uid, psionic, true, bonusMuliplier); - psionic.CanReroll = true; - } + args.FlatModifier += component.PsychicStaminaDamage; + } + + /// + /// Now we handle Potentia calculations, the more powers you have, the harder it is to obtain psionics, but the content of your roll carries over to the next roll. + /// Your first power costs 100(2^0 is always 1), your second power costs 200, your 3rd power costs 400, and so on. This also considers people with roundstart powers. + /// Such that a Mystagogue(who has 3 powers at roundstart) needs 800 Potentia to gain his 4th power. + /// + /// + /// This exponential cost is mainly done to prevent stations from becoming "Space Hogwarts", + /// which was a common complaint with Psionic Refactor opening up the opportunity for people to have multiple powers. + /// + private bool HandlePotentiaCalculations(EntityUid uid, PsionicComponent component, float psionicChance) + { + component.Potentia += _random.NextFloat(0 + psionicChance, 100 + psionicChance); + + if (component.Potentia < component.NextPowerCost) + return false; + + component.Potentia -= component.NextPowerCost; + _psionicAbilitiesSystem.AddPsionics(uid); + component.NextPowerCost = 100 * MathF.Pow(2, component.PowerSlotsTaken); + return true; + } + + /// + /// Provide the player with feedback about their roll failure, so they don't just think nothing happened. + /// TODO: Add an audio cue to this and other areas of psionic player feedback. + /// + private void HandleRollFeedback(EntityUid uid) + { + if (!_playerManager.TryGetSessionByEntity(uid, out var session) + || !Loc.TryGetString(PsionicRollFailedMessage, out var rollFailedMessage)) + return; + + _popups.PopupEntity(rollFailedMessage, uid, uid, PopupType.MediumCaution); + + // Popups only last a few seconds, and are easily ignored. + // So we also put a message in chat to make it harder to miss. + var feedbackMessage = $"[font size={PsionicRollFailedFontSize}][color={PsionicRollFailedColor}]{rollFailedMessage}[/color][/font]"; + _chatManager.ChatMessageToOne( + PsionicRollFailedChatChannel, + feedbackMessage, + feedbackMessage, + EntityUid.Invalid, + false, + session.Channel); + } + + /// + /// This function attempts to generate a psionic power by incrementing a Psion's Potentia stat by a random amount, then checking if it beats a certain threshold. + /// Please consider going through RerollPsionics or PsionicAbilitiesSystem.InitializePsionicPower instead of this function, particularly if you don't have a good reason to call this directly. + /// + public void RollPsionics(EntityUid uid, PsionicComponent component, bool applyGlimmer = true, float rollEventMultiplier = 1f) + { + if (!_cfg.GetCVar(CCVars.PsionicRollsEnabled) + || !component.Removable) + return; + + // Calculate the initial odds based on the innate potential + var baselineChance = component.Chance + * component.PowerRollMultiplier + + component.PowerRollFlatBonus; + + // Increase the initial odds based on Glimmer. + // TODO: Change this equation when I do my Glimmer Refactor + baselineChance += applyGlimmer + ? (float) _glimmerSystem.Glimmer / 1000 //Convert from Glimmer to %chance + : 0; + + // Certain sources of power rolls provide their own multiplier. + baselineChance *= rollEventMultiplier; + + // Ask if the Roller has any other effects to contribute, such as Traits. + var ev = new OnRollPsionicsEvent(uid, baselineChance); + RaiseLocalEvent(uid, ref ev); + + if (HandlePotentiaCalculations(uid, component, ev.BaselineChance)) + return; + + HandleRollFeedback(uid); + } + + /// + /// Each person has a single free reroll for their Psionics, which certain conditions can restore. + /// This function attempts to "Spend" a reroll, if one is available. + /// + public void RerollPsionics(EntityUid uid, PsionicComponent? psionic = null, float bonusMuliplier = 1f) + { + if (!Resolve(uid, ref psionic, false) + || !psionic.Removable + || !psionic.CanReroll) + return; + + RollPsionics(uid, psionic, true, bonusMuliplier); + psionic.CanReroll = false; } } diff --git a/Content.Server/StationEvents/Events/NoosphericZapRule.cs b/Content.Server/StationEvents/Events/NoosphericZapRule.cs index 4be7b6e63fc..96c33612036 100644 --- a/Content.Server/StationEvents/Events/NoosphericZapRule.cs +++ b/Content.Server/StationEvents/Events/NoosphericZapRule.cs @@ -36,9 +36,9 @@ protected override void Started(EntityUid uid, NoosphericZapRuleComponent compon _stunSystem.TryParalyze(psion, TimeSpan.FromSeconds(component.StunDuration), false); _statusEffectsSystem.TryAddStatusEffect(psion, "Stutter", TimeSpan.FromSeconds(component.StutterDuration), false, "StutteringAccent"); - if (psionicComponent.CanReroll) + if (!psionicComponent.CanReroll) { - psionicComponent.CanReroll = false; + psionicComponent.CanReroll = true; _popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize-potential-regained"), psion, psion, Shared.Popups.PopupType.LargeCaution); } else diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs index 63529576152..85b7e380fea 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -8,11 +8,19 @@ namespace Content.Shared.Abilities.Psionics public sealed partial class PsionicComponent : Component { /// - /// How close a Psion is to awakening a new power. - /// TODO: Implement this in a separate PR. + /// How close a Psion is to generating a new power. When Potentia reaches the NextPowerCost, it is "Spent" in order to "Buy" a random new power. + /// TODO: Psi-Potentiometry should be able to read how much Potentia a person has. + /// + [DataField] + public float Potentia; + + /// + /// 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. + /// This variable stores the cost of the next power. /// [DataField] - public float Potentia = 0; + public float NextPowerCost = 100; /// /// The baseline chance of obtaining a psionic power when rolling for one. @@ -24,7 +32,7 @@ public sealed partial class PsionicComponent : Component /// Whether or not a Psion has an available "Reroll" to spend on attempting to gain powers. /// [DataField] - public bool CanReroll; + public bool CanReroll = true; /// /// The Base amount of time (in minutes) this Psion is given the stutter effect if they become mindbroken. @@ -142,6 +150,18 @@ private set public float CurrentDampening; /// + /// How many "Slots" an entity has for psionic powers. This is not a hard limit, and is instead used for calculating the cost to generate new powers. + /// Exceeding this limit causes an entity to become a Glimmer Source. + /// + [DataField] + public int PowerSlots = 1; + + /// + /// How many "Slots" are currently occupied by psionic powers. + /// + [ViewVariables(VVAccess.ReadWrite)] + public int PowerSlotsTaken; + /// List of descriptors this entity will bring up for psychognomy. Used to remove /// unneccesary subs for unique psionic entities like e.g. Oracle. /// diff --git a/Content.Shared/Psionics/PsionicPowerPrototype.cs b/Content.Shared/Psionics/PsionicPowerPrototype.cs index 3d389f6cdbe..c4a3326733a 100644 --- a/Content.Shared/Psionics/PsionicPowerPrototype.cs +++ b/Content.Shared/Psionics/PsionicPowerPrototype.cs @@ -86,4 +86,10 @@ public sealed partial class PsionicPowerPrototype : IPrototype /// [DataField] public float DampeningModifier = 0; + + /// + /// How many "Power Slots" this power occupies. + /// + [DataField] + public int PowerSlotCost = 1; } \ No newline at end of file diff --git a/Resources/Locale/en-US/psionics/psionic-powers.ftl b/Resources/Locale/en-US/psionics/psionic-powers.ftl index ad6bbef02bd..c68bb2a4968 100644 --- a/Resources/Locale/en-US/psionics/psionic-powers.ftl +++ b/Resources/Locale/en-US/psionics/psionic-powers.ftl @@ -86,7 +86,9 @@ telepathy-power-initialization-feedback = The voices I've heard all my life begin to clear, yet they do not leave me. Before, they were as incoherent whispers, now my senses broaden, I come to a realization that they are part of a communal shared hallucination. Behind every voice is a glimmering sentience. +# Psionic System Messages mindbreaking-feedback = The light of life vanishes from {CAPITALIZE($entity)}'s eyes, leaving behind a husk pretending at sapience 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. \ No newline at end of file + There is nothing behind its gaze, no evidence there can be found of the divine light of creation. +psionic-roll-failed = For a moment, my consciousness expands, yet I feel that it is not enough. \ No newline at end of file diff --git a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml index 1f78015cd63..f7a31205706 100644 --- a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml +++ b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml @@ -32,6 +32,7 @@ - !type:AddComponentSpecial components: - type: Psionic + powerSlots: 2 - !type:AddComponentSpecial components: - type: InnatePsionicPowers diff --git a/Resources/Prototypes/Psionics/psionics.yml b/Resources/Prototypes/Psionics/psionics.yml index b8f798d4b63..461098eab21 100644 --- a/Resources/Prototypes/Psionics/psionics.yml +++ b/Resources/Prototypes/Psionics/psionics.yml @@ -119,6 +119,7 @@ - type: UniversalLanguageSpeaker initializationFeedback: xenoglossy-power-initialization-feedback metapsionicFeedback: psionic-language-power-feedback # Reuse for telepathy, clairaudience, etc + powerSlotCost: 0 - type: psionicPower id: PsychognomyPower #i.e. reverse physiognomy @@ -128,6 +129,7 @@ - type: Psychognomist initializationFeedback: psychognomy-power-initialization-feedback metapsionicFeedback: psionic-language-power-feedback + powerSlotCost: 0 - type: psionicPower id: TelepathyPower @@ -137,3 +139,4 @@ - type: Telepathy initializationFeedback: telepathy-power-initialization-feedback metapsionicFeedback: psionic-language-power-feedback # Reuse for telepathy, clairaudience, etc + powerSlotCost: 0 diff --git a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml index e2d06f3af96..46d91ee00ee 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml @@ -38,6 +38,7 @@ components: - type: BibleUser # Nyano - Lets them heal with bibles - type: Psionic # Nyano - They start with telepathic chat + powerSlots: 3 - !type:AddImplantSpecial implants: [ MindShieldImplant ] - !type:AddComponentSpecial