Makes the singularity engine actually work stably. (#4068)

* Add GNU Octave script for tuning singularity engine. startsingularityengine is now properly tuned & sets up radiation collectors. FTLize RadiationCollectorComponent.

* Fix bugs with radiation collectors producing infinite power.

* Ensure singularities don't instantly annihilate other singularities (causing new singularities to instantly dissolve)

Technically found by a "bug" where a singularity generator would make multiple singularities, but this renders that bug harmless.

* Tune singularity shield emitters to hopefully randomly fail less, and add an Octave script for looking into that

* Fix singularity shader

* Map in an unfinished PA into Saltern

* Correct PA particles being counted twice by singularity calculations, add singulo food component

* Hopefully stop "level 1 singulo stuck in a corner" issues by freezing it when it goes to level 1 from any other level

* Apply suggestions on 'jazz' PR
This commit is contained in:
20kdc
2021-05-28 10:44:13 +01:00
committed by GitHub
parent 3ba0c01e4f
commit a3d9562532
20 changed files with 424 additions and 153 deletions

View File

@@ -20,21 +20,7 @@ namespace Content.Server.GameObjects.Components.PA
private ParticleAcceleratorPowerState _state;
void IStartCollide.CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold)
{
if (otherFixture.Body.Owner.TryGetComponent<ServerSingularityComponent>(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<SingularityGeneratorComponent>(out var singularityGeneratorComponent))
if (otherFixture.Body.Owner.TryGetComponent<SingularityGeneratorComponent>(out var singularityGeneratorComponent))
{
singularityGeneratorComponent.Power += _state switch
{
@@ -67,6 +53,22 @@ namespace Content.Server.GameObjects.Components.PA
}
projectileComponent.IgnoreEntity(firer);
if (!Owner.TryGetComponent<SinguloFoodComponent>(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",

View File

@@ -11,11 +11,12 @@ using Robust.Shared.IoC;
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 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
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 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
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 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
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)

View File

@@ -22,7 +22,7 @@ namespace Content.Server.GameObjects.Components.Singularity
get => _sharedEnergyPool;
set
{
_sharedEnergyPool = Math.Clamp(value, 0, 10);
_sharedEnergyPool = Math.Clamp(value, 0, 25);
if (_sharedEnergyPool == 0)
{
Dispose();

View File

@@ -168,8 +168,8 @@ namespace Content.Server.GameObjects.Components.Singularity
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);
}
}

View File

@@ -7,6 +7,7 @@ using Robust.Shared.Audio;
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.Physics.Dynamics;
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 @@ namespace Content.Server.GameObjects.Components.Singularity
}
private int _energy = 180;
[ViewVariables]
public int Level
{
get => _level;
@@ -58,6 +62,11 @@ namespace Content.Server.GameObjects.Components.Singularity
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 @@ namespace Content.Server.GameObjects.Components.Singularity
}
private int _level;
[ViewVariables]
public int EnergyDrain =>
Level switch
{
@@ -88,6 +98,12 @@ namespace Content.Server.GameObjects.Components.Singularity
_ => 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 @@ namespace Content.Server.GameObjects.Components.Singularity
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<IMapGridComponent>(out var mapGridComponent))
@@ -143,8 +164,16 @@ namespace Content.Server.GameObjects.Components.Singularity
if (otherEntity.IsInContainer())
return;
// Singularity priority management / etc.
if (otherEntity.TryGetComponent<ServerSingularityComponent>(out var otherSingulo))
otherSingulo.BeingDeletedByAnotherSingularity = true;
otherEntity.QueueDelete();
Energy++;
if (otherEntity.TryGetComponent<SinguloFoodComponent>(out var singuloFood))
Energy += singuloFood.Energy;
else
Energy++;
}
public override void OnRemove()

View File

@@ -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
{
/// <summary>
/// Overrides exactly how much energy this object gives to a singularity.
/// </summary>
[RegisterComponent]
public class SinguloFoodComponent : Component
{
public override string Name => "SinguloFood";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("energy")]
public int Energy { get; set; } = 1;
}
}