diff --git a/Content.Client/GameObjects/Components/Singularity/ClientSingularityComponent.cs b/Content.Client/GameObjects/Components/Singularity/ClientSingularityComponent.cs index 69c7ef384dd..4f7cbaee496 100644 --- a/Content.Client/GameObjects/Components/Singularity/ClientSingularityComponent.cs +++ b/Content.Client/GameObjects/Components/Singularity/ClientSingularityComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.GameObjects.Components.Singularity; using Robust.Shared.GameObjects; +using Robust.Shared.ViewVariables; namespace Content.Client.GameObjects.Components.Singularity { @@ -7,18 +8,60 @@ namespace Content.Client.GameObjects.Components.Singularity [ComponentReference(typeof(IClientSingularityInstance))] class ClientSingularityComponent : SharedSingularityComponent, IClientSingularityInstance { - public int Level + [ViewVariables] + public int Level { get; set; } + + //I am lazy + [ViewVariables] + public float Intensity { get { - return _level; + switch (Level) + { + case 0: + return 0.0f; + case 1: + return 2.7f; + case 2: + return 14.4f; + case 3: + return 47.2f; + case 4: + return 180.0f; + case 5: + return 600.0f; + case 6: + return 800.0f; + } + return -1.0f; } - set + } + [ViewVariables] + public float Falloff + { + get { - _level = value; + switch (Level) + { + case 0: + return 9999f; + case 1: + return 6.4f; + case 2: + return 7.0f; + case 3: + return 8.0f; + case 4: + return 10.0f; + case 5: + return 12.0f; + case 6: + return 12.0f; + } + return -1.0f; } } - private int _level; public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) { @@ -26,7 +69,7 @@ public override void HandleComponentState(ComponentState? curState, ComponentSta { return; } - _level = state.Level; + Level = state.Level; } } } diff --git a/Content.Client/GameObjects/Components/Singularity/ISingularity.cs b/Content.Client/GameObjects/Components/Singularity/ISingularity.cs index fe5d9b2be18..7c7290dfe71 100644 --- a/Content.Client/GameObjects/Components/Singularity/ISingularity.cs +++ b/Content.Client/GameObjects/Components/Singularity/ISingularity.cs @@ -6,6 +6,7 @@ namespace Content.Client.GameObjects.Components.Singularity { interface IClientSingularityInstance { - public int Level { get; set; } + public float Intensity { get; } + public float Falloff { get; } } } diff --git a/Content.Client/GameObjects/Components/Singularity/ToySingularityComponent.cs b/Content.Client/GameObjects/Components/Singularity/ToySingularityComponent.cs index 4711d36c305..e52dd917e26 100644 --- a/Content.Client/GameObjects/Components/Singularity/ToySingularityComponent.cs +++ b/Content.Client/GameObjects/Components/Singularity/ToySingularityComponent.cs @@ -1,4 +1,5 @@ using Robust.Shared.GameObjects; +using Robust.Shared.ViewVariables; namespace Content.Client.GameObjects.Components.Singularity { @@ -8,12 +9,9 @@ namespace Content.Client.GameObjects.Components.Singularity public class ToySingularityComponent : Component, IClientSingularityInstance { public override string Name => "ToySingularity"; - public int Level { - get { - return 1; - } - set { - } - } + [ViewVariables(VVAccess.ReadWrite)] + public float Falloff { get; set; } = 2.0f; + [ViewVariables(VVAccess.ReadWrite)] + public float Intensity { get; set; } = 0.25f; } } diff --git a/Content.Client/Graphics/Overlays/SingularityOverlay.cs b/Content.Client/Graphics/Overlays/SingularityOverlay.cs index 263100a85fb..bf1e0eb3b24 100644 --- a/Content.Client/Graphics/Overlays/SingularityOverlay.cs +++ b/Content.Client/Graphics/Overlays/SingularityOverlay.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Robust.Client.Graphics; using System.Linq; +using System; using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Content.Client.GameObjects.Components.Singularity; @@ -17,7 +18,6 @@ public class SingularityOverlay : Overlay [Dependency] private readonly IComponentManager _componentManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IClyde _displayManager = default!; public override OverlaySpace Space => OverlaySpace.WorldSpace; @@ -40,22 +40,28 @@ public override bool OverwriteTargetFrameBuffer() protected override void Draw(in OverlayDrawArgs args) { - SingularityQuery(); + SingularityQuery(args.Viewport.Eye); + var viewportWB = args.WorldBounds; + // This is a blatant cheat. + // The correct way of doing this would be if the singularity shader performed the matrix transforms. + // I don't need to explain why I'm not doing that. + var resolution = Math.Max(0.125f, Math.Min(args.Viewport.RenderScale.X, args.Viewport.RenderScale.Y)); foreach (SingularityShaderInstance instance in _singularities.Values) { - var tempCoords = _eyeManager.WorldToScreen(instance.CurrentMapCoords); - tempCoords.Y = _displayManager.ScreenSize.Y - tempCoords.Y; + // To be clear, this needs to use "inside-viewport" pixels. + // In other words, specifically NOT IViewportControl.WorldToScreen (which uses outer coordinates). + var tempCoords = args.Viewport.WorldToLocal(instance.CurrentMapCoords); + tempCoords.Y = args.Viewport.Size.Y - tempCoords.Y; _shader?.SetParameter("positionInput", tempCoords); if (ScreenTexture != null) _shader?.SetParameter("SCREEN_TEXTURE", ScreenTexture); - _shader?.SetParameter("intensity", LevelToIntensity(instance.Level)); - _shader?.SetParameter("falloff", LevelToFalloff(instance.Level)); + _shader?.SetParameter("intensity", instance.Intensity / resolution); + _shader?.SetParameter("falloff", instance.Falloff / resolution); var worldHandle = args.WorldHandle; worldHandle.UseShader(_shader); - var viewport = _eyeManager.GetWorldViewport(); - worldHandle.DrawRect(viewport, Color.White); + worldHandle.DrawRect(viewportWB, Color.White); } } @@ -64,19 +70,24 @@ protected override void Draw(in OverlayDrawArgs args) //Queries all singulos on the map and either adds or removes them from the list of rendered singulos based on whether they should be drawn (in range? on the same z-level/map? singulo entity still exists?) private float _maxDist = 15.0f; - private void SingularityQuery() + private void SingularityQuery(IEye? currentEye) { - var currentEyeLoc = _eyeManager.CurrentEye.Position; - var currentMap = _eyeManager.CurrentMap; //TODO: support multiple viewports once it is added + if (currentEye == null) + { + _singularities.Clear(); + return; + } + var currentEyeLoc = currentEye.Position; + var currentMap = currentEye.Position.MapId; var singuloComponents = _componentManager.EntityQuery(); foreach (var singuloInterface in singuloComponents) //Add all singulos that are not added yet but qualify { var singuloComponent = (Component)singuloInterface; var singuloEntity = singuloComponent.Owner; - if (!_singularities.Keys.Contains(singuloEntity.Uid) && singuloEntity.Transform.MapID == currentMap && singuloEntity.Transform.Coordinates.InRange(_entityManager, EntityCoordinates.FromMap(_entityManager, singuloEntity.Transform.ParentUid, currentEyeLoc), _maxDist)) + if (!_singularities.Keys.Contains(singuloEntity.Uid) && SinguloQualifies(singuloEntity, currentEyeLoc)) { - _singularities.Add(singuloEntity.Uid, new SingularityShaderInstance(singuloEntity.Transform.MapPosition.Position, singuloInterface.Level)); + _singularities.Add(singuloEntity.Uid, new SingularityShaderInstance(singuloEntity.Transform.MapPosition.Position, singuloInterface.Intensity, singuloInterface.Falloff)); } } @@ -85,7 +96,7 @@ private void SingularityQuery() { if (_entityManager.TryGetEntity(activeSinguloUid, out IEntity? singuloEntity)) { - if (singuloEntity.Transform.MapID != currentMap || !singuloEntity.Transform.Coordinates.InRange(_entityManager, EntityCoordinates.FromMap(_entityManager, singuloEntity.Transform.ParentUid, currentEyeLoc), _maxDist)) + if (!SinguloQualifies(singuloEntity, currentEyeLoc)) { _singularities.Remove(activeSinguloUid); } @@ -99,7 +110,8 @@ private void SingularityQuery() { var shaderInstance = _singularities[activeSinguloUid]; shaderInstance.CurrentMapCoords = singuloEntity.Transform.MapPosition.Position; - shaderInstance.Level = singuloInterface.Level; + shaderInstance.Intensity = singuloInterface.Intensity; + shaderInstance.Falloff = singuloInterface.Falloff; } } @@ -112,62 +124,21 @@ private void SingularityQuery() } - - - - //I am lazy - private float LevelToIntensity(int level) - { - switch (level) - { - case 0: - return 0.0f; - case 1: - return 2.7f; - case 2: - return 14.4f; - case 3: - return 47.2f; - case 4: - return 180.0f; - case 5: - return 600.0f; - case 6: - return 800.0f; - - } - return -1.0f; - } - private float LevelToFalloff(int level) + private bool SinguloQualifies(IEntity singuloEntity, MapCoordinates currentEyeLoc) { - switch (level) - { - case 0: - return 9999f; - case 1: - return 6.4f; - case 2: - return 7.0f; - case 3: - return 8.0f; - case 4: - return 10.0f; - case 5: - return 12.0f; - case 6: - return 12.0f; - } - return -1.0f; + return singuloEntity.Transform.MapID == currentEyeLoc.MapId && singuloEntity.Transform.Coordinates.InRange(_entityManager, EntityCoordinates.FromMap(_entityManager, singuloEntity.Transform.ParentUid, currentEyeLoc), _maxDist); } private sealed class SingularityShaderInstance { public Vector2 CurrentMapCoords; - public int Level; - public SingularityShaderInstance(Vector2 mapCoords, int level) + public float Intensity; + public float Falloff; + public SingularityShaderInstance(Vector2 mapCoords, float intensity, float falloff) { CurrentMapCoords = mapCoords; - Level = level; + Intensity = intensity; + Falloff = falloff; } } } diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 4b4fe6adc7c..a8063e47fb2 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -229,6 +229,7 @@ public static class IgnoredComponents "GlassBeaker", "SliceableFood", "DamageOtherOnHit", + "SinguloFood", "DamageOnLand", "SmokeSolutionAreaEffect", "FoamSolutionAreaEffect", diff --git a/Content.Server/Commands/StartSingularityEngineCommand.cs b/Content.Server/Commands/StartSingularityEngineCommand.cs index 2f56bb82519..dc826ff206f 100644 --- a/Content.Server/Commands/StartSingularityEngineCommand.cs +++ b/Content.Server/Commands/StartSingularityEngineCommand.cs @@ -2,6 +2,7 @@ using Content.Server.Administration; using Content.Server.GameObjects.Components.Singularity; using Content.Server.GameObjects.Components.PA; +using Content.Server.GameObjects.Components.Power.PowerNetComponents; using Content.Shared.Administration; using Content.Shared.GameObjects.Components; using Robust.Shared.Console; @@ -30,11 +31,15 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) { ent.GetComponent().SwitchOn(); } + foreach (var ent in entityManager.GetEntities(new TypeEntityQuery(typeof(RadiationCollectorComponent)))) + { + ent.GetComponent().Collecting = true; + } foreach (var ent in entityManager.GetEntities(new TypeEntityQuery(typeof(ParticleAcceleratorControlBoxComponent)))) { var pacb = ent.GetComponent(); pacb.RescanParts(); - pacb.SetStrength(ParticleAcceleratorPowerState.Level1); + pacb.SetStrength(ParticleAcceleratorPowerState.Level0); pacb.SwitchOn(); } shell.WriteLine("Done!"); diff --git a/Content.Server/GameObjects/Components/PA/ParticleProjectileComponent.cs b/Content.Server/GameObjects/Components/PA/ParticleProjectileComponent.cs index f67af768dce..546a04022d7 100644 --- a/Content.Server/GameObjects/Components/PA/ParticleProjectileComponent.cs +++ b/Content.Server/GameObjects/Components/PA/ParticleProjectileComponent.cs @@ -20,21 +20,7 @@ public class ParticleProjectileComponent : Component, IStartCollide private ParticleAcceleratorPowerState _state; void IStartCollide.CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold) { - if (otherFixture.Body.Owner.TryGetComponent(out var singularityComponent)) - { - var multiplier = _state switch - { - ParticleAcceleratorPowerState.Standby => 0, - ParticleAcceleratorPowerState.Level0 => 1, - ParticleAcceleratorPowerState.Level1 => 3, - ParticleAcceleratorPowerState.Level2 => 6, - ParticleAcceleratorPowerState.Level3 => 10, - _ => 0 - }; - singularityComponent.Energy += 10 * multiplier; - Owner.QueueDelete(); - } - else if (otherFixture.Body.Owner.TryGetComponent(out var singularityGeneratorComponent)) + if (otherFixture.Body.Owner.TryGetComponent(out var singularityGeneratorComponent)) { singularityGeneratorComponent.Power += _state switch { @@ -67,6 +53,22 @@ public void Fire(ParticleAcceleratorPowerState state, Angle angle, IEntity firer } projectileComponent.IgnoreEntity(firer); + if (!Owner.TryGetComponent(out var singuloFoodComponent)) + { + Logger.Error("ParticleProjectile tried firing, but it was spawned without a SinguloFoodComponent"); + return; + } + var multiplier = _state switch + { + ParticleAcceleratorPowerState.Standby => 0, + ParticleAcceleratorPowerState.Level0 => 1, + ParticleAcceleratorPowerState.Level1 => 3, + ParticleAcceleratorPowerState.Level2 => 6, + ParticleAcceleratorPowerState.Level3 => 10, + _ => 0 + }; + singuloFoodComponent.Energy = 10 * multiplier; + var suffix = state switch { ParticleAcceleratorPowerState.Level0 => "0", diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/RadiationCollectorComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/RadiationCollectorComponent.cs index 72de6f934d1..5b521110a38 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/RadiationCollectorComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/RadiationCollectorComponent.cs @@ -11,11 +11,12 @@ using Robust.Shared.Localization; using Robust.Shared.Physics; using Robust.Shared.Timing; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Power.PowerNetComponents { [RegisterComponent] - public class RadiationCollectorComponent : PowerSupplierComponent, IInteractHand, IRadiationAct + public class RadiationCollectorComponent : Component, IInteractHand, IRadiationAct { [Dependency] private readonly IGameTiming _gameTiming = default!; @@ -23,14 +24,20 @@ public class RadiationCollectorComponent : PowerSupplierComponent, IInteractHand private bool _enabled; private TimeSpan _coolDownEnd; - [ComponentDependency] private readonly PhysicsComponent? _collidableComponent = default!; - - public void OnAnchoredChanged() - { - if(_collidableComponent != null && _collidableComponent.BodyType == BodyType.Static) - Owner.SnapToGrid(); + [ViewVariables(VVAccess.ReadWrite)] + public bool Collecting { + get => _enabled; + set + { + if (_enabled == value) return; + _enabled = value; + SetAppearance(_enabled ? RadiationCollectorVisualState.Activating : RadiationCollectorVisualState.Deactivating); + } } + [ComponentDependency] private readonly BatteryComponent? _batteryComponent = default!; + [ComponentDependency] private readonly BatteryDischargerComponent? _batteryDischargerComponent = default!; + bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) { var curTime = _gameTiming.CurTime; @@ -40,13 +47,13 @@ bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) if (!_enabled) { - Owner.PopupMessage(eventArgs.User, Loc.GetString("The collector turns on.")); - EnableCollection(); + Owner.PopupMessage(eventArgs.User, Loc.GetString("radiation-collector-component-use-on")); + Collecting = true; } else { - Owner.PopupMessage(eventArgs.User, Loc.GetString("The collector turns off.")); - DisableCollection(); + Owner.PopupMessage(eventArgs.User, Loc.GetString("radiation-collector-component-use-off")); + Collecting = false; } _coolDownEnd = curTime + TimeSpan.FromSeconds(0.81f); @@ -54,23 +61,25 @@ bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) return true; } - void EnableCollection() - { - _enabled = true; - SetAppearance(RadiationCollectorVisualState.Activating); - } - - void DisableCollection() - { - _enabled = false; - SetAppearance(RadiationCollectorVisualState.Deactivating); - } - void IRadiationAct.RadiationAct(float frameTime, SharedRadiationPulseComponent radiation) { if (!_enabled) return; - SupplyRate = (int) (frameTime * radiation.RadsPerSecond * 3000f); + // No idea if this is even vaguely accurate to the previous logic. + // The maths is copied from that logic even though it works differently. + // But the previous logic would also make the radiation collectors never ever stop providing energy. + // And since frameTime was used there, I'm assuming that this is what the intent was. + // This still won't stop things being potentially hilarously unbalanced though. + if (_batteryComponent != null) + { + _batteryComponent!.CurrentCharge += frameTime * radiation.RadsPerSecond * 3000f; + if (_batteryDischargerComponent != null) + { + // The battery discharger is controlled like this to ensure it won't drain the entire battery in a single tick. + // If that occurs then the battery discharger ends up shutting down. + _batteryDischargerComponent!.ActiveSupplyRate = (int) Math.Max(1, _batteryComponent!.CurrentCharge); + } + } } protected void SetAppearance(RadiationCollectorVisualState state) diff --git a/Content.Server/GameObjects/Components/Singularity/ContainmentFieldConnection.cs b/Content.Server/GameObjects/Components/Singularity/ContainmentFieldConnection.cs index fbb2bc3690d..e061e1fa0d0 100644 --- a/Content.Server/GameObjects/Components/Singularity/ContainmentFieldConnection.cs +++ b/Content.Server/GameObjects/Components/Singularity/ContainmentFieldConnection.cs @@ -22,7 +22,7 @@ public int SharedEnergyPool get => _sharedEnergyPool; set { - _sharedEnergyPool = Math.Clamp(value, 0, 10); + _sharedEnergyPool = Math.Clamp(value, 0, 25); if (_sharedEnergyPool == 0) { Dispose(); diff --git a/Content.Server/GameObjects/Components/Singularity/ContainmentFieldGeneratorComponent.cs b/Content.Server/GameObjects/Components/Singularity/ContainmentFieldGeneratorComponent.cs index 17381b7a667..13b43a18337 100644 --- a/Content.Server/GameObjects/Components/Singularity/ContainmentFieldGeneratorComponent.cs +++ b/Content.Server/GameObjects/Components/Singularity/ContainmentFieldGeneratorComponent.cs @@ -168,8 +168,8 @@ public void RemoveConnection(ContainmentFieldConnection? connection) void IStartCollide.CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold) { - if(otherFixture.Body.Owner.HasTag("EmitterBolt")) { - ReceivePower(4); + if (otherFixture.Body.Owner.HasTag("EmitterBolt")) { + ReceivePower(6); } } diff --git a/Content.Server/GameObjects/Components/Singularity/ServerSingularityComponent.cs b/Content.Server/GameObjects/Components/Singularity/ServerSingularityComponent.cs index 11a3af885a0..87e44216843 100644 --- a/Content.Server/GameObjects/Components/Singularity/ServerSingularityComponent.cs +++ b/Content.Server/GameObjects/Components/Singularity/ServerSingularityComponent.cs @@ -7,6 +7,7 @@ using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Physics.Collision; using Robust.Shared.Physics.Collision.Shapes; @@ -14,12 +15,14 @@ using Robust.Shared.Player; using Robust.Shared.Players; using Robust.Shared.Timing; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Singularity { [RegisterComponent] public class ServerSingularityComponent : SharedSingularityComponent, IStartCollide { + [ViewVariables(VVAccess.ReadWrite)] public int Energy { get => _energy; @@ -48,6 +51,7 @@ public int Energy } private int _energy = 180; + [ViewVariables] public int Level { get => _level; @@ -58,6 +62,11 @@ public int Level if (value > 6) value = 6; _level = value; + if ((_level > 1) && (value <= 1)) + { + // Prevents it getting stuck (see SingularityController.MoveSingulo) + if (_collidableComponent != null) _collidableComponent.LinearVelocity = Vector2.Zero; + } if(_radiationPulseComponent != null) _radiationPulseComponent.RadsPerSecond = 10 * value; @@ -76,6 +85,7 @@ public int Level } private int _level; + [ViewVariables] public int EnergyDrain => Level switch { @@ -88,6 +98,12 @@ public int Level _ => 0 }; + // This is an interesting little workaround. + // See, two singularities queuing deletion of each other at the same time will annihilate. + // This is undesirable behaviour, so this flag allows the imperatively first one processed to take priority. + [ViewVariables(VVAccess.ReadWrite)] + public bool BeingDeletedByAnotherSingularity { get; set; } = false; + private PhysicsComponent _collidableComponent = default!; private RadiationPulseComponent _radiationPulseComponent = default!; private SpriteComponent _spriteComponent = default!; @@ -123,6 +139,11 @@ public void Update(int seconds) void IStartCollide.CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold) { + // If we're being deleted by another singularity, this call is probably for that singularity. + // Even if not, just don't bother. + if (BeingDeletedByAnotherSingularity) + return; + var otherEntity = otherFixture.Body.Owner; if (otherEntity.TryGetComponent(out var mapGridComponent)) @@ -143,8 +164,16 @@ void IStartCollide.CollideWith(Fixture ourFixture, Fixture otherFixture, in Mani if (otherEntity.IsInContainer()) return; + // Singularity priority management / etc. + if (otherEntity.TryGetComponent(out var otherSingulo)) + otherSingulo.BeingDeletedByAnotherSingularity = true; + otherEntity.QueueDelete(); - Energy++; + + if (otherEntity.TryGetComponent(out var singuloFood)) + Energy += singuloFood.Energy; + else + Energy++; } public override void OnRemove() diff --git a/Content.Server/GameObjects/Components/Singularity/SinguloFoodComponent.cs b/Content.Server/GameObjects/Components/Singularity/SinguloFoodComponent.cs new file mode 100644 index 00000000000..e6f4ba2e56b --- /dev/null +++ b/Content.Server/GameObjects/Components/Singularity/SinguloFoodComponent.cs @@ -0,0 +1,23 @@ +using Content.Shared.GameObjects.Components; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Log; +using Robust.Shared.Maths; +using Robust.Shared.ViewVariables; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.GameObjects.Components.Singularity +{ + /// + /// Overrides exactly how much energy this object gives to a singularity. + /// + [RegisterComponent] + public class SinguloFoodComponent : Component + { + public override string Name => "SinguloFood"; + [ViewVariables(VVAccess.ReadWrite)] + [DataField("energy")] + public int Energy { get; set; } = 1; + } +} diff --git a/Content.Server/GameObjects/EntitySystems/Power/RadiationCollectorSystem.cs b/Content.Server/GameObjects/EntitySystems/Power/RadiationCollectorSystem.cs deleted file mode 100644 index f98b65a90fb..00000000000 --- a/Content.Server/GameObjects/EntitySystems/Power/RadiationCollectorSystem.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Content.Server.GameObjects.Components.Power.PowerNetComponents; -using Robust.Shared.GameObjects; - -namespace Content.Server.GameObjects.EntitySystems -{ - public sealed class RadiationCollectorSystem : EntitySystem - { - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(BodyTypeChanged); - } - - public override void Shutdown() - { - base.Shutdown(); - - UnsubscribeLocalEvent(); - } - - private static void BodyTypeChanged( - EntityUid uid, - RadiationCollectorComponent component, - PhysicsBodyTypeChangedEvent args) - { - component.OnAnchoredChanged(); - } - } -} diff --git a/Content.Server/Physics/Controllers/SingularityController.cs b/Content.Server/Physics/Controllers/SingularityController.cs index b7336f3d534..d4256f885f2 100644 --- a/Content.Server/Physics/Controllers/SingularityController.cs +++ b/Content.Server/Physics/Controllers/SingularityController.cs @@ -59,6 +59,7 @@ public override void UpdateBeforeSolve(bool prediction, float frameTime) private void MoveSingulo(ServerSingularityComponent singularity, PhysicsComponent physics) { + // To prevent getting stuck, ServerSingularityComponent will zero the velocity of a singularity when it goes to a level <= 1 (see here). if (singularity.Level <= 1) return; // TODO: Could try gradual changes instead but for now just try to replicate diff --git a/Resources/Locale/en-US/components/radiation-collector-component.ftl b/Resources/Locale/en-US/components/radiation-collector-component.ftl new file mode 100644 index 00000000000..6cfb9ec4401 --- /dev/null +++ b/Resources/Locale/en-US/components/radiation-collector-component.ftl @@ -0,0 +1,3 @@ +radiation-collector-component-use-on = The collector turns on. +radiation-collector-component-use-off = The collector turns off. + diff --git a/Resources/Maps/saltern.yml b/Resources/Maps/saltern.yml index 5a5466b59a8..0d719922c65 100644 --- a/Resources/Maps/saltern.yml +++ b/Resources/Maps/saltern.yml @@ -47820,4 +47820,46 @@ entities: EntityStorageComponent: !type:Container ents: [] type: ContainerContainer +- uid: 4897 + type: ParticleAcceleratorEndCapUnfinished + components: + - pos: 49.5,-9.5 + parent: 853 + type: Transform +- uid: 4898 + type: ParticleAcceleratorFuelChamberUnfinished + components: + - pos: 49.5,-10.5 + parent: 853 + type: Transform +- uid: 4899 + type: ParticleAcceleratorPowerBoxUnfinished + components: + - pos: 49.5,-11.5 + parent: 853 + type: Transform +- uid: 4900 + type: ParticleAcceleratorControlBoxUnfinished + components: + - pos: 48.5,-10.5 + parent: 853 + type: Transform +- uid: 4901 + type: ParticleAcceleratorEmitterCenterUnfinished + components: + - pos: 49.5,-12.5 + parent: 853 + type: Transform +- uid: 4902 + type: ParticleAcceleratorEmitterLeftUnfinished + components: + - pos: 48.5,-12.5 + parent: 853 + type: Transform +- uid: 4903 + type: ParticleAcceleratorEmitterRightUnfinished + components: + - pos: 50.5,-12.5 + parent: 853 + type: Transform ... diff --git a/Resources/Prototypes/Entities/Constructible/Power/Engines/PA/particles.yml b/Resources/Prototypes/Entities/Constructible/Power/Engines/PA/particles.yml index be8f4d8a310..7e653b1d84c 100644 --- a/Resources/Prototypes/Entities/Constructible/Power/Engines/PA/particles.yml +++ b/Resources/Prototypes/Entities/Constructible/Power/Engines/PA/particles.yml @@ -26,3 +26,6 @@ - MobMask - Opaque - type: ParticleProjectile + - type: SinguloFood + # Energy is setup by the PA particle fire function. + diff --git a/Resources/Prototypes/Entities/Constructible/Power/Engines/Singularity/collector.yml b/Resources/Prototypes/Entities/Constructible/Power/Engines/Singularity/collector.yml index 44ada0eed78..2fae75c43c5 100644 --- a/Resources/Prototypes/Entities/Constructible/Power/Engines/Singularity/collector.yml +++ b/Resources/Prototypes/Entities/Constructible/Power/Engines/Singularity/collector.yml @@ -39,6 +39,16 @@ !type:AdjacentNode nodeGroupID: HVPower - type: RadiationCollector + # Note that this doesn't matter too much (see next comment) + # However it does act as a cap on power receivable via the collector. + - type: Battery + maxCharge: 100000 + startingCharge: 0 + - type: BatteryDischarger + # This is JUST a default. It has to be dynamically adjusted to ensure that the battery doesn't discharge "too fast" & run out immediately, while still scaling by input power. + activeSupplyRate: 100000 + - type: PowerSupplier + supplyRate: 0 - type: Anchorable - type: Rotatable - type: Pullable diff --git a/Tools/singulo.m b/Tools/singulo.m new file mode 100644 index 00000000000..51d3e274599 --- /dev/null +++ b/Tools/singulo.m @@ -0,0 +1,95 @@ +# This is a script to be loaded into GNU Octave. + +# - Notes - +# + Be sure to check all parameters are up to date with game before use. +# + The way things are tuned, only PA level 1 is stable on Saltern. +# A singularity timestep is one second. + +# - Parameters - +# It's expected that you dynamically modify these if relevant to your scenario. +global pa_particle_energy_for_level_table pa_level pa_time_between_shots +pa_particle_energy_for_level_table = [10, 30, 60, 100] +# Note that level 0 is 1 here. +pa_level = 1 +pa_time_between_shots = 6 + +# Horizontal size (interior tiles) of mapped singulo cage +global cage_area cage_pa1 cage_pa2 cage_pa3 +# __123__ +# +---+---+ +cage_area = 7 +cage_pa1 = 2.5 +cage_pa2 = 3.5 +cage_pa3 = 4.5 + +global energy_drain_for_level_table +energy_drain_for_level_table = [1, 2, 5, 10, 15, 20] +function retval = level_for_energy (energy) + retval = 1 + if energy >= 1500 retval = 6; return; endif + if energy >= 1000 retval = 5; return; endif + if energy >= 600 retval = 4; return; endif + if energy >= 300 retval = 3; return; endif + if energy >= 200 retval = 2; return; endif +endfunction +function retval = radius_for_level (level) + retval = level - 0.5 +endfunction + +# - Simulator - + +global singulo_shot_timer +singulo_shot_timer = 0 + +function retval = singulo_step (energy) + global energy_drain_for_level_table + global pa_particle_energy_for_level_table pa_level pa_time_between_shots + global cage_area cage_pa1 cage_pa2 cage_pa3 + global singulo_shot_timer + level = level_for_energy(energy) + energy_drain = energy_drain_for_level_table(level) + energy -= energy_drain + singulo_shot_timer += 1 + if singulo_shot_timer == pa_time_between_shots + energy_gain_per_hit = pa_particle_energy_for_level_table(pa_level) + # This is the bit that's complicated: the area and probability calculation. + # Rather than try to work it out, let's do things by simply trying it. + # This is the area of the singulo. + singulo_area = radius_for_level(level) * 2 + # This is therefore the area in which it can move. + effective_area = max(0, cage_area - singulo_area) + # Assume it's at some random position within the area it can move. + # (This is the weak point of the maths. It's not as simple as this really.) + singulo_lpos = (rand() * effective_area) + singulo_rpos = singulo_lpos + singulo_area + # Check each of 3 points. + n = 0.5 + if singulo_lpos < (cage_pa1 + n) && singulo_rpos > (cage_pa1 - n) + energy += energy_gain_per_hit + endif + if singulo_lpos < (cage_pa2 + n) && singulo_rpos > (cage_pa2 - n) + energy += energy_gain_per_hit + endif + if singulo_lpos < (cage_pa3 + n) && singulo_rpos > (cage_pa3 - n) + energy += energy_gain_per_hit + endif + singulo_shot_timer = 0 + endif + retval = energy +endfunction + +# - Scenario - + +global scenario_energy +scenario_energy = 100 + +function retval = scenario (x) + global scenario_energy + sce = scenario_energy + scenario_energy = singulo_step(sce) + retval = scenario_energy +endfunction + +# x is in seconds. +x = 0:1:960 +plot(x, arrayfun(@scenario, x)) diff --git a/Tools/singulo_emitter.m b/Tools/singulo_emitter.m new file mode 100644 index 00000000000..385cb661ab8 --- /dev/null +++ b/Tools/singulo_emitter.m @@ -0,0 +1,65 @@ +# This is a script to be loaded into GNU Octave. + +# - Notes - +# + Be sure to check all parameters are up to date with game before use. +# + This plots *worst-case* performance, the assumption is that it shouldn't ever randomly fail. +# + The assumption is that there is one emitter per shield point. +# + Keep in mind that to prevent the generator being destroyed, either shield must be above a limit. +# This limit is (level*2)+1. +# The timestep used for simulation is one second. + +global emitter_state emitter_timer shield_energy + +emitter_state = 0 +emitter_timer = 0 +shield_energy = 0 + +function shield_clamp () + global shield_energy + # ContainmentFieldConnection.SharedEnergyPool + shield_energy = min(max(shield_energy, 0), 25) +endfunction +function shield_tick () + global shield_energy + shield_energy -= 1 + shield_clamp() +endfunction +function shield_hit () + global shield_energy + emitter_count = 2 # one per connection side + receive_power = 6 # ContainmentFieldGeneratorComponent.IStartCollide.CollideWith + power_per_connection = receive_power / 2 # ContainmentFieldGeneratorComponent.ReceivePower + shield_energy += power_per_connection * emitter_count + shield_clamp() +endfunction + +function retval = scenario (x) + global emitter_state emitter_timer shield_energy + # Tick (degrade) shield + shield_tick() + # Timer... + if emitter_timer > 0 + emitter_timer -= 1 + else + # Note the logic here is written to match how EmitterComponent does it. + # Fire first... + shield_hit() + # Then check if < fireBurstSize + if emitter_state < 3 + # Then increment & reset + emitter_state += 1 + # to fireInterval + emitter_timer = 2 + else + # Reset state + emitter_state = 0 + # Worst case, fireBurstDelayMax + emitter_timer = 10 + endif + endif + retval = shield_energy +endfunction + +# x is in seconds. +x = 0:1:960 +plot(x, arrayfun(@scenario, x))