Converts the particle accelerator over to ECS + misc (#17075)

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
TemporalOroboros
2023-06-07 23:25:59 -07:00
committed by GitHub
parent a3137cc0f0
commit d71b6c84e5
61 changed files with 1293 additions and 1169 deletions

View File

@@ -1,6 +1,5 @@
using Content.Shared.Singularity.Components; using Content.Shared.Singularity.Components;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
namespace Content.Client.ParticleAccelerator.UI namespace Content.Client.ParticleAccelerator.UI
{ {

View File

@@ -19,10 +19,10 @@ namespace Content.Client.ParticleAccelerator.UI
{ {
private readonly ShaderInstance _greyScaleShader; private readonly ShaderInstance _greyScaleShader;
private readonly ParticleAcceleratorBoundUserInterface Owner; private readonly ParticleAcceleratorBoundUserInterface _owner;
private readonly Label _drawLabel; private readonly Label _drawLabel;
private readonly NoiseGenerator _drawNoiseGenerator; private readonly FastNoiseLite _drawNoiseGenerator;
private readonly Button _onButton; private readonly Button _onButton;
private readonly Button _offButton; private readonly Button _offButton;
private readonly Button _scanButton; private readonly Button _scanButton;
@@ -36,9 +36,9 @@ namespace Content.Client.ParticleAccelerator.UI
private readonly PASegmentControl _fuelChamberTexture; private readonly PASegmentControl _fuelChamberTexture;
private readonly PASegmentControl _controlBoxTexture; private readonly PASegmentControl _controlBoxTexture;
private readonly PASegmentControl _powerBoxTexture; private readonly PASegmentControl _powerBoxTexture;
private readonly PASegmentControl _emitterCenterTexture; private readonly PASegmentControl _emitterForeTexture;
private readonly PASegmentControl _emitterRightTexture; private readonly PASegmentControl _emitterPortTexture;
private readonly PASegmentControl _emitterLeftTexture; private readonly PASegmentControl _emitterStarboardTexture;
private float _time; private float _time;
private int _lastDraw; private int _lastDraw;
@@ -47,14 +47,16 @@ namespace Content.Client.ParticleAccelerator.UI
private bool _blockSpinBox; private bool _blockSpinBox;
private bool _assembled; private bool _assembled;
private bool _shouldContinueAnimating; private bool _shouldContinueAnimating;
private int _maxStrength = 3;
public ParticleAcceleratorControlMenu(ParticleAcceleratorBoundUserInterface owner) public ParticleAcceleratorControlMenu(ParticleAcceleratorBoundUserInterface owner)
{ {
SetSize = (400, 320); SetSize = (400, 320);
_greyScaleShader = IoCManager.Resolve<IPrototypeManager>().Index<ShaderPrototype>("Greyscale").Instance(); _greyScaleShader = IoCManager.Resolve<IPrototypeManager>().Index<ShaderPrototype>("Greyscale").Instance();
Owner = owner; _owner = owner;
_drawNoiseGenerator = new NoiseGenerator(NoiseGenerator.NoiseType.Fbm); _drawNoiseGenerator = new();
_drawNoiseGenerator.SetFractalType(FastNoiseLite.FractalType.FBm);
_drawNoiseGenerator.SetFrequency(0.5f); _drawNoiseGenerator.SetFrequency(0.5f);
var resourceCache = IoCManager.Resolve<IResourceCache>(); var resourceCache = IoCManager.Resolve<IResourceCache>();
@@ -98,7 +100,7 @@ namespace Content.Client.ParticleAccelerator.UI
MouseFilter = MouseFilterMode.Pass MouseFilter = MouseFilterMode.Pass
}); });
_stateSpinBox = new SpinBox {Value = 0, IsValid = StrengthSpinBoxValid,}; _stateSpinBox = new SpinBox { Value = 0, IsValid = StrengthSpinBoxValid };
_stateSpinBox.InitDefaultButtons(); _stateSpinBox.InitDefaultButtons();
_stateSpinBox.ValueChanged += PowerStateChanged; _stateSpinBox.ValueChanged += PowerStateChanged;
_stateSpinBox.LineEditDisabled = true; _stateSpinBox.LineEditDisabled = true;
@@ -107,7 +109,7 @@ namespace Content.Client.ParticleAccelerator.UI
{ {
ToggleMode = false, ToggleMode = false,
Text = Loc.GetString("particle-accelerator-control-menu-off-button"), Text = Loc.GetString("particle-accelerator-control-menu-off-button"),
StyleClasses = {StyleBase.ButtonOpenRight}, StyleClasses = { StyleBase.ButtonOpenRight },
}; };
_offButton.OnPressed += args => owner.SendEnableMessage(false); _offButton.OnPressed += args => owner.SendEnableMessage(false);
@@ -115,13 +117,13 @@ namespace Content.Client.ParticleAccelerator.UI
{ {
ToggleMode = false, ToggleMode = false,
Text = Loc.GetString("particle-accelerator-control-menu-on-button"), Text = Loc.GetString("particle-accelerator-control-menu-on-button"),
StyleClasses = {StyleBase.ButtonOpenLeft}, StyleClasses = { StyleBase.ButtonOpenLeft },
}; };
_onButton.OnPressed += args => owner.SendEnableMessage(true); _onButton.OnPressed += args => owner.SendEnableMessage(true);
var closeButton = new TextureButton var closeButton = new TextureButton
{ {
StyleClasses = {"windowCloseButton"}, StyleClasses = { "windowCloseButton" },
HorizontalAlignment = HAlignment.Right, HorizontalAlignment = HAlignment.Right,
Margin = new Thickness(0, 0, 8, 0) Margin = new Thickness(0, 0, 8, 0)
}; };
@@ -130,7 +132,7 @@ namespace Content.Client.ParticleAccelerator.UI
var serviceManual = new Label var serviceManual = new Label
{ {
HorizontalAlignment = HAlignment.Center, HorizontalAlignment = HAlignment.Center,
StyleClasses = {StyleBase.StyleClassLabelSubText}, StyleClasses = { StyleBase.StyleClassLabelSubText },
Text = Loc.GetString("particle-accelerator-control-menu-service-manual-reference") Text = Loc.GetString("particle-accelerator-control-menu-service-manual-reference")
}; };
_drawLabel = new Label(); _drawLabel = new Label();
@@ -227,7 +229,8 @@ namespace Content.Client.ParticleAccelerator.UI
Align = Label.AlignMode.Center Align = Label.AlignMode.Center
}, },
serviceManual serviceManual
} },
Visible = false,
}), }),
} }
}, },
@@ -267,9 +270,9 @@ namespace Content.Client.ParticleAccelerator.UI
new Control {MinSize = imgSize}, new Control {MinSize = imgSize},
(_powerBoxTexture = Segment("power_box")), (_powerBoxTexture = Segment("power_box")),
new Control {MinSize = imgSize}, new Control {MinSize = imgSize},
(_emitterLeftTexture = Segment("emitter_left")), (_emitterStarboardTexture = Segment("emitter_starboard")),
(_emitterCenterTexture = Segment("emitter_center")), (_emitterForeTexture = Segment("emitter_fore")),
(_emitterRightTexture = Segment("emitter_right")), (_emitterPortTexture = Segment("emitter_port")),
} }
} }
} }
@@ -312,7 +315,7 @@ namespace Content.Client.ParticleAccelerator.UI
} }
}); });
_scanButton.OnPressed += args => Owner.SendScanPartsMessage(); _scanButton.OnPressed += args => _owner.SendScanPartsMessage();
_alarmControl.AnimationCompleted += s => _alarmControl.AnimationCompleted += s =>
{ {
@@ -336,7 +339,7 @@ namespace Content.Client.ParticleAccelerator.UI
private bool StrengthSpinBoxValid(int n) private bool StrengthSpinBoxValid(int n)
{ {
return (n >= 0 && n <= 3 && !_blockSpinBox); return n >= 0 && n <= _maxStrength && !_blockSpinBox;
} }
private void PowerStateChanged(ValueChangedEventArgs e) private void PowerStateChanged(ValueChangedEventArgs e)
@@ -356,16 +359,15 @@ namespace Content.Client.ParticleAccelerator.UI
case 3: case 3:
newState = ParticleAcceleratorPowerState.Level2; newState = ParticleAcceleratorPowerState.Level2;
break; break;
// They can't reach this level anyway and I just want to fix the bugginess for now. case 4:
//case 4: newState = ParticleAcceleratorPowerState.Level3;
// newState = ParticleAcceleratorPowerState.Level3; break;
// break;
default: default:
return; return;
} }
_stateSpinBox.SetButtonDisabled(true); _stateSpinBox.SetButtonDisabled(true);
Owner.SendPowerStateMessage(newState); _owner.SendPowerStateMessage(newState);
} }
protected override DragMode GetDragModeFor(Vector2 relativeMousePos) protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
@@ -402,14 +404,15 @@ namespace Content.Client.ParticleAccelerator.UI
}); });
_shouldContinueAnimating = false; _maxStrength = maxState == ParticleAcceleratorPowerState.Level3 ? 4 : 3;
_alarmControl.StopAnimation("warningAnim"); if (_maxStrength > 3 && enabled && assembled)
_alarmControl.Visible = false;
if (maxState == ParticleAcceleratorPowerState.Level3 && enabled && assembled)
{ {
_shouldContinueAnimating = true; _shouldContinueAnimating = true;
if (!_alarmControl.Visible)
_alarmControl.PlayAnimation(_alarmControlAnimation, "warningAnim"); _alarmControl.PlayAnimation(_alarmControlAnimation, "warningAnim");
} }
else
_shouldContinueAnimating = false;
} }
private void UpdateUI(bool assembled, bool blocked, bool enabled, bool powerBlock) private void UpdateUI(bool assembled, bool blocked, bool enabled, bool powerBlock)
@@ -430,12 +433,12 @@ namespace Content.Client.ParticleAccelerator.UI
private void UpdatePreview(ParticleAcceleratorUIState updateMessage) private void UpdatePreview(ParticleAcceleratorUIState updateMessage)
{ {
_endCapTexture.SetPowerState(updateMessage, updateMessage.EndCapExists); _endCapTexture.SetPowerState(updateMessage, updateMessage.EndCapExists);
_fuelChamberTexture.SetPowerState(updateMessage, updateMessage.FuelChamberExists);
_controlBoxTexture.SetPowerState(updateMessage, true); _controlBoxTexture.SetPowerState(updateMessage, true);
_fuelChamberTexture.SetPowerState(updateMessage, updateMessage.FuelChamberExists);
_powerBoxTexture.SetPowerState(updateMessage, updateMessage.PowerBoxExists); _powerBoxTexture.SetPowerState(updateMessage, updateMessage.PowerBoxExists);
_emitterCenterTexture.SetPowerState(updateMessage, updateMessage.EmitterCenterExists); _emitterStarboardTexture.SetPowerState(updateMessage, updateMessage.EmitterStarboardExists);
_emitterLeftTexture.SetPowerState(updateMessage, updateMessage.EmitterLeftExists); _emitterForeTexture.SetPowerState(updateMessage, updateMessage.EmitterForeExists);
_emitterRightTexture.SetPowerState(updateMessage, updateMessage.EmitterRightExists); _emitterPortTexture.SetPowerState(updateMessage, updateMessage.EmitterPortExists);
} }
protected override void FrameUpdate(FrameEventArgs args) protected override void FrameUpdate(FrameEventArgs args)
@@ -453,7 +456,7 @@ namespace Content.Client.ParticleAccelerator.UI
var watts = 0; var watts = 0;
if (_lastDraw != 0) if (_lastDraw != 0)
{ {
var val = _drawNoiseGenerator.GetNoise(_time); var val = _drawNoiseGenerator.GetNoise(_time, 0f);
watts = (int) (_lastDraw + val * 5); watts = (int) (_lastDraw + val * 5);
} }
@@ -476,7 +479,7 @@ namespace Content.Client.ParticleAccelerator.UI
_baseState = name; _baseState = name;
_rsi = cache.GetResource<RSIResource>($"/Textures/Structures/Power/Generation/PA/{name}.rsi").RSI; _rsi = cache.GetResource<RSIResource>($"/Textures/Structures/Power/Generation/PA/{name}.rsi").RSI;
AddChild(_base = new TextureRect {Texture = _rsi[$"completed"].Frame0}); AddChild(_base = new TextureRect { Texture = _rsi[$"completed"].Frame0 });
AddChild(_unlit = new TextureRect()); AddChild(_unlit = new TextureRect());
MinSize = _rsi.Size; MinSize = _rsi.Size;
} }

View File

