From 34cd477267695f28fc0e1c614bb7e387ad65aff5 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Fri, 30 Aug 2024 14:38:27 -0400 Subject: [PATCH 01/11] Complete --- Content.Server/Psionics/PsionicsSystem.cs | 290 +++++++++++------- .../StationEvents/Events/NoosphericZapRule.cs | 4 +- Content.Shared/Psionics/PsionicComponent.cs | 7 +- .../Locale/en-US/psionics/psionic-powers.ftl | 4 +- 4 files changed, 189 insertions(+), 116 deletions(-) diff --git a/Content.Server/Psionics/PsionicsSystem.cs b/Content.Server/Psionics/PsionicsSystem.cs index 23cf6aeb80b..b49bcde8fb8 100644 --- a/Content.Server/Psionics/PsionicsSystem.cs +++ b/Content.Server/Psionics/PsionicsSystem.cs @@ -11,139 +11,209 @@ 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; -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!; + + 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) + { + _rollers.Enqueue((component, uid)); + } - private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, MeleeHitEvent args) + private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, MeleeHitEvent args) + { + foreach (var entity in args.HitEntities) { - foreach (var entity in args.HitEntities) + if (HasComp(entity)) { - 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); + _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); } - } - - 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)); - if (!component.Removable - || !TryComp(uid, out var factions) - || _npcFactonSystem.ContainsFaction(uid, "GlimmerMonster", factions)) + if (TryComp(entity, out var swapped)) + { + _mindSwapPowerSystem.Swap(entity, swapped.OriginalEntity, true); return; + } - _npcFactonSystem.AddFaction(uid, "PsionicInterloper"); + if (component.Punish && !HasComp(entity) && _random.Prob(component.PunishChances)) + _electrocutionSystem.TryDoElectrocution(args.User, null, component.PunishSelfDamage, TimeSpan.FromSeconds(component.PunishStunDuration), false); } + } - private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove args) - { - if (!HasComp(uid)) - return; + 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)); - _npcFactonSystem.RemoveFaction(uid, "PsionicInterloper"); - } + if (!component.Removable + || !TryComp(uid, out var factions) + || _npcFactonSystem.ContainsFaction(uid, "GlimmerMonster", factions)) + return; - private void OnStamHit(EntityUid uid, AntiPsionicWeaponComponent component, TakeStaminaDamageEvent args) - { - if (HasComp(args.Target)) - args.FlatModifier += component.PsychicStaminaDamage; - } + _npcFactonSystem.AddFaction(uid, "PsionicInterloper"); + } - public void RollPsionics(EntityUid uid, PsionicComponent component, bool applyGlimmer = true, float rollEventMultiplier = 1f) - { - if (!_cfg.GetCVar(CCVars.PsionicRollsEnabled) - || !component.Removable) - return; + private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove args) + { + if (!HasComp(uid)) + return; - // Calculate the initial odds based on the innate potential - var baselineChance = component.Chance - * component.PowerRollMultiplier - + component.PowerRollFlatBonus; + _npcFactonSystem.RemoveFaction(uid, "PsionicInterloper"); + } - // 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; + private void OnStamHit(EntityUid uid, AntiPsionicWeaponComponent component, TakeStaminaDamageEvent args) + { + if (HasComp(args.Target)) + args.FlatModifier += component.PsychicStaminaDamage; + } - // Certain sources of power rolls provide their own multiplier. - baselineChance *= rollEventMultiplier; + /// + /// 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) + { - // Ask if the Roller has any other effects to contribute, such as Traits. - var ev = new OnRollPsionicsEvent(uid, baselineChance); - RaiseLocalEvent(uid, ref ev); + var nextPowerCost = 100 * MathF.Pow(2, component.ActivePowers.Count); + component.Potentia += _random.NextFloat(0 + psionicChance, 100 + psionicChance); - if (_random.Prob(Math.Clamp(ev.BaselineChance, 0, 1))) - _psionicAbilitiesSystem.AddPsionics(uid); - } + if (component.Potentia < nextPowerCost) + return false; - public void RerollPsionics(EntityUid uid, PsionicComponent? psionic = null, float bonusMuliplier = 1f) - { - if (!Resolve(uid, ref psionic, false) - || !psionic.Removable - || psionic.CanReroll) - return; + component.Potentia -= nextPowerCost; + _psionicAbilitiesSystem.AddPsionics(uid); + return true; + } - RollPsionics(uid, psionic, true, bonusMuliplier); - psionic.CanReroll = 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 a452f55ed2a..38123c4d338 100644 --- a/Content.Server/StationEvents/Events/NoosphericZapRule.cs +++ b/Content.Server/StationEvents/Events/NoosphericZapRule.cs @@ -35,9 +35,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 9ff332327a4..429db20e9b8 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -8,11 +8,12 @@ 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. + /// 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. + /// TODO: Psi-Potentiometry should be able to read how much Potentia a person has. /// [DataField] - public float Potentia = 0; + public float Potentia; /// /// The baseline chance of obtaining a psionic power 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 535c4ef3286..45ae7c1063f 100644 --- a/Resources/Locale/en-US/psionics/psionic-powers.ftl +++ b/Resources/Locale/en-US/psionics/psionic-powers.ftl @@ -80,7 +80,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 From 6ee70d265af6347ddc53852dacd2f7f3b7bb5f53 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Fri, 30 Aug 2024 14:44:41 -0400 Subject: [PATCH 02/11] Store NextPowerCost instead of calculating it every time --- Content.Server/Psionics/PsionicsSystem.cs | 7 +++---- Content.Shared/Psionics/PsionicComponent.cs | 8 ++++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Content.Server/Psionics/PsionicsSystem.cs b/Content.Server/Psionics/PsionicsSystem.cs index b49bcde8fb8..f17fbe2da17 100644 --- a/Content.Server/Psionics/PsionicsSystem.cs +++ b/Content.Server/Psionics/PsionicsSystem.cs @@ -132,15 +132,14 @@ private void OnStamHit(EntityUid uid, AntiPsionicWeaponComponent component, Take /// private bool HandlePotentiaCalculations(EntityUid uid, PsionicComponent component, float psionicChance) { - - var nextPowerCost = 100 * MathF.Pow(2, component.ActivePowers.Count); component.Potentia += _random.NextFloat(0 + psionicChance, 100 + psionicChance); - if (component.Potentia < nextPowerCost) + if (component.Potentia < component.NextPowerCost) return false; - component.Potentia -= nextPowerCost; + component.Potentia -= component.NextPowerCost; _psionicAbilitiesSystem.AddPsionics(uid); + component.NextPowerCost = 100 * MathF.Pow(2, component.ActivePowers.Count); return true; } diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs index 429db20e9b8..00d3e8c468d 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -15,6 +15,14 @@ public sealed partial class PsionicComponent : Component [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 NextPowerCost; + /// /// The baseline chance of obtaining a psionic power when rolling for one. /// From 2a6f8054b13edf15a39097869558526d5fb28d71 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Fri, 30 Aug 2024 14:56:31 -0400 Subject: [PATCH 03/11] Last update --- Content.Server/Psionics/PsionicsSystem.cs | 45 +++++++++++++++-------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/Content.Server/Psionics/PsionicsSystem.cs b/Content.Server/Psionics/PsionicsSystem.cs index f17fbe2da17..d6a14450676 100644 --- a/Content.Server/Psionics/PsionicsSystem.cs +++ b/Content.Server/Psionics/PsionicsSystem.cs @@ -68,30 +68,41 @@ public override void Initialize() private void OnStartup(EntityUid uid, PsionicComponent component, MapInitEvent args) { + if (!component.Removable + || !component.CanReroll) + return; + _rollers.Enqueue((component, uid)); } 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(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); + _audio.PlayPvs("/Audio/Effects/lightburn.ogg", entity); + args.ModifiersList.Add(component.Modifiers); + + if (!_random.Prob(component.DisableChance)) return; - } - if (component.Punish && !HasComp(entity) && _random.Prob(component.PunishChances)) - _electrocutionSystem.TryDoElectrocution(args.User, null, component.PunishSelfDamage, TimeSpan.FromSeconds(component.PunishStunDuration), false); + _statusEffects.TryAddStatusEffect(entity, component.DisableStatus, TimeSpan.FromSeconds(component.DisableDuration), true, component.DisableStatus); } + + if (TryComp(entity, out var swapped)) + _mindSwapPowerSystem.Swap(entity, swapped.OriginalEntity, true); + + if (!component.Punish + || HasComp(entity) + || !_random.Prob(component.PunishChances)) + return; + + _electrocutionSystem.TryDoElectrocution(args.User, null, component.PunishSelfDamage, TimeSpan.FromSeconds(component.PunishStunDuration), false); } private void OnInit(EntityUid uid, PsionicComponent component, ComponentStartup args) @@ -117,8 +128,10 @@ private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove private void OnStamHit(EntityUid uid, AntiPsionicWeaponComponent component, TakeStaminaDamageEvent args) { - if (HasComp(args.Target)) - args.FlatModifier += component.PsychicStaminaDamage; + if (!HasComp(args.Target)) + return; + + args.FlatModifier += component.PsychicStaminaDamage; } /// From a1484ad828fefb5dc4675c736365ffcb2c216252 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Fri, 30 Aug 2024 14:57:00 -0400 Subject: [PATCH 04/11] Update PsionicComponent.cs --- Content.Shared/Psionics/PsionicComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs index 00d3e8c468d..0270864e783 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -33,7 +33,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. From 74420ff7b9a5c1d66c11e35ba22a16d17d4bacee Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Fri, 30 Aug 2024 15:09:12 -0400 Subject: [PATCH 05/11] Update PsionicComponent.cs --- Content.Shared/Psionics/PsionicComponent.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs index 0270864e783..1d9057cbb73 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -8,8 +8,7 @@ namespace Content.Shared.Abilities.Psionics public sealed partial class PsionicComponent : Component { /// - /// 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. + /// 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] From 956d43c744e03b30d4b13d6637f64b30bdfca5c8 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Fri, 30 Aug 2024 15:13:20 -0400 Subject: [PATCH 06/11] Account for InnatePowers BEFORE rolling. --- Content.Server/Psionics/PsionicsSystem.cs | 3 +++ Content.Shared/Psionics/PsionicComponent.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Content.Server/Psionics/PsionicsSystem.cs b/Content.Server/Psionics/PsionicsSystem.cs index d6a14450676..cd3a62a7e47 100644 --- a/Content.Server/Psionics/PsionicsSystem.cs +++ b/Content.Server/Psionics/PsionicsSystem.cs @@ -72,6 +72,9 @@ private void OnStartup(EntityUid uid, PsionicComponent component, MapInitEvent a || !component.CanReroll) return; + if (TryComp(uid, out var innate)) + component.NextPowerCost = 100 * MathF.Pow(2, innate.PowersToAdd.Count); + _rollers.Enqueue((component, uid)); } diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs index 1d9057cbb73..c663f0c936a 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -20,7 +20,7 @@ public sealed partial class PsionicComponent : Component /// This variable stores the cost of the next power. /// [DataField] - public float NextPowerCost; + public float NextPowerCost = 100; /// /// The baseline chance of obtaining a psionic power when rolling for one. From 55d8e05586ed5f047d1520ad077b1c34721ad8be Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Fri, 30 Aug 2024 15:59:48 -0400 Subject: [PATCH 07/11] Rane's Suggestion --- .../Psionics/PsionicAbilitiesSystem.cs | 21 +++++++++++++++ Content.Server/Psionics/PsionicsSystem.cs | 26 ++++++++++++++++--- Content.Shared/Psionics/PsionicComponent.cs | 13 ++++++++++ .../Psionics/PsionicPowerPrototype.cs | 6 +++++ .../Roles/Jobs/Epistemics/forensicmantis.yml | 1 + Resources/Prototypes/Psionics/psionics.yml | 4 ++- .../Roles/Jobs/Science/research_director.yml | 1 + 7 files changed, 67 insertions(+), 5 deletions(-) diff --git a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs index 1ac5db70151..1bd4aec1f28 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); //SendFeedbackAudio(uid, proto, playPopup); // TODO: This one is coming next! } @@ -297,6 +300,24 @@ 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; + } + + private void UpdatePsionicDanger(EntityUid uid, PsionicComponent psionic) + { + if (psionic.PowerSlotsTaken <= psionic.PowerSlots) + return; + + EnsureComp(uid, out var glimmerSource); + glimmerSource.SecondsPerGlimmer = 10 / psionic.PowerSlotsTaken; + } + /// /// 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 cd3a62a7e47..a590696741b 100644 --- a/Content.Server/Psionics/PsionicsSystem.cs +++ b/Content.Server/Psionics/PsionicsSystem.cs @@ -15,6 +15,8 @@ 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; @@ -32,6 +34,7 @@ public sealed class PsionicsSystem : EntitySystem [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"; @@ -72,12 +75,27 @@ private void OnStartup(EntityUid uid, PsionicComponent component, MapInitEvent a || !component.CanReroll) return; - if (TryComp(uid, out var innate)) - component.NextPowerCost = 100 * MathF.Pow(2, innate.PowersToAdd.Count); - + CheckPowerCost(uid, component); _rollers.Enqueue((component, uid)); } + /// + /// 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; + + var powerCount = 0; + foreach (var powerId in innate.PowersToAdd) + if (_protoMan.TryIndex(powerId, out var power)) + powerCount += power.PowerSlotCost; + + component.NextPowerCost = 100 * MathF.Pow(2, powerCount); + } + private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, MeleeHitEvent args) { foreach (var entity in args.HitEntities) @@ -155,7 +173,7 @@ private bool HandlePotentiaCalculations(EntityUid uid, PsionicComponent componen component.Potentia -= component.NextPowerCost; _psionicAbilitiesSystem.AddPsionics(uid); - component.NextPowerCost = 100 * MathF.Pow(2, component.ActivePowers.Count); + component.NextPowerCost = 100 * MathF.Pow(2, component.PowerSlotsTaken); return true; } diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs index c663f0c936a..3e6e567d23f 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -148,5 +148,18 @@ private set /// [ViewVariables(VVAccess.ReadWrite)] 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; } } 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/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml index def18209637..2741f989ef3 100644 --- a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml +++ b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml @@ -23,6 +23,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 132365a37b0..dde3ea8d30f 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: TelepathyPower @@ -127,4 +128,5 @@ components: - type: Telepathy initializationFeedback: telepathy-power-initialization-feedback - metapsionicFeedback: psionic-language-power-feedback # Reuse for telepathy, clairaudience, etc \ No newline at end of file + metapsionicFeedback: psionic-language-power-feedback # Reuse for telepathy, clairaudience, etc + powerSlotCost: 0 \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml index fc2e85fe2b9..1656b6d54de 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml @@ -29,6 +29,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 From a8c06a63eb86b15ac7b18cd0b80745728134138c Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Fri, 30 Aug 2024 16:05:26 -0400 Subject: [PATCH 08/11] Update PsionicAbilitiesSystem.cs --- Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs index 1bd4aec1f28..b37b85e8ea5 100644 --- a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs +++ b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs @@ -315,7 +315,7 @@ private void UpdatePsionicDanger(EntityUid uid, PsionicComponent psionic) return; EnsureComp(uid, out var glimmerSource); - glimmerSource.SecondsPerGlimmer = 10 / psionic.PowerSlotsTaken; + glimmerSource.SecondsPerGlimmer = 10 / (psionic.PowerSlotsTaken - psionic.PowerSlots); } /// From d519cdb5adb858bc941576ffd6b3f2b210bfb636 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Fri, 30 Aug 2024 16:08:56 -0400 Subject: [PATCH 09/11] Update PsionicsSystem.cs --- Content.Server/Psionics/PsionicsSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Psionics/PsionicsSystem.cs b/Content.Server/Psionics/PsionicsSystem.cs index a590696741b..0d05000a3cd 100644 --- a/Content.Server/Psionics/PsionicsSystem.cs +++ b/Content.Server/Psionics/PsionicsSystem.cs @@ -90,7 +90,7 @@ private void CheckPowerCost(EntityUid uid, PsionicComponent component) var powerCount = 0; foreach (var powerId in innate.PowersToAdd) - if (_protoMan.TryIndex(powerId, out var power)) + if (_protoMan.TryIndex(powerId, out var power)) powerCount += power.PowerSlotCost; component.NextPowerCost = 100 * MathF.Pow(2, powerCount); From 6d4cc190e20101d3e5116d25031f1d589cd0881a Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Fri, 30 Aug 2024 23:27:56 -0400 Subject: [PATCH 10/11] Disable UpdatePsionicDanger --- .../Psionics/PsionicAbilitiesSystem.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs index b37b85e8ea5..0c442313e53 100644 --- a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs +++ b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs @@ -124,7 +124,7 @@ public void InitializePsionicPower(EntityUid uid, PsionicPowerPrototype proto, P RefreshPsionicModifiers(uid, psionic); SendFeedbackMessage(uid, proto, playFeedback); UpdatePowerSlots(psionic); - UpdatePsionicDanger(uid, psionic); + //UpdatePsionicDanger(uid, psionic); // TODO: After Glimmer Refactor //SendFeedbackAudio(uid, proto, playPopup); // TODO: This one is coming next! } @@ -309,14 +309,17 @@ private void UpdatePowerSlots(PsionicComponent psionic) psionic.PowerSlotsTaken = slotsUsed; } - 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); - } + /// + /// 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. From e8fd173be1d5f5e264dce44e290c1a60a6993805 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 2 Sep 2024 23:32:13 -0400 Subject: [PATCH 11/11] Update psionics.yml --- Resources/Prototypes/Psionics/psionics.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Psionics/psionics.yml b/Resources/Prototypes/Psionics/psionics.yml index 417cf3ee226..461098eab21 100644 --- a/Resources/Prototypes/Psionics/psionics.yml +++ b/Resources/Prototypes/Psionics/psionics.yml @@ -129,7 +129,7 @@ - type: Psychognomist initializationFeedback: psychognomy-power-initialization-feedback metapsionicFeedback: psionic-language-power-feedback - powerSlotClost: 0 + powerSlotCost: 0 - type: psionicPower id: TelepathyPower