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:
@@ -1,5 +1,6 @@
|
|||||||
using Content.Shared.GameObjects.Components.Singularity;
|
using Content.Shared.GameObjects.Components.Singularity;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Client.GameObjects.Components.Singularity
|
namespace Content.Client.GameObjects.Components.Singularity
|
||||||
{
|
{
|
||||||
@@ -7,18 +8,60 @@ namespace Content.Client.GameObjects.Components.Singularity
|
|||||||
[ComponentReference(typeof(IClientSingularityInstance))]
|
[ComponentReference(typeof(IClientSingularityInstance))]
|
||||||
class ClientSingularityComponent : SharedSingularityComponent, IClientSingularityInstance
|
class ClientSingularityComponent : SharedSingularityComponent, IClientSingularityInstance
|
||||||
{
|
{
|
||||||
public int Level
|
[ViewVariables]
|
||||||
|
public int Level { get; set; }
|
||||||
|
|
||||||
|
//I am lazy
|
||||||
|
[ViewVariables]
|
||||||
|
public float Intensity
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _level;
|
switch (Level)
|
||||||
}
|
{
|
||||||
set
|
case 0:
|
||||||
{
|
return 0.0f;
|
||||||
_level = value;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[ViewVariables]
|
||||||
|
public float Falloff
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
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)
|
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||||
{
|
{
|
||||||
@@ -26,7 +69,7 @@ namespace Content.Client.GameObjects.Components.Singularity
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_level = state.Level;
|
Level = state.Level;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace Content.Client.GameObjects.Components.Singularity
|
|||||||
{
|
{
|
||||||
interface IClientSingularityInstance
|
interface IClientSingularityInstance
|
||||||
{
|
{
|
||||||
public int Level { get; set; }
|
public float Intensity { get; }
|
||||||
|
public float Falloff { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Client.GameObjects.Components.Singularity
|
namespace Content.Client.GameObjects.Components.Singularity
|
||||||
{
|
{
|
||||||
@@ -8,12 +9,9 @@ namespace Content.Client.GameObjects.Components.Singularity
|
|||||||
public class ToySingularityComponent : Component, IClientSingularityInstance
|
public class ToySingularityComponent : Component, IClientSingularityInstance
|
||||||
{
|
{
|
||||||
public override string Name => "ToySingularity";
|
public override string Name => "ToySingularity";
|
||||||
public int Level {
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
get {
|
public float Falloff { get; set; } = 2.0f;
|
||||||
return 1;
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
}
|
public float Intensity { get; set; } = 0.25f;
|
||||||
set {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Robust.Shared.Prototypes;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System;
|
||||||
using Robust.Shared.Enums;
|
using Robust.Shared.Enums;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Content.Client.GameObjects.Components.Singularity;
|
using Content.Client.GameObjects.Components.Singularity;
|
||||||
@@ -17,7 +18,6 @@ namespace Content.Client.Graphics.Overlays
|
|||||||
[Dependency] private readonly IComponentManager _componentManager = default!;
|
[Dependency] private readonly IComponentManager _componentManager = default!;
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
|
||||||
[Dependency] private readonly IClyde _displayManager = default!;
|
[Dependency] private readonly IClyde _displayManager = default!;
|
||||||
|
|
||||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||||
@@ -40,22 +40,28 @@ namespace Content.Client.Graphics.Overlays
|
|||||||
|
|
||||||
protected override void Draw(in OverlayDrawArgs args)
|
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)
|
foreach (SingularityShaderInstance instance in _singularities.Values)
|
||||||
{
|
{
|
||||||
var tempCoords = _eyeManager.WorldToScreen(instance.CurrentMapCoords);
|
// To be clear, this needs to use "inside-viewport" pixels.
|
||||||
tempCoords.Y = _displayManager.ScreenSize.Y - tempCoords.Y;
|
// 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);
|
_shader?.SetParameter("positionInput", tempCoords);
|
||||||
if (ScreenTexture != null)
|
if (ScreenTexture != null)
|
||||||
_shader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
_shader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||||
_shader?.SetParameter("intensity", LevelToIntensity(instance.Level));
|
_shader?.SetParameter("intensity", instance.Intensity / resolution);
|
||||||
_shader?.SetParameter("falloff", LevelToFalloff(instance.Level));
|
_shader?.SetParameter("falloff", instance.Falloff / resolution);
|
||||||
|
|
||||||
var worldHandle = args.WorldHandle;
|
var worldHandle = args.WorldHandle;
|
||||||
worldHandle.UseShader(_shader);
|
worldHandle.UseShader(_shader);
|
||||||
var viewport = _eyeManager.GetWorldViewport();
|
worldHandle.DrawRect(viewportWB, Color.White);
|
||||||
worldHandle.DrawRect(viewport, Color.White);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -64,19 +70,24 @@ namespace Content.Client.Graphics.Overlays
|
|||||||
|
|
||||||
//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?)
|
//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 float _maxDist = 15.0f;
|
||||||
private void SingularityQuery()
|
private void SingularityQuery(IEye? currentEye)
|
||||||
{
|
{
|
||||||
var currentEyeLoc = _eyeManager.CurrentEye.Position;
|
if (currentEye == null)
|
||||||
var currentMap = _eyeManager.CurrentMap; //TODO: support multiple viewports once it is added
|
{
|
||||||
|
_singularities.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var currentEyeLoc = currentEye.Position;
|
||||||
|
var currentMap = currentEye.Position.MapId;
|
||||||
|
|
||||||
var singuloComponents = _componentManager.EntityQuery<IClientSingularityInstance>();
|
var singuloComponents = _componentManager.EntityQuery<IClientSingularityInstance>();
|
||||||
foreach (var singuloInterface in singuloComponents) //Add all singulos that are not added yet but qualify
|
foreach (var singuloInterface in singuloComponents) //Add all singulos that are not added yet but qualify
|
||||||
{
|
{
|
||||||
var singuloComponent = (Component)singuloInterface;
|
var singuloComponent = (Component)singuloInterface;
|
||||||
var singuloEntity = singuloComponent.Owner;
|
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 @@ namespace Content.Client.Graphics.Overlays
|
|||||||
{
|
{
|
||||||
if (_entityManager.TryGetEntity(activeSinguloUid, out IEntity? singuloEntity))
|
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);
|
_singularities.Remove(activeSinguloUid);
|
||||||
}
|
}
|
||||||
@@ -99,7 +110,8 @@ namespace Content.Client.Graphics.Overlays
|
|||||||
{
|
{
|
||||||
var shaderInstance = _singularities[activeSinguloUid];
|
var shaderInstance = _singularities[activeSinguloUid];
|
||||||
shaderInstance.CurrentMapCoords = singuloEntity.Transform.MapPosition.Position;
|
shaderInstance.CurrentMapCoords = singuloEntity.Transform.MapPosition.Position;
|
||||||
shaderInstance.Level = singuloInterface.Level;
|
shaderInstance.Intensity = singuloInterface.Intensity;
|
||||||
|
shaderInstance.Falloff = singuloInterface.Falloff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,62 +124,21 @@ namespace Content.Client.Graphics.Overlays
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool SinguloQualifies(IEntity singuloEntity, MapCoordinates currentEyeLoc)
|
||||||
|
|
||||||
|
|
||||||
//I am lazy
|
|
||||||
private float LevelToIntensity(int level)
|
|
||||||
{
|
{
|
||||||
switch (level)
|
return singuloEntity.Transform.MapID == currentEyeLoc.MapId && singuloEntity.Transform.Coordinates.InRange(_entityManager, EntityCoordinates.FromMap(_entityManager, singuloEntity.Transform.ParentUid, currentEyeLoc), _maxDist);
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
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 sealed class SingularityShaderInstance
|
private sealed class SingularityShaderInstance
|
||||||
{
|
{
|
||||||
public Vector2 CurrentMapCoords;
|
public Vector2 CurrentMapCoords;
|
||||||
public int Level;
|
public float Intensity;
|
||||||
public SingularityShaderInstance(Vector2 mapCoords, int level)
|
public float Falloff;
|
||||||
|
public SingularityShaderInstance(Vector2 mapCoords, float intensity, float falloff)
|
||||||
{
|
{
|
||||||
CurrentMapCoords = mapCoords;
|
CurrentMapCoords = mapCoords;
|
||||||
Level = level;
|
Intensity = intensity;
|
||||||
|
Falloff = falloff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,6 +229,7 @@ namespace Content.Client
|
|||||||
"GlassBeaker",
|
"GlassBeaker",
|
||||||
"SliceableFood",
|
"SliceableFood",
|
||||||
"DamageOtherOnHit",
|
"DamageOtherOnHit",
|
||||||
|
"SinguloFood",
|
||||||
"DamageOnLand",
|
"DamageOnLand",
|
||||||
"SmokeSolutionAreaEffect",
|
"SmokeSolutionAreaEffect",
|
||||||
"FoamSolutionAreaEffect",
|
"FoamSolutionAreaEffect",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
using Content.Server.GameObjects.Components.Singularity;
|
using Content.Server.GameObjects.Components.Singularity;
|
||||||
using Content.Server.GameObjects.Components.PA;
|
using Content.Server.GameObjects.Components.PA;
|
||||||
|
using Content.Server.GameObjects.Components.Power.PowerNetComponents;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.GameObjects.Components;
|
using Content.Shared.GameObjects.Components;
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
@@ -30,11 +31,15 @@ namespace Content.Server.Commands
|
|||||||
{
|
{
|
||||||
ent.GetComponent<EmitterComponent>().SwitchOn();
|
ent.GetComponent<EmitterComponent>().SwitchOn();
|
||||||
}
|
}
|
||||||
|
foreach (var ent in entityManager.GetEntities(new TypeEntityQuery(typeof(RadiationCollectorComponent))))
|
||||||
|
{
|
||||||
|
ent.GetComponent<RadiationCollectorComponent>().Collecting = true;
|
||||||
|
}
|
||||||
foreach (var ent in entityManager.GetEntities(new TypeEntityQuery(typeof(ParticleAcceleratorControlBoxComponent))))
|
foreach (var ent in entityManager.GetEntities(new TypeEntityQuery(typeof(ParticleAcceleratorControlBoxComponent))))
|
||||||
{
|
{
|
||||||
var pacb = ent.GetComponent<ParticleAcceleratorControlBoxComponent>();
|
var pacb = ent.GetComponent<ParticleAcceleratorControlBoxComponent>();
|
||||||
pacb.RescanParts();
|
pacb.RescanParts();
|
||||||
pacb.SetStrength(ParticleAcceleratorPowerState.Level1);
|
pacb.SetStrength(ParticleAcceleratorPowerState.Level0);
|
||||||
pacb.SwitchOn();
|
pacb.SwitchOn();
|
||||||
}
|
}
|
||||||
shell.WriteLine("Done!");
|
shell.WriteLine("Done!");
|
||||||
|
|||||||
@@ -20,21 +20,7 @@ namespace Content.Server.GameObjects.Components.PA
|
|||||||
private ParticleAcceleratorPowerState _state;
|
private ParticleAcceleratorPowerState _state;
|
||||||
void IStartCollide.CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold)
|
void IStartCollide.CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold)
|
||||||
{
|
{
|
||||||
if (otherFixture.Body.Owner.TryGetComponent<ServerSingularityComponent>(out var singularityComponent))
|
if (otherFixture.Body.Owner.TryGetComponent<SingularityGeneratorComponent>(out var singularityGeneratorComponent))
|
||||||
{
|
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
singularityGeneratorComponent.Power += _state switch
|
singularityGeneratorComponent.Power += _state switch
|
||||||
{
|
{
|
||||||
@@ -67,6 +53,22 @@ namespace Content.Server.GameObjects.Components.PA
|
|||||||
}
|
}
|
||||||
projectileComponent.IgnoreEntity(firer);
|
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
|
var suffix = state switch
|
||||||
{
|
{
|
||||||
ParticleAcceleratorPowerState.Level0 => "0",
|
ParticleAcceleratorPowerState.Level0 => "0",
|
||||||
|
|||||||
@@ -11,11 +11,12 @@ using Robust.Shared.IoC;
|
|||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
|
namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class RadiationCollectorComponent : PowerSupplierComponent, IInteractHand, IRadiationAct
|
public class RadiationCollectorComponent : Component, IInteractHand, IRadiationAct
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
@@ -23,14 +24,20 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
|
|||||||
private bool _enabled;
|
private bool _enabled;
|
||||||
private TimeSpan _coolDownEnd;
|
private TimeSpan _coolDownEnd;
|
||||||
|
|
||||||
[ComponentDependency] private readonly PhysicsComponent? _collidableComponent = default!;
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool Collecting {
|
||||||
public void OnAnchoredChanged()
|
get => _enabled;
|
||||||
{
|
set
|
||||||
if(_collidableComponent != null && _collidableComponent.BodyType == BodyType.Static)
|
{
|
||||||
Owner.SnapToGrid();
|
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)
|
bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
var curTime = _gameTiming.CurTime;
|
var curTime = _gameTiming.CurTime;
|
||||||
@@ -40,13 +47,13 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
|
|||||||
|
|
||||||
if (!_enabled)
|
if (!_enabled)
|
||||||
{
|
{
|
||||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("The collector turns on."));
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("radiation-collector-component-use-on"));
|
||||||
EnableCollection();
|
Collecting = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("The collector turns off."));
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("radiation-collector-component-use-off"));
|
||||||
DisableCollection();
|
Collecting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_coolDownEnd = curTime + TimeSpan.FromSeconds(0.81f);
|
_coolDownEnd = curTime + TimeSpan.FromSeconds(0.81f);
|
||||||
@@ -54,23 +61,25 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnableCollection()
|
|
||||||
{
|
|
||||||
_enabled = true;
|
|
||||||
SetAppearance(RadiationCollectorVisualState.Activating);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisableCollection()
|
|
||||||
{
|
|
||||||
_enabled = false;
|
|
||||||
SetAppearance(RadiationCollectorVisualState.Deactivating);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRadiationAct.RadiationAct(float frameTime, SharedRadiationPulseComponent radiation)
|
void IRadiationAct.RadiationAct(float frameTime, SharedRadiationPulseComponent radiation)
|
||||||
{
|
{
|
||||||
if (!_enabled) return;
|
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)
|
protected void SetAppearance(RadiationCollectorVisualState state)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace Content.Server.GameObjects.Components.Singularity
|
|||||||
get => _sharedEnergyPool;
|
get => _sharedEnergyPool;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_sharedEnergyPool = Math.Clamp(value, 0, 10);
|
_sharedEnergyPool = Math.Clamp(value, 0, 25);
|
||||||
if (_sharedEnergyPool == 0)
|
if (_sharedEnergyPool == 0)
|
||||||
{
|
{
|
||||||
Dispose();
|
Dispose();
|
||||||
|
|||||||
@@ -168,8 +168,8 @@ namespace Content.Server.GameObjects.Components.Singularity
|
|||||||
|
|
||||||
void IStartCollide.CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold)
|
void IStartCollide.CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold)
|
||||||
{
|
{
|
||||||
if(otherFixture.Body.Owner.HasTag("EmitterBolt")) {
|
if (otherFixture.Body.Owner.HasTag("EmitterBolt")) {
|
||||||
ReceivePower(4);
|
ReceivePower(6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Robust.Shared.Audio;
|
|||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
using Robust.Shared.Physics.Collision;
|
using Robust.Shared.Physics.Collision;
|
||||||
using Robust.Shared.Physics.Collision.Shapes;
|
using Robust.Shared.Physics.Collision.Shapes;
|
||||||
@@ -14,12 +15,14 @@ using Robust.Shared.Physics.Dynamics;
|
|||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Players;
|
using Robust.Shared.Players;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Singularity
|
namespace Content.Server.GameObjects.Components.Singularity
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class ServerSingularityComponent : SharedSingularityComponent, IStartCollide
|
public class ServerSingularityComponent : SharedSingularityComponent, IStartCollide
|
||||||
{
|
{
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public int Energy
|
public int Energy
|
||||||
{
|
{
|
||||||
get => _energy;
|
get => _energy;
|
||||||
@@ -48,6 +51,7 @@ namespace Content.Server.GameObjects.Components.Singularity
|
|||||||
}
|
}
|
||||||
private int _energy = 180;
|
private int _energy = 180;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
public int Level
|
public int Level
|
||||||
{
|
{
|
||||||
get => _level;
|
get => _level;
|
||||||
@@ -58,6 +62,11 @@ namespace Content.Server.GameObjects.Components.Singularity
|
|||||||
if (value > 6) value = 6;
|
if (value > 6) value = 6;
|
||||||
|
|
||||||
_level = value;
|
_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;
|
if(_radiationPulseComponent != null) _radiationPulseComponent.RadsPerSecond = 10 * value;
|
||||||
|
|
||||||
@@ -76,6 +85,7 @@ namespace Content.Server.GameObjects.Components.Singularity
|
|||||||
}
|
}
|
||||||
private int _level;
|
private int _level;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
public int EnergyDrain =>
|
public int EnergyDrain =>
|
||||||
Level switch
|
Level switch
|
||||||
{
|
{
|
||||||
@@ -88,6 +98,12 @@ namespace Content.Server.GameObjects.Components.Singularity
|
|||||||
_ => 0
|
_ => 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 PhysicsComponent _collidableComponent = default!;
|
||||||
private RadiationPulseComponent _radiationPulseComponent = default!;
|
private RadiationPulseComponent _radiationPulseComponent = default!;
|
||||||
private SpriteComponent _spriteComponent = 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)
|
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;
|
var otherEntity = otherFixture.Body.Owner;
|
||||||
|
|
||||||
if (otherEntity.TryGetComponent<IMapGridComponent>(out var mapGridComponent))
|
if (otherEntity.TryGetComponent<IMapGridComponent>(out var mapGridComponent))
|
||||||
@@ -143,8 +164,16 @@ namespace Content.Server.GameObjects.Components.Singularity
|
|||||||
if (otherEntity.IsInContainer())
|
if (otherEntity.IsInContainer())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Singularity priority management / etc.
|
||||||
|
if (otherEntity.TryGetComponent<ServerSingularityComponent>(out var otherSingulo))
|
||||||
|
otherSingulo.BeingDeletedByAnotherSingularity = true;
|
||||||
|
|
||||||
otherEntity.QueueDelete();
|
otherEntity.QueueDelete();
|
||||||
Energy++;
|
|
||||||
|
if (otherEntity.TryGetComponent<SinguloFoodComponent>(out var singuloFood))
|
||||||
|
Energy += singuloFood.Energy;
|
||||||
|
else
|
||||||
|
Energy++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnRemove()
|
public override void OnRemove()
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<RadiationCollectorComponent, PhysicsBodyTypeChangedEvent>(BodyTypeChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Shutdown()
|
|
||||||
{
|
|
||||||
base.Shutdown();
|
|
||||||
|
|
||||||
UnsubscribeLocalEvent<RadiationCollectorComponent, PhysicsBodyTypeChangedEvent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void BodyTypeChanged(
|
|
||||||
EntityUid uid,
|
|
||||||
RadiationCollectorComponent component,
|
|
||||||
PhysicsBodyTypeChangedEvent args)
|
|
||||||
{
|
|
||||||
component.OnAnchoredChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -59,6 +59,7 @@ namespace Content.Server.Physics.Controllers
|
|||||||
|
|
||||||
private void MoveSingulo(ServerSingularityComponent singularity, PhysicsComponent physics)
|
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;
|
if (singularity.Level <= 1) return;
|
||||||
// TODO: Could try gradual changes instead but for now just try to replicate
|
// TODO: Could try gradual changes instead but for now just try to replicate
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
radiation-collector-component-use-on = The collector turns on.
|
||||||
|
radiation-collector-component-use-off = The collector turns off.
|
||||||
|
|
||||||
@@ -47820,4 +47820,46 @@ entities:
|
|||||||
EntityStorageComponent: !type:Container
|
EntityStorageComponent: !type:Container
|
||||||
ents: []
|
ents: []
|
||||||
type: ContainerContainer
|
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
|
||||||
...
|
...
|
||||||
|
|||||||
@@ -26,3 +26,6 @@
|
|||||||
- MobMask
|
- MobMask
|
||||||
- Opaque
|
- Opaque
|
||||||
- type: ParticleProjectile
|
- type: ParticleProjectile
|
||||||
|
- type: SinguloFood
|
||||||
|
# Energy is setup by the PA particle fire function.
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,16 @@
|
|||||||
!type:AdjacentNode
|
!type:AdjacentNode
|
||||||
nodeGroupID: HVPower
|
nodeGroupID: HVPower
|
||||||
- type: RadiationCollector
|
- 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: Anchorable
|
||||||
- type: Rotatable
|
- type: Rotatable
|
||||||
- type: Pullable
|
- type: Pullable
|
||||||
|
|||||||
95
Tools/singulo.m
Normal file
95
Tools/singulo.m
Normal file
@@ -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))
|
||||||
65
Tools/singulo_emitter.m
Normal file
65
Tools/singulo_emitter.m
Normal file
@@ -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))
|
||||||
Reference in New Issue
Block a user