From 5090aabb201688924c84df344508528dc2fb2048 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Tue, 9 Jul 2024 18:56:55 -0400 Subject: [PATCH 1/4] Update Probers and Power Cooldown Scaling --- .../Psionics/Abilities/DispelPowerSystem.cs | 32 ++++++------- .../Abilities/MetapsionicPowerSystem.cs | 36 +++++++------- .../Psionics/Abilities/MindSwapPowerSystem.cs | 32 ++++++------- .../Abilities/NoosphericZapPowerSystem.cs | 28 +++++------ .../PsionicRegenerationPowerSystem.cs | 24 +++++----- .../Abilities/PyrokinesisPowerSystem.cs | 30 +++++------- .../RegenerativeStasisPowerSystem.cs | 20 ++++---- .../Abilities/TelegnosisPowerSystem.cs | 19 ++++---- .../Structures/GlimmerSourceComponent.cs | 28 +++++++++-- .../Structures/GlimmerStructuresSystem.cs | 47 +++++++++++++++++-- .../PsionicInvisibilityPowerSystem.cs | 27 ++++++----- .../Psionics/SharedPsionicAbilitiesSystem.cs | 18 +++++++ .../Structures/Research/glimmer_prober.yml | 4 +- 13 files changed, 210 insertions(+), 135 deletions(-) diff --git a/Content.Server/Psionics/Abilities/DispelPowerSystem.cs b/Content.Server/Psionics/Abilities/DispelPowerSystem.cs index 73c6f5d339..3d7d5c20c7 100644 --- a/Content.Server/Psionics/Abilities/DispelPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/DispelPowerSystem.cs @@ -32,7 +32,7 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); + SubscribeLocalEvent(OnPowerUsed); SubscribeLocalEvent(OnDispelled); SubscribeLocalEvent(OnDmgDispelled); @@ -44,18 +44,17 @@ public override void Initialize() private void OnInit(EntityUid uid, DispelPowerComponent component, ComponentInit args) { - _actions.AddAction(uid, ref component.DispelActionEntity, component.DispelActionId ); - _actions.TryGetActionData( component.DispelActionEntity, out var actionData ); + EnsureComp(uid, out var psionic); + _actions.AddAction(uid, ref component.DispelActionEntity, component.DispelActionId); + _actions.TryGetActionData(component.DispelActionEntity, out var actionData); if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.DispelActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.DispelFeedback); - //It's fully intended that Dispel doesn't increase Amplification, and instead heavily spikes Dampening - //Antimage archetype. - psionic.Dampening += 1f; - } + _actions.SetCooldown(component.DispelActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); + + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.DispelFeedback); + //It's fully intended that Dispel doesn't increase Amplification, and instead heavily spikes Dampening + //Antimage archetype. + psionic.Dampening += 1f; } private void OnShutdown(EntityUid uid, DispelPowerComponent component, ComponentShutdown args) @@ -70,11 +69,9 @@ private void OnShutdown(EntityUid uid, DispelPowerComponent component, Component } } - private void OnPowerUsed(DispelPowerActionEvent args) + private void OnPowerUsed(EntityUid uid, DispelPowerComponent component, DispelPowerActionEvent args) { - if (HasComp(args.Target) || HasComp(args.Performer)) - return; - if (!TryComp(args.Performer, out var psionic) || !HasComp(args.Target)) + if (!_psionics.CheckCanTargetCast(uid, args.Target, out var psionic)) return; var ev = new DispelledEvent(); @@ -82,6 +79,9 @@ private void OnPowerUsed(DispelPowerActionEvent args) if (ev.Handled) { + _actions.TryGetActionData(component.DispelActionEntity, out var actionData); + if (actionData is { UseDelay: not null }) + _actions.SetCooldown(component.DispelActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); args.Handled = true; _psionics.LogPowerUsed(args.Performer, "dispel", psionic, 1, 1, true); diff --git a/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs b/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs index 8c72a02737..a1b40b9232 100644 --- a/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs @@ -33,32 +33,30 @@ public override void Initialize() private void OnInit(EntityUid uid, MetapsionicPowerComponent component, ComponentInit args) { + EnsureComp(uid, out var psionic); if (!TryComp(uid, out ActionsComponent? comp)) return; _actions.AddAction(uid, ref component.ActionWideMetapsionicEntity, component.ActionWideMetapsionic, component: comp); _actions.AddAction(uid, ref component.ActionFocusedMetapsionicEntity, component.ActionFocusedMetapsionic, component: comp); - _actions.TryGetActionData(component.ActionWideMetapsionicEntity, out var actionData); - if (actionData is { UseDelay: not null }) - { - _actions.StartUseDelay(component.ActionWideMetapsionicEntity); - _actions.StartUseDelay(component.ActionFocusedMetapsionicEntity); - } - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.MetapsionicFeedback); - psionic.Amplification += 0.1f; - psionic.Dampening += 0.5f; - } + UpdateActions(uid, component, psionic); + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.MetapsionicFeedback); + psionic.Amplification += 0.1f; + psionic.Dampening += 0.5f; } - private void UpdateActions(EntityUid uid, MetapsionicPowerComponent? component = null) + private void UpdateActions(EntityUid uid, MetapsionicPowerComponent? component = null, PsionicComponent? psionic = null) { - if (!Resolve(uid, ref component)) + if (!Resolve(uid, ref component) || !Resolve(uid, ref psionic) + || !_actions.TryGetActionData(component.ActionWideMetapsionicEntity, out var actionData)) return; - _actions.StartUseDelay(component.ActionWideMetapsionicEntity); - _actions.StartUseDelay(component.ActionFocusedMetapsionicEntity); + + if (actionData is { UseDelay: not null }) + { + _actions.SetCooldown(component.ActionWideMetapsionicEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); + _actions.SetCooldown(component.ActionFocusedMetapsionicEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); + } } private void OnShutdown(EntityUid uid, MetapsionicPowerComponent component, ComponentShutdown args) @@ -95,7 +93,7 @@ private void OnWidePowerUsed(EntityUid uid, MetapsionicPowerComponent component, } _popups.PopupEntity(Loc.GetString("metapsionic-pulse-failure"), uid, uid, PopupType.Large); _psionics.LogPowerUsed(uid, "metapsionic pulse", psionic, 2, 4); - UpdateActions(uid, component); + UpdateActions(uid, component, psionic); args.Handled = true; } @@ -128,7 +126,7 @@ private void OnFocusedPowerUsed(FocusedMetapsionicPowerActionEvent args) _psionics.LogPowerUsed(args.Performer, "focused metapsionic pulse", psionic, 3, 6); args.Handled = true; - UpdateActions(args.Performer, component); + UpdateActions(args.Performer, component, psionic); } private void OnDoAfter(EntityUid uid, MetapsionicPowerComponent component, FocusedMetapsionicDoAfterEvent args) diff --git a/Content.Server/Psionics/Abilities/MindSwapPowerSystem.cs b/Content.Server/Psionics/Abilities/MindSwapPowerSystem.cs index 25f0434c79..b4129581ec 100644 --- a/Content.Server/Psionics/Abilities/MindSwapPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/MindSwapPowerSystem.cs @@ -29,7 +29,7 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); + SubscribeLocalEvent(OnPowerUsed); SubscribeLocalEvent(OnPowerReturned); SubscribeLocalEvent(OnDispelled); SubscribeLocalEvent(OnMobStateChanged); @@ -41,16 +41,15 @@ public override void Initialize() private void OnInit(EntityUid uid, MindSwapPowerComponent component, ComponentInit args) { + EnsureComp(uid, out var psionic); _actions.AddAction(uid, ref component.MindSwapActionEntity, component.MindSwapActionId); - _actions.TryGetActionData( component.MindSwapActionEntity, out var actionData); + _actions.TryGetActionData(component.MindSwapActionEntity, out var actionData); if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.MindSwapActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.MindSwapFeedback); - psionic.Amplification += 1f; - } + _actions.SetCooldown(component.MindSwapActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); + + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.MindSwapFeedback); + psionic.Amplification += 1f; } private void OnShutdown(EntityUid uid, MindSwapPowerComponent component, ComponentShutdown args) @@ -64,17 +63,16 @@ private void OnShutdown(EntityUid uid, MindSwapPowerComponent component, Compone } } - private void OnPowerUsed(MindSwapPowerActionEvent args) + private void OnPowerUsed(EntityUid uid, MindSwapPowerComponent component, MindSwapPowerActionEvent args) { - if (!(TryComp(args.Target, out var damageable) && damageable.DamageContainerID == "Biological")) + if (!(TryComp(args.Target, out var damageable) && damageable.DamageContainerID == "Biological") + || !_psionics.CheckCanTargetCast(uid, args.Target, out var psionic)) return; - if (HasComp(args.Target)) - return; - - if (!TryComp(args.Performer, out var psionic)) - return; + _actions.TryGetActionData(component.MindSwapActionEntity, out var actionData); + if (actionData is { UseDelay: not null }) + _actions.SetCooldown(component.MindSwapActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); Swap(args.Performer, args.Target); @@ -159,6 +157,8 @@ private void OnSwapInit(EntityUid uid, MindSwappedComponent component, Component { psionic.ActivePowers.Add(component); psionic.PsychicFeedback.Add(component.MindSwappedFeedback); + if (actionData is { UseDelay: not null }) + _actions.SetCooldown(component.MindSwapReturnActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); } } diff --git a/Content.Server/Psionics/Abilities/NoosphericZapPowerSystem.cs b/Content.Server/Psionics/Abilities/NoosphericZapPowerSystem.cs index ffadc61c19..a1776acbd3 100644 --- a/Content.Server/Psionics/Abilities/NoosphericZapPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/NoosphericZapPowerSystem.cs @@ -23,21 +23,20 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); + SubscribeLocalEvent(OnPowerUsed); } private void OnInit(EntityUid uid, NoosphericZapPowerComponent component, ComponentInit args) { + EnsureComp(uid, out var psionic); _actions.AddAction(uid, ref component.NoosphericZapActionEntity, component.NoosphericZapActionId ); - _actions.TryGetActionData( component.NoosphericZapActionEntity, out var actionData ); + _actions.TryGetActionData(component.NoosphericZapActionEntity, out var actionData); if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.NoosphericZapActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.NoosphericZapFeedback); - psionic.Amplification += 1f; - } + _actions.SetCooldown(component.NoosphericZapActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); + + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.NoosphericZapFeedback); + psionic.Amplification += 1f; } private void OnShutdown(EntityUid uid, NoosphericZapPowerComponent component, ComponentShutdown args) @@ -51,13 +50,14 @@ private void OnShutdown(EntityUid uid, NoosphericZapPowerComponent component, Co } } - private void OnPowerUsed(NoosphericZapPowerActionEvent args) + private void OnPowerUsed(EntityUid uid, NoosphericZapPowerComponent component, NoosphericZapPowerActionEvent args) { - if (!TryComp(args.Performer, out var psionic)) - return; - - if (!HasComp(args.Performer)) + if (_psionics.CheckCanTargetCast(uid, args.Target, out var psionic)) { + + _actions.TryGetActionData(component.NoosphericZapActionEntity, out var actionData); + if (actionData is { UseDelay: not null }) + _actions.SetCooldown(component.NoosphericZapActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); _beam.TryCreateBeam(args.Performer, args.Target, "LightningNoospheric"); _stunSystem.TryParalyze(args.Target, TimeSpan.FromSeconds(1 * psionic.Amplification), false); diff --git a/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs b/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs index 15fc092ebc..f675cc6ed8 100644 --- a/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs @@ -39,17 +39,16 @@ public override void Initialize() private void OnInit(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentInit args) { - _actions.AddAction(uid, ref component.PsionicRegenerationActionEntity, component.PsionicRegenerationActionId ); - _actions.TryGetActionData( component.PsionicRegenerationActionEntity, out var actionData ); + EnsureComp(uid, out var psionic); + _actions.AddAction(uid, ref component.PsionicRegenerationActionEntity, component.PsionicRegenerationActionId); + _actions.TryGetActionData(component.PsionicRegenerationActionEntity, out var actionData); if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.PsionicRegenerationActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.RegenerationFeedback); - psionic.Amplification += 0.5f; - psionic.Dampening += 0.5f; - } + _actions.SetCooldown(component.PsionicRegenerationActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); + + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.RegenerationFeedback); + psionic.Amplification += 0.5f; + psionic.Dampening += 0.5f; } private void OnPowerUsed(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationPowerActionEvent args) @@ -66,6 +65,9 @@ private void OnPowerUsed(EntityUid uid, PsionicRegenerationPowerComponent compon if (actionData != null && actionData.Cooldown.HasValue && actionData.Cooldown.Value.End > curTime) return; + if (actionData is { UseDelay: not null }) + _actions.SetCooldown(component.PsionicRegenerationActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); + _doAfterSystem.TryStartDoAfter(doAfterArgs, out var doAfterId); component.DoAfter = doAfterId; @@ -116,7 +118,7 @@ private void OnMobStateChangedEvent(EntityUid uid, PsionicRegenerationPowerCompo _psionics.LogPowerUsed(uid, "psionic regeneration", psionic, 10, 20); - _actions.StartUseDelay(component.PsionicRegenerationActionEntity); + _actions.SetCooldown(component.PsionicRegenerationActionEntity, 2 * (actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification))); } } } diff --git a/Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs b/Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs index f3e2cc69fd..62e0c3ce56 100644 --- a/Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs @@ -4,10 +4,9 @@ using Content.Shared.Psionics.Glimmer; using Content.Server.Atmos.Components; using Content.Server.Weapons.Ranged.Systems; -using Robust.Server.GameObjects; using Content.Shared.Actions.Events; using Content.Server.Explosion.Components; -using Robust.Server.Audio; +using Robust.Shared.Audio.Systems; using Robust.Shared.Timing; using Content.Shared.Popups; using Content.Shared.Psionics.Events; @@ -17,13 +16,13 @@ namespace Content.Server.Psionics.Abilities { public sealed class PyrokinesisPowerSystem : EntitySystem { - [Dependency] private readonly TransformSystem _xform = default!; + [Dependency] private readonly SharedTransformSystem _xform = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly GunSystem _gunSystem = default!; [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly AudioSystem _audioSystem = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; public override void Initialize() @@ -38,28 +37,26 @@ public override void Initialize() private void OnInit(EntityUid uid, PyrokinesisPowerComponent component, ComponentInit args) { + EnsureComp(uid, out var psionic); _actions.AddAction(uid, ref component.PyrokinesisPrechargeActionEntity, component.PyrokinesisPrechargeActionId); _actions.TryGetActionData(component.PyrokinesisPrechargeActionEntity, out var actionData); if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.PyrokinesisPrechargeActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.PyrokinesisFeedback); - psionic.Amplification += 1f; - } + _actions.SetCooldown(component.PyrokinesisPrechargeActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); + + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.PyrokinesisFeedback); + psionic.Amplification += 1f; } private void OnPrecharge(PyrokinesisPrechargeActionEvent args) { - if (!HasComp(args.Performer) - && TryComp(args.Performer, out var psionic) + if (_psionics.CheckCanSelfCast(args.Performer, out var psionic) && TryComp(args.Performer, out var pyroComp)) { _actions.AddAction(args.Performer, ref pyroComp.PyrokinesisActionEntity, pyroComp.PyrokinesisActionId); _actions.TryGetActionData(pyroComp.PyrokinesisActionEntity, out var actionData); if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(pyroComp.PyrokinesisActionEntity); + _actions.SetCooldown(pyroComp.PyrokinesisActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); _actions.TryGetActionData(pyroComp.PyrokinesisPrechargeActionEntity, out var prechargeData); if (prechargeData is { UseDelay: not null }) _actions.StartUseDelay(pyroComp.PyrokinesisPrechargeActionEntity); @@ -100,15 +97,14 @@ private void OnShutdown(EntityUid uid, PyrokinesisPowerComponent component, Comp private void OnPowerUsed(PyrokinesisPowerActionEvent args) { - if (!HasComp(args.Performer) - && TryComp(args.Performer, out var psionic) + if (_psionics.CheckCanSelfCast(args.Performer, out var psionic) && TryComp(args.Performer, out var pyroComp)) { var spawnCoords = Transform(args.Performer).Coordinates; var ent = Spawn("ProjectileAnomalyFireball", spawnCoords); - if (_glimmerSystem.GlimmerOutput >= 25 * psionic.Dampening) + if (_glimmerSystem.GlimmerOutput <= 25 * psionic.Dampening) EnsureComp(ent); if (TryComp(ent, out var fireball)) diff --git a/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs b/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs index cc67badbe7..87ec5adbce 100644 --- a/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs @@ -26,17 +26,16 @@ public override void Initialize() private void OnInit(EntityUid uid, RegenerativeStasisPowerComponent component, ComponentInit args) { + EnsureComp(uid, out var psionic); _actions.AddAction(uid, ref component.RegenerativeStasisActionEntity, component.RegenerativeStasisActionId); _actions.TryGetActionData(component.RegenerativeStasisActionEntity, out var actionData); if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.RegenerativeStasisActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.RegenerativeStasisFeedback); - psionic.Amplification += 0.5f; - psionic.Dampening += 0.5f; - } + _actions.SetCooldown(component.RegenerativeStasisActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); + + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.RegenerativeStasisFeedback); + psionic.Amplification += 0.5f; + psionic.Dampening += 0.5f; } private void OnShutdown(EntityUid uid, RegenerativeStasisPowerComponent component, ComponentShutdown args) @@ -63,8 +62,11 @@ private void OnPowerUsed(EntityUid uid, RegenerativeStasisPowerComponent compone solution.AddReagent("Epinephrine", FixedPoint2.New(MathF.Min(2.5f * psionic.Dampening + psionic.Amplification, 15f))); solution.AddReagent("Nocturine", 10f + (1 * psionic.Amplification + psionic.Dampening)); _bloodstreamSystem.TryAddToChemicals(args.Target, solution, stream); - _popupSystem.PopupEntity(Loc.GetString("regenerative-stasis-begin", ("entity", uid)), uid, PopupType.Medium); + _popupSystem.PopupEntity(Loc.GetString("regenerative-stasis-begin", ("entity", args.Target)), args.Target, PopupType.Medium); _psionics.LogPowerUsed(uid, "regenerative stasis", psionic, 4, 6); + _actions.TryGetActionData(component.RegenerativeStasisActionEntity, out var actionData); + if (actionData is { UseDelay: not null }) + _actions.SetCooldown(component.RegenerativeStasisActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); args.Handled = true; } } diff --git a/Content.Server/Psionics/Abilities/TelegnosisPowerSystem.cs b/Content.Server/Psionics/Abilities/TelegnosisPowerSystem.cs index c5f8471a65..95b7995e67 100644 --- a/Content.Server/Psionics/Abilities/TelegnosisPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/TelegnosisPowerSystem.cs @@ -27,17 +27,16 @@ public override void Initialize() private void OnInit(EntityUid uid, TelegnosisPowerComponent component, ComponentInit args) { + EnsureComp(uid, out var psionic); _actions.AddAction(uid, ref component.TelegnosisActionEntity, component.TelegnosisActionId ); _actions.TryGetActionData( component.TelegnosisActionEntity, out var actionData ); if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.TelegnosisActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.TelegnosisFeedback); - psionic.Amplification += 0.3f; - psionic.Dampening += 0.3f; - } + _actions.SetCooldown(component.TelegnosisActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); + + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.TelegnosisFeedback); + psionic.Amplification += 0.3f; + psionic.Dampening += 0.3f; } private void OnShutdown(EntityUid uid, TelegnosisPowerComponent component, ComponentShutdown args) @@ -67,6 +66,10 @@ private void OnPowerUsed(EntityUid uid, TelegnosisPowerComponent component, Tele component.ProjectionUid = projection; _mindSwap.Swap(uid, projection); + _actions.TryGetActionData( component.TelegnosisActionEntity, out var actionData ); + if (actionData is { UseDelay: not null }) + _actions.SetCooldown(component.TelegnosisActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); + if (EnsureComp(projection, out var projectionComponent)) projectionComponent.OriginalEntity = uid; diff --git a/Content.Server/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs b/Content.Server/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs index 5babb6c446..34cdba29bb 100644 --- a/Content.Server/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs +++ b/Content.Server/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs @@ -6,22 +6,42 @@ namespace Content.Server.Psionics.Glimmer /// public sealed partial class GlimmerSourceComponent : Component { - [DataField("accumulator")] + [DataField] public float Accumulator = 0f; - [DataField("active")] + [DataField] public bool Active = true; /// /// Since glimmer is an int, we'll do it like this. /// - [DataField("secondsPerGlimmer")] + [DataField] public float SecondsPerGlimmer = 10f; /// /// True if it produces glimmer, false if it subtracts it. /// - [DataField("addToGlimmer")] + [DataField] public bool AddToGlimmer = true; + + /// + /// If not null, this entity generates this value as a baseline number of research points per second, eg: Probers. + /// Actual glimmer research sources will scale with GlimmerEquilibriumRatio + /// + [DataField] + public int? ResearchPointGeneration = null; + + /// + /// Controls whether this entity requires electrical power to generate research points. + /// + [DataField] + public bool RequiresPower = true; + + /// + /// Above GlimmerEquilibrium, glimmer generation is increased exponentially, but has an offset to prevent things from spiralling out of control. + /// Increasing the offset will make this entity's exponential growth weaker, while decreasing it makes it stronger. Negative numbers are valid by the way :) + /// + [DataField] + public int GlimmerExponentOffset = 0; } } diff --git a/Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs b/Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs index 309bd732ef..76ff5a823b 100644 --- a/Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs +++ b/Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Anomaly.Components; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; +using Content.Server.Research.Components; using Content.Shared.Anomaly.Components; using Content.Shared.Mobs; using Content.Shared.Psionics.Glimmer; @@ -24,6 +25,17 @@ public override void Initialize() SubscribeLocalEvent(OnAnomalyPulse); SubscribeLocalEvent(OnAnomalySupercritical); SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnInit); + } + + private void OnInit(EntityUid uid, GlimmerSourceComponent component, ComponentStartup args) + { + if (component.ResearchPointGeneration != null) + { + EnsureComp(uid, out var points); + points.PointsPerSecond = component.ResearchPointGeneration.Value; + points.Active = true; + } } private void OnAnomalyVesselPowerChanged(EntityUid uid, AnomalyVesselComponent component, ref PowerChangedEvent args) @@ -47,7 +59,7 @@ private void OnAnomalyPulse(EntityUid uid, GlimmerSourceComponent component, ref // component. if (TryComp(uid, out var anomaly)) - _glimmerSystem.DeltaGlimmerInput(5f * anomaly.Severity); + _glimmerSystem.DeltaGlimmerOutput(5f * anomaly.Severity); } private void OnAnomalySupercritical(EntityUid uid, GlimmerSourceComponent component, ref AnomalySupercriticalEvent args) @@ -64,31 +76,56 @@ private void OnMobStateChanged(EntityUid uid, GlimmerSourceComponent component, public override void Update(float frameTime) { base.Update(frameTime); + var glimmerSources = Count(); foreach (var source in EntityQuery()) { - if (!_powerReceiverSystem.IsPowered(source.Owner)) + if (!_powerReceiverSystem.IsPowered(source.Owner) + && source.RequiresPower) + { + glimmerSources--; continue; + } if (!source.Active) + { + glimmerSources--; continue; + } source.Accumulator += frameTime; if (source.Accumulator > source.SecondsPerGlimmer) { - var glimmerEquilibrium = GlimmerSystem.GlimmerEquilibrium; source.Accumulator -= source.SecondsPerGlimmer; + // https://www.desmos.com/calculator/zjzefpue03 + // In Short: 1 prober makes 20 research points. 4 probers makes twice as many points as 1 prober. 9 probers makes 69 points in total between all 9. + // This is then modified by afterwards by GlimmerEquilibrium, to help smooth out the curves. But also, now if you have more drainers than probers, the probers won't generate research! + // Also, this counts things like Anomalies & Glimmer Mites! Which means scientists should be more encouraged to actively hunt mites. + // As a fun novelty, this means that a highly psionic Epistemics department can essentially "Study" their powers for actual research points! + if (source.ResearchPointGeneration != null + && TryComp(source.Owner, out var research)) + research.PointsPerSecond = (int) MathF.Round( + source.ResearchPointGeneration.Value / (MathF.Log(glimmerSources, 4) + 1) + * _glimmerSystem.GetGlimmerEquilibriumRatio()); + // Shorthand explanation: // This makes glimmer far more "Swingy", by making both positive and negative glimmer sources scale quite dramatically with glimmer + if (!_glimmerSystem.GetGlimmerEnabled()) + return; + + var glimmerEquilibrium = GlimmerSystem.GlimmerEquilibrium; + if (source.AddToGlimmer) { - _glimmerSystem.DeltaGlimmerInput((_glimmerSystem.GlimmerOutput > glimmerEquilibrium ? _glimmerSystem.GetGlimmerOutputInteger() : 1f) + _glimmerSystem.DeltaGlimmerInput((_glimmerSystem.GlimmerOutput > glimmerEquilibrium + ? MathF.Pow(_glimmerSystem.GetGlimmerOutputInteger() - source.GlimmerExponentOffset + glimmerSources, 2) : 1f) * (_glimmerSystem.GlimmerOutput < glimmerEquilibrium ? _glimmerSystem.GetGlimmerEquilibriumRatio() : 1f)); } else { - _glimmerSystem.DeltaGlimmerInput(-(_glimmerSystem.GlimmerOutput > glimmerEquilibrium ? _glimmerSystem.GetGlimmerOutputInteger() : 1f) + _glimmerSystem.DeltaGlimmerInput(-(_glimmerSystem.GlimmerOutput > glimmerEquilibrium + ? MathF.Pow(_glimmerSystem.GetGlimmerOutputInteger() - source.GlimmerExponentOffset + glimmerSources, 2) : 1f) * (_glimmerSystem.GlimmerOutput > glimmerEquilibrium ? _glimmerSystem.GetGlimmerEquilibriumRatio() : 1f)); } } diff --git a/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerSystem.cs b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerSystem.cs index 540dc03341..0eeb7ec280 100644 --- a/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerSystem.cs +++ b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerSystem.cs @@ -40,20 +40,20 @@ public override void Initialize() SubscribeLocalEvent(OnShootAttempt); SubscribeLocalEvent(OnThrowAttempt); SubscribeLocalEvent(OnInsulated); + SubscribeLocalEvent(OnDoAfter); } private void OnInit(EntityUid uid, PsionicInvisibilityPowerComponent component, ComponentInit args) { + EnsureComp(uid, out var psionic); _actions.AddAction(uid, ref component.PsionicInvisibilityActionEntity, component.PsionicInvisibilityActionId); - _actions.TryGetActionData( component.PsionicInvisibilityActionEntity, out var actionData); + _actions.TryGetActionData(component.PsionicInvisibilityActionEntity, out var actionData); if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.PsionicInvisibilityActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.InvisibilityFeedback); - psionic.Amplification += 0.5f; - } + _actions.SetCooldown(component.PsionicInvisibilityActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); + + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.InvisibilityFeedback); + psionic.Amplification += 0.5f; } private void OnShutdown(EntityUid uid, PsionicInvisibilityPowerComponent component, ComponentShutdown args) @@ -71,10 +71,7 @@ private void OnShutdown(EntityUid uid, PsionicInvisibilityPowerComponent compone private void OnPowerUsed(EntityUid uid, PsionicInvisibilityPowerComponent component, PsionicInvisibilityPowerActionEvent args) { - if (!TryComp(uid, out var psionic)) - return; - - if (HasComp(uid)) + if (!_psionics.CheckCanSelfCast(uid, out var psionic)) return; var ev = new PsionicInvisibilityTimerEvent(_gameTiming.CurTime); @@ -91,6 +88,10 @@ private void OnPowerUsed(EntityUid uid, PsionicInvisibilityPowerComponent compon _psionics.LogPowerUsed(uid, "psionic invisibility", psionic, 8, 12); args.Handled = true; } + + _actions.TryGetActionData(component.PsionicInvisibilityActionEntity, out var actionData); + if (actionData is { UseDelay: not null }) + _actions.SetCooldown(component.PsionicInvisibilityActionEntity, actionData.UseDelay.Value - TimeSpan.FromSeconds(psionic.Dampening + psionic.Amplification)); } private void OnPowerOff(RemovePsionicInvisibilityOffPowerActionEvent args) @@ -162,7 +163,7 @@ public void ToggleInvisibility(EntityUid uid) } } - public void OnDoAfter(EntityUid uid, PsionicInvisibilityPowerComponent component, PsionicInvisibilityTimerEvent args) + private void OnDoAfter(EntityUid uid, PsionicInvisibilityPowerComponent component, PsionicInvisibilityTimerEvent args) { if (!args.Cancelled) RemComp(uid); diff --git a/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs b/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs index 4dd8fa2442..7a87978582 100644 --- a/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs +++ b/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Content.Shared.Actions; using Content.Shared.Administration.Logs; using Content.Shared.Mobs; @@ -82,6 +83,23 @@ private bool IsEligibleForPsionics(EntityUid uid) && (!TryComp(uid, out var mobstate) || mobstate.CurrentState == MobState.Alive); } + public bool CheckCanSelfCast(EntityUid uid, [NotNullWhen(true)] out PsionicComponent? psiComp) + { + if (!HasComp(uid)) + return TryComp(uid, out psiComp); + psiComp = null; + return false; + } + + public bool CheckCanTargetCast(EntityUid performer, EntityUid target, [NotNullWhen(true)] out PsionicComponent? psiComp) + { + if (!HasComp(performer) + && !HasComp(target)) + return TryComp(performer, out psiComp); + psiComp = null; + return false; + } + public void LogPowerUsed(EntityUid uid, string power, PsionicComponent? psionic = null, int minGlimmer = 8, int maxGlimmer = 12, bool overrideGlimmer = false) { _adminLogger.Add(Database.LogType.Psionics, Database.LogImpact.Medium, $"{ToPrettyString(uid):player} used {power}"); diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml index eca5b5e375..47aaad219d 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml @@ -11,12 +11,10 @@ psychicFeedback: - "prober-feedback" - type: GlimmerSource + researchPointGeneration: 20 - type: Construction graph: GlimmerDevices node: glimmerProber - - type: ResearchPointSource - pointspersecond: 20 - active: true - type: Sprite sprite: DeltaV/Structures/Machines/glimmer_machines.rsi # DeltaV reskin noRot: true From df38eed8a08ae906f26d1edd7cf160bfb7976fb6 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Tue, 9 Jul 2024 19:17:19 -0400 Subject: [PATCH 2/4] Revert "Reapply "Don't look at this please."" This reverts commit 0a7acbf32dbb141b75d5bef584a07714099fef53. --- Content.Client/Entry/EntryPoint.cs | 1 - .../TypingIndicator/TypingIndicatorSystem.cs | 2 +- .../Components/NPCConversationComponent.cs | 152 ----- .../NPC/Events/NPCConversationEvents.cs | 63 -- .../NPCConversationTreePrototype.cs | 154 ----- .../NPC/Systems/NPCConversationSystem.cs | 558 ------------------ .../SophicScribe/SophicScribeSystem.cs | 36 -- .../Locale/en-US/npc/conversation/sophia.ftl | 82 --- .../Structures/Research/sophicscribe.yml | 194 +----- 9 files changed, 2 insertions(+), 1240 deletions(-) delete mode 100644 Content.Server/NPC/Components/NPCConversationComponent.cs delete mode 100644 Content.Server/NPC/Events/NPCConversationEvents.cs delete mode 100644 Content.Server/NPC/Prototypes/NPCConversationTreePrototype.cs delete mode 100644 Content.Server/NPC/Systems/NPCConversationSystem.cs delete mode 100644 Resources/Locale/en-US/npc/conversation/sophia.ftl diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 8636e0eb6a..a1fc68bbd2 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -125,7 +125,6 @@ public override void Init() _prototypeManager.RegisterIgnore("alertLevels"); _prototypeManager.RegisterIgnore("nukeopsRole"); _prototypeManager.RegisterIgnore("stationGoal"); - _prototypeManager.RegisterIgnore("npcConversationTree"); _componentFactory.GenerateNetIds(); _adminManager.Initialize(); diff --git a/Content.Server/Chat/TypingIndicator/TypingIndicatorSystem.cs b/Content.Server/Chat/TypingIndicator/TypingIndicatorSystem.cs index 443923f675..c923738930 100644 --- a/Content.Server/Chat/TypingIndicator/TypingIndicatorSystem.cs +++ b/Content.Server/Chat/TypingIndicator/TypingIndicatorSystem.cs @@ -54,7 +54,7 @@ private void OnClientTypingChanged(TypingChangedEvent ev, EntitySessionEventArgs SetTypingIndicatorEnabled(uid.Value, ev.IsTyping); } - public void SetTypingIndicatorEnabled(EntityUid uid, bool isEnabled, AppearanceComponent? appearance = null) + private void SetTypingIndicatorEnabled(EntityUid uid, bool isEnabled, AppearanceComponent? appearance = null) { if (!Resolve(uid, ref appearance, false)) return; diff --git a/Content.Server/NPC/Components/NPCConversationComponent.cs b/Content.Server/NPC/Components/NPCConversationComponent.cs deleted file mode 100644 index c2a8ca31d7..0000000000 --- a/Content.Server/NPC/Components/NPCConversationComponent.cs +++ /dev/null @@ -1,152 +0,0 @@ -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Content.Server.NPC.Events; -using Content.Server.NPC.Prototypes; -using Content.Server.NPC.Systems; - -namespace Content.Server.NPC.Components; - -[RegisterComponent] -[Access(typeof(NPCConversationSystem))] -public sealed partial class NPCConversationComponent : Component -{ - /// - /// Whether or not the listening logic is turned on. - /// - /// - /// Queued responses will still play through, but no new attempts to listen will be made. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("enabled")] - public bool Enabled = true; - - /* NYI: - /// - /// The NPC will pay attention when one of these words are said. - /// - [ViewVariables] - [DataField("aliases")] - public List Aliases = new(); - */ - - [ViewVariables] - [DataField("tree", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? ConversationTreeId; - - /// - /// This is the cached prototype. - /// - [ViewVariables] - public NPCConversationTreePrototype ConversationTree = default!; - - /// - /// Topics that are unlocked in the NPC's conversation tree. - /// - [ViewVariables] - public HashSet UnlockedTopics = new(); - - /// - /// How long until we stop paying attention to someone for a prompt. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("attentionSpan")] - public TimeSpan AttentionSpan = TimeSpan.FromSeconds(20); - - /// - /// This is the minimum delay before the NPC makes a response. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("delayBeforeResponse")] - public TimeSpan DelayBeforeResponse = TimeSpan.FromSeconds(0.3); - - /// - /// This is the approximate delay per letter typed in text. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("typingDelay")] - public TimeSpan TypingDelay = TimeSpan.FromSeconds(0.05); - - [ViewVariables] - public Stack ResponseQueue = new(); - - /// - /// This is when the NPC will respond with its top response. - /// - [ViewVariables] - [DataField("nextResponse", customTypeSerializer: typeof(TimeOffsetSerializer))] - public TimeSpan NextResponse; - - /// - /// This is the direction the NPC was facing before looking towards a conversation partner. - /// - [ViewVariables] - public Angle OriginalFacing; - - /// - /// This is who the NPC is paying attention to for conversation. - /// - [ViewVariables] - public EntityUid? AttendingTo; - - /// - /// This is when the NPC will stop paying attention to a specific person. - /// - [ViewVariables] - [DataField("nextAttentionLoss", customTypeSerializer: typeof(TimeOffsetSerializer))] - public TimeSpan NextAttentionLoss; - - /// - /// This event is fired the next time the NPC hears something from the - /// person they're speaking with and it takes control of the response. - /// - [ViewVariables] - public NPCConversationListenEvent? ListeningEvent; - -#region Idle Chatter - - /// - /// Whether or not the NPC will say things unprompted. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("idleEnabled")] - public bool IdleEnabled = true; - - /// - /// This is the approximate delay between idle chats. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("idleChatDelay")] - public TimeSpan IdleChatDelay = TimeSpan.FromMinutes(3); - - /// - /// This is the order in which idle chat lines are given. - /// - /// - /// This is randomized both on init and when the lines have been exhausted - /// to prevent repeating lines twice in a row and to avoid predictable patterns. - /// - /// It technically reduces randomness, with the benefit of less repetition. - /// - [ViewVariables(VVAccess.ReadWrite)] - public List IdleChatOrder = new(); - - /// - /// This is the next idle chat line that will be used. - /// - [ViewVariables(VVAccess.ReadWrite)] - public int IdleChatIndex = 0; - - /// - /// This is when the NPC will say something out of its list of idle lines. - /// - /// - /// This is reset every time the NPC speaks. - /// - [ViewVariables] - [DataField("nextIdleChat", customTypeSerializer: typeof(TimeOffsetSerializer))] - public TimeSpan NextIdleChat; - -#endregion - -} - diff --git a/Content.Server/NPC/Events/NPCConversationEvents.cs b/Content.Server/NPC/Events/NPCConversationEvents.cs deleted file mode 100644 index eb04f59bdd..0000000000 --- a/Content.Server/NPC/Events/NPCConversationEvents.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Robust.Shared.Audio; -using Content.Server.NPC.Systems; - -namespace Content.Server.NPC.Events; - -/// -/// This is used for dynamic responses and post-response events. -/// -[ImplicitDataDefinitionForInheritors] -[Access(typeof(NPCConversationSystem))] -public abstract partial class NPCConversationEvent : EntityEventArgs -{ - /// - /// This is the entity that the NPC is speaking to. - /// - public EntityUid? TalkingTo; -} - -/// -/// This event type is raised when an NPC hears a response when it was set to listen for one. -/// -/// -/// Set Handled to true when you want the NPC to stop listening. -/// The NPC will otherwise keep listening and block any attempt to find a prompt in the speaker's words. -/// -[ImplicitDataDefinitionForInheritors] -[Access(typeof(NPCConversationSystem))] -public abstract partial class NPCConversationListenEvent : HandledEntityEventArgs -{ - /// - /// This is the entity that said the message. - /// - public EntityUid? Speaker; - - /// - /// This is the original message that the NPC heard. - /// - public string Message = default!; - - /// - /// This is the message, parsed into separate words. - /// - public List Words = default!; -} - -public sealed partial class NPCConversationHelpEvent : NPCConversationEvent -{ - [DataField("text")] - public string? Text; - - [DataField("audio")] - public SoundSpecifier? Audio; -} - -/// -/// This event can be raised after a response to cause an NPC to stop paying attention to someone. -/// -public sealed partial class NPCConversationByeEvent : NPCConversationEvent { } - -// The following classes help demonstrate some of the features of the system. -// They may be separated out at some point. -public sealed partial class NPCConversationToldNameEvent : NPCConversationListenEvent { } - diff --git a/Content.Server/NPC/Prototypes/NPCConversationTreePrototype.cs b/Content.Server/NPC/Prototypes/NPCConversationTreePrototype.cs deleted file mode 100644 index 20a616d830..0000000000 --- a/Content.Server/NPC/Prototypes/NPCConversationTreePrototype.cs +++ /dev/null @@ -1,154 +0,0 @@ -using Robust.Shared.Audio; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; -using Content.Server.NPC.Events; - -namespace Content.Server.NPC.Prototypes; - -[Prototype("npcConversationTree")] -public sealed class NPCConversationTreePrototype : IPrototype, ISerializationHooks -{ - [ViewVariables] - [IdDataField] - public string ID { get; } = default!; - - /// - /// Dialogue contains all the topics to which an NPC can discuss. - /// - [ViewVariables] - [DataField("dialogue", required: true)] - public readonly NPCTopic[] Dialogue = default!; - - /// - /// Attention responses are what the NPC says when they start paying - /// attention to you without a specific question or prompt to respond to. - /// - [ViewVariables] - [DataField("attention", required: true)] - public readonly NPCResponse[] Attention = default!; - - /// - /// Idle responses are just things the NPC will say when nothing else is - /// going on, after some time. - /// - [ViewVariables] - [DataField("idle", required: true)] - public readonly NPCResponse[] Idle = default!; - - /// - /// Unknown responses are what the NPC says when they can't respond to a - /// particular question or prompt. - /// - [ViewVariables] - [DataField("unknown", required: true)] - public readonly NPCResponse[] Unknown = default!; - - /// - /// Custom responses are available to use in extensions to the NPC - /// Conversation system. - /// - // NOTE: This may be removed in favor of storing NPCResponses on custom - // components, i.e. an NPCShopkeeperComponent, but for now, it lives here - // to help demonstrate some features. - [ViewVariables] - [DataField("custom")] - public readonly Dictionary Custom = default!; - - /// - /// This exists as a quick way to map a prompt to a topic. - /// - public readonly Dictionary PromptToTopic = new(); - - // ISerializationHooks _is_ obsolete, but ConstructionGraphPrototype is using it as of this commit, - // and I'm not quite sure how to otherwise do this. - // - // I will look at that prototype when ISerializationHooks is phased out. - void ISerializationHooks.AfterDeserialization() - { - // Cache the strings mapping to prompts. - foreach (var topic in Dialogue) - { - foreach (var prompt in topic.Prompts) - { - PromptToTopic[prompt] = topic; - } - } - } -} - -[DataDefinition] -public sealed partial class NPCTopic -{ - [DataField] - public string[] Prompts = default!; - - /// - /// This determines the likelihood of this topic being selected over any - /// other, given the existence of multiple candidates. - /// - [DataField] - public float Weight = 1.0f; - - /// - /// Locked topics will not be accessible through dialogue until unlocked. - /// - [DataField] - public bool Locked; - - /// - /// Hidden topics won't show up in any form of "help" question. - /// - [DataField] - public bool Hidden; - - [DataField("responses", required: true)] - public NPCResponse[] Responses = default!; -} - -[DataDefinition] -public sealed partial class NPCResponse -{ - public NPCResponse() { } - - public NPCResponse(string? text, SoundSpecifier? audio = null, NPCConversationEvent? ev = null) - { - Text = text; - Audio = audio; - Event = ev; - } - - public override string ToString() - { - return $"NPCResponse({Text})"; - } - - [DataField] - public string? Text; - - [DataField] - public SoundSpecifier? Audio; - - /* [DataField("emote")] */ - /* public string? Emote; */ - - /// - /// This event is raised when the response is queued, - /// for the purpose of dynamic responses. - /// - [DataField] - public NPCConversationEvent? Is; - - /// - /// This event is raised after the response is made. - /// - [DataField] - public NPCConversationEvent? Event; - - /// - /// This event is raised when the NPC next hears a response, - /// allowing the response to be processed by other systems. - /// - [DataField] - public NPCConversationListenEvent? ListenEvent; -} - diff --git a/Content.Server/NPC/Systems/NPCConversationSystem.cs b/Content.Server/NPC/Systems/NPCConversationSystem.cs deleted file mode 100644 index 015adb19de..0000000000 --- a/Content.Server/NPC/Systems/NPCConversationSystem.cs +++ /dev/null @@ -1,558 +0,0 @@ -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text.RegularExpressions; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Timing; -using Content.Server.Chat.Systems; -using Content.Server.Chat.TypingIndicator; -using Content.Server.NPC.HTN; -using Content.Server.NPC.Components; -using Content.Server.NPC.Events; -using Content.Server.NPC.Prototypes; -using Content.Server.Speech; -using Content.Shared.Interaction; -using Content.Server.Radio.Components; - -namespace Content.Server.NPC.Systems; - -public sealed class NPCConversationSystem : EntitySystem -{ - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IPrototypeManager _prototype = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedAudioSystem _audioSystem = default!; - [Dependency] private readonly ChatSystem _chatSystem = default!; - [Dependency] private readonly NPCSystem _npcSystem = default!; - [Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!; - [Dependency] private readonly TransformSystem _transformSystem = default!; - [Dependency] private readonly TypingIndicatorSystem _typingIndicatorSystem = default!; - - private ISawmill _sawmill = default!; - - // TODO: attention attenuation. distance, facing, visible - // TODO: attending to multiple people, multiple streams of conversation - // TODO: multi-word prompts - // TODO: nameless prompting (pointing is good) - // TODO: aliases - - public static readonly string[] QuestionWords = { "who", "what", "when", "why", "where", "how" }; - public static readonly string[] Copulae = { "is", "are" }; - - public override void Initialize() - { - base.Initialize(); - - _sawmill = Logger.GetSawmill("npc.conversation"); - - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnUnpaused); - SubscribeLocalEvent(OnListenAttempt); - SubscribeLocalEvent(OnListen); - - SubscribeLocalEvent(OnBye); - SubscribeLocalEvent(OnHelp); - - SubscribeLocalEvent(OnToldName); - } - -#region API - - /// - /// Toggle the ability of an NPC to listen for topics. - /// - public void EnableConversation(EntityUid uid, bool enable = true, NPCConversationComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.Enabled = enable; - } - - /// - /// Toggle the NPC's willingness to make idle comments. - /// - public void EnableIdleChat(EntityUid uid, bool enable = true, NPCConversationComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.IdleEnabled = enable; - } - - /// - /// Return locked status of a dialogue topic. - /// - public bool IsDialogueLocked(EntityUid uid, string option, NPCConversationComponent? component = null) - { - if (!Resolve(uid, ref component)) - return true; - - if (!component.ConversationTree.PromptToTopic.TryGetValue(option, out var topic)) - { - _sawmill.Warning($"Tried to check locked status of missing dialogue option `{option}` on {ToPrettyString(uid)}"); - return true; - } - - if (component.UnlockedTopics.Contains(topic)) - return false; - - return topic.Locked; - } - - /// - /// Unlock dialogue options normally locked in an NPC's conversation tree. - /// - public void UnlockDialogue(EntityUid uid, string option, NPCConversationComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (component.ConversationTree.PromptToTopic.TryGetValue(option, out var topic)) - component.UnlockedTopics.Add(topic); - else - _sawmill.Warning($"Tried to unlock missing dialogue option `{option}` on {ToPrettyString(uid)}"); - } - - /// - public void UnlockDialogue(EntityUid uid, HashSet options, NPCConversationComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - foreach (var option in options) - UnlockDialogue(uid, option, component); - } - - /// - /// Queue a response for an NPC with a visible typing indicator and delay between messages. - /// - /// - /// This can be used as opposed to the typical method. - /// - public void QueueResponse(EntityUid uid, NPCResponse response, NPCConversationComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (response.Is is {} ev) - { - // This is a dynamic response which will call QueueResponse with static responses of its own. - ev.TalkingTo = component.AttendingTo; - RaiseLocalEvent(uid, (object) ev); - return; - } - - if (component.ResponseQueue.Count == 0) - { - DelayResponse(uid, component, response); - _typingIndicatorSystem.SetTypingIndicatorEnabled(uid, true); - } - - component.ResponseQueue.Push(response); - } - - /// - /// Make an NPC stop paying attention to someone. - /// - public void LoseAttention(EntityUid uid, NPCConversationComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.AttendingTo = null; - component.ListeningEvent = null; - _rotateToFaceSystem.TryFaceAngle(uid, component.OriginalFacing); - } - -#endregion - - private void DelayResponse(EntityUid uid, NPCConversationComponent component, NPCResponse response) - { - if (response.Text == null) - return; - - component.NextResponse = _gameTiming.CurTime + - component.DelayBeforeResponse + - component.TypingDelay.TotalSeconds * TimeSpan.FromSeconds(response.Text.Length) * - _random.NextDouble(0.9, 1.1); - } - - private IEnumerable GetAvailableTopics(EntityUid uid, NPCConversationComponent component) - { - HashSet availableTopics = new(); - - foreach (var topic in component.ConversationTree.Dialogue) - { - if (!topic.Locked || component.UnlockedTopics.Contains(topic)) - availableTopics.Add(topic); - } - - return availableTopics; - } - - private IEnumerable GetVisibleTopics(EntityUid uid, NPCConversationComponent component) - { - HashSet visibleTopics = new(); - - foreach (var topic in component.ConversationTree.Dialogue) - { - if (!topic.Hidden && (!topic.Locked || component.UnlockedTopics.Contains(topic))) - visibleTopics.Add(topic); - } - - return visibleTopics; - } - - private void OnInit(EntityUid uid, NPCConversationComponent component, ComponentInit args) - { - if (component.ConversationTreeId == null) - return; - - component.ConversationTree = _prototype.Index(component.ConversationTreeId); - component.NextIdleChat = _gameTiming.CurTime + component.IdleChatDelay; - - for (var i = 0; i < component.ConversationTree.Idle.Length; ++i) - component.IdleChatOrder.Add(i); - - _random.Shuffle(component.IdleChatOrder); - } - - private void OnUnpaused(EntityUid uid, NPCConversationComponent component, ref EntityUnpausedEvent args) - { - component.NextResponse += args.PausedTime; - component.NextAttentionLoss += args.PausedTime; - component.NextIdleChat += args.PausedTime; - } - - private bool TryGetIdleChatLine(EntityUid uid, NPCConversationComponent component, [NotNullWhen(true)] out NPCResponse? line) - { - line = null; - - if (component.IdleChatOrder.Count() == 0) - return false; - - if (++component.IdleChatIndex == component.IdleChatOrder.Count()) - { - // Exhausted all lines in the pre-shuffled order. - // Reset the index and shuffle again. - component.IdleChatIndex = 0; - _random.Shuffle(component.IdleChatOrder); - } - - var index = component.IdleChatOrder[component.IdleChatIndex]; - - line = component.ConversationTree.Idle[index]; - - return true; - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var component)) - { - var curTime = _gameTiming.CurTime; - - if (curTime >= component.NextResponse && component.ResponseQueue.Count > 0) - { - // Make a response. - Respond(uid, component, component.ResponseQueue.Pop()); - } - - if (curTime >= component.NextAttentionLoss && component.AttendingTo != null) - { - // Forget who we were talking to. - LoseAttention(uid, component); - } - - if (component.IdleEnabled && - curTime >= component.NextIdleChat && - TryGetIdleChatLine(uid, component, out var line)) - { - Respond(uid, component, line); - } - } - } - - private void OnListenAttempt(EntityUid uid, NPCConversationComponent component, ListenAttemptEvent args) - { - if (!component.Enabled || - // Don't listen to myself... - uid == args.Source || - // Don't bother listening to other NPCs. For now. - HasComp(args.Source) || - // We're already "typing" a response, so do that first. - component.ResponseQueue.Count > 0) - { - args.Cancel(); - } - } - - private void PayAttentionTo(EntityUid uid, NPCConversationComponent component, EntityUid speaker) - { - component.AttendingTo = speaker; - component.NextAttentionLoss = _gameTiming.CurTime + component.AttentionSpan; - component.OriginalFacing = _transformSystem.GetWorldRotation(uid); - } - - private void Respond(EntityUid uid, NPCConversationComponent component, NPCResponse response) - { - if (component.ResponseQueue.Count == 0) - _typingIndicatorSystem.SetTypingIndicatorEnabled(uid, false); - else - DelayResponse(uid, component, component.ResponseQueue.Peek()); - - if (component.AttendingTo != null) - { - // TODO: This line is a mouthful. Maybe write a public API that supports EntityCoordinates later? - var speakerCoords = Transform(component.AttendingTo.Value).Coordinates.ToMap(EntityManager, _transformSystem).Position; - _rotateToFaceSystem.TryFaceCoordinates(uid, speakerCoords); - } - - if (response.Event is {} ev) - { - ev.TalkingTo = component.AttendingTo; - RaiseLocalEvent(uid, (object) ev); - } - - if (response.ListenEvent != null) - component.ListeningEvent = response.ListenEvent; - - if (response.Text != null) - _chatSystem.TrySendInGameICMessage(uid, Loc.GetString(response.Text), InGameICChatType.Speak, false); - - if (response.Audio != null) - _audioSystem.PlayPvs(response.Audio, uid, - // TODO: Allow this to be configured per NPC/response. - AudioParams.Default - .WithVolume(8f) - .WithMaxDistance(9f) - .WithRolloffFactor(0.5f)); - - // Refresh our attention. - component.NextAttentionLoss = _gameTiming.CurTime + component.AttentionSpan; - component.NextIdleChat = component.NextAttentionLoss + component.IdleChatDelay; - } - - private List ParseMessageIntoWords(string message) - { - return Regex.Replace(message.Trim().ToLower(), @"(\p{P})", "") - .Split() - .ToList(); - } - - private bool FindResponse(EntityUid uid, NPCConversationComponent component, List words, [NotNullWhen(true)] out NPCResponse? response) - { - response = null; - - var availableTopics = GetAvailableTopics(uid, component); - - // Some topics are more interesting than others. - var greatestWeight = 0f; - NPCTopic? candidate = null; - - foreach (var word in words) - { - if (component.ConversationTree.PromptToTopic.TryGetValue(word, out var topic) && - availableTopics.Contains(topic) && - topic.Weight > greatestWeight) - { - greatestWeight = topic.Weight; - candidate = topic; - } - } - - if (candidate != null) - { - response = _random.Pick(candidate.Responses); - return true; - } - - return false; - } - - private bool JudgeQuestionLikelihood(EntityUid uid, NPCConversationComponent component, List words, string message) - { - if (message.Length > 0 && message[^1] == '?') - // A question mark is an absolute mark of a question. - return true; - - if (words.Count == 1) - // The usefulness of this is dubious, but it's definitely a question. - return QuestionWords.Contains(words[0]); - - if (words.Count >= 2) - return QuestionWords.Contains(words[0]) && Copulae.Contains(words[1]); - - return false; - } - - private void OnBye(EntityUid uid, NPCConversationComponent component, NPCConversationByeEvent args) - { - LoseAttention(uid, component); - } - - private void OnHelp(EntityUid uid, NPCConversationComponent component, NPCConversationHelpEvent args) - { - if (args.Text == null) - { - _sawmill.Error($"{ToPrettyString(uid)} heard a Help prompt but has no text for it."); - return; - } - - var availableTopics = GetVisibleTopics(uid, component); - var availablePrompts = availableTopics.Select(topic => topic.Prompts.FirstOrDefault()).ToArray(); - - string availablePromptsText; - if (availablePrompts.Count() <= 2) - { - availablePromptsText = Loc.GetString(args.Text, - ("availablePrompts", string.Join(" or ", availablePrompts)) - ); - } - else - { - availablePrompts[^1] = $"or {availablePrompts[^1]}"; - availablePromptsText = Loc.GetString(args.Text, - ("availablePrompts", string.Join(", ", availablePrompts)) - ); - } - - // Unlikely we'll be able to do audio that isn't hard-coded, - // so best to keep it general. - var response = new NPCResponse(availablePromptsText, args.Audio); - QueueResponse(uid, response, component); - } - - private void OnToldName(EntityUid uid, NPCConversationComponent component, NPCConversationListenEvent args) - { - if (!component.ConversationTree.Custom.TryGetValue("toldName", out var responses)) - return; - - var response = _random.Pick(responses); - if (response.Text == null) - { - _sawmill.Error($"{ToPrettyString(uid)} was told a name but had no text response."); - return; - } - - // The world's simplest heuristic for names: - if (args.Words.Count > 3) - { - // It didn't seem like a name, so wait for something that does. - return; - } - - var cleanedName = string.Join(" ", args.Words); - cleanedName = char.ToUpper(cleanedName[0]) + cleanedName.Remove(0, 1); - - var formattedResponse = new NPCResponse(Loc.GetString(response.Text, - ("name", cleanedName)), - response.Audio); - - QueueResponse(uid, formattedResponse, component); - args.Handled = true; - } - - private void OnListen(EntityUid uid, NPCConversationComponent component, ListenEvent args) - { - if (HasComp(args.Source)) - return; - - if (component.AttendingTo != null && component.AttendingTo != args.Source) - // Ignore someone speaking to us if we're already paying attention to someone else. - return; - - var words = ParseMessageIntoWords(args.Message); - if (words.Count == 0) - return; - - if (component.AttendingTo == args.Source) - { - // The person we're talking to said something to us. - - if (component.ListeningEvent is {} ev) - { - // We were waiting on this person to say something, and they've said something. - ev.Handled = false; - ev.Speaker = component.AttendingTo; - ev.Message = args.Message; - ev.Words = words; - RaiseLocalEvent(uid, (object) ev); - - if (ev.Handled) - component.ListeningEvent = null; - - return; - } - - // We're already paying attention to this person, - // so try to figure out if they said something we can talk about. - if (FindResponse(uid, component, words, out var response)) - { - // A response was found so go ahead with it. - QueueResponse(uid, response, component); - } - else if(JudgeQuestionLikelihood(uid, component, words, args.Message)) - { - // The message didn't match any of the prompts, but it seemed like a question. - var unknownResponse = _random.Pick(component.ConversationTree.Unknown); - QueueResponse(uid, unknownResponse, component); - } - - // If the message didn't seem like a question, - // and it didn't raise any of our topics, - // then politely ignore who we're talking with. - // - // It's better than spamming them with "I don't understand." - return; - } - - // See if someone said our name. - var myName = MetaData(uid).EntityName.ToLower(); - - // So this is a rough heuristic, but if our name occurs within the first three words, - // or is the very last one, someone might be trying to talk to us. - var payAttention = words[0] == myName || words[^1] == myName; - if (!payAttention) - { - for (int i = 1; i < Math.Min(2, words.Count); ++i) - { - if (words[i] == myName) - { - payAttention = true; - break; - } - } - } - - if (payAttention) - { - PayAttentionTo(uid, component, args.Source); - - if (!FindResponse(uid, component, words, out var response)) - { - if(JudgeQuestionLikelihood(uid, component, words, args.Message) && - // This subcondition exists to block our name being interpreted as a question in its own right. - words.Count > 1) - { - response = _random.Pick(component.ConversationTree.Unknown); - } - else - { - response = _random.Pick(component.ConversationTree.Attention); - } - } - - QueueResponse(uid, response, component); - } - } -} - diff --git a/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs b/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs index ba5ff0a056..b1a6c1e9de 100644 --- a/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs +++ b/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs @@ -1,8 +1,5 @@ using Content.Server.Psionics.Abilities; using Content.Server.Chat.Systems; -using Content.Server.NPC.Events; -using Content.Server.NPC.Systems; -using Content.Server.NPC.Prototypes; using Content.Server.Radio.Components; using Content.Server.Radio.EntitySystems; using Content.Server.StationEvents.Events; @@ -21,8 +18,6 @@ public sealed partial class SophicScribeSystem : EntitySystem [Dependency] private readonly RadioSystem _radioSystem = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly NPCConversationSystem _conversationSystem = default!; - protected ISawmill Sawmill = default!; public override void Update(float frameTime) { @@ -56,32 +51,6 @@ public override void Initialize() SubscribeLocalEvent(OnInteractHand); SubscribeLocalEvent(OnGlimmerEventEnded); - SubscribeLocalEvent(OnGetGlimmer); - } - - private void OnGetGlimmer(EntityUid uid, SophicScribeComponent component, NPCConversationGetGlimmerEvent args) - { - if (args.Text == null) - { - Sawmill.Error($"{uid} heard a glimmer reading prompt but has no text for it"); - return; - } - - var tier = _glimmerSystem.GetGlimmerTier() switch - { - GlimmerTier.Minimal => Loc.GetString("glimmer-reading-minimal"), - GlimmerTier.Low => Loc.GetString("glimmer-reading-low"), - GlimmerTier.Moderate => Loc.GetString("glimmer-reading-moderate"), - GlimmerTier.High => Loc.GetString("glimmer-reading-high"), - GlimmerTier.Dangerous => Loc.GetString("glimmer-reading-dangerous"), - _ => Loc.GetString("glimmer-reading-critical"), - }; - - var glimmerReadingText = Loc.GetString(args.Text, - ("glimmer", (int) Math.Round(_glimmerSystem.GlimmerOutput)), ("tier", tier)); - - var response = new NPCResponse(glimmerReadingText); - _conversationSystem.QueueResponse(uid, response); } private void OnInteractHand(EntityUid uid, SophicScribeComponent component, InteractHandEvent args) @@ -114,9 +83,4 @@ private void OnGlimmerEventEnded(GlimmerEventEndedEvent args) _radioSystem.SendRadioMessage(speaker, message, channel, speaker); } } - public sealed partial class NPCConversationGetGlimmerEvent : NPCConversationEvent - { - [DataField] - public string? Text; - } } diff --git a/Resources/Locale/en-US/npc/conversation/sophia.ftl b/Resources/Locale/en-US/npc/conversation/sophia.ftl deleted file mode 100644 index c832d9fc17..0000000000 --- a/Resources/Locale/en-US/npc/conversation/sophia.ftl +++ /dev/null @@ -1,82 +0,0 @@ -sophia-response-name = You may call me Sophia. -sophia-response-help = You may inquire about one of the following topics: {$availablePrompts}. - -sophia-response-hello-1 = Greetings. -sophia-response-hello-2 = Salutations. - -sophia-response-bye-1 = Fare thee well. -sophia-response-bye-2 = Gods be with you. -sophia-response-bye-3 = Come back wiser. - -sophia-idle-phrase-1 = Mmmm, another portent. -sophia-idle-phrase-2 = The noösphere is quite beautiful today. However, I don't think I could describe it in a way you could understand. -sophia-idle-phrase-3 = I've been here before. You have, too. - -sophia-response-attention-1 = What is it? -sophia-response-attention-2 = What do you seek? -sophia-response-attention-3 = Out with it. - -sophia-response-sorry-1 = That's not a question for me. -sophia-response-sorry-2 = Ask someone else. -sophia-response-sorry-3 = Maybe I know the answer, maybe I do not. Either way, I will not be answering that question. - -sophia-response-nature = My nature doesn't really matter, does it? I'm fulfilling my purpose. Can you say the same, or are you just wasting time? - -sophia-response-epi = 'Epistemics' is a word. Aspiring Hellenes they are, they wished to displace the Latin 'science.' However, in English, epistemics has undesired connotations as a study of knowledge itself, even though the Greek word is a literal replacement for 'science.' - -sophia-response-mantis = 'Mantis' means seer, soothsayer, or prophet. They must be so named because they seek to uncover the truth. And, fittingly with their psionic aptitude, 'mantis' and 'mind' both descend, to the best of our knowledge, from an absolutely ancient word that sounded something like 'mentis.' - -sophia-response-mystagogue = 'Mystagogue' literally means 'leader of the mystics.' You may know the suffix -gogue from 'demogogue.' - -sophia-response-oracle = Oracle? I don't know much about her, and she isn't keen to share her secrets with me. - -sophia-response-psionics = Psionics are extraordinary abilities originating from one's mind. There doesn't seem to be any dominant word to refer to someone with the ability to practice these, although I prefer 'psion' or 'psychic.' - -sophia-response-noosphere = The noösphere is a field connecting all of consciousness. It's the medium through which psionics works. Its strength and effects on the illusory world of the material are based on its pressure. Colloquially, noöspheric pressure is called 'glimmer.' - -sophia-response-god = 'God' is such a vague term. There are so many entities out there that have defeated mortality. How you choose to regard them is your business. - -sophia-response-morphotype = In the first century PCC, several entities reshaped men into their image. I had done the same, if you would believe it. I can offer no evidence of their existence, other than faint memories. Any specific morphotype you want to know about? - -sophia-response-calendar = It's currently 417 PCC. The casuality crisis neccesitated a new year to count from. Due to the nature of the crisis, it can only be said with certainty that 1 PCC is between 2400 and 2700 CE. - -sophia-response-crisis = The first FTL travel was incompatible with the old ways. Fortunately, its resolution made more apparent the inherent futility in trying to give one history, one narrative, one account. Truth cannot be found in the material world, only higher ones. - -sophia-response-metempsychosis = You've died thousands of times, and you'll die thousands more. Some of those lives you may dedicate to trying to stop the cycle. We all carry at least some memory of past lives, usually temporally recent ones. One of the great mysteries of the persistence of fragments is the high concentration of memories from the early 21st century CE, which, inverse to other periods, seem to be more common among the ignorant. - -sophia-response-truth = If you seek the truth, you're in the wrong place. From a perspective tainted by material reality, the best you can hope is to try and divine higher truths that are not dependent on it. - -sophia-response-job = I observe the glimmer here, and record it. - -sophia-response-human = Humans were the base for all the others. But they, too, were shaped. Long, long before the others. - -sophia-response-felinid = Felinids were the first, and the most willing. In true feline nature, they shaped themselves. - -sophia-response-oni = Oni, it is said, originated in Sirius. The brightest star in the night sky from Earth may have attracted some chromatically inclined entities, explaining their vivid coloring. But, that's just speculation. - -sophia-response-arachne = Arachne are the strangest of them. They're not fully mortal. They took the form of humans, but not their genes. Their creator wrote his name in their stead. - -sophia-response-moth = Moths scarecely look human, but, strangely, their genes confirm they are. Their creator shares his name with a genus of moths, and was responsible for the other outlier. - -sophia-response-lamiae = So, you remember? You must be remembering their mythological namesake. If you've really retained that fleeting memory over so many metempsychoses... Perhaps I've said too much. - -sophia-response-cyno = Were those... no... So faint. Ignorance! You cannot remember them! It's impossible! - -sophia-response-harpy = Harpies, it is said, were once men and women, sculpted by greed for a purpose long gone. They were abandoned by their creators on a world named Valerian 4b. - -sophia-response-valerian = The Harpy homeworld? Magestic mountains gleaming in white, forests of brilliant scarlet, oceans wine dark, yet no light to be seen by mortal eyes. The Harpies were made to thrive there. To them, their world was bathed in beautiful silver light. - -sophia-response-grue = You do not know of those. You cannot. I had so hoped to live a few cycles under normal causality. - -sophia-response-abraxas = That's a name of power, and I avoid speaking of him. He's the least content to rest, and the most infatuated with creating things from ignorance. - -sophia-response-zork = You wander into the slavering fangs of a hungry grue. There, did you enjoy this game? - -sophia-response-glimmer = The current glimmer reading is {$glimmer}. {$tier} - -glimmer-reading-minimal = That is extremely low. Nothing bad will happen, but I hope this is not at the cost of progression in your understanding of the universe. -glimmer-reading-low = That is quite low. Just barely enough to register any psionic activity here. -glimmer-reading-moderate = That is about the expected level on a psionically active station. You may notice manageable, minor effects. -glimmer-reading-high = That is sure to start attracting attention, although still quite manageable. -glimmer-reading-dangerous = That's a bit concerning. You may want to redirect efforts to reducing it. -glimmer-reading-critical = That's apocalyptic, in the original sense of the word. That is, to say, revealing. This is the sort of time and place to acquire secret knowledge. diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml index 5213608d95..8e34a07ea5 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml @@ -1,7 +1,7 @@ - type: entity parent: BaseStructure id: SophicScribe - name: Sophie + name: sophie description: Latest reports on the Noösphere! components: - type: Sprite @@ -27,10 +27,6 @@ channels: - Common - Science - - type: ActiveListener - - type: TypingIndicator - - type: NPCConversation - tree: SophiaTree - type: PotentialPsionic #this makes her easier to access for glimmer events, dw about it - type: Psionic psychicFeedback: @@ -43,191 +39,3 @@ - type: GuideHelp guides: - Psionics - -- type: npcConversationTree - id: SophiaTree - dialogue: - - prompts: [glimmer, reading] - responses: - - is: !type:NPCConversationGetGlimmerEvent - text: sophia-response-glimmer - - - prompts: [purpose, job, occupation, profession] - weight: 0.5 - responses: - - text: sophia-response-job - - - prompts: [help, topics] - weight: 0.5 - hidden: true - responses: - - is: !type:NPCConversationHelpEvent - text: sophia-response-help - - - prompts: [nature, statue, snake, being] - weight: 0.3 - responses: - - text: sophia-response-nature - - - prompts: [epistemics, epi] - weight: 0.2 - responses: - - text: sophia-response-epi - - - prompts: [mantis] - weight: 0.2 - responses: - - text: sophia-response-mantis - - - prompts: [mystagogue, mysta] - weight: 0.2 - responses: - - text: sophia-response-mystagogue - - - prompts: [psionics, psychic] - weight: 0.2 - responses: - - text: sophia-response-psionics - - - prompts: [noösphere, noosphere] - weight: 0.2 - responses: - - text: sophia-response-noosphere - - - prompts: [metempsychosis, metempsychoses, reincarnation, death, dying, afterlife] - weight: 0.2 - responses: - - text: sophia-response-metempsychosis - - - prompts: [calendar] - weight: 0.2 - responses: - - text: sophia-response-calendar - - - prompts: [morphotypes, morphotype, species] - weight: 0.2 - responses: - - text: sophia-response-morphotype - - - prompts: [gods, god] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-god - - - prompts: [truth, "true", "false", falsity, falsehood] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-truth - - - prompts: [human, humans, humanoid, unmutated] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-human - - - prompts: [felinid, felinids, felid, felids, catperson, catpeople] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-felinid - - - prompts: [oni, onis] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-oni - - - prompts: [arachne, arachnid, arachnids, spiderperson, spiderpeople] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-arachne - - - prompts: [moth, moths, moff, moths] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-moth - - - prompts: [lamiae, lamia, lamias] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-lamiae - - - prompts: [grue, grues, batperson, batpeople] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-grue - - - prompts: [cynocephalus, cynocephali, cyno, cynos] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-cyno - - - prompts: [harpy, harpies] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-harpy - - - prompts: [valerian, Valerian, 4b] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-valerian - - - prompts: [crisis, causality] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-crisis - - - prompts: [oracle] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-oracle - - - prompts: [abraxas] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-abraxas - - - prompts: [hi, hello, hey, greetings, salutations] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-hello-1 - - text: sophia-response-hello-2 - - - prompts: [bye, goodbye, done, farewell, later, seeya] - weight: 0.1 - hidden: true - responses: - - text: sophia-response-bye-1 - event: !type:NPCConversationByeEvent - - text: sophia-response-bye-2 - event: !type:NPCConversationByeEvent - - text: sophia-response-bye-3 - event: !type:NPCConversationByeEvent - - attention: - - text: sophia-response-attention-1 - - text: sophia-response-attention-2 - - text: sophia-response-attention-3 - - idle: - - text: sophia-idle-phrase-1 - - text: sophia-idle-phrase-2 - - text: sophia-idle-phrase-3 - - unknown: - - text: sophia-response-sorry-1 - - text: sophia-response-sorry-2 - - text: sophia-response-sorry-3 From 05e97b572cd136e989e281c9fa422aa8dbed4fc5 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 11 Jul 2024 21:40:07 -0400 Subject: [PATCH 3/4] Update GlimmerStructuresSystem.cs --- .../Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs b/Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs index 76ff5a823b..61bd8ecd41 100644 --- a/Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs +++ b/Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs @@ -25,10 +25,10 @@ public override void Initialize() SubscribeLocalEvent(OnAnomalyPulse); SubscribeLocalEvent(OnAnomalySupercritical); SubscribeLocalEvent(OnMobStateChanged); - SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnInit); } - private void OnInit(EntityUid uid, GlimmerSourceComponent component, ComponentStartup args) + private void OnInit(EntityUid uid, GlimmerSourceComponent component, ComponentInit args) { if (component.ResearchPointGeneration != null) { From 2b9d813c54d5a9094375cc378120a059d4ccecce Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 11 Jul 2024 21:53:47 -0400 Subject: [PATCH 4/4] Make the test shut up --- .../Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs | 4 ++-- .../Entities/Structures/Research/glimmer_prober.yml | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs b/Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs index 61bd8ecd41..76ff5a823b 100644 --- a/Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs +++ b/Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs @@ -25,10 +25,10 @@ public override void Initialize() SubscribeLocalEvent(OnAnomalyPulse); SubscribeLocalEvent(OnAnomalySupercritical); SubscribeLocalEvent(OnMobStateChanged); - SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnInit); } - private void OnInit(EntityUid uid, GlimmerSourceComponent component, ComponentInit args) + private void OnInit(EntityUid uid, GlimmerSourceComponent component, ComponentStartup args) { if (component.ResearchPointGeneration != null) { diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml index 47aaad219d..161c5b4ef5 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml @@ -15,6 +15,9 @@ - type: Construction graph: GlimmerDevices node: glimmerProber + - type: ResearchPointSource + pointspersecond: 20 + active: true - type: Sprite sprite: DeltaV/Structures/Machines/glimmer_machines.rsi # DeltaV reskin noRot: true