@@ -20,9 +20,9 @@ public sealed class MachineBoardTest
"MachineParticleAcceleratorFuelChamberCircuitboard", "MachineParticleAcceleratorFuelChamberCircuitboard",
"MachineParticleAcceleratorFuelChamberCircuitboard", "MachineParticleAcceleratorFuelChamberCircuitboard",
"MachineParticleAcceleratorPowerBoxCircuitboard", "MachineParticleAcceleratorPowerBoxCircuitboard",
"MachineParticleAcceleratorEmitterLeftCircuitboard", "MachineParticleAcceleratorEmitterStarboardCircuitboard",
"MachineParticleAcceleratorEmitterCenterCircuitboard", "MachineParticleAcceleratorEmitterForeCircuitboard",
"MachineParticleAcceleratorEmitterRightCircuitboard", "MachineParticleAcceleratorEmitterPortCircuitboard",
"ParticleAcceleratorComputerCircuitboard" "ParticleAcceleratorComputerCircuitboard"
}; };
@@ -33,7 +33,7 @@ public sealed class MachineBoardTest
[Test] [Test]
public async Task TestMachineBoardHasValidMachine() public async Task TestMachineBoardHasValidMachine()
{ {
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true}); await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings { NoClient = true });
var server = pairTracker.Pair.Server; var server = pairTracker.Pair.Server;
var protoMan = server.ResolveDependency<IPrototypeManager>(); var protoMan = server.ResolveDependency<IPrototypeManager>();
@@ -46,6 +46,8 @@ public sealed class MachineBoardTest
continue; continue;
var mId = mbc.Prototype; var mId = mbc.Prototype;
Assert.Multiple(() =>
{
Assert.That(mId, Is.Not.Null, $"Machine board {p.ID} does not have a corresponding machine."); Assert.That(mId, Is.Not.Null, $"Machine board {p.ID} does not have a corresponding machine.");
Assert.That(protoMan.TryIndex<EntityPrototype>(mId, out var mProto), Assert.That(protoMan.TryIndex<EntityPrototype>(mId, out var mProto),
$"Machine board {p.ID}'s corresponding machine has an invalid prototype."); $"Machine board {p.ID}'s corresponding machine has an invalid prototype.");
@@ -53,6 +55,7 @@ public sealed class MachineBoardTest
$"Machine board {p.ID}'s corresponding machine {mId} does not have MachineComponent"); $"Machine board {p.ID}'s corresponding machine {mId} does not have MachineComponent");
Assert.That(mComp.BoardPrototype, Is.EqualTo(p.ID), Assert.That(mComp.BoardPrototype, Is.EqualTo(p.ID),
$"Machine {mId}'s BoardPrototype is not equal to it's corresponding machine board, {p.ID}"); $"Machine {mId}'s BoardPrototype is not equal to it's corresponding machine board, {p.ID}");
});
} }
}); });
@@ -66,7 +69,7 @@ public sealed class MachineBoardTest
[Test] [Test]
public async Task TestComputerBoardHasValidComputer() public async Task TestComputerBoardHasValidComputer()
{ {
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true}); await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings { NoClient = true });
var server = pairTracker.Pair.Server; var server = pairTracker.Pair.Server;
var protoMan = server.ResolveDependency<IPrototypeManager>(); var protoMan = server.ResolveDependency<IPrototypeManager>();
@@ -79,6 +82,8 @@ public sealed class MachineBoardTest
continue; continue;
var cId = cbc.Prototype; var cId = cbc.Prototype;
Assert.Multiple(() =>
{
Assert.That(cId, Is.Not.Null, $"Computer board \"{p.ID}\" does not have a corresponding computer."); Assert.That(cId, Is.Not.Null, $"Computer board \"{p.ID}\" does not have a corresponding computer.");
Assert.That(protoMan.TryIndex<EntityPrototype>(cId, out var cProto), Assert.That(protoMan.TryIndex<EntityPrototype>(cId, out var cProto),
$"Computer board \"{p.ID}\"'s corresponding computer has an invalid prototype."); $"Computer board \"{p.ID}\"'s corresponding computer has an invalid prototype.");
@@ -86,6 +91,7 @@ public sealed class MachineBoardTest
$"Computer board {p.ID}'s corresponding computer \"{cId}\" does not have ComputerComponent"); $"Computer board {p.ID}'s corresponding computer \"{cId}\" does not have ComputerComponent");
Assert.That(cComp.BoardPrototype, Is.EqualTo(p.ID), Assert.That(cComp.BoardPrototype, Is.EqualTo(p.ID),
$"Computer \"{cId}\"'s BoardPrototype is not equal to it's corresponding computer board, \"{p.ID}\""); $"Computer \"{cId}\"'s BoardPrototype is not equal to it's corresponding computer board, \"{p.ID}\"");
});
} }
}); });

View File

@@ -1,65 +1,134 @@
using System.Diagnostics; using Content.Server.ParticleAccelerator.Wires;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Content.Server.Administration.Logs;
using Content.Server.Mind.Components;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.UserInterface;
using Content.Shared.Database;
using Content.Shared.Singularity.Components; using Content.Shared.Singularity.Components;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Utility;
using Timer = Robust.Shared.Timing.Timer;
// using Content.Server.WireHacking;
// using static Content.Shared.Wires.SharedWiresComponent;
namespace Content.Server.ParticleAccelerator.Components namespace Content.Server.ParticleAccelerator.Components;
// This component is in control of the PA's logic because it's the one to contain the wires for hacking.
// And also it's the only PA component that meaningfully needs to work on its own.
/// <summary>
/// Is the computer thing people interact with to control the PA.
/// Also contains primary logic for actual PA behavior, part scanning, etc...
/// </summary>
[RegisterComponent]
public sealed class ParticleAcceleratorControlBoxComponent : Component
{ {
// This component is in control of the PA's logic because it's the one to contain the wires for hacking.
// And also it's the only PA component that meaningfully needs to work on its own.
/// <summary> /// <summary>
/// Is the computer thing people interact with to control the PA. /// Whether the PA parts have been correctly arranged to make a functional device.
/// Also contains primary logic for actual PA behavior, part scanning, etc...
/// </summary> /// </summary>
[RegisterComponent]
public sealed class ParticleAcceleratorControlBoxComponent : ParticleAcceleratorPartComponent
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[ViewVariables] [ViewVariables]
private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ParticleAcceleratorControlBoxUiKey.Key); public bool Assembled = false;
/// <summary> /// <summary>
/// Power receiver for the control console itself. /// Whether the PA is currently set to fire at the console.
/// Requires <see cref="Assembled"/> to be true.
/// </summary> /// </summary>
[ViewVariables] private ApcPowerReceiverComponent _apcPowerReceiverComponent = default!; [ViewVariables]
public bool Enabled = false;
[ViewVariables] private ParticleAcceleratorFuelChamberComponent? _partFuelChamber; /// <summary>
[ViewVariables] private ParticleAcceleratorEndCapComponent? _partEndCap; /// Whether the PA actually has the power necessary to fire.
[ViewVariables] private ParticleAcceleratorPowerBoxComponent? _partPowerBox; /// Requires <see cref="Enabled"/> to be true.
[ViewVariables] private ParticleAcceleratorEmitterComponent? _partEmitterLeft; /// </summary>
[ViewVariables] private ParticleAcceleratorEmitterComponent? _partEmitterCenter; [ViewVariables]
[ViewVariables] private ParticleAcceleratorEmitterComponent? _partEmitterRight; public bool Powered = false;
[ViewVariables] private ParticleAcceleratorPowerState _selectedStrength = ParticleAcceleratorPowerState.Standby;
[ViewVariables] private bool _isAssembled; /// <summary>
/// Whether the PA is currently firing or charging to fire.
/// Requires <see cref="Powered"/> to be true.
/// </summary>
[ViewVariables]
public bool Firing = false;
// Enabled: power switch is on /// <summary>
[ViewVariables] private bool _isEnabled; /// Whether the PA is currently firing or charging to fire.
/// Bounded by <see cref="ParticleAcceleratorPowerState.Standby"/> and <see cref="MaxStrength"/>.
/// Modified by <see cref="ParticleAcceleratorStrengthWireAction"/>.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public ParticleAcceleratorPowerState SelectedStrength = ParticleAcceleratorPowerState.Standby;
// Powered: power switch is on AND the PA is actively firing (if not on standby) /// <summary>
[ViewVariables] private bool _isPowered; /// The maximum strength level this particle accelerator can be set to operate at.
[ViewVariables] private bool _wireInterfaceBlocked; /// Modified by <see cref="ParticleAcceleratorLimiterWireAction"/>.
[ViewVariables] private bool _wirePowerBlocked; /// </summary>
[ViewVariables] private bool _wireLimiterCut; [ViewVariables]
[ViewVariables] private bool _wireStrengthCut; public ParticleAcceleratorPowerState MaxStrength = ParticleAcceleratorPowerState.Level2;
[ViewVariables] private CancellationTokenSource? _fireCancelTokenSrc;
/// <summary>
/// The power supply unit of the assembled particle accelerator.
/// Implies the existance of a <see cref="ParticleAcceleratorPowerBoxComponent"/> attached to this entity.
/// </summary>
[ViewVariables]
public EntityUid? PowerBox;
/// <summary>
/// Whether the PA is currently firing or charging to fire.
/// Implies the existance of a <see cref="ParticleAcceleratorEndCapComponent"/> attached to this entity.
/// </summary>
[ViewVariables]
public EntityUid? EndCap;
/// <summary>
/// Whether the PA is currently firing or charging to fire.
/// Implies the existance of a <see cref="ParticleAcceleratorFuelChamberComponent"/> attached to this entity.
/// </summary>
[ViewVariables]
public EntityUid? FuelChamber;
/// <summary>
/// Whether the PA is currently firing or charging to fire.
/// Implies the existance of a <see cref="ParticleAcceleratorEmitterComponent"/> attached to this entity.
/// </summary>
[ViewVariables]
public EntityUid? PortEmitter;
/// <summary>
/// Whether the PA is currently firing or charging to fire.
/// Implies the existance of a <see cref="ParticleAcceleratorEmitterComponent"/> attached to this entity.
/// </summary>
[ViewVariables]
public EntityUid? ForeEmitter;
/// <summary>
/// Whether the PA is currently firing or charging to fire.
/// Implies the existance of a <see cref="ParticleAcceleratorEmitterComponent"/> attached to this entity.
/// </summary>
[ViewVariables]
public EntityUid? StarboardEmitter;
/// <summary>
/// The amount of power the particle accelerator must be provided with relative to the expected power draw to function.
/// </summary>
[ViewVariables]
public const float RequiredPowerRatio = 0.999f;
/// <summary>
/// The amount of power (in watts) the PA draws just by existing as a functional machine.
/// </summary>
[DataField("powerDrawBase")]
[ViewVariables(VVAccess.ReadWrite)]
public int BasePowerDraw = 500;
/// <summary>
/// The amount of power (in watts) the PA draws per level when turned on.
/// </summary>
[DataField("powerDrawMult")]
[ViewVariables(VVAccess.ReadWrite)]
public int LevelPowerDraw = 1500;
/// <summary>
/// The time at which the PA last fired a wave of particles.
/// </summary>
[DataField("lastFire")]
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan LastFire;
/// <summary>
/// The time at which the PA will next fire a wave of particles.
/// </summary>
[DataField("nextFire")]
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan NextFire;
/// <summary> /// <summary>
/// Delay between consecutive PA shots. /// Delay between consecutive PA shots.
@@ -74,642 +143,28 @@ namespace Content.Server.ParticleAccelerator.Components
// So the *actual* effective firing delay of the PA is 6 seconds, not 5 as listed in the code. // So the *actual* effective firing delay of the PA is 6 seconds, not 5 as listed in the code.
// So... // So...
// I have reflected that here to be authentic. // I have reflected that here to be authentic.
[ViewVariables(VVAccess.ReadWrite)] [DataField("fireDelay")] private TimeSpan _firingDelay = TimeSpan.FromSeconds(6); [DataField("chargeTime")]
[ViewVariables(VVAccess.ReadWrite)]
[ViewVariables(VVAccess.ReadWrite)] [DataField("powerDrawBase")] private int _powerDrawBase = 500; public TimeSpan ChargeTime = TimeSpan.FromSeconds(6.0);
[ViewVariables(VVAccess.ReadWrite)] [DataField("powerDrawMult")] private int _powerDrawMult = 1500;
[ViewVariables] private bool ConsolePowered => _apcPowerReceiverComponent?.Powered ?? true;
public ParticleAcceleratorControlBoxComponent()
{
Master = this;
}
private ParticleAcceleratorPowerState MaxPower => _wireLimiterCut
? ParticleAcceleratorPowerState.Level3
: ParticleAcceleratorPowerState.Level2;
protected override void Initialize()
{
base.Initialize();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
Owner.EnsureComponent(out _apcPowerReceiverComponent);
_apcPowerReceiverComponent.Load = 250;
}
// This is the power state for the PA control box itself.
// Keep in mind that the PA itself can keep firing as long as the HV cable under the power box has... power.
public void OnPowerStateChanged(PowerChangedEvent e)
{
UpdateAppearance();
if (!e.Powered)
{
UserInterface?.CloseAll();
}
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
if (!ConsolePowered)
{
return;
}
if (_wireInterfaceBlocked)
{
return;
}
switch (obj.Message)
{
case ParticleAcceleratorSetEnableMessage enableMessage:
if (enableMessage.Enabled)
{
SwitchOn(obj.Session);
}
else
{
SwitchOff(obj.Session);
}
break;
case ParticleAcceleratorSetPowerStateMessage stateMessage:
SetStrength(stateMessage.State, obj.Session);
break;
case ParticleAcceleratorRescanPartsMessage _:
RescanParts(obj.Session);
break;
}
UpdateUI();
}
public void UpdateUI()
{
var draw = 0f;
var receive = 0f;
if (_isEnabled)
{
draw = _partPowerBox!.PowerConsumerComponent!.DrawRate;
receive = _partPowerBox!.PowerConsumerComponent!.ReceivedPower;
}
var state = new ParticleAcceleratorUIState(
_isAssembled,
_isEnabled,
_selectedStrength,
(int) draw,
(int) receive,
_partEmitterLeft != null,
_partEmitterCenter != null,
_partEmitterRight != null,
_partPowerBox != null,
_partFuelChamber != null,
_partEndCap != null,
_wireInterfaceBlocked,
MaxPower,
_wirePowerBlocked);
UserInterface?.SetState(state);
}
protected override void OnRemove()
{
_fireCancelTokenSrc?.Cancel();
_fireCancelTokenSrc = null;
Master = null;
foreach (var part in AllParts())
{
if (_entMan.TryGetComponent(part.Owner, out ParticleAcceleratorPartComponent? paPart))
paPart.Master = null;
}
base.OnRemove();
}
/*
void IWires.RegisterWires(WiresComponent.WiresBuilder builder)
{
builder.CreateWire(ParticleAcceleratorControlBoxWires.Toggle);
builder.CreateWire(ParticleAcceleratorControlBoxWires.Strength);
builder.CreateWire(ParticleAcceleratorControlBoxWires.Interface);
builder.CreateWire(ParticleAcceleratorControlBoxWires.Limiter);
builder.CreateWire(ParticleAcceleratorControlBoxWires.Nothing);
}
public void WiresUpdate(WiresUpdateEventArgs args)
{
switch (args.Identifier)
{
case ParticleAcceleratorControlBoxWires.Toggle:
if (args.Action == WiresAction.Pulse)
{
if (_isEnabled)
{
SwitchOff();
}
else
{
SwitchOn();
}
}
else
{
_wirePowerBlocked = args.Action == WiresAction.Cut;
if (_isEnabled)
{
SwitchOff();
}
}
break;
case ParticleAcceleratorControlBoxWires.Strength:
if (args.Action == WiresAction.Pulse)
{
SetStrength(_selectedStrength + 1);
}
else
{
_wireStrengthCut = args.Action == WiresAction.Cut;
}
break;
case ParticleAcceleratorControlBoxWires.Interface:
if (args.Action == WiresAction.Pulse)
{
_wireInterfaceBlocked ^= true;
}
else
{
_wireInterfaceBlocked = args.Action == WiresAction.Cut;
}
break;
case ParticleAcceleratorControlBoxWires.Limiter:
if (args.Action == WiresAction.Pulse)
{
Owner.PopupMessageEveryone(Loc.GetString("particle-accelerator-control-box-component-wires-update-limiter-on-pulse"));
}
else
{
_wireLimiterCut = args.Action == WiresAction.Cut;
if (_selectedStrength == ParticleAcceleratorPowerState.Level3 && !_wireLimiterCut)
{
// Yes, it's a feature that mending this wire WON'T WORK if the strength wire is also cut.
// Since that blocks SetStrength().
SetStrength(ParticleAcceleratorPowerState.Level2);
}
}
break;
}
UpdateUI();
UpdateWireStatus();
}
private void UpdateWireStatus()
{
if (!_entMan.TryGetComponent(Owner, out WiresComponent? wires))
{
return;
}
var powerBlock = _wirePowerBlocked;
var keyboardLight = new StatusLightData(Color.LimeGreen,
_wireInterfaceBlocked
? StatusLightState.BlinkingFast
: StatusLightState.On,
"KEYB");
var powerLight = new StatusLightData(
Color.Yellow,
powerBlock ? StatusLightState.Off : StatusLightState.On,
"POWR");
var limiterLight = new StatusLightData(
_wireLimiterCut ? Color.Purple : Color.Teal,
StatusLightState.On,
"LIMT");
var strengthLight = new StatusLightData(
Color.Blue,
_wireStrengthCut ? StatusLightState.BlinkingSlow : StatusLightState.On,
"STRC");
wires.SetStatus(ParticleAcceleratorWireStatus.Keyboard, keyboardLight);
wires.SetStatus(ParticleAcceleratorWireStatus.Power, powerLight);
wires.SetStatus(ParticleAcceleratorWireStatus.Limiter, limiterLight);
wires.SetStatus(ParticleAcceleratorWireStatus.Strength, strengthLight);
}
*/
public void RescanParts(IPlayerSession? playerSession = null)
{
SwitchOff(playerSession, true);
foreach (var part in AllParts())
{
if (_entMan.TryGetComponent(part.Owner, out ParticleAcceleratorPartComponent? paPart))
paPart.Master = null;
}
_isAssembled = false;
_partFuelChamber = null;
_partEndCap = null;
_partPowerBox = null;
_partEmitterLeft = null;
_partEmitterCenter = null;
_partEmitterRight = null;
var xform = _entMan.GetComponent<TransformComponent>(Owner);
// Find fuel chamber first by scanning cardinals.
if (xform.Anchored && _entMan.TryGetComponent(xform.GridUid, out MapGridComponent? grid))
{
foreach (var maybeFuel in grid.GetCardinalNeighborCells(xform.Coordinates))
{
if (_entMan.TryGetComponent(maybeFuel, out _partFuelChamber))
{
break;
}
}
}
if (_partFuelChamber == null)
{
UpdateUI();
return;
}
// Align ourselves to match fuel chamber orientation.
// This means that if you mess up the orientation of the control box it's not a big deal,
// because the sprite is far from obvious about the orientation.
xform.LocalRotation = _entMan.GetComponent<TransformComponent>(_partFuelChamber.Owner).LocalRotation;
var offsetEndCap = RotateOffset((1, 1));
var offsetPowerBox = RotateOffset((1, -1));
var offsetEmitterLeft = RotateOffset((0, -2));
var offsetEmitterCenter = RotateOffset((1, -2));
var offsetEmitterRight = RotateOffset((2, -2));
ScanPart(offsetEndCap, out _partEndCap);
ScanPart(offsetPowerBox, out _partPowerBox);
if (!ScanPart(offsetEmitterCenter, out _partEmitterCenter) ||
_partEmitterCenter.Type != ParticleAcceleratorEmitterType.Center)
{
// if it's the wrong type we need to clear this to avoid shenanigans.
_partEmitterCenter = null;
}
if (ScanPart(offsetEmitterLeft, out _partEmitterLeft) &&
_partEmitterLeft.Type != ParticleAcceleratorEmitterType.Left)
{
_partEmitterLeft = null;
}
if (ScanPart(offsetEmitterRight, out _partEmitterRight) &&
_partEmitterRight.Type != ParticleAcceleratorEmitterType.Right)
{
_partEmitterRight = null;
}
_isAssembled = _partFuelChamber != null &&
_partPowerBox != null &&
_partEmitterCenter != null &&
_partEmitterLeft != null &&
_partEmitterRight != null &&
_partEndCap != null;
foreach (var part in AllParts())
{
if (_entMan.TryGetComponent(part.Owner, out ParticleAcceleratorPartComponent? paPart))
paPart.Master = this;
}
UpdateUI();
Vector2i RotateOffset(in Vector2i vec)
{
var rot = new Angle(_entMan.GetComponent<TransformComponent>(Owner).LocalRotation);
return (Vector2i) rot.RotateVec(vec);
}
}
private bool ScanPart<T>(Vector2i offset, [NotNullWhen(true)] out T? part)
where T : Component
{
var xform = _entMan.GetComponent<TransformComponent>(Owner);
if (!_mapManager.TryGetGrid(xform.GridUid, out var grid))
{
part = default;
return false;
}
var coords = xform.Coordinates;
foreach (var ent in grid.GetOffset(coords, offset))
{
if (_entMan.TryGetComponent(ent, out part) && !part.Deleted)
{
return true;
}
}
part = default;
return false;
}
private IEnumerable<Component> AllParts()
{
if (_partFuelChamber != null)
yield return _partFuelChamber;
if (_partEndCap != null)
yield return _partEndCap;
if (_partPowerBox != null)
yield return _partPowerBox;
if (_partEmitterLeft != null)
yield return _partEmitterLeft;
if (_partEmitterCenter != null)
yield return _partEmitterCenter;
if (_partEmitterRight != null)
yield return _partEmitterRight;
}
public void SwitchOn(IPlayerSession? playerSession = null)
{
DebugTools.Assert(_isAssembled);
if (_isEnabled)
{
return;
}
// Logging
_entMan.TryGetComponent(playerSession?.AttachedEntity, out MindComponent? mindComponent);
if(mindComponent != null)
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{_entMan.ToPrettyString(mindComponent.Owner):player} has set {_entMan.ToPrettyString(Owner)} to on");
_isEnabled = true;
UpdatePowerDraw();
// If we don't have power yet we'll turn on when we receive more power from the powernet.
// if we do we'll just go and turn on right now.
if (_partPowerBox!.PowerConsumerComponent!.ReceivedPower >= _partPowerBox.PowerConsumerComponent.DrawRate)
{
PowerOn();
}
UpdateUI();
}
private void UpdatePowerDraw()
{
_partPowerBox!.PowerConsumerComponent!.DrawRate = PowerDrawFor(_selectedStrength);
}
public void SwitchOff(IPlayerSession? playerSession = null, bool rescan = false)
{
// Logging
_entMan.TryGetComponent(playerSession?.AttachedEntity, out MindComponent? mindComponent);
if(mindComponent != null)
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{_entMan.ToPrettyString(mindComponent.Owner):player} has set {_entMan.ToPrettyString(Owner)} to off{(rescan ? " via rescan" : "")}");
_isEnabled = false;
PowerOff();
UpdateUI();
}
private void PowerOn()
{
DebugTools.Assert(_isEnabled);
DebugTools.Assert(_isAssembled);
if (_isPowered)
{
return;
}
_isPowered = true;
UpdateFiring();
UpdatePartVisualStates();
UpdateUI();
}
private void PowerOff()
{
if (!_isPowered)
{
return;
}
_isPowered = false;
UpdateFiring();
UpdateUI();
UpdatePartVisualStates();
}
public void SetStrength(ParticleAcceleratorPowerState state, IPlayerSession? playerSession = null)
{
if (_wireStrengthCut)
{
return;
}
state = (ParticleAcceleratorPowerState) MathHelper.Clamp(
(int) state,
(int) ParticleAcceleratorPowerState.Standby,
(int) MaxPower);
_selectedStrength = state;
UpdateAppearance();
UpdatePartVisualStates();
// Logging
_entMan.TryGetComponent(playerSession?.AttachedEntity, out MindComponent? mindComponent);
LogImpact impact;
switch (state)
{
default:
case ParticleAcceleratorPowerState.Standby:
case ParticleAcceleratorPowerState.Level0:
impact = LogImpact.Low;
break;
case ParticleAcceleratorPowerState.Level1:
impact = LogImpact.High;
break;
case ParticleAcceleratorPowerState.Level2:
case ParticleAcceleratorPowerState.Level3:
impact = LogImpact.Extreme;
break;
}
if(mindComponent != null)
_adminLogger.Add(LogType.Action, impact, $"{_entMan.ToPrettyString(mindComponent.Owner):player} has set the strength of {_entMan.ToPrettyString(Owner)} to {state}");
if (_isEnabled)
{
UpdatePowerDraw();
UpdateFiring();
}
}
private void UpdateAppearance()
{
if (_entMan.TryGetComponent(Owner, out AppearanceComponent? appearance))
{
appearance.SetData(ParticleAcceleratorVisuals.VisualState,
_apcPowerReceiverComponent!.Powered
? (ParticleAcceleratorVisualState) _selectedStrength
: ParticleAcceleratorVisualState.Unpowered);
}
}
private void UpdateFiring()
{
if (!_isPowered || _selectedStrength == ParticleAcceleratorPowerState.Standby)
{
StopFiring();
}
else
{
StartFiring();
}
}
private void StartFiring()
{
EverythingIsWellToFire();
_fireCancelTokenSrc?.Cancel();
_fireCancelTokenSrc = new CancellationTokenSource();
var cancelToken = _fireCancelTokenSrc.Token;
Timer.SpawnRepeating(_firingDelay, Fire, cancelToken);
}
private void Fire()
{
EverythingIsWellToFire();
_partEmitterCenter!.Fire(_selectedStrength);
_partEmitterLeft!.Fire(_selectedStrength);
_partEmitterRight!.Fire(_selectedStrength);
}
[Conditional("DEBUG")]
private void EverythingIsWellToFire()
{
DebugTools.Assert(!Deleted);
DebugTools.Assert(_isPowered);
DebugTools.Assert(_selectedStrength != ParticleAcceleratorPowerState.Standby);
DebugTools.Assert(_isAssembled);
DebugTools.Assert(_partEmitterCenter != null);
DebugTools.Assert(_partEmitterLeft != null);
DebugTools.Assert(_partEmitterRight != null);
}
private void StopFiring()
{
_fireCancelTokenSrc?.Cancel();
_fireCancelTokenSrc = null;
}
private int PowerDrawFor(ParticleAcceleratorPowerState strength)
{
return strength switch
{
ParticleAcceleratorPowerState.Standby => 0,
ParticleAcceleratorPowerState.Level0 => 1,
ParticleAcceleratorPowerState.Level1 => 3,
ParticleAcceleratorPowerState.Level2 => 4,
ParticleAcceleratorPowerState.Level3 => 5,
_ => 0
} * _powerDrawMult + _powerDrawBase;
}
public void PowerBoxReceivedChanged(PowerConsumerReceivedChanged eventArgs)
{
DebugTools.Assert(_isAssembled);
if (!_isEnabled)
{
return;
}
var isPowered = eventArgs.ReceivedPower >= eventArgs.DrawRate;
if (isPowered)
{
PowerOn();
}
else
{
PowerOff();
}
UpdateUI();
}
private void UpdatePartVisualStates()
{
// UpdatePartVisualState(ControlBox);
UpdatePartVisualState(_partFuelChamber);
UpdatePartVisualState(_partPowerBox);
UpdatePartVisualState(_partEmitterCenter);
UpdatePartVisualState(_partEmitterLeft);
UpdatePartVisualState(_partEmitterRight);
//no endcap because it has no powerlevel-sprites
}
private void UpdatePartVisualState(Component? component)
{
if (component == null || !_entMan.TryGetComponent<AppearanceComponent?>(component.Owner, out var appearanceComponent))
{
return;
}
var state = _isPowered
? (ParticleAcceleratorVisualState) _selectedStrength
: ParticleAcceleratorVisualState.Unpowered;
appearanceComponent.SetData(ParticleAcceleratorVisuals.VisualState, state);
}
public enum ParticleAcceleratorControlBoxWires
{
/// <summary>
/// Pulse toggles Power. Cut permanently turns off until Mend.
/// </summary>
Toggle,
/// <summary> /// <summary>
/// Pulsing increases level until at limit. /// Whether the interface has been disabled via a cut wire or not.
/// Modified by <see cref="ParticleAcceleratorKeyboardWireAction"/>.
/// </summary> /// </summary>
Strength, [ViewVariables]
public bool InterfaceDisabled = false;
/// <summary> /// <summary>
/// Pulsing toggles Button-Disabled on UI. Cut disables, Mend enables. /// Whether the ability to change the strength of the PA has been disabled via a cut wire or not.
/// Modified by <see cref="ParticleAcceleratorStrengthWireAction"/>.
/// </summary> /// </summary>
Interface, [ViewVariables]
public bool StrengthLocked = false;
/// <summary> /// <summary>
/// Pulsing will produce short message about whirring noise. Cutting increases the max level to 3. Mending reduces it back to 2. /// Whether the PA can be turned on.
/// Modified by <see cref="ParticleAcceleratorPowerWireAction"/>.
/// </summary> /// </summary>
Limiter, [ViewVariables]
public bool CanBeEnabled = true;
/// <summary>
/// Does Nothing
/// </summary>
Nothing
}
}
} }

View File

@@ -1,36 +1,27 @@
using Content.Shared.Singularity.Components; using Content.Shared.Singularity.Components;
namespace Content.Server.ParticleAccelerator.Components namespace Content.Server.ParticleAccelerator.Components;
[RegisterComponent]
public sealed class ParticleAcceleratorEmitterComponent : Component
{ {
[RegisterComponent] [DataField("emittedPrototype")]
public sealed class ParticleAcceleratorEmitterComponent : Component [ViewVariables(VVAccess.ReadWrite)]
{ public string EmittedPrototype = "ParticlesProjectile";
[DataField("emitterType")] [DataField("emitterType")]
public ParticleAcceleratorEmitterType Type = ParticleAcceleratorEmitterType.Center; [ViewVariables(VVAccess.ReadWrite)]
public ParticleAcceleratorEmitterType Type = ParticleAcceleratorEmitterType.Fore;
public void Fire(ParticleAcceleratorPowerState strength)
{
var entities = IoCManager.Resolve<IEntityManager>();
var projectile = entities.SpawnEntity("ParticlesProjectile", entities.GetComponent<TransformComponent>(Owner).Coordinates);
if (!entities.TryGetComponent<ParticleProjectileComponent?>(projectile, out var particleProjectileComponent))
{
Logger.Error("ParticleAcceleratorEmitter tried firing particles, but they was spawned without a ParticleProjectileComponent");
return;
}
particleProjectileComponent.Fire(strength, entities.GetComponent<TransformComponent>(Owner).WorldRotation, Owner);
}
public override string ToString() public override string ToString()
{ {
return base.ToString() + $" EmitterType:{Type}"; return base.ToString() + $" EmitterType:{Type}";
} }
} }
public enum ParticleAcceleratorEmitterType public enum ParticleAcceleratorEmitterType
{ {
Left, Port,
Center, Fore,
Right Starboard
}
} }

View File

@@ -1,39 +1,8 @@
namespace Content.Server.ParticleAccelerator.Components namespace Content.Server.ParticleAccelerator.Components;
[RegisterComponent]
public sealed class ParticleAcceleratorPartComponent : Component
{ {
[RegisterComponent] [ViewVariables]
[Virtual] public EntityUid? Master;
public class ParticleAcceleratorPartComponent : Component
{
[ViewVariables] public ParticleAcceleratorControlBoxComponent? Master;
protected override void Initialize()
{
base.Initialize();
// FIXME: this has to be an entity system, full stop.
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(Owner).Anchored = true;
}
public void OnAnchorChanged()
{
RescanIfPossible();
}
protected override void OnRemove()
{
base.OnRemove();
RescanIfPossible();
}
private void RescanIfPossible()
{
Master?.RescanParts();
}
public void Moved()
{
RescanIfPossible();
}
}
} }

View File

@@ -1,16 +1,6 @@
using Content.Server.Power.Components; namespace Content.Server.ParticleAccelerator.Components;
namespace Content.Server.ParticleAccelerator.Components;
[RegisterComponent] [RegisterComponent]
public sealed class ParticleAcceleratorPowerBoxComponent : Component public sealed class ParticleAcceleratorPowerBoxComponent : Component
{ {
[ViewVariables] public PowerConsumerComponent? PowerConsumerComponent;
protected override void Initialize()
{
base.Initialize();
PowerConsumerComponent = Owner.EnsureComponentWarn<PowerConsumerComponent>();
}
} }

View File

@@ -1,66 +1,9 @@
using Content.Server.Projectiles;
using Content.Server.Singularity.Components;
using Content.Shared.Projectiles;
using Content.Shared.Singularity.Components; using Content.Shared.Singularity.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
namespace Content.Server.ParticleAccelerator.Components namespace Content.Server.ParticleAccelerator.Components;
[RegisterComponent]
public sealed class ParticleProjectileComponent : Component
{ {
[RegisterComponent]
public sealed class ParticleProjectileComponent : Component
{
[Dependency] private readonly IEntityManager _entMan = default!;
public ParticleAcceleratorPowerState State; public ParticleAcceleratorPowerState State;
public void Fire(ParticleAcceleratorPowerState state, Angle angle, EntityUid firer)
{
State = state;
if (!_entMan.TryGetComponent<PhysicsComponent>(Owner, out var physicsComponent))
{
Logger.Error("ParticleProjectile tried firing, but it was spawned without a CollidableComponent");
return;
}
var physics = _entMan.System<SharedPhysicsSystem>();
physics.SetBodyStatus(physicsComponent, BodyStatus.InAir);
if (!_entMan.TryGetComponent<ProjectileComponent>(Owner, out var projectileComponent))
{
Logger.Error("ParticleProjectile tried firing, but it was spawned without a ProjectileComponent");
return;
}
_entMan.EntitySysManager.GetEntitySystem<ProjectileSystem>().SetShooter(projectileComponent, firer);
if (!_entMan.TryGetComponent<SinguloFoodComponent>(Owner, 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;
if (_entMan.TryGetComponent(Owner, out AppearanceComponent? appearance))
{
appearance.SetData(ParticleAcceleratorVisuals.VisualState, state);
}
physics.SetLinearVelocity(Owner, angle.ToWorldVec() * 20f, body: physicsComponent);
_entMan.GetComponent<TransformComponent>(Owner).LocalRotation = angle;
Timer.Spawn(3000, () => _entMan.DeleteEntity(Owner));
}
}
} }

View File

@@ -1,5 +1,11 @@
using Content.Server.Mind.Components;
using Content.Server.ParticleAccelerator.Components; using Content.Server.ParticleAccelerator.Components;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Shared.Database;
using Content.Shared.Singularity.Components;
using Robust.Server.Player;
using Robust.Shared.Utility;
using System.Diagnostics;
namespace Content.Server.ParticleAccelerator.EntitySystems; namespace Content.Server.ParticleAccelerator.EntitySystems;
@@ -7,11 +13,361 @@ public sealed partial class ParticleAcceleratorSystem
{ {
private void InitializeControlBoxSystem() private void InitializeControlBoxSystem()
{ {
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, ComponentShutdown>(OnComponentShutdown);
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, PowerChangedEvent>(OnControlBoxPowerChange); SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, PowerChangedEvent>(OnControlBoxPowerChange);
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, ParticleAcceleratorSetEnableMessage>(OnUISetEnableMessage);
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, ParticleAcceleratorSetPowerStateMessage>(OnUISetPowerMessage);
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, ParticleAcceleratorRescanPartsMessage>(OnUIRescanMessage);
} }
private static void OnControlBoxPowerChange(EntityUid uid, ParticleAcceleratorControlBoxComponent component, ref PowerChangedEvent args) public override void Update(float frameTime)
{ {
component.OnPowerStateChanged(args); var curTime = _gameTiming.CurTime;
var query = EntityQueryEnumerator<ParticleAcceleratorControlBoxComponent>();
while (query.MoveNext(out var uid, out var controller))
{
if (controller.Firing && curTime >= controller.NextFire)
Fire(uid, curTime, controller);
}
}
[Conditional("DEBUG")]
private void EverythingIsWellToFire(ParticleAcceleratorControlBoxComponent controller)
{
DebugTools.Assert(controller.Powered);
DebugTools.Assert(controller.SelectedStrength != ParticleAcceleratorPowerState.Standby);
DebugTools.Assert(controller.Assembled);
DebugTools.Assert(EntityManager.EntityExists(controller.PortEmitter));
DebugTools.Assert(EntityManager.EntityExists(controller.ForeEmitter));
DebugTools.Assert(EntityManager.EntityExists(controller.StarboardEmitter));
}
public void Fire(EntityUid uid, TimeSpan curTime, ParticleAcceleratorControlBoxComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return;
comp.LastFire = curTime;
comp.NextFire = curTime + comp.ChargeTime;
EverythingIsWellToFire(comp);
var strength = comp.SelectedStrength;
FireEmitter(comp.PortEmitter!.Value, strength);
FireEmitter(comp.ForeEmitter!.Value, strength);
FireEmitter(comp.StarboardEmitter!.Value, strength);
}
public void SwitchOn(EntityUid uid, IPlayerSession? user = null, ParticleAcceleratorControlBoxComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return;
DebugTools.Assert(comp.Assembled);
if (comp.Enabled || !comp.CanBeEnabled)
return;
if (HasComp<MindComponent>(user?.AttachedEntity))
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{EntityManager.ToPrettyString((EntityUid) user!.AttachedEntity):player} has turned {EntityManager.ToPrettyString(uid)} on");
comp.Enabled = true;
UpdatePowerDraw(uid, comp);
if (!TryComp<PowerConsumerComponent>(comp.PowerBox, out var powerConsumer)
|| powerConsumer.ReceivedPower >= powerConsumer.DrawRate * ParticleAcceleratorControlBoxComponent.RequiredPowerRatio)
PowerOn(uid, comp);
UpdateUI(uid, comp);
}
public void SwitchOff(EntityUid uid, IPlayerSession? user = null, ParticleAcceleratorControlBoxComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return;
if (!comp.Enabled)
return;
if (HasComp<MindComponent>(user?.AttachedEntity))
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{EntityManager.ToPrettyString((EntityUid) user!.AttachedEntity):player} has turned {EntityManager.ToPrettyString(uid)} off");
comp.Enabled = false;
UpdatePowerDraw(uid, comp);
PowerOff(uid, comp);
UpdateUI(uid, comp);
}
public void PowerOn(EntityUid uid, ParticleAcceleratorControlBoxComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return;
DebugTools.Assert(comp.Enabled);
DebugTools.Assert(comp.Assembled);
if (comp.Powered)
return;
comp.Powered = true;
UpdatePowerDraw(uid, comp);
UpdateFiring(uid, comp);
UpdatePartVisualStates(uid, comp);
UpdateUI(uid, comp);
}
public void PowerOff(EntityUid uid, ParticleAcceleratorControlBoxComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return;
if (!comp.Powered)
return;
comp.Powered = false;
UpdatePowerDraw(uid, comp);
UpdateFiring(uid, comp);
UpdatePartVisualStates(uid, comp);
UpdateUI(uid, comp);
}
public void SetStrength(EntityUid uid, ParticleAcceleratorPowerState strength, IPlayerSession? user = null, ParticleAcceleratorControlBoxComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return;
if (comp.StrengthLocked)
return;
strength = (ParticleAcceleratorPowerState) MathHelper.Clamp(
(int) strength,
(int) ParticleAcceleratorPowerState.Standby,
(int) comp.MaxStrength
);
if (strength == comp.SelectedStrength)
return;
if (HasComp<MindComponent>(user?.AttachedEntity))
{
var impact = strength switch
{
ParticleAcceleratorPowerState.Standby => LogImpact.Low,
ParticleAcceleratorPowerState.Level0 => LogImpact.Medium,
ParticleAcceleratorPowerState.Level1 => LogImpact.High,
ParticleAcceleratorPowerState.Level2
or ParticleAcceleratorPowerState.Level3
or _ => LogImpact.Extreme,
};
_adminLogger.Add(LogType.Action, impact, $"{EntityManager.ToPrettyString(user!.AttachedEntity!.Value):player} has set the strength of {EntityManager.ToPrettyString(uid)} to {strength}");
}
comp.SelectedStrength = strength;
UpdateAppearance(uid, comp);
UpdatePartVisualStates(uid, comp);
if (comp.Enabled)
{
UpdatePowerDraw(uid, comp);
UpdateFiring(uid, comp);
}
}
private void UpdateFiring(EntityUid uid, ParticleAcceleratorControlBoxComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return;
if (!comp.Powered || comp.SelectedStrength < ParticleAcceleratorPowerState.Level0)
{
comp.Firing = false;
return;
}
EverythingIsWellToFire(comp);
var curTime = _gameTiming.CurTime;
comp.LastFire = curTime;
comp.NextFire = curTime + comp.ChargeTime;
comp.Firing = true;
}
private void UpdatePowerDraw(EntityUid uid, ParticleAcceleratorControlBoxComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return;
if (!TryComp<PowerConsumerComponent>(comp.PowerBox, out var powerConsumer))
return;
var powerDraw = comp.BasePowerDraw;
if (comp.Enabled)
powerDraw += comp.LevelPowerDraw * (int) comp.SelectedStrength;
powerConsumer.DrawRate = powerDraw;
}
private void UpdateUI(EntityUid uid, ParticleAcceleratorControlBoxComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return;
if (!_uiSystem.TryGetUi(uid, ParticleAcceleratorControlBoxUiKey.Key, out var bui))
return;
var draw = 0f;
var receive = 0f;
if (TryComp<PowerConsumerComponent>(comp.PowerBox, out var powerConsumer))
{
draw = powerConsumer.DrawRate;
receive = powerConsumer.ReceivedPower;
}
_uiSystem.SetUiState(bui, new ParticleAcceleratorUIState(
comp.Assembled,
comp.Enabled,
comp.SelectedStrength,
(int) draw,
(int) receive,
comp.StarboardEmitter != null,
comp.ForeEmitter != null,
comp.PortEmitter != null,
comp.PowerBox != null,
comp.FuelChamber != null,
comp.EndCap != null,
comp.InterfaceDisabled,
comp.MaxStrength,
comp.StrengthLocked
));
}
private void UpdateAppearance(EntityUid uid, ParticleAcceleratorControlBoxComponent? comp = null, AppearanceComponent? appearance = null)
{
if (!Resolve(uid, ref comp))
return;
_appearanceSystem.SetData(
uid,
ParticleAcceleratorVisuals.VisualState,
TryComp<ApcPowerReceiverComponent>(uid, out var apcPower) && !apcPower.Powered
? ParticleAcceleratorVisualState.Unpowered
: (ParticleAcceleratorVisualState) comp.SelectedStrength,
appearance
);
}
private void UpdatePartVisualStates(EntityUid uid, ParticleAcceleratorControlBoxComponent? controller = null)
{
if (!Resolve(uid, ref controller))
return;
var state = controller.Powered ? (ParticleAcceleratorVisualState) controller.SelectedStrength : ParticleAcceleratorVisualState.Unpowered;
// UpdatePartVisualState(ControlBox); (We are the control box)
if (controller.FuelChamber.HasValue)
_appearanceSystem.SetData(controller.FuelChamber!.Value, ParticleAcceleratorVisuals.VisualState, state);
if (controller.PowerBox.HasValue)
_appearanceSystem.SetData(controller.PowerBox!.Value, ParticleAcceleratorVisuals.VisualState, state);
if (controller.PortEmitter.HasValue)
_appearanceSystem.SetData(controller.PortEmitter!.Value, ParticleAcceleratorVisuals.VisualState, state);
if (controller.ForeEmitter.HasValue)
_appearanceSystem.SetData(controller.ForeEmitter!.Value, ParticleAcceleratorVisuals.VisualState, state);
if (controller.StarboardEmitter.HasValue)
_appearanceSystem.SetData(controller.StarboardEmitter!.Value, ParticleAcceleratorVisuals.VisualState, state);
//no endcap because it has no powerlevel-sprites
}
private IEnumerable<EntityUid> AllParts(EntityUid uid, ParticleAcceleratorControlBoxComponent? comp = null)
{
if (Resolve(uid, ref comp))
{
if (comp.FuelChamber.HasValue)
yield return comp.FuelChamber.Value;
if (comp.EndCap.HasValue)
yield return comp.EndCap.Value;
if (comp.PowerBox.HasValue)
yield return comp.PowerBox.Value;
if (comp.PortEmitter.HasValue)
yield return comp.PortEmitter.Value;
if (comp.ForeEmitter.HasValue)
yield return comp.ForeEmitter.Value;
if (comp.StarboardEmitter.HasValue)
yield return comp.StarboardEmitter.Value;
}
}
private void OnComponentStartup(EntityUid uid, ParticleAcceleratorControlBoxComponent comp, ComponentStartup args)
{
if (TryComp<ParticleAcceleratorPartComponent>(uid, out var part))
part.Master = uid;
}
private void OnComponentShutdown(EntityUid uid, ParticleAcceleratorControlBoxComponent comp, ComponentShutdown args)
{
if (TryComp<ParticleAcceleratorPartComponent>(uid, out var partStatus))
partStatus.Master = null;
var partQuery = GetEntityQuery<ParticleAcceleratorPartComponent>();
foreach (var part in AllParts(uid, comp))
{
if (partQuery.TryGetComponent(part, out var partData))
partData.Master = null;
}
}
// This is the power state for the PA control box itself.
// Keep in mind that the PA itself can keep firing as long as the HV cable under the power box has... power.
private void OnControlBoxPowerChange(EntityUid uid, ParticleAcceleratorControlBoxComponent comp, ref PowerChangedEvent args)
{
UpdateAppearance(uid, comp);
if (!args.Powered)
_uiSystem.TryCloseAll(uid, ParticleAcceleratorControlBoxUiKey.Key);
}
private void OnUISetEnableMessage(EntityUid uid, ParticleAcceleratorControlBoxComponent comp, ParticleAcceleratorSetEnableMessage msg)
{
if (!ParticleAcceleratorControlBoxUiKey.Key.Equals(msg.UiKey))
return;
if (comp.InterfaceDisabled)
return;
if (TryComp<ApcPowerReceiverComponent>(uid, out var apcPower) && !apcPower.Powered)
return;
if (msg.Enabled)
{
if (comp.Assembled)
SwitchOn(uid, (IPlayerSession?) msg.Session, comp);
}
else
SwitchOff(uid, (IPlayerSession?) msg.Session, comp);
UpdateUI(uid, comp);
}
private void OnUISetPowerMessage(EntityUid uid, ParticleAcceleratorControlBoxComponent comp, ParticleAcceleratorSetPowerStateMessage msg)
{
if (!ParticleAcceleratorControlBoxUiKey.Key.Equals(msg.UiKey))
return;
if (comp.InterfaceDisabled)
return;
if (TryComp<ApcPowerReceiverComponent>(uid, out var apcPower) && !apcPower.Powered)
return;
SetStrength(uid, msg.State, (IPlayerSession?) msg.Session, comp);
UpdateUI(uid, comp);
}
private void OnUIRescanMessage(EntityUid uid, ParticleAcceleratorControlBoxComponent comp, ParticleAcceleratorRescanPartsMessage msg)
{
if (!ParticleAcceleratorControlBoxUiKey.Key.Equals(msg.UiKey))
return;
if (comp.InterfaceDisabled)
return;
if (TryComp<ApcPowerReceiverComponent>(uid, out var apcPower) && !apcPower.Powered)
return;
RescanParts(uid, (IPlayerSession?) msg.Session, comp);
UpdateUI(uid, comp);
} }
} }

View File

@@ -0,0 +1,62 @@
using Content.Server.ParticleAccelerator.Components;
using Content.Server.Singularity.Components;
using Content.Shared.Projectiles;
using Content.Shared.Singularity.Components;
using Robust.Shared.Physics.Components;
namespace Content.Server.ParticleAccelerator.EntitySystems;
public sealed partial class ParticleAcceleratorSystem
{
private void FireEmitter(EntityUid uid, ParticleAcceleratorPowerState strength, ParticleAcceleratorEmitterComponent? emitter = null)
{
if (!Resolve(uid, ref emitter))
return;
var xformQuery = GetEntityQuery<TransformComponent>();
if (!xformQuery.TryGetComponent(uid, out var xform))
{
Logger.Error("ParticleAccelerator attempted to emit a particle without (having) a transform from which to base its initial position and orientation.");
return;
}
var emitted = Spawn(emitter.EmittedPrototype, xform.Coordinates);
if (xformQuery.TryGetComponent(emitted, out var particleXform))
_transformSystem.SetLocalRotation(emitted, xform.LocalRotation, particleXform);
if (TryComp<PhysicsComponent>(emitted, out var particlePhys))
{
var angle = _transformSystem.GetWorldRotation(uid, xformQuery);
_physicsSystem.SetBodyStatus(particlePhys, BodyStatus.InAir);
var velocity = angle.ToWorldVec() * 20f;
if (TryComp<PhysicsComponent>(uid, out var phys))
velocity += phys.LinearVelocity; // Inherit velocity from parent so if the clown has strapped a dozen engines to departures we don't outpace the particles.
_physicsSystem.SetLinearVelocity(emitted, velocity, body: particlePhys);
}
if (TryComp<ProjectileComponent>(emitted, out var proj))
_projectileSystem.SetShooter(proj, uid);
if (TryComp<SinguloFoodComponent>(emitted, out var food))
{
// TODO: Unhardcode this.
food.Energy = strength switch
{
ParticleAcceleratorPowerState.Standby => 0,
ParticleAcceleratorPowerState.Level0 => 1,
ParticleAcceleratorPowerState.Level1 => 3,
ParticleAcceleratorPowerState.Level2 => 6,
ParticleAcceleratorPowerState.Level3 => 10,
_ => 0,
} * 10;
}
if (TryComp<ParticleProjectileComponent>(emitted, out var particle))
particle.State = strength;
_appearanceSystem.SetData(emitted, ParticleAcceleratorVisuals.VisualState, strength);
}
}

View File

@@ -1,30 +1,162 @@
using Content.Server.ParticleAccelerator.Components; using Content.Server.ParticleAccelerator.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Physics.Components; using Robust.Server.Player;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Events;
using System.Diagnostics.CodeAnalysis;
namespace Content.Server.ParticleAccelerator.EntitySystems namespace Content.Server.ParticleAccelerator.EntitySystems;
[UsedImplicitly]
public sealed partial class ParticleAcceleratorSystem
{ {
[UsedImplicitly]
public sealed partial class ParticleAcceleratorSystem
{
private void InitializePartSystem() private void InitializePartSystem()
{ {
SubscribeLocalEvent<ParticleAcceleratorPartComponent, ComponentShutdown>(OnComponentShutdown);
SubscribeLocalEvent<ParticleAcceleratorPartComponent, MoveEvent>(OnMoveEvent); SubscribeLocalEvent<ParticleAcceleratorPartComponent, MoveEvent>(OnMoveEvent);
SubscribeLocalEvent<ParticleAcceleratorPartComponent, PhysicsBodyTypeChangedEvent>(BodyTypeChanged); SubscribeLocalEvent<ParticleAcceleratorPartComponent, PhysicsBodyTypeChangedEvent>(BodyTypeChanged);
} }
private static void BodyTypeChanged( public void RescanParts(EntityUid uid, IPlayerSession? user = null, ParticleAcceleratorControlBoxComponent? controller = null)
EntityUid uid,
ParticleAcceleratorPartComponent component,
ref PhysicsBodyTypeChangedEvent args)
{ {
component.OnAnchorChanged(); if (!Resolve(uid, ref controller))
return;
SwitchOff(uid, user, controller);
var partQuery = GetEntityQuery<ParticleAcceleratorPartComponent>();
foreach (var part in AllParts(uid, controller))
{
if (partQuery.TryGetComponent(part, out var partState))
partState.Master = null;
} }
private static void OnMoveEvent(EntityUid uid, ParticleAcceleratorPartComponent component, ref MoveEvent args) controller.Assembled = false;
controller.FuelChamber = null;
controller.EndCap = null;
controller.PowerBox = null;
controller.PortEmitter = null;
controller.ForeEmitter = null;
controller.StarboardEmitter = null;
var xformQuery = GetEntityQuery<TransformComponent>();
if (!xformQuery.TryGetComponent(uid, out var xform) || !xform.Anchored)
return;
var gridUid = xform.GridUid;
if (gridUid == null || gridUid != xform.ParentUid || !_mapManager.TryGetGrid(gridUid, out var grid))
return;
// Find fuel chamber first by scanning cardinals.
var fuelQuery = GetEntityQuery<ParticleAcceleratorFuelChamberComponent>();
foreach (var adjacent in grid.GetCardinalNeighborCells(xform.Coordinates))
{ {
component.Moved(); if (fuelQuery.HasComponent(adjacent)
&& partQuery.TryGetComponent(adjacent, out var partState)
&& partState.Master == null)
{
controller.FuelChamber = adjacent;
break;
} }
} }
if (controller.FuelChamber == null)
{
UpdateUI(uid, controller);
return;
}
// Align ourselves to match fuel chamber orientation.
// This means that if you mess up the orientation of the control box it's not a big deal,
// because the sprite is far from obvious about the orientation.
var fuelXform = xformQuery.GetComponent(controller.FuelChamber!.Value);
var rotation = fuelXform.LocalRotation;
_transformSystem.SetLocalRotation(uid, rotation, xform);
// Calculate offsets for each of the parts of the PA.
// These are all done relative to the fuel chamber BC that is basically the center of the machine.
var positionFuelChamber = grid.TileIndicesFor(fuelXform.Coordinates); // //
var positionEndCap = positionFuelChamber + (Vector2i) rotation.RotateVec((0, 1)); // n // n: End Cap
var positionPowerBox = positionFuelChamber + (Vector2i) rotation.RotateVec((0, -1)); // CF // C: Control Box, F: Fuel Chamber
var positionPortEmitter = positionFuelChamber + (Vector2i) rotation.RotateVec((1, -2)); // P // P: Power Box
var positionForeEmitter = positionFuelChamber + (Vector2i) rotation.RotateVec((0, -2)); // EEE // E: Emitter (Starboard, Fore, Port)
var positionStarboardEmitter = positionFuelChamber + (Vector2i) rotation.RotateVec((-1, -2)); // //
ScanPart<ParticleAcceleratorEndCapComponent>(gridUid!.Value, positionEndCap, rotation, out controller.EndCap, out var _, grid);
ScanPart<ParticleAcceleratorPowerBoxComponent>(gridUid!.Value, positionPowerBox, rotation, out controller.PowerBox, out var _, grid);
if (!ScanPart<ParticleAcceleratorEmitterComponent>(gridUid!.Value, positionPortEmitter, rotation, out controller.PortEmitter, out var portEmitter, grid)
|| portEmitter!.Type != ParticleAcceleratorEmitterType.Port)
controller.PortEmitter = null;
if (!ScanPart<ParticleAcceleratorEmitterComponent>(gridUid!.Value, positionForeEmitter, rotation, out controller.ForeEmitter, out var foreEmitter, grid)
|| foreEmitter!.Type != ParticleAcceleratorEmitterType.Fore)
controller.ForeEmitter = null;
if (!ScanPart<ParticleAcceleratorEmitterComponent>(gridUid!.Value, positionStarboardEmitter, rotation, out controller.StarboardEmitter, out var starboardEmitter, grid)
|| starboardEmitter!.Type != ParticleAcceleratorEmitterType.Starboard)
controller.StarboardEmitter = null;
controller.Assembled =
controller.FuelChamber.HasValue
&& controller.EndCap.HasValue
&& controller.PowerBox.HasValue
&& controller.PortEmitter.HasValue
&& controller.ForeEmitter.HasValue
&& controller.StarboardEmitter.HasValue;
foreach (var part in AllParts(uid, controller))
{
if (partQuery.TryGetComponent(part, out var partState))
partState.Master = uid;
}
UpdatePowerDraw(uid, controller);
UpdateUI(uid, controller);
}
private bool ScanPart<T>(EntityUid uid, Vector2i coordinates, Angle? rotation, [NotNullWhen(true)] out EntityUid? part, [NotNullWhen(true)] out T? comp, MapGridComponent? grid = null)
where T : Component
{
if (!Resolve(uid, ref grid))
{
part = null;
comp = null;
return false;
}
var compQuery = GetEntityQuery<T>();
foreach (var entity in grid.GetAnchoredEntities(coordinates))
{
if (compQuery.TryGetComponent(entity, out comp)
&& TryComp<ParticleAcceleratorPartComponent>(entity, out var partState) && partState.Master == null
&& (rotation == null || MathHelper.CloseTo(Transform(entity).LocalRotation.Theta, rotation!.Value.Theta)))
{
part = entity;
return true;
}
}
part = null;
comp = null;
return false;
}
private void OnComponentShutdown(EntityUid uid, ParticleAcceleratorPartComponent comp, ComponentShutdown args)
{
if (EntityManager.EntityExists(comp.Master))
RescanParts(comp.Master!.Value);
}
private void BodyTypeChanged(EntityUid uid, ParticleAcceleratorPartComponent comp, ref PhysicsBodyTypeChangedEvent args)
{
if (EntityManager.EntityExists(comp.Master))
RescanParts(comp.Master!.Value);
}
private void OnMoveEvent(EntityUid uid, ParticleAcceleratorPartComponent comp, ref MoveEvent args)
{
if (EntityManager.EntityExists(comp.Master))
RescanParts(comp.Master!.Value);
}
} }

View File

@@ -1,22 +1,28 @@
using Content.Server.ParticleAccelerator.Components; using Content.Server.ParticleAccelerator.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
namespace Content.Server.ParticleAccelerator.EntitySystems namespace Content.Server.ParticleAccelerator.EntitySystems;
public sealed partial class ParticleAcceleratorSystem
{ {
public sealed partial class ParticleAcceleratorSystem
{
private void InitializePowerBoxSystem() private void InitializePowerBoxSystem()
{ {
SubscribeLocalEvent<ParticleAcceleratorPowerBoxComponent, PowerConsumerReceivedChanged>(PowerBoxReceivedChanged); SubscribeLocalEvent<ParticleAcceleratorPowerBoxComponent, PowerConsumerReceivedChanged>(PowerBoxReceivedChanged);
} }
private void PowerBoxReceivedChanged( private void PowerBoxReceivedChanged(EntityUid uid, ParticleAcceleratorPowerBoxComponent component, ref PowerConsumerReceivedChanged args)
EntityUid uid,
ParticleAcceleratorPowerBoxComponent component,
ref PowerConsumerReceivedChanged args)
{ {
if (TryComp(uid, out ParticleAcceleratorPartComponent? paPart)) if (!TryComp<ParticleAcceleratorPartComponent>(uid, out var part))
paPart.Master?.PowerBoxReceivedChanged(args); return;
} if (!TryComp<ParticleAcceleratorControlBoxComponent>(part.Master, out var controller))
return;
var master = part.Master!.Value;
if (controller.Enabled && args.ReceivedPower >= args.DrawRate * ParticleAcceleratorControlBoxComponent.RequiredPowerRatio)
PowerOn(master, comp: controller);
else
PowerOff(master, comp: controller);
UpdateUI(master, controller);
} }
} }

View File

@@ -1,7 +1,23 @@
using Content.Server.Administration.Logs;
using Content.Server.Projectiles;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Map;
using Robust.Shared.Timing;
using Robust.Server.GameObjects;
namespace Content.Server.ParticleAccelerator.EntitySystems; namespace Content.Server.ParticleAccelerator.EntitySystems;
public sealed partial class ParticleAcceleratorSystem : EntitySystem public sealed partial class ParticleAcceleratorSystem : EntitySystem
{ {
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ProjectileSystem _projectileSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();

View File

@@ -0,0 +1,35 @@
using Content.Server.ParticleAccelerator.Components;
using Content.Server.Wires;
using Content.Shared.Singularity.Components;
using Content.Shared.Wires;
namespace Content.Server.ParticleAccelerator.Wires;
public sealed class ParticleAcceleratorKeyboardWireAction : ComponentWireAction<ParticleAcceleratorControlBoxComponent>
{
public override string Name { get; set; } = "wire-name-pa-keyboard";
public override Color Color { get; set; } = Color.LimeGreen;
public override object StatusKey { get; } = ParticleAcceleratorWireStatus.Keyboard;
public override StatusLightState? GetLightState(Wire wire, ParticleAcceleratorControlBoxComponent component)
{
return component.InterfaceDisabled ? StatusLightState.BlinkingFast : StatusLightState.On;
}
public override bool Cut(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller)
{
controller.InterfaceDisabled = true;
return true;
}
public override bool Mend(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller)
{
controller.InterfaceDisabled = false;
return true;
}
public override void Pulse(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller)
{
controller.InterfaceDisabled = !controller.InterfaceDisabled;
}
}

View File

@@ -0,0 +1,69 @@
using Content.Server.ParticleAccelerator.Components;
using Content.Server.ParticleAccelerator.EntitySystems;
using Content.Server.Popups;
using Content.Server.Wires;
using Content.Shared.Popups;
using Content.Shared.Singularity.Components;
using Content.Shared.Wires;
using Robust.Server.GameObjects;
namespace Content.Server.ParticleAccelerator.Wires;
public sealed class ParticleAcceleratorLimiterWireAction : ComponentWireAction<ParticleAcceleratorControlBoxComponent>
{
public override string Name { get; set; } = "wire-name-pa-limiter";
public override Color Color { get; set; } = Color.Teal;
public override object StatusKey { get; } = ParticleAcceleratorWireStatus.Limiter;
public override StatusLightData? GetStatusLightData(Wire wire)
{
var result = base.GetStatusLightData(wire);
if (result.HasValue
&& EntityManager.TryGetComponent<ParticleAcceleratorControlBoxComponent>(wire.Owner, out var controller)
&& controller.MaxStrength >= ParticleAcceleratorPowerState.Level3)
result = new(Color.Purple, result.Value.State, result.Value.Text);
return result;
}
public override StatusLightState? GetLightState(Wire wire, ParticleAcceleratorControlBoxComponent component)
{
return StatusLightState.On;
}
public override bool Cut(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller)
{
controller.MaxStrength = ParticleAcceleratorPowerState.Level3;
return true;
}
public override bool Mend(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller)
{
controller.MaxStrength = ParticleAcceleratorPowerState.Level2;
if (controller.SelectedStrength <= controller.MaxStrength || controller.StrengthLocked)
return true;
// Yes, it's a feature that mending this wire WON'T WORK if the strength wire is also cut.
// Since that blocks SetStrength().
var paSystem = EntityManager.System<ParticleAcceleratorSystem>();
var userSession = EntityManager.TryGetComponent<ActorComponent>(user, out var actor) ? actor.PlayerSession : null;
paSystem.SetStrength(wire.Owner, controller.MaxStrength, userSession, controller);
return true;
}
public override void Pulse(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller)
{
EntityManager.System<PopupSystem>().PopupEntity(
Loc.GetString("particle-accelerator-control-box-component-wires-update-limiter-on-pulse"),
user,
PopupType.SmallCaution
);
}
public override void Update(Wire wire)
{
}
}

View File

@@ -0,0 +1,40 @@
using Content.Server.ParticleAccelerator.Components;
using Content.Server.ParticleAccelerator.EntitySystems;
using Content.Server.Wires;
using Content.Shared.Singularity.Components;
using Content.Shared.Wires;
using Robust.Server.GameObjects;
using Robust.Shared.Random;
namespace Content.Server.ParticleAccelerator.Wires;
public sealed class ParticleAcceleratorStrengthWireAction : ComponentWireAction<ParticleAcceleratorControlBoxComponent>
{
public override string Name { get; set; } = "wire-name-pa-strength";
public override Color Color { get; set; } = Color.Blue;
public override object StatusKey { get; } = ParticleAcceleratorWireStatus.Strength;
public override StatusLightState? GetLightState(Wire wire, ParticleAcceleratorControlBoxComponent component)
{
return component.StrengthLocked ? StatusLightState.BlinkingSlow : StatusLightState.On;
}
public override bool Cut(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller)
{
controller.StrengthLocked = true;
return true;
}
public override bool Mend(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller)
{
controller.StrengthLocked = false;
return true;
}
public override void Pulse(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller)
{
var paSystem = EntityManager.System<ParticleAcceleratorSystem>();
var userSession = EntityManager.TryGetComponent<ActorComponent>(user, out var actor) ? actor.PlayerSession : null;
paSystem.SetStrength(wire.Owner, (ParticleAcceleratorPowerState) ((int) controller.SelectedStrength + 1), userSession, controller);
}
}

View File

@@ -0,0 +1,49 @@
using Content.Server.ParticleAccelerator.Components;
using Content.Server.ParticleAccelerator.EntitySystems;
using Content.Server.Wires;
using Content.Shared.Singularity.Components;
using Content.Shared.Wires;
using Robust.Server.GameObjects;
namespace Content.Server.ParticleAccelerator.Wires;
public sealed class ParticleAcceleratorPowerWireAction : ComponentWireAction<ParticleAcceleratorControlBoxComponent>
{
public override string Name { get; set; } = "wire-name-pa-power";
public override Color Color { get; set; } = Color.Yellow;
public override object StatusKey { get; } = ParticleAcceleratorWireStatus.Power;
public override StatusLightState? GetLightState(Wire wire, ParticleAcceleratorControlBoxComponent component)
{
if (!component.CanBeEnabled)
return StatusLightState.Off;
return component.Enabled ? StatusLightState.On : StatusLightState.BlinkingSlow;
}
public override bool Cut(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller)
{
var paSystem = EntityManager.System<ParticleAcceleratorSystem>();
var userSession = EntityManager.TryGetComponent<ActorComponent>(user, out var actor) ? actor.PlayerSession : null;
controller.CanBeEnabled = false;
paSystem.SwitchOff(wire.Owner, userSession, controller);
return true;
}
public override bool Mend(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller)
{
controller.CanBeEnabled = true;
return true;
}
public override void Pulse(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller)
{
var paSystem = EntityManager.System<ParticleAcceleratorSystem>();
var userSession = EntityManager.TryGetComponent<ActorComponent>(user, out var actor) ? actor.PlayerSession : null;
if (controller.Enabled)
paSystem.SwitchOff(wire.Owner, userSession, controller);
else if (controller.Assembled)
paSystem.SwitchOn(wire.Owner, userSession, controller);
}
}

View File

@@ -7,9 +7,9 @@ namespace Content.Server.Singularity.EntitySystems;
public sealed class SingularityGeneratorSystem : EntitySystem public sealed class SingularityGeneratorSystem : EntitySystem
{ {
#region Dependencies #region Dependencies
[Dependency] private readonly IViewVariablesManager _vvm = default!; [Dependency] private readonly IViewVariablesManager _vvm = default!;
#endregion Dependencies #endregion Dependencies
public override void Initialize() public override void Initialize()
{ {
@@ -40,29 +40,32 @@ public sealed class SingularityGeneratorSystem : EntitySystem
/// <param name="comp">The state of the singularity generator.</param> /// <param name="comp">The state of the singularity generator.</param>
private void OnPassThreshold(EntityUid uid, SingularityGeneratorComponent? comp) private void OnPassThreshold(EntityUid uid, SingularityGeneratorComponent? comp)
{ {
if(!Resolve(uid, ref comp)) if (!Resolve(uid, ref comp))
return; return;
SetPower(comp, 0); SetPower(uid, 0, comp);
EntityManager.SpawnEntity(comp.SpawnPrototype, Transform(comp.Owner).Coordinates); EntityManager.SpawnEntity(comp.SpawnPrototype, Transform(uid).Coordinates);
} }
#region Getters/Setters #region Getters/Setters
/// <summary> /// <summary>
/// Setter for <see cref="SingularityGeneratorComponent.Power"/> /// Setter for <see cref="SingularityGeneratorComponent.Power"/>
/// If the singularity generator passes its threshold it also spawns a singularity. /// If the singularity generator passes its threshold it also spawns a singularity.
/// </summary> /// </summary>
/// <param name="comp">The singularity generator component.</param> /// <param name="comp">The singularity generator component.</param>
/// <param name="value">The new power level for the generator component to have.</param> /// <param name="value">The new power level for the generator component to have.</param>
public void SetPower(SingularityGeneratorComponent comp, float value) public void SetPower(EntityUid uid, float value, SingularityGeneratorComponent? comp = null)
{ {
if (!Resolve(uid, ref comp))
return;
var oldValue = comp.Power; var oldValue = comp.Power;
if (value == oldValue) if (value == oldValue)
return; return;
comp.Power = value; comp.Power = value;
if (comp.Power >= comp.Threshold && oldValue < comp.Threshold) if (comp.Power >= comp.Threshold && oldValue < comp.Threshold)
OnPassThreshold(comp.Owner, comp); OnPassThreshold(uid, comp);
} }
/// <summary> /// <summary>
@@ -71,48 +74,22 @@ public sealed class SingularityGeneratorSystem : EntitySystem
/// </summary> /// </summary>
/// <param name="comp">The singularity generator component.</param> /// <param name="comp">The singularity generator component.</param>
/// <param name="value">The new threshold power level for the generator component to have.</param> /// <param name="value">The new threshold power level for the generator component to have.</param>
public void SetThreshold(SingularityGeneratorComponent comp, float value) public void SetThreshold(EntityUid uid, float value, SingularityGeneratorComponent? comp = null)
{ {
if (!Resolve(uid, ref comp))
return;
var oldValue = comp.Threshold; var oldValue = comp.Threshold;
if (value == comp.Threshold) if (value == comp.Threshold)
return; return;
comp.Power = value; comp.Power = value;
if (comp.Power >= comp.Threshold && comp.Power < oldValue) if (comp.Power >= comp.Threshold && comp.Power < oldValue)
OnPassThreshold(comp.Owner, comp); OnPassThreshold(uid, comp);
}
#region VV
/// <summary>
/// VV setter for <see cref="SingularityGeneratorComponent.Power"/>
/// If the singularity generator passes its threshold it also spawns a singularity.
/// </summary>
/// <param name="uid">The entity hosting the singularity generator that is being modified.</param>
/// <param name="value">The value of the new power level the singularity generator should have.</param>
/// <param name="comp">The singularity generator to change the power level of.</param>
public void SetPower(EntityUid uid, float value, SingularityGeneratorComponent? comp)
{
if(!Resolve(uid, ref comp))
return;
SetPower(comp, value);
} }
#endregion Getters/Setters
/// <summary> #region Event Handlers
/// VV setter for <see cref="SingularityGeneratorComponent.Threshold"/>
/// If the singularity generator has passed its new threshold it also spawns a singularity.
/// </summary>
/// <param name="uid">The entity hosting the singularity generator that is being modified.</param>
/// <param name="value">The value of the new threshold power level the singularity generator should have.</param>
/// <param name="comp">The singularity generator to change the threshold power level of.</param>
public void SetThreshold(EntityUid uid, float value, SingularityGeneratorComponent? comp)
{
if(!Resolve(uid, ref comp))
return;
SetThreshold(comp, value);
}
#endregion VV
#endregion Getters/Setters
#region Event Handlers
/// <summary> /// <summary>
/// Handles PA Particles colliding with a singularity generator. /// Handles PA Particles colliding with a singularity generator.
/// Adds the power from the particles to the generator. /// Adds the power from the particles to the generator.
@@ -123,10 +100,10 @@ public sealed class SingularityGeneratorSystem : EntitySystem
/// <param name="args">The state of the beginning of the collision.</param> /// <param name="args">The state of the beginning of the collision.</param>
private void HandleParticleCollide(EntityUid uid, ParticleProjectileComponent component, ref StartCollideEvent args) private void HandleParticleCollide(EntityUid uid, ParticleProjectileComponent component, ref StartCollideEvent args)
{ {
if (EntityManager.TryGetComponent<SingularityGeneratorComponent?>(args.OtherEntity, out var singularityGeneratorComponent)) if (EntityManager.TryGetComponent<SingularityGeneratorComponent>(args.OtherEntity, out var singularityGeneratorComponent))
{ {
SetPower( SetPower(
singularityGeneratorComponent, args.OtherEntity,
singularityGeneratorComponent.Power + component.State switch singularityGeneratorComponent.Power + component.State switch
{ {
ParticleAcceleratorPowerState.Standby => 0, ParticleAcceleratorPowerState.Standby => 0,
@@ -135,9 +112,11 @@ public sealed class SingularityGeneratorSystem : EntitySystem
ParticleAcceleratorPowerState.Level2 => 4, ParticleAcceleratorPowerState.Level2 => 4,
ParticleAcceleratorPowerState.Level3 => 8, ParticleAcceleratorPowerState.Level3 => 8,
_ => 0 _ => 0
}); },
singularityGeneratorComponent
);
EntityManager.QueueDeleteEntity(uid); EntityManager.QueueDeleteEntity(uid);
} }
} }
#endregion Event Handlers #endregion Event Handlers
} }

View File

@@ -1,5 +1,6 @@
using Content.Server.Administration; using Content.Server.Administration;
using Content.Server.ParticleAccelerator.Components; using Content.Server.ParticleAccelerator.Components;
using Content.Server.ParticleAccelerator.EntitySystems;
using Content.Server.Singularity.Components; using Content.Server.Singularity.Components;
using Content.Server.Singularity.EntitySystems; using Content.Server.Singularity.EntitySystems;
using Content.Shared.Administration; using Content.Shared.Administration;
@@ -44,12 +45,18 @@ namespace Content.Server.Singularity
} }
// Setup PA // Setup PA
foreach (var comp in entityManager.EntityQuery<ParticleAcceleratorControlBoxComponent>()) var paSystem = entitySystemManager.GetEntitySystem<ParticleAcceleratorSystem>();
var paQuery = entityManager.EntityQueryEnumerator<ParticleAcceleratorControlBoxComponent>();
while (paQuery.MoveNext(out var paId, out var paControl))
{ {
comp.RescanParts(); paSystem.RescanParts(paId, controller: paControl);
comp.SetStrength(ParticleAcceleratorPowerState.Level0); if (!paControl.Assembled)
comp.SwitchOn(); continue;
paSystem.SetStrength(paId, ParticleAcceleratorPowerState.Level0, comp: paControl);
paSystem.SwitchOn(paId, comp: paControl);
} }
shell.WriteLine("Done!"); shell.WriteLine("Done!");
} }
} }

View File

@@ -22,13 +22,13 @@ namespace Content.Shared.Singularity.Components
} }
[NetSerializable, Serializable] [NetSerializable, Serializable]
public enum ParticleAcceleratorPowerState public enum ParticleAcceleratorPowerState : byte
{ {
Standby = ParticleAcceleratorVisualState.Powered, Standby = ParticleAcceleratorVisualState.Powered,
Level0 = ParticleAcceleratorVisualState.Level0, Level0 = ParticleAcceleratorVisualState.Level0,
Level1 = ParticleAcceleratorVisualState.Level1, Level1 = ParticleAcceleratorVisualState.Level1,
Level2 = ParticleAcceleratorVisualState.Level2, Level2 = ParticleAcceleratorVisualState.Level2,
Level3 = ParticleAcceleratorVisualState.Level3 Level3 = ParticleAcceleratorVisualState.Level3,
} }
public enum ParticleAcceleratorVisualLayers public enum ParticleAcceleratorVisualLayers
@@ -56,9 +56,9 @@ namespace Content.Shared.Singularity.Components
public int PowerReceive; public int PowerReceive;
//dont need a bool for the controlbox because... this is sent to the controlbox :D //dont need a bool for the controlbox because... this is sent to the controlbox :D
public bool EmitterLeftExists; public bool EmitterStarboardExists;
public bool EmitterCenterExists; public bool EmitterForeExists;
public bool EmitterRightExists; public bool EmitterPortExists;
public bool PowerBoxExists; public bool PowerBoxExists;
public bool FuelChamberExists; public bool FuelChamberExists;
public bool EndCapExists; public bool EndCapExists;
@@ -67,16 +67,16 @@ namespace Content.Shared.Singularity.Components
public ParticleAcceleratorPowerState MaxLevel; public ParticleAcceleratorPowerState MaxLevel;
public bool WirePowerBlock; public bool WirePowerBlock;
public ParticleAcceleratorUIState(bool assembled, bool enabled, ParticleAcceleratorPowerState state, int powerReceive, int powerDraw, bool emitterLeftExists, bool emitterCenterExists, bool emitterRightExists, bool powerBoxExists, bool fuelChamberExists, bool endCapExists, bool interfaceBlock, ParticleAcceleratorPowerState maxLevel, bool wirePowerBlock) public ParticleAcceleratorUIState(bool assembled, bool enabled, ParticleAcceleratorPowerState state, int powerReceive, int powerDraw, bool emitterStarboardExists, bool emitterForeExists, bool emitterPortExists, bool powerBoxExists, bool fuelChamberExists, bool endCapExists, bool interfaceBlock, ParticleAcceleratorPowerState maxLevel, bool wirePowerBlock)
{ {
Assembled = assembled; Assembled = assembled;
Enabled = enabled; Enabled = enabled;
State = state; State = state;
PowerDraw = powerDraw; PowerDraw = powerDraw;
PowerReceive = powerReceive; PowerReceive = powerReceive;
EmitterLeftExists = emitterLeftExists; EmitterStarboardExists = emitterStarboardExists;
EmitterCenterExists = emitterCenterExists; EmitterForeExists = emitterForeExists;
EmitterRightExists = emitterRightExists; EmitterPortExists = emitterPortExists;
PowerBoxExists = powerBoxExists; PowerBoxExists = powerBoxExists;
FuelChamberExists = fuelChamberExists; FuelChamberExists = fuelChamberExists;
EndCapExists = endCapExists; EndCapExists = endCapExists;

View File

@@ -14,3 +14,7 @@ wire-name-power = POWR
wire-name-arcade-invincible = MNGR wire-name-arcade-invincible = MNGR
wire-name-vending-contraband = MNGR wire-name-vending-contraband = MNGR
wire-name-vending-eject = VEND wire-name-vending-eject = VEND
wire-name-pa-keyboard = KEYB
wire-name-pa-limiter = LIMT
wire-name-pa-power = POWR
wire-name-pa-strength = STRC

View File

@@ -69,9 +69,9 @@
- type: StorageFill - type: StorageFill
contents: contents:
- id: MachineParticleAcceleratorEndCapCircuitboard - id: MachineParticleAcceleratorEndCapCircuitboard
- id: MachineParticleAcceleratorEmitterLeftCircuitboard - id: MachineParticleAcceleratorEmitterStarboardCircuitboard
- id: MachineParticleAcceleratorEmitterCenterCircuitboard - id: MachineParticleAcceleratorEmitterForeCircuitboard
- id: MachineParticleAcceleratorEmitterRightCircuitboard - id: MachineParticleAcceleratorEmitterPortCircuitboard
- id: MachineParticleAcceleratorFuelChamberCircuitboard - id: MachineParticleAcceleratorFuelChamberCircuitboard
- id: MachineParticleAcceleratorPowerBoxCircuitboard - id: MachineParticleAcceleratorPowerBoxCircuitboard
- id: ParticleAcceleratorComputerCircuitboard - id: ParticleAcceleratorComputerCircuitboard

View File

@@ -48,43 +48,43 @@
# Emitter # Emitter
- type: entity - type: entity
id: MachineParticleAcceleratorEmitterLeftCircuitboard id: MachineParticleAcceleratorEmitterStarboardCircuitboard
parent: BaseMachineCircuitboard parent: BaseMachineCircuitboard
name: PA emitter left board name: PA starboard emitter board
description: A machine board for a particle accelerator left emitter description: A machine board for a particle accelerator left emitter
components: components:
- type: Sprite - type: Sprite
state: engineering state: engineering
- type: MachineBoard - type: MachineBoard
prototype: ParticleAcceleratorEmitterLeftUnfinished prototype: ParticleAcceleratorEmitterStarboardUnfinished
materialRequirements: materialRequirements:
Glass: 5 Glass: 5
Steel: 5 Steel: 5
- type: entity - type: entity
id: MachineParticleAcceleratorEmitterCenterCircuitboard id: MachineParticleAcceleratorEmitterForeCircuitboard
parent: BaseMachineCircuitboard parent: BaseMachineCircuitboard
name: PA emitter center board name: PA fore emitter board
description: A machine board for a particle accelerator center emitter description: A machine board for a particle accelerator center emitter
components: components:
- type: Sprite - type: Sprite
state: engineering state: engineering
- type: MachineBoard - type: MachineBoard
prototype: ParticleAcceleratorEmitterCenterUnfinished prototype: ParticleAcceleratorEmitterForeUnfinished
materialRequirements: materialRequirements:
Glass: 5 Glass: 5
Steel: 5 Steel: 5
- type: entity - type: entity
id: MachineParticleAcceleratorEmitterRightCircuitboard id: MachineParticleAcceleratorEmitterPortCircuitboard
parent: BaseMachineCircuitboard parent: BaseMachineCircuitboard
name: PA emitter right board name: PA port emitter board
description: A machine board for a particle accelerator right emitter description: A machine board for a particle accelerator right emitter
components: components:
- type: Sprite - type: Sprite
state: engineering state: engineering
- type: MachineBoard - type: MachineBoard
prototype: ParticleAcceleratorEmitterRightUnfinished prototype: ParticleAcceleratorEmitterPortUnfinished
materialRequirements: materialRequirements:
Glass: 5 Glass: 5
Steel: 5 Steel: 5

View File

@@ -1,28 +1,17 @@
- type: entity - type: entity
parent: ParticleAcceleratorBase parent: ParticleAcceleratorFinishedPart
id: ParticleAcceleratorControlBox id: ParticleAcceleratorControlBox
name: PA control computer name: PA control computer
description: This controls the density of the particles. description: This controls the density of the particles.
components: components:
- type: Sprite - type: Sprite
sprite: Structures/Power/Generation/PA/control_box.rsi sprite: Structures/Power/Generation/PA/control_box.rsi
layers: - type: ParticleAcceleratorControlBox
- state: completed
map: [ "enum.ParticleAcceleratorVisualLayers.Base" ]
- state: unlitp
map: [ "enum.ParticleAcceleratorVisualLayers.Unlit" ]
shader: unshaded
visible: false
- type: Appearance
- type: ParticleAcceleratorPartVisuals
stateBase: unlit
- type: ApcPowerReceiver - type: ApcPowerReceiver
powerLoad: 250 powerLoad: 250
- type: ExtensionCableReceiver - type: ExtensionCableReceiver
- type: ParticleAcceleratorControlBox
- type: Construction - type: Construction
graph: ParticleAcceleratorControlBox graph: ParticleAcceleratorControlBox
node: completed
- type: ActivatableUI - type: ActivatableUI
key: enum.ParticleAcceleratorControlBoxUiKey.Key key: enum.ParticleAcceleratorControlBoxUiKey.Key
- type: ActivatableUIRequiresPower - type: ActivatableUIRequiresPower

View File

@@ -1,76 +1,76 @@
- type: entity - type: entity
parent: ParticleAcceleratorFinishedPart parent: ParticleAcceleratorFinishedPart
id: ParticleAcceleratorEmitterLeft id: ParticleAcceleratorEmitterPort
name: PA containment emitter L name: PA port containment emitter
description: This launchs the Alpha particles, might not want to stand near this end. description: This launchs the Alpha particles, might not want to stand near this end.
components: components:
- type: Sprite - type: Sprite
sprite: Structures/Power/Generation/PA/emitter_left.rsi sprite: Structures/Power/Generation/PA/emitter_port.rsi
- type: ParticleAcceleratorEmitter - type: ParticleAcceleratorEmitter
emitterType: Left emitterType: Port
- type: Construction - type: Construction
graph: ParticleAcceleratorEmitterLeft graph: ParticleAcceleratorEmitterPort
- type: entity - type: entity
parent: ParticleAcceleratorFinishedPart parent: ParticleAcceleratorFinishedPart
id: ParticleAcceleratorEmitterCenter id: ParticleAcceleratorEmitterFore
name: PA containment emitter C name: PA fore containment emitter
description: This launchs the Alpha particles, might not want to stand near this end. description: This launchs the Alpha particles, might not want to stand near this end.
components: components:
- type: Sprite - type: Sprite
sprite: Structures/Power/Generation/PA/emitter_center.rsi sprite: Structures/Power/Generation/PA/emitter_fore.rsi
- type: ParticleAcceleratorEmitter - type: ParticleAcceleratorEmitter
emitterType: Center emitterType: Fore
- type: Construction - type: Construction
graph: ParticleAcceleratorEmitterCenter graph: ParticleAcceleratorEmitterFore
- type: entity - type: entity
parent: ParticleAcceleratorFinishedPart parent: ParticleAcceleratorFinishedPart
id: ParticleAcceleratorEmitterRight id: ParticleAcceleratorEmitterStarboard
name: PA containment emitter R name: PA starboard containment emitter
description: This launchs the Alpha particles, might not want to stand near this end. description: This launchs the Alpha particles, might not want to stand near this end.
components: components:
- type: Sprite - type: Sprite
sprite: Structures/Power/Generation/PA/emitter_right.rsi sprite: Structures/Power/Generation/PA/emitter_starboard.rsi
- type: ParticleAcceleratorEmitter - type: ParticleAcceleratorEmitter
emitterType: Right emitterType: Starboard
- type: Construction - type: Construction
graph: ParticleAcceleratorEmitterRight graph: ParticleAcceleratorEmitterStarboard
# Unfinished # Unfinished
- type: entity - type: entity
parent: ParticleAcceleratorUnfinishedBase parent: ParticleAcceleratorUnfinishedBase
id: ParticleAcceleratorEmitterLeftUnfinished id: ParticleAcceleratorEmitterPortUnfinished
name: PA containment emitter L name: PA port containment emitter
suffix: Unfinished, Left suffix: Unfinished, Port
description: This launchs the Alpha particles, might not want to stand near this end. It looks unfinished. description: This launchs the Alpha particles, might not want to stand near this end. It looks unfinished.
components: components:
- type: Sprite - type: Sprite
sprite: Structures/Power/Generation/PA/emitter_left.rsi sprite: Structures/Power/Generation/PA/emitter_port.rsi
- type: Construction - type: Construction
graph: ParticleAcceleratorEmitterLeft graph: ParticleAcceleratorEmitterPort
- type: entity - type: entity
parent: ParticleAcceleratorUnfinishedBase parent: ParticleAcceleratorUnfinishedBase
id: ParticleAcceleratorEmitterCenterUnfinished id: ParticleAcceleratorEmitterForeUnfinished
name: PA containment emitter C name: PA fore containment emitter
suffix: Unfinished suffix: Unfinished, Fore
description: This launchs the Alpha particles, might not want to stand near this end. It looks unfinished. description: This launchs the Alpha particles, might not want to stand near this end. It looks unfinished.
components: components:
- type: Sprite - type: Sprite
sprite: Structures/Power/Generation/PA/emitter_center.rsi sprite: Structures/Power/Generation/PA/emitter_fore.rsi
- type: Construction - type: Construction
graph: ParticleAcceleratorEmitterCenter graph: ParticleAcceleratorEmitterFore
- type: entity - type: entity
parent: ParticleAcceleratorUnfinishedBase parent: ParticleAcceleratorUnfinishedBase
id: ParticleAcceleratorEmitterRightUnfinished id: ParticleAcceleratorEmitterStarboardUnfinished
name: PA containment emitter R name: PA starboard containment emitter
suffix: Unfinished suffix: Unfinished, Starboard
description: This launchs the Alpha particles, might not want to stand near this end. It looks unfinished. description: This launchs the Alpha particles, might not want to stand near this end. It looks unfinished.
components: components:
- type: Sprite - type: Sprite
sprite: Structures/Power/Generation/PA/emitter_right.rsi sprite: Structures/Power/Generation/PA/emitter_starboard.rsi
- type: Construction - type: Construction
graph: ParticleAcceleratorEmitterRight graph: ParticleAcceleratorEmitterStarboard

View File

@@ -34,6 +34,8 @@
- type: ParticleProjectile - type: ParticleProjectile
- type: SinguloFood - type: SinguloFood
# Energy is setup by the PA particle fire function. # Energy is setup by the PA particle fire function.
- type: TimedDespawn
lifetime: 3.0
- type: Appearance - type: Appearance
- type: GenericVisualizer - type: GenericVisualizer
visuals: visuals:

View File

@@ -29,4 +29,3 @@
sprite: Structures/Power/Generation/PA/power_box.rsi sprite: Structures/Power/Generation/PA/power_box.rsi
- type: Construction - type: Construction
graph: ParticleAcceleratorPowerBox graph: ParticleAcceleratorPowerBox

View File

@@ -197,11 +197,11 @@
doAfter: 0.5 doAfter: 0.5
- type: constructionGraph - type: constructionGraph
id: ParticleAcceleratorEmitterLeft id: ParticleAcceleratorEmitterPort
start: start start: start
graph: graph:
- node: start - node: start
entity: ParticleAcceleratorEmitterLeftUnfinished entity: ParticleAcceleratorEmitterPortUnfinished
actions: actions:
- !type:AppearanceChange - !type:AppearanceChange
edges: edges:
@@ -213,7 +213,7 @@
doAfter: 0.5 doAfter: 0.5
- node: wired - node: wired
entity: ParticleAcceleratorEmitterLeftUnfinished entity: ParticleAcceleratorEmitterPortUnfinished
actions: actions:
- !type:AppearanceChange - !type:AppearanceChange
edges: edges:
@@ -236,7 +236,7 @@
doAfter: 0.5 doAfter: 0.5
- node: completed - node: completed
entity: ParticleAcceleratorEmitterLeft entity: ParticleAcceleratorEmitterPort
edges: edges:
- to: wired - to: wired
conditions: conditions:
@@ -246,11 +246,11 @@
doAfter: 0.5 doAfter: 0.5
- type: constructionGraph - type: constructionGraph
id: ParticleAcceleratorEmitterCenter id: ParticleAcceleratorEmitterFore
start: start start: start
graph: graph:
- node: start - node: start
entity: ParticleAcceleratorEmitterCenterUnfinished entity: ParticleAcceleratorEmitterForeUnfinished
actions: actions:
- !type:AppearanceChange - !type:AppearanceChange
edges: edges:
@@ -262,7 +262,7 @@
doAfter: 0.5 doAfter: 0.5
- node: wired - node: wired
entity: ParticleAcceleratorEmitterCenterUnfinished entity: ParticleAcceleratorEmitterForeUnfinished
actions: actions:
- !type:AppearanceChange - !type:AppearanceChange
edges: edges:
@@ -285,7 +285,7 @@
doAfter: 0.5 doAfter: 0.5
- node: completed - node: completed
entity: ParticleAcceleratorEmitterCenter entity: ParticleAcceleratorEmitterFore
edges: edges:
- to: wired - to: wired
conditions: conditions:
@@ -295,11 +295,11 @@
doAfter: 0.5 doAfter: 0.5
- type: constructionGraph - type: constructionGraph
id: ParticleAcceleratorEmitterRight id: ParticleAcceleratorEmitterStarboard
start: start start: start
graph: graph:
- node: start - node: start
entity: ParticleAcceleratorEmitterRightUnfinished entity: ParticleAcceleratorEmitterStarboardUnfinished
actions: actions:
- !type:AppearanceChange - !type:AppearanceChange
edges: edges:
@@ -311,7 +311,7 @@
doAfter: 0.5 doAfter: 0.5
- node: wired - node: wired
entity: ParticleAcceleratorEmitterRightUnfinished entity: ParticleAcceleratorEmitterStarboardUnfinished
actions: actions:
- !type:AppearanceChange - !type:AppearanceChange
edges: edges:
@@ -334,7 +334,7 @@
doAfter: 0.5 doAfter: 0.5
- node: completed - node: completed
entity: ParticleAcceleratorEmitterRight entity: ParticleAcceleratorEmitterStarboard
edges: edges:
- to: wired - to: wired
conditions: conditions:

View File

@@ -75,3 +75,12 @@
wires: wires:
- !type:PowerWireAction - !type:PowerWireAction
- !type:CryoPodEjectLockWireAction - !type:CryoPodEjectLockWireAction
- type: wireLayout
id: ParticleAccelerator
dummyWires: 1
wires:
- !type:ParticleAcceleratorKeyboardWireAction
- !type:ParticleAcceleratorLimiterWireAction
- !type:ParticleAcceleratorPowerWireAction
- !type:ParticleAcceleratorStrengthWireAction

View File

@@ -48,9 +48,9 @@ They connect to HV cables and generate power from nearby radiation sources when
</Box> </Box>
<Box> <Box>
<GuideEntityEmbed Entity="ParticleAcceleratorEmitterLeft" Caption="Emitter L"/> <GuideEntityEmbed Entity="ParticleAcceleratorEmitterStarboard" Caption="Starboard Emitter"/>
<GuideEntityEmbed Entity="ParticleAcceleratorEmitterCenter" Caption="Emitter C"/> <GuideEntityEmbed Entity="ParticleAcceleratorEmitterFore" Caption="Fore Emitter"/>
<GuideEntityEmbed Entity="ParticleAcceleratorEmitterRight" Caption="Emitter R"/> <GuideEntityEmbed Entity="ParticleAcceleratorEmitterPort" Caption="Port Emitter"/>
</Box> </Box>
The Particle Accelerator (PA) is a multi-tile structure that launches acclerated particles from its emitters. Its emitters should always face the gravitational singularity generator. The Particle Accelerator (PA) is a multi-tile structure that launches acclerated particles from its emitters. Its emitters should always face the gravitational singularity generator.

View File

@@ -60,3 +60,17 @@ OrGate: null
IHSVoidsuit: null IHSVoidsuit: null
ClothingHeadHelmetIHSVoidHelm: null ClothingHeadHelmetIHSVoidHelm: null
ClothingHandsGlovesIhscombat: null ClothingHandsGlovesIhscombat: null
# 2023-06-02
# Yes, this is right. They were, in fact, reversed because the default orientation of the particle accelerator was _down_ relative to the screen.
# This resulted in the parts being named assuming that the person building the accelerator treated it like a rocket with the particles coming out the _back_.
# As this was confusing they were converted to nautical orientation with forward being towards the emitters.
MachineParticleAcceleratorEmitterCenterCircuitboard: MachineParticleAcceleratorEmitterForeCircuitboard
MachineParticleAcceleratorEmitterLeftCircuitboard: MachineParticleAcceleratorEmitterStarboardCircuitboard
MachineParticleAcceleratorEmitterRightCircuitboard: MachineParticleAcceleratorEmitterPortCircuitboard
ParticleAcceleratorEmitterCenter: ParticleAcceleratorEmitterFore
ParticleAcceleratorEmitterCenterUnfinished: ParticleAcceleratorEmitterForeUnfinished
ParticleAcceleratorEmitterLeft: ParticleAcceleratorEmitterStarboard
ParticleAcceleratorEmitterLeftUnfinished: ParticleAcceleratorEmitterStarboardUnfinished
ParticleAcceleratorEmitterRight: ParticleAcceleratorEmitterPort
ParticleAcceleratorEmitterRightUnfinished: ParticleAcceleratorEmitterPortUnfinished