using System.Numerics; using Content.Shared.Radiation.Components; using Content.Shared.Singularity.Components; using Content.Shared.Singularity.Events; using Robust.Shared.Containers; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; using Robust.Shared.Serialization; namespace Content.Shared.Singularity.EntitySystems; /// /// The entity system primarily responsible for managing s. /// public abstract class SharedSingularitySystem : EntitySystem { #region Dependencies [Dependency] private readonly SharedAppearanceSystem _visualizer = default!; [Dependency] private readonly SharedContainerSystem _containers = default!; [Dependency] private readonly SharedEventHorizonSystem _horizons = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] protected readonly IViewVariablesManager Vvm = default!; #endregion Dependencies /// /// The minimum level a singularity can be set to. /// public const byte MinSingularityLevel = 0; /// /// The maximum level a singularity can be set to. /// public const byte MaxSingularityLevel = 6; /// /// The amount to scale a singularities distortion shader by when it's in a container. /// This is the inverse of an exponent, not a linear scaling factor. /// ie. n => intensity = intensity ** (1/n) /// public const float DistortionContainerScaling = 4f; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnSingularityStartup); SubscribeLocalEvent(UpdateDistortion); SubscribeLocalEvent(UpdateDistortion); SubscribeLocalEvent(UpdateDistortion); var vvHandle = Vvm.GetTypeHandler(); vvHandle.AddPath(nameof(SingularityComponent.Level), (_, comp) => comp.Level, SetLevel); vvHandle.AddPath(nameof(SingularityComponent.RadsPerLevel), (_, comp) => comp.RadsPerLevel, SetRadsPerLevel); } public override void Shutdown() { var vvHandle = Vvm.GetTypeHandler(); vvHandle.RemovePath(nameof(SingularityComponent.Level)); vvHandle.RemovePath(nameof(SingularityComponent.RadsPerLevel)); base.Shutdown(); } #region Getters/Setters /// /// Setter for /// Also sends out an event alerting that the singularities level has changed. /// /// The uid of the singularity to change the level of. /// The new level the singularity should have. /// The state of the singularity to change the level of. public void SetLevel(EntityUid uid, byte value, SingularityComponent? singularity = null) { if(!Resolve(uid, ref singularity)) return; value = MathHelper.Clamp(value, MinSingularityLevel, MaxSingularityLevel); var oldValue = singularity.Level; if (oldValue == value) return; singularity.Level = value; UpdateSingularityLevel(uid, oldValue, singularity); if (!Deleted(uid)) Dirty(uid, singularity); } /// /// Setter for /// Also updates the radiation output of the singularity according to the new values. /// /// The uid of the singularity to change the radioactivity of. /// The new radioactivity the singularity should have. /// The state of the singularity to change the radioactivity of. public void SetRadsPerLevel(EntityUid uid, float value, SingularityComponent? singularity = null) { if(!Resolve(uid, ref singularity)) return; var oldValue = singularity.RadsPerLevel; if (oldValue == value) return; singularity.RadsPerLevel = value; UpdateRadiation(uid, singularity); } /// /// Alerts the entity hosting the singularity that the level of the singularity has changed. /// Usually follows a SharedSingularitySystem.SetLevel call, but is also used on component startup to sync everything. /// /// The uid of the singularity which's level has changed. /// The old level of the singularity. May be equal to if the component is starting. /// The state of the singularity which's level has changed. public void UpdateSingularityLevel(EntityUid uid, byte oldValue, SingularityComponent? singularity = null) { if (!Resolve(uid, ref singularity)) return; if (TryComp(uid, out var eventHorizon)) { _horizons.SetRadius(uid, EventHorizonRadius(singularity), false, eventHorizon); _horizons.SetCanBreachContainment(uid, CanBreachContainment(singularity), false, eventHorizon); _horizons.UpdateEventHorizonFixture(uid, eventHorizon: eventHorizon); } if (TryComp(uid, out var body)) { if (singularity.Level <= 1 && oldValue > 1) // Apparently keeps singularities from getting stuck in the corners of containment fields. _physics.SetLinearVelocity(uid, Vector2.Zero, body: body); // No idea how stopping the singularities movement keeps it from getting stuck though. } if (TryComp(uid, out var appearance)) { _visualizer.SetData(uid, SingularityAppearanceKeys.Singularity, singularity.Level, appearance); } if (TryComp(uid, out var radiationSource)) { UpdateRadiation(uid, singularity, radiationSource); } RaiseLocalEvent(uid, new SingularityLevelChangedEvent(singularity.Level, oldValue, singularity)); if (singularity.Level <= 0) QueueDel(uid); } /// /// Alerts the entity hosting the singularity that the level of the singularity has changed without the level actually changing. /// Used to sync components when the singularity component is added to an entity. /// /// The uid of the singularity. /// The state of the singularity. public void UpdateSingularityLevel(EntityUid uid, SingularityComponent? singularity = null) { if (Resolve(uid, ref singularity)) UpdateSingularityLevel(uid, singularity.Level, singularity); } /// /// Updates the amount of radiation the singularity emits to reflect a change in the level or radioactivity per level of the singularity. /// /// The uid of the singularity to update the radiation of. /// The state of the singularity to update the radiation of. /// The state of the radioactivity of the singularity to update. private void UpdateRadiation(EntityUid uid, SingularityComponent? singularity = null, RadiationSourceComponent? rads = null) { if(!Resolve(uid, ref singularity, ref rads, logMissing: false)) return; rads.Intensity = singularity.Level * singularity.RadsPerLevel; } #endregion Getters/Setters #region Derivations /// /// The scaling factor for the size of a singularities gravity well. /// public const float BaseGravityWellRadius = 2f; /// /// The scaling factor for the base acceleration of a singularities gravity well. /// public const float BaseGravityWellAcceleration = 10f; /// /// The level at and above which a singularity should be capable of breaching containment. /// public const byte SingularityBreachThreshold = 5; /// /// Derives the proper gravity well radius for a singularity from its state. /// /// A singularity. /// The gravity well radius the singularity should have given its state. public float GravPulseRange(SingularityComponent singulo) => BaseGravityWellRadius * (singulo.Level + 1); /// /// Derives the proper base gravitational acceleration for a singularity from its state. /// /// A singularity. /// The base gravitational acceleration the singularity should have given its state. public (float, float) GravPulseAcceleration(SingularityComponent singulo) => (BaseGravityWellAcceleration * singulo.Level, 0f); /// /// Derives the proper event horizon radius for a singularity from its state. /// /// A singularity. /// The event horizon radius the singularity should have given its state. public float EventHorizonRadius(SingularityComponent singulo) => singulo.Level - 0.5f; /// /// Derives whether a singularity should be able to breach containment from its state. /// /// A singularity. /// Whether the singularity should be able to breach containment. public bool CanBreachContainment(SingularityComponent singulo) => singulo.Level >= SingularityBreachThreshold; /// /// Derives the proper distortion shader falloff for a singularity from its state. /// /// A singularity. /// The distortion shader falloff the singularity should have given its state. public float GetFalloff(float level) { return level switch { 0 => 9999f, 1 => MathF.Sqrt(6.4f), 2 => MathF.Sqrt(7.0f), 3 => MathF.Sqrt(8.0f), 4 => MathF.Sqrt(10.0f), 5 => MathF.Sqrt(12.0f), 6 => MathF.Sqrt(12.0f), _ => -1.0f }; } /// /// Derives the proper distortion shader intensity for a singularity from its state. /// /// A singularity. /// The distortion shader intensity the singularity should have given its state. public float GetIntensity(float level) { return level switch { 0 => 0.0f, 1 => 3645f, 2 => 103680f, 3 => 1113920f, 4 => 16200000f, 5 => 180000000f, 6 => 180000000f, _ => -1.0f }; } #endregion Derivations #region Serialization /// /// A state wrapper used to sync the singularity between the server and client. /// [Serializable, NetSerializable] protected sealed class SingularityComponentState : ComponentState { /// /// The level of the singularity to sync. /// public readonly byte Level; public SingularityComponentState(SingularityComponent singulo) { Level = singulo.Level; } } #endregion Serialization #region EventHandlers /// /// Syncs other components with the state of the singularity via event on startup. /// /// The entity that is becoming a singularity. /// The singularity component that is being added to the entity. /// The event arguments. protected virtual void OnSingularityStartup(EntityUid uid, SingularityComponent comp, ComponentStartup args) { UpdateSingularityLevel(uid, comp); } /// /// Updates the distortion shader associated with a singularity when the singuarity changes levels. /// /// The uid of the distortion shader. /// The state of the distortion shader. /// The event arguments. private void UpdateDistortion(EntityUid uid, SingularityDistortionComponent comp, SingularityLevelChangedEvent args) { var newFalloffPower = GetFalloff(args.NewValue); var newIntensity = GetIntensity(args.NewValue); if (_containers.IsEntityInContainer(uid)) { var absFalloffPower = MathF.Abs(newFalloffPower); var absIntensity = MathF.Abs(newIntensity); var factor = (1f / DistortionContainerScaling) - 1f; newFalloffPower = absFalloffPower > 1f ? newFalloffPower * MathF.Pow(absFalloffPower, factor) : newFalloffPower; newIntensity = absIntensity > 1f ? newIntensity * MathF.Pow(absIntensity, factor) : newIntensity; } comp.FalloffPower = newFalloffPower; comp.Intensity = newIntensity; Dirty(uid, comp); } /// /// Updates the distortion shader associated with a singularity when the singuarity is inserted into a container. /// /// The uid of the distortion shader. /// The state of the distortion shader. /// The event arguments. private void UpdateDistortion(EntityUid uid, SingularityDistortionComponent comp, EntGotInsertedIntoContainerMessage args) { var absFalloffPower = MathF.Abs(comp.FalloffPower); var absIntensity = MathF.Abs(comp.Intensity); var factor = (1f / DistortionContainerScaling) - 1f; comp.FalloffPower = absFalloffPower > 1 ? comp.FalloffPower * MathF.Pow(absFalloffPower, factor) : comp.FalloffPower; comp.Intensity = absIntensity > 1 ? comp.Intensity * MathF.Pow(absIntensity, factor) : comp.Intensity; } /// /// Updates the distortion shader associated with a singularity when the singuarity is removed from a container. /// /// The uid of the distortion shader. /// The state of the distortion shader. /// The event arguments. private void UpdateDistortion(EntityUid uid, SingularityDistortionComponent comp, EntGotRemovedFromContainerMessage args) { var absFalloffPower = MathF.Abs(comp.FalloffPower); var absIntensity = MathF.Abs(comp.Intensity); var factor = DistortionContainerScaling - 1; comp.FalloffPower = absFalloffPower > 1 ? comp.FalloffPower * MathF.Pow(absFalloffPower, factor) : comp.FalloffPower; comp.Intensity = absIntensity > 1 ? comp.Intensity * MathF.Pow(absIntensity, factor) : comp.Intensity; } #endregion EventHandlers }