Solar panels occlusion and tracking (#961)
This commit is contained in:
@@ -0,0 +1,231 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Client.UserInterface.Stylesheets;
|
||||||
|
using Content.Shared.GameObjects.Components.Power;
|
||||||
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Client.Graphics.Drawing;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.Interfaces.Timing;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Power
|
||||||
|
{
|
||||||
|
public class SolarControlConsoleBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
[Dependency]
|
||||||
|
private IGameTiming _gameTiming;
|
||||||
|
|
||||||
|
private SolarControlWindow _window;
|
||||||
|
private SolarControlConsoleBoundInterfaceState _lastState = new SolarControlConsoleBoundInterfaceState(0, 0, 0, 0);
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
_window = new SolarControlWindow(_gameTiming);
|
||||||
|
_window.OnClose += Close;
|
||||||
|
_window.PanelRotation.OnTextEntered += (text) => {
|
||||||
|
double value;
|
||||||
|
if (double.TryParse(text.Text, out value))
|
||||||
|
{
|
||||||
|
SolarControlConsoleAdjustMessage msg = new SolarControlConsoleAdjustMessage();
|
||||||
|
msg.Rotation = Angle.FromDegrees(value);
|
||||||
|
msg.AngularVelocity = _lastState.AngularVelocity;
|
||||||
|
SendMessage(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_window.PanelVelocity.OnTextEntered += (text) => {
|
||||||
|
double value;
|
||||||
|
if (double.TryParse(text.Text, out value))
|
||||||
|
{
|
||||||
|
SolarControlConsoleAdjustMessage msg = new SolarControlConsoleAdjustMessage();
|
||||||
|
msg.Rotation = _lastState.Rotation;
|
||||||
|
msg.AngularVelocity = Angle.FromDegrees(value / 60);
|
||||||
|
SendMessage(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_window.OpenCenteredMinSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SolarControlConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatAngle(Angle d)
|
||||||
|
{
|
||||||
|
return d.Degrees.ToString("F1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The idea behind this is to prevent every update from the server
|
||||||
|
// breaking the textfield.
|
||||||
|
private void UpdateField(LineEdit field, string newValue)
|
||||||
|
{
|
||||||
|
if (!field.HasKeyboardFocus())
|
||||||
|
{
|
||||||
|
field.Text = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
SolarControlConsoleBoundInterfaceState scc = (SolarControlConsoleBoundInterfaceState) state;
|
||||||
|
_lastState = scc;
|
||||||
|
_window.NotARadar.UpdateState(scc);
|
||||||
|
_window.OutputPower.Text = ((int) Math.Floor(scc.OutputPower)).ToString();
|
||||||
|
_window.SunAngle.Text = FormatAngle(scc.TowardsSun);
|
||||||
|
UpdateField(_window.PanelRotation, FormatAngle(scc.Rotation));
|
||||||
|
UpdateField(_window.PanelVelocity, FormatAngle(scc.AngularVelocity * 60));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_window.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SolarControlWindow : SS14Window
|
||||||
|
{
|
||||||
|
public Label OutputPower;
|
||||||
|
public Label SunAngle;
|
||||||
|
|
||||||
|
public SolarControlNotARadar NotARadar;
|
||||||
|
|
||||||
|
public LineEdit PanelRotation;
|
||||||
|
public LineEdit PanelVelocity;
|
||||||
|
|
||||||
|
public SolarControlWindow(IGameTiming igt)
|
||||||
|
{
|
||||||
|
Title = "Solar Control Window";
|
||||||
|
|
||||||
|
var rows = new GridContainer();
|
||||||
|
rows.Columns = 2;
|
||||||
|
|
||||||
|
// little secret: the reason I put the values
|
||||||
|
// in the first column is because otherwise the UI
|
||||||
|
// layouter autoresizes the window to be too small
|
||||||
|
rows.AddChild(new Label {Text = "Output Power:"});
|
||||||
|
rows.AddChild(new Label {Text = ""});
|
||||||
|
|
||||||
|
rows.AddChild(OutputPower = new Label {Text = "?"});
|
||||||
|
rows.AddChild(new Label {Text = "W"});
|
||||||
|
|
||||||
|
rows.AddChild(new Label {Text = "Sun Angle:"});
|
||||||
|
rows.AddChild(new Label {Text = ""});
|
||||||
|
|
||||||
|
rows.AddChild(SunAngle = new Label {Text = "?"});
|
||||||
|
rows.AddChild(new Label {Text = "°"});
|
||||||
|
|
||||||
|
rows.AddChild(new Label {Text = "Panel Angle:"});
|
||||||
|
rows.AddChild(new Label {Text = ""});
|
||||||
|
|
||||||
|
rows.AddChild(PanelRotation = new LineEdit());
|
||||||
|
rows.AddChild(new Label {Text = "°"});
|
||||||
|
|
||||||
|
rows.AddChild(new Label {Text = "Panel Angular Velocity:"});
|
||||||
|
rows.AddChild(new Label {Text = ""});
|
||||||
|
|
||||||
|
rows.AddChild(PanelVelocity = new LineEdit());
|
||||||
|
rows.AddChild(new Label {Text = "°/min."});
|
||||||
|
|
||||||
|
rows.AddChild(new Label {Text = "Press Enter to confirm."});
|
||||||
|
rows.AddChild(new Label {Text = ""});
|
||||||
|
|
||||||
|
PanelRotation.SizeFlagsHorizontal = SizeFlags.FillExpand;
|
||||||
|
PanelVelocity.SizeFlagsHorizontal = SizeFlags.FillExpand;
|
||||||
|
rows.SizeFlagsHorizontal = SizeFlags.Fill;
|
||||||
|
rows.SizeFlagsVertical = SizeFlags.Fill;
|
||||||
|
|
||||||
|
NotARadar = new SolarControlNotARadar(igt);
|
||||||
|
|
||||||
|
var outerColumns = new HBoxContainer();
|
||||||
|
outerColumns.AddChild(rows);
|
||||||
|
outerColumns.AddChild(NotARadar);
|
||||||
|
outerColumns.SizeFlagsHorizontal = SizeFlags.Fill;
|
||||||
|
outerColumns.SizeFlagsVertical = SizeFlags.Fill;
|
||||||
|
Contents.AddChild(outerColumns);
|
||||||
|
Resizable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SolarControlNotARadar : Control
|
||||||
|
{
|
||||||
|
// IoC doesn't apply here, so it's propagated from the parent class.
|
||||||
|
// This is used for client-side prediction of the panel rotation.
|
||||||
|
// This makes the display feel a lot smoother.
|
||||||
|
private IGameTiming _gameTiming;
|
||||||
|
|
||||||
|
private SolarControlConsoleBoundInterfaceState _lastState = new SolarControlConsoleBoundInterfaceState(0, 0, 0, 0);
|
||||||
|
|
||||||
|
private TimeSpan _lastStateTime = TimeSpan.Zero;
|
||||||
|
|
||||||
|
public const int SizeFull = 290;
|
||||||
|
public const int RadiusCircle = 140;
|
||||||
|
|
||||||
|
public SolarControlNotARadar(IGameTiming igt)
|
||||||
|
{
|
||||||
|
_gameTiming = igt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateState(SolarControlConsoleBoundInterfaceState ls)
|
||||||
|
{
|
||||||
|
_lastState = ls;
|
||||||
|
_lastStateTime = _gameTiming.CurTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Vector2 CalculateMinimumSize()
|
||||||
|
{
|
||||||
|
return (SizeFull, SizeFull);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(DrawingHandleScreen handle)
|
||||||
|
{
|
||||||
|
int point = SizeFull / 2;
|
||||||
|
Color fakeAA = new Color(0.08f, 0.08f, 0.08f);
|
||||||
|
Color gridLines = new Color(0.08f, 0.08f, 0.08f);
|
||||||
|
int panelExtentCutback = 4;
|
||||||
|
int gridLinesRadial = 8;
|
||||||
|
int gridLinesEquatorial = 8;
|
||||||
|
|
||||||
|
// Draw base
|
||||||
|
handle.DrawCircle((point, point), RadiusCircle + 1, fakeAA);
|
||||||
|
handle.DrawCircle((point, point), RadiusCircle, Color.Black);
|
||||||
|
|
||||||
|
// Draw grid lines
|
||||||
|
for (int i = 0; i < gridLinesEquatorial; i++)
|
||||||
|
{
|
||||||
|
handle.DrawCircle((point, point), (RadiusCircle / gridLinesEquatorial) * i, gridLines, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < gridLinesRadial; i++)
|
||||||
|
{
|
||||||
|
Angle angle = (Math.PI / gridLinesRadial) * i;
|
||||||
|
Vector2 aExtent = angle.ToVec() * RadiusCircle;
|
||||||
|
handle.DrawLine((point, point) - aExtent, (point, point) + aExtent, gridLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rotations need to be adjusted because Y is inverted in Robust (like BYOND)
|
||||||
|
Vector2 rotMul = (1, -1);
|
||||||
|
|
||||||
|
Angle predictedPanelRotation = _lastState.Rotation + (_lastState.AngularVelocity * ((_gameTiming.CurTime - _lastStateTime).TotalSeconds));
|
||||||
|
|
||||||
|
Vector2 extent = predictedPanelRotation.ToVec() * rotMul * RadiusCircle;
|
||||||
|
Vector2 extentOrtho = (extent.Y, -extent.X);
|
||||||
|
handle.DrawLine((point, point) - extentOrtho, (point, point) + extentOrtho, Color.White);
|
||||||
|
handle.DrawLine((point, point) + (extent / panelExtentCutback), (point, point) + extent - (extent / panelExtentCutback), Color.DarkGray);
|
||||||
|
|
||||||
|
Vector2 sunExtent = _lastState.TowardsSun.ToVec() * rotMul * RadiusCircle;
|
||||||
|
handle.DrawLine((point, point) + sunExtent, (point, point), Color.Yellow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using Content.Server.GameObjects.Components.Power;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Server.Interfaces.GameTicking;
|
||||||
|
using Content.Shared.GameObjects.Components.Power;
|
||||||
|
using Robust.Server.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
|
using Robust.Server.Interfaces.Player;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Power
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(IActivate))]
|
||||||
|
public class SolarControlConsoleComponent : SharedSolarControlConsoleComponent, IActivate
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private IEntitySystemManager _entitySystemManager;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
private BoundUserInterface _userInterface;
|
||||||
|
private PowerDeviceComponent _powerDevice;
|
||||||
|
private PowerSolarSystem _powerSolarSystem;
|
||||||
|
private bool Powered => _powerDevice.Powered;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(SolarControlConsoleUiKey.Key);
|
||||||
|
_userInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
|
||||||
|
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
|
||||||
|
_powerSolarSystem = _entitySystemManager.GetEntitySystem<PowerSolarSystem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateUIState()
|
||||||
|
{
|
||||||
|
_userInterface.SetState(new SolarControlConsoleBoundInterfaceState(_powerSolarSystem.TargetPanelRotation, _powerSolarSystem.TargetPanelVelocity, _powerSolarSystem.TotalPanelPower, _powerSolarSystem.TowardsSun));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage obj)
|
||||||
|
{
|
||||||
|
switch (obj.Message)
|
||||||
|
{
|
||||||
|
case SolarControlConsoleAdjustMessage msg:
|
||||||
|
if (double.IsFinite(msg.Rotation))
|
||||||
|
{
|
||||||
|
_powerSolarSystem.TargetPanelRotation = msg.Rotation.Reduced();
|
||||||
|
}
|
||||||
|
if (double.IsFinite(msg.AngularVelocity))
|
||||||
|
{
|
||||||
|
_powerSolarSystem.TargetPanelVelocity = msg.AngularVelocity.Reduced();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Powered)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// always update the UI immediately before opening, just in case
|
||||||
|
UpdateUIState();
|
||||||
|
_userInterface.Open(actor.playerSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,6 +60,15 @@ namespace Content.Server.GameObjects.Components.Power
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The game time (<see cref='IGameTiming'/>) of the next coverage update.
|
||||||
|
/// This may have a random offset applied.
|
||||||
|
/// This is used to reduce solar panel updates and stagger them to prevent lagspikes.
|
||||||
|
/// This should only be updated by the PowerSolarSystem but is viewable for debugging.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public TimeSpan TimeOfNextCoverageUpdate = TimeSpan.MinValue;
|
||||||
|
|
||||||
private void UpdateSupply()
|
private void UpdateSupply()
|
||||||
{
|
{
|
||||||
if (_powerGenerator != null)
|
if (_powerGenerator != null)
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using Content.Server.GameObjects.Components.Power;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Content.Shared.Physics;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Physics;
|
||||||
|
using Robust.Shared.Interfaces.Random;
|
||||||
|
using Robust.Shared.Interfaces.Timing;
|
||||||
|
using Robust.Shared.Physics;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.EntitySystems
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Responsible for updating solar control consoles.
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class PowerSolarControlConsoleSystem : EntitySystem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Timer used to avoid updating the UI state every frame (which would be overkill)
|
||||||
|
/// </summary>
|
||||||
|
private float UpdateTimer = 0f;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
EntityQuery = new TypeEntityQuery(typeof(SolarControlConsoleComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
UpdateTimer += frameTime;
|
||||||
|
if (UpdateTimer >= 1)
|
||||||
|
{
|
||||||
|
UpdateTimer = 0;
|
||||||
|
foreach (var entity in RelevantEntities)
|
||||||
|
{
|
||||||
|
entity.GetComponent<SolarControlConsoleComponent>().UpdateUIState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
using Content.Server.GameObjects.Components.Power;
|
using Content.Server.GameObjects.Components.Power;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using Content.Shared.Physics;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameObjects.Systems;
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Physics;
|
||||||
|
using Robust.Shared.Interfaces.Random;
|
||||||
|
using Robust.Shared.Interfaces.Timing;
|
||||||
|
using Robust.Shared.Physics;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.EntitySystems
|
namespace Content.Server.GameObjects.EntitySystems
|
||||||
{
|
{
|
||||||
@@ -11,55 +19,137 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
/// Responsible for maintaining the solar-panel sun angle and updating <see cref='SolarPanelComponent'/> coverage.
|
/// Responsible for maintaining the solar-panel sun angle and updating <see cref='SolarPanelComponent'/> coverage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public class PowerSolarSystem: EntitySystem
|
public class PowerSolarSystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private IGameTiming _gameTiming;
|
||||||
|
[Dependency] private IRobustRandom _robustRandom;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current sun angle.
|
/// The current sun angle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Angle TowardsSun = Angle.South;
|
public Angle TowardsSun = Angle.South;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current sun angular velocity. (This is changed in Initialize)
|
||||||
|
/// </summary>
|
||||||
|
public Angle SunAngularVelocity = Angle.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The distance before the sun is considered to have been 'visible anyway'.
|
||||||
|
/// This value, like the occlusion semantics, is borrowed from all the other SS13 stations with solars.
|
||||||
|
/// </summary>
|
||||||
|
public float SunOcclusionCheckDistance = 20;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is the per-second value used to reduce solar panel coverage updates
|
||||||
|
/// (and the resulting occlusion raycasts)
|
||||||
|
/// to within sane boundaries.
|
||||||
|
/// Keep in mind, this is not exact, as the random interval is also applied.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan SolarCoverageUpdateInterval = TimeSpan.FromSeconds(0.5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A random interval used to stagger solar coverage updates reliably.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan SolarCoverageUpdateRandomInterval = TimeSpan.FromSeconds(0.5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TODO: *Should be moved into the solar tracker when powernet allows for it.*
|
||||||
|
/// The current target panel rotation.
|
||||||
|
/// </summary>
|
||||||
|
public Angle TargetPanelRotation = Angle.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TODO: *Should be moved into the solar tracker when powernet allows for it.*
|
||||||
|
/// The current target panel velocity.
|
||||||
|
/// </summary>
|
||||||
|
public Angle TargetPanelVelocity = Angle.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TODO: *Should be moved into the solar tracker when powernet allows for it.*
|
||||||
|
/// Last update of total panel power.
|
||||||
|
/// </summary>
|
||||||
|
public float TotalPanelPower = 0;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
EntityQuery = new TypeEntityQuery(typeof(SolarPanelComponent));
|
EntityQuery = new TypeEntityQuery(typeof(SolarPanelComponent));
|
||||||
|
// Initialize the sun to something random
|
||||||
|
TowardsSun = Math.PI * 2 * _robustRandom.NextDouble();
|
||||||
|
SunAngularVelocity = Angle.FromDegrees(0.1 + ((_robustRandom.NextDouble() - 0.5) * 0.05));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
TowardsSun += Angle.FromDegrees(frameTime / 10);
|
TowardsSun += SunAngularVelocity * frameTime;
|
||||||
TowardsSun = TowardsSun.Reduced();
|
TowardsSun = TowardsSun.Reduced();
|
||||||
|
|
||||||
|
TargetPanelRotation += TargetPanelVelocity * frameTime;
|
||||||
|
TargetPanelRotation = TargetPanelRotation.Reduced();
|
||||||
|
|
||||||
|
TotalPanelPower = 0;
|
||||||
|
|
||||||
foreach (var entity in RelevantEntities)
|
foreach (var entity in RelevantEntities)
|
||||||
{
|
{
|
||||||
// In the 'sunRelative' coordinate system:
|
// There's supposed to be rotational logic here, but that implies putting it somewhere.
|
||||||
// the sun is considered to be an infinite distance directly up.
|
entity.Transform.WorldRotation = TargetPanelRotation;
|
||||||
// this is the rotation of the panel relative to that.
|
|
||||||
// directly upwards (theta = 0) = coverage 1
|
|
||||||
// left/right 90 degrees (abs(theta) = (pi / 2)) = coverage 0
|
|
||||||
// directly downwards (abs(theta) = pi) = coverage -1
|
|
||||||
// as TowardsSun + = CCW,
|
|
||||||
// panelRelativeToSun should - = CW
|
|
||||||
var panelRelativeToSun = entity.Transform.WorldRotation - TowardsSun;
|
|
||||||
// essentially, given cos = X & sin = Y & Y is 'downwards',
|
|
||||||
// then for the first 90 degrees of rotation in either direction,
|
|
||||||
// this plots the lower-right quadrant of a circle.
|
|
||||||
// now basically assume a line going from the negated X/Y to there,
|
|
||||||
// and that's the hypothetical solar panel.
|
|
||||||
//
|
|
||||||
// since, again, the sun is considered to be an infinite distance upwards,
|
|
||||||
// this essentially means Cos(panelRelativeToSun) is half of the cross-section,
|
|
||||||
// and since the full cross-section has a max of 2, effectively-halving it is fine.
|
|
||||||
//
|
|
||||||
// as for when it goes negative, it only does that when (abs(theta) > pi)
|
|
||||||
// and that's expected behavior.
|
|
||||||
float coverage = (float) Math.Max(0, Math.Cos(panelRelativeToSun));
|
|
||||||
|
|
||||||
// Would determine occlusion, but that requires raytraces.
|
|
||||||
// And I'm not sure where those are in the codebase.
|
|
||||||
// Luckily, auto-rotation isn't in yet, so it won't matter anyway.
|
|
||||||
|
|
||||||
// Total coverage calculated; apply it to the panel.
|
|
||||||
var panel = entity.GetComponent<SolarPanelComponent>();
|
var panel = entity.GetComponent<SolarPanelComponent>();
|
||||||
panel.Coverage = coverage;
|
if (panel.TimeOfNextCoverageUpdate < _gameTiming.CurTime)
|
||||||
|
{
|
||||||
|
// Setup the next coverage check.
|
||||||
|
TimeSpan future = SolarCoverageUpdateInterval + (SolarCoverageUpdateRandomInterval * _robustRandom.NextDouble());
|
||||||
|
panel.TimeOfNextCoverageUpdate = _gameTiming.CurTime + future;
|
||||||
|
UpdatePanelCoverage(panel);
|
||||||
|
}
|
||||||
|
TotalPanelPower += panel.Coverage * panel.MaxSupply;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdatePanelCoverage(SolarPanelComponent panel) {
|
||||||
|
IEntity entity = panel.Owner;
|
||||||
|
|
||||||
|
// So apparently, and yes, I *did* only find this out later,
|
||||||
|
// this is just a really fancy way of saying "Lambert's law of cosines".
|
||||||
|
// ...I still think this explaination makes more sense.
|
||||||
|
|
||||||
|
// In the 'sunRelative' coordinate system:
|
||||||
|
// the sun is considered to be an infinite distance directly up.
|
||||||
|
// this is the rotation of the panel relative to that.
|
||||||
|
// directly upwards (theta = 0) = coverage 1
|
||||||
|
// left/right 90 degrees (abs(theta) = (pi / 2)) = coverage 0
|
||||||
|
// directly downwards (abs(theta) = pi) = coverage -1
|
||||||
|
// as TowardsSun + = CCW,
|
||||||
|
// panelRelativeToSun should - = CW
|
||||||
|
var panelRelativeToSun = entity.Transform.WorldRotation - TowardsSun;
|
||||||
|
// essentially, given cos = X & sin = Y & Y is 'downwards',
|
||||||
|
// then for the first 90 degrees of rotation in either direction,
|
||||||
|
// this plots the lower-right quadrant of a circle.
|
||||||
|
// now basically assume a line going from the negated X/Y to there,
|
||||||
|
// and that's the hypothetical solar panel.
|
||||||
|
//
|
||||||
|
// since, again, the sun is considered to be an infinite distance upwards,
|
||||||
|
// this essentially means Cos(panelRelativeToSun) is half of the cross-section,
|
||||||
|
// and since the full cross-section has a max of 2, effectively-halving it is fine.
|
||||||
|
//
|
||||||
|
// as for when it goes negative, it only does that when (abs(theta) > pi)
|
||||||
|
// and that's expected behavior.
|
||||||
|
float coverage = (float)Math.Max(0, Math.Cos(panelRelativeToSun));
|
||||||
|
|
||||||
|
if (coverage > 0)
|
||||||
|
{
|
||||||
|
// Determine if the solar panel is occluded, and zero out coverage if so.
|
||||||
|
// FIXME: The "Opaque" collision group doesn't seem to work right now.
|
||||||
|
var ray = new CollisionRay(entity.Transform.WorldPosition, TowardsSun.ToVec(), (int) CollisionGroup.Opaque);
|
||||||
|
var rayCastResults = IoCManager.Resolve<IPhysicsManager>().IntersectRay(entity.Transform.MapID, ray, SunOcclusionCheckDistance, entity);
|
||||||
|
if (rayCastResults.Any())
|
||||||
|
coverage = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total coverage calculated; apply it to the panel.
|
||||||
|
panel.Coverage = coverage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Shared.GameObjects.Components.Power
|
||||||
|
{
|
||||||
|
public class SharedSolarControlConsoleComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "SolarControlConsole";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public class SolarControlConsoleBoundInterfaceState : BoundUserInterfaceState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The target rotation of the panels in radians.
|
||||||
|
/// </summary>
|
||||||
|
public Angle Rotation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The target velocity of the panels in radians/minute.
|
||||||
|
/// </summary>
|
||||||
|
public Angle AngularVelocity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total amount of power the panels are supplying.
|
||||||
|
/// </summary>
|
||||||
|
public float OutputPower;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current sun angle.
|
||||||
|
/// </summary>
|
||||||
|
public Angle TowardsSun;
|
||||||
|
|
||||||
|
public SolarControlConsoleBoundInterfaceState(Angle r, Angle vm, float p, Angle tw)
|
||||||
|
{
|
||||||
|
Rotation = r;
|
||||||
|
AngularVelocity = vm;
|
||||||
|
OutputPower = p;
|
||||||
|
TowardsSun = tw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class SolarControlConsoleAdjustMessage : BoundUserInterfaceMessage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// New target rotation of the panels in radians.
|
||||||
|
/// </summary>
|
||||||
|
public Angle Rotation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// New target velocity of the panels in radians/second.
|
||||||
|
/// </summary>
|
||||||
|
public Angle AngularVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum SolarControlConsoleUiKey
|
||||||
|
{
|
||||||
|
Key
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -201,3 +201,19 @@
|
|||||||
interfaces:
|
interfaces:
|
||||||
- key: enum.CommunicationsConsoleUiKey.Key
|
- key: enum.CommunicationsConsoleUiKey.Key
|
||||||
type: CommunicationsConsoleBoundUserInterface
|
type: CommunicationsConsoleBoundUserInterface
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: ComputerSolarControl
|
||||||
|
parent: ComputerBase
|
||||||
|
name: Solar Control Computer
|
||||||
|
components:
|
||||||
|
- type: Appearance
|
||||||
|
visuals:
|
||||||
|
- type: ComputerVisualizer2D
|
||||||
|
key: generic_key
|
||||||
|
screen: solar_screen
|
||||||
|
- type: SolarControlConsole
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.SolarControlConsoleUiKey.Key
|
||||||
|
type: SolarControlConsoleBoundUserInterface
|
||||||
|
|||||||
@@ -84,7 +84,6 @@
|
|||||||
- type: PowerGenerator
|
- type: PowerGenerator
|
||||||
- type: SolarPanel
|
- type: SolarPanel
|
||||||
supply: 1500
|
supply: 1500
|
||||||
- type: Rotatable
|
|
||||||
- type: SnapGrid
|
- type: SnapGrid
|
||||||
offset: Center
|
offset: Center
|
||||||
- type: Damageable
|
- type: Damageable
|
||||||
|
|||||||
Reference in New Issue
Block a user