diff --git a/Content.Server/_DEN/Glimmer/Commands/GlimmerCommand.cs b/Content.Server/_DEN/Glimmer/Commands/GlimmerCommand.cs new file mode 100644 index 00000000000..3d9bc143649 --- /dev/null +++ b/Content.Server/_DEN/Glimmer/Commands/GlimmerCommand.cs @@ -0,0 +1,115 @@ +using Content.Server._DEN.Glimmer.EntitySystems; +using Content.Server.Administration; +using Content.Shared._DEN.Glimmer.Components; +using Content.Shared.Administration; +using Content.Shared.FixedPoint; +using Robust.Shared.Toolshed; + +namespace Content.Server._DEN.Glimmer.Commands; + +[ToolshedCommand, AdminCommand(AdminFlags.VarEdit)] +public sealed partial class GlimmerCommand : ToolshedCommand +{ + private GlimmerTrackerSystem? _glimmerTracker; + + [CommandImplementation("getTracker")] + public Entity? GetTracker([PipedArgument] EntityUid @target) + { + _glimmerTracker ??= GetSys(); + + if (_glimmerTracker.TryGetClosestGlimmerTracker(@target, out var tracker)) + return tracker; + + return null; + } + + [CommandImplementation("getTracker")] + public Entity? GetTracker(IInvocationContext ctx) + { + _glimmerTracker ??= GetSys(); + + if (ctx.Session?.AttachedEntity is not { } entity) + return null; + + if (_glimmerTracker.TryGetClosestGlimmerTracker(entity, out var tracker)) + return tracker; + + return null; + } + + [CommandImplementation("getValue")] + public FixedPoint2? GetValue([PipedArgument] Entity @tracker) + { + _glimmerTracker ??= GetSys(); + return _glimmerTracker.GetCurrentGlimmer(@tracker); + } + + [CommandImplementation("getValue")] + public FixedPoint2? GetValue([PipedArgument] Entity? @tracker) + { + if (@tracker == null) + return FixedPoint2.Zero; + + _glimmerTracker ??= GetSys(); + return _glimmerTracker.GetCurrentGlimmer(@tracker.Value); + } + + [CommandImplementation("getLevel")] + public int? GetLevel([PipedArgument] Entity @tracker) + { + _glimmerTracker ??= GetSys(); + return _glimmerTracker.GetCurrentGlimmerLevel(@tracker); + } + + [CommandImplementation("getLevel")] + public int? GetLevel([PipedArgument] Entity? @tracker) + { + if (@tracker == null) + return 0; + + _glimmerTracker ??= GetSys(); + return _glimmerTracker.GetCurrentGlimmerLevel(@tracker.Value); + } + + [CommandImplementation("setValue")] + public Entity SetValue([PipedArgument] Entity @tracker, FixedPoint2 value) + { + _glimmerTracker ??= GetSys(); + _glimmerTracker.SetGlimmer(@tracker, value); + + return @tracker; + } + + [CommandImplementation("setValue")] + public Entity? SetValue([PipedArgument] Entity? @tracker, FixedPoint2 value) + { + if (@tracker == null) + return @tracker; + + _glimmerTracker ??= GetSys(); + _glimmerTracker.SetGlimmer(@tracker.Value, value); + + return @tracker; + } + + [CommandImplementation("setLevel")] + public Entity SetLevel([PipedArgument] Entity @tracker, int level) + { + _glimmerTracker ??= GetSys(); + _glimmerTracker.SetGlimmerLevel(@tracker, level); + + return @tracker; + } + + [CommandImplementation("setLevel")] + public Entity? SetLevel([PipedArgument] Entity? @tracker, int level) + { + if (@tracker == null) + return @tracker; + + _glimmerTracker ??= GetSys(); + _glimmerTracker.SetGlimmerLevel(@tracker.Value, level); + + return @tracker; + } +} diff --git a/Content.Server/_DEN/Glimmer/EntitySystems/GlimmerSystem.cs b/Content.Server/_DEN/Glimmer/EntitySystems/GlimmerSystem.cs new file mode 100644 index 00000000000..84e20520968 --- /dev/null +++ b/Content.Server/_DEN/Glimmer/EntitySystems/GlimmerSystem.cs @@ -0,0 +1,194 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Server.Station.Systems; +using Content.Shared._DEN.Glimmer.Components; +using Content.Shared._DEN.Glimmer.EntitySystems; +using Content.Shared.FixedPoint; +using JetBrains.Annotations; +using Robust.Shared.Random; + +namespace Content.Server._DEN.Glimmer.EntitySystems; + +/// +/// Glimmer is a measure of "noospheric instability", a value that affects both the frequency +/// and severity of paranormal-themed events. It also affects the level of connection that +/// psionic users have with the noosphere - this is to say, powers scaling with glimmer. +/// +public sealed partial class GlimmerTrackerSystem : SharedGlimmerTrackerSystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly StationSystem _station = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGlimmerTrackerStartup); + } + + /// + /// + /// + /// + /// + private void OnGlimmerTrackerStartup(Entity ent, ref ComponentStartup args) + { + var comp = ent.Comp; + var min = comp.StartingGlimmerLevelRange.X; + var max = comp.StartingGlimmerLevelRange.Y; + var level = _random.Next(min, max); + + SetGlimmerLevel(ent, level); + } + + /// + /// Attempt to get the closest applicable glimmer tracker for a given entity. + /// + /// + /// The entity itself will be checked if it is a glimmer tracker, then the entity's station, + /// then all trackers will be iterated over to find the closest applicable tracker. + /// + /// The entity to retrieve the nearest valid glimmer tracker for. + /// A glimmer tracker that applies to this entity. + /// Whether or not we successfully retrieved a glimmer tracker. + [PublicAPI] + public bool TryGetClosestGlimmerTracker(EntityUid uid, + [NotNullWhen(true)] out Entity? trackerEnt) + { + // This entity is a glimmer tracker + if (TryComp(uid, out var tracker)) + { + trackerEnt = (uid, tracker); + return true; + } + + // This is a station map and the station has a glimmer tracker + var xform = Transform(uid); + if (_station.GetStationInMap(xform.MapID) is EntityUid station + && TryComp(station, out tracker)) + { + trackerEnt = (station, tracker); + return true; + } + + // We get the closest glimmer tracker in the same map + var query = EntityQueryEnumerator(); + (Entity Tracker, float Distance)? closestTracker = null; + while (query.MoveNext(out var trackerUid, out tracker)) + { + var ent = (trackerUid, tracker); + var trackerXform = Transform(trackerUid); + + if (xform.Coordinates.TryDistance(EntityManager, trackerXform.Coordinates, out var distance) + && distance > closestTracker?.Distance) + closestTracker = (ent, distance); + + // Global trackers do not need to be in the same map to be detected + else if (tracker.Global) + closestTracker ??= (ent, float.MaxValue); + } + + // May still be null, if there are no trackers at all + trackerEnt = closestTracker?.Tracker; + return trackerEnt != null; + } + + /// + /// + /// + /// + /// + [PublicAPI] + public void SetGlimmer(Entity tracker, FixedPoint2 glimmer) + { + var comp = tracker.Comp; + var maxGlimmer = comp.GlimmerLevels * comp.GlimmerPerLevel; + + if (glimmer < FixedPoint2.Zero || glimmer > maxGlimmer) + throw new ArgumentOutOfRangeException(nameof(glimmer)); + + comp.CurrentGlimmer = glimmer; + + // We're adding 1 to this, so that 0 glimmer becomes level 1. + var glimmerDivisor = (glimmer + 1) / comp.GlimmerPerLevel; + var glimmerLevel = Math.Floor(glimmerDivisor.Float()); + comp.CurrentGlimmerLevel = (int)glimmerLevel; + } + + /// + /// + /// + /// + /// + [PublicAPI] + public void SetGlimmerLevel(Entity tracker, int level) + { + var comp = tracker.Comp; + + if (level < 0 || level > comp.GlimmerLevels) + throw new ArgumentOutOfRangeException(nameof(level)); + + comp.CurrentGlimmerLevel = level; + + // The highest glimmer level always represents the highest possible glimmer value. + if (level == comp.GlimmerLevels) + { + comp.CurrentGlimmer = level * comp.GlimmerPerLevel; + return; + } + + var min = (level - 1) * comp.GlimmerPerLevel; // INCLUSIVE + var max = level * comp.GlimmerPerLevel; // EXCLUSIVE + var newGlimmer = _random.NextFloat(min.Float(), max.Float()); + comp.CurrentGlimmer = FixedPoint2.New(newGlimmer); + } + + /// + /// + /// + /// + /// + [PublicAPI] + public FixedPoint2 GetCurrentGlimmer(Entity tracker) + { + return tracker.Comp.CurrentGlimmer; + } + + /// + /// + /// + /// + /// + [PublicAPI] + public FixedPoint2 GetCurrentGlimmer(EntityUid uid) + { + if (!TryGetClosestGlimmerTracker(uid, out var tracker)) + return FixedPoint2.Zero; + + return GetCurrentGlimmer(tracker.Value); + } + + /// + /// + /// + /// + /// + [PublicAPI] + public int GetCurrentGlimmerLevel(Entity tracker) + { + return tracker.Comp.CurrentGlimmerLevel; + } + + /// + /// + /// + /// + /// + [PublicAPI] + public int GetCurrentGlimmerLevel(EntityUid uid) + { + if (!TryGetClosestGlimmerTracker(uid, out var tracker)) + return 0; + + return GetCurrentGlimmerLevel(tracker.Value); + } +} diff --git a/Content.Shared/_DEN/Glimmer/Components/GlimmerTrackerComponent.cs b/Content.Shared/_DEN/Glimmer/Components/GlimmerTrackerComponent.cs new file mode 100644 index 00000000000..6cace2b8eed --- /dev/null +++ b/Content.Shared/_DEN/Glimmer/Components/GlimmerTrackerComponent.cs @@ -0,0 +1,93 @@ +using Content.Shared._DEN.Glimmer.EntitySystems; +using Content.Shared.FixedPoint; + +namespace Content.Shared._DEN.Glimmer.Components; + +/// +/// A tracker component for measuring glimmer, a measure of current noospheric +/// instability and influence. Glimmer naturally shifts and fluctuates over time, but +/// player interference may cause it to shift one way or the other. +/// +[RegisterComponent] +[Access(typeof(SharedGlimmerTrackerSystem))] +public sealed partial class GlimmerTrackerComponent : Component +{ + /// + /// How many levels of glimmer should we have? + /// + [DataField] + public int GlimmerLevels = 10; + + /// + /// How many points of glimmer are in a glimmer level threshold? + /// + [DataField] + public FixedPoint2 GlimmerPerLevel = 100.0f; + + /// + /// What level of glimmer should we start with? + /// + [DataField] + public Vector2i StartingGlimmerLevelRange = new(0, 2); + + /// + /// An optional limit to what range of glimmer levels we can have. + /// + /// + /// This is meaningful because if GLs runs from 0 to 10, then capping GLs + /// between, say, 4 to 7, would ensure constant "moderate-to-high glimmer". + /// + [DataField] + public Vector2i? PossibleGlimmerLevelRange = null; + + /// + /// Whether or not glimmer can be changed via gameplay means - random + /// fluctuation, events, and player interference. + /// + /// + /// Admins are always capable of changing the current level or amount of glimmer. + /// + [DataField] + public bool AllowGlimmerChange = true; + + /// + /// Whether or not this tracker can be used by other maps. + /// + [DataField] + public bool Global = false; + + /// + /// Whether or not actions that happen off the map this entity is contained in + /// can affect this glimmer tracker. + /// + /// + /// For example: if this is disabled, then psionics usage at CentComm will not + /// affect the station glimmer tracker. + /// + [DataField] + public bool OffMapAffectsGlimmer = false; + + /// + /// How much glimmer do we currently have? + /// + /// + /// Note that setting glimmer directly is not super meaningful or long-term; + /// glimmer frequently fluctuates. + /// + [ViewVariables(VVAccess.ReadOnly)] + public FixedPoint2 CurrentGlimmer = 0; + + /// + /// What is our current glimmer level? + /// + [ViewVariables(VVAccess.ReadOnly)] + public int CurrentGlimmerLevel = 0; + + /// + /// Glimmer is represented as a range from 0 to (GlimmerLevels * GlimmerPerLevel). + /// Assuming there are 10 levels with 100 glimmer each: [0-100) glimmer is level 1, + /// [100-200) glimmer is level 2, et cetera. 1000 glimmer is level 10. + /// + [ViewVariables(VVAccess.ReadOnly)] + public FixedPoint2 MaxGlimmer => GlimmerPerLevel * GlimmerLevels; +} diff --git a/Content.Shared/_DEN/Glimmer/EntitySystems/SharedGlimmerSystem.cs b/Content.Shared/_DEN/Glimmer/EntitySystems/SharedGlimmerSystem.cs new file mode 100644 index 00000000000..e1a598610a4 --- /dev/null +++ b/Content.Shared/_DEN/Glimmer/EntitySystems/SharedGlimmerSystem.cs @@ -0,0 +1,4 @@ +namespace Content.Shared._DEN.Glimmer.EntitySystems; + +public abstract partial class SharedGlimmerTrackerSystem : EntitySystem +{ } diff --git a/Resources/Prototypes/Entities/Stations/nanotrasen.yml b/Resources/Prototypes/Entities/Stations/nanotrasen.yml index 0b1e143703c..9a260bd97e1 100644 --- a/Resources/Prototypes/Entities/Stations/nanotrasen.yml +++ b/Resources/Prototypes/Entities/Stations/nanotrasen.yml @@ -27,6 +27,7 @@ - BaseStationAllEventsEligible - BaseStationNanotrasen - BaseStationDeliveries + - BaseStationGlimmer # DEN: Glimmer tracking categories: [ HideSpawnMenu ] components: - type: Transform diff --git a/Resources/Prototypes/_DEN/Stations/base.yml b/Resources/Prototypes/_DEN/Stations/base.yml new file mode 100644 index 00000000000..7b04bf57ae4 --- /dev/null +++ b/Resources/Prototypes/_DEN/Stations/base.yml @@ -0,0 +1,10 @@ +- type: entity + abstract: true + id: BaseStationGlimmer + components: + - type: GlimmerTracker + glimmerLevels: 10 + glimmerPerLevel: 100 + startingGlimmerLevelRange: 0, 2 + global: true + offMapAffectsGlimmer: true