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 Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
namespace Content.Client.ParticleAccelerator.UI
{

View File

@@ -19,10 +19,10 @@ namespace Content.Client.ParticleAccelerator.UI
{
private readonly ShaderInstance _greyScaleShader;
private readonly ParticleAcceleratorBoundUserInterface Owner;
private readonly ParticleAcceleratorBoundUserInterface _owner;
private readonly Label _drawLabel;
private readonly NoiseGenerator _drawNoiseGenerator;
private readonly FastNoiseLite _drawNoiseGenerator;
private readonly Button _onButton;
private readonly Button _offButton;
private readonly Button _scanButton;
@@ -36,9 +36,9 @@ namespace Content.Client.ParticleAccelerator.UI
private readonly PASegmentControl _fuelChamberTexture;
private readonly PASegmentControl _controlBoxTexture;
private readonly PASegmentControl _powerBoxTexture;
private readonly PASegmentControl _emitterCenterTexture;
private readonly PASegmentControl _emitterRightTexture;
private readonly PASegmentControl _emitterLeftTexture;
private readonly PASegmentControl _emitterForeTexture;
private readonly PASegmentControl _emitterPortTexture;
private readonly PASegmentControl _emitterStarboardTexture;
private float _time;
private int _lastDraw;
@@ -47,14 +47,16 @@ namespace Content.Client.ParticleAccelerator.UI
private bool _blockSpinBox;
private bool _assembled;
private bool _shouldContinueAnimating;
private int _maxStrength = 3;
public ParticleAcceleratorControlMenu(ParticleAcceleratorBoundUserInterface owner)
{
SetSize = (400, 320);
_greyScaleShader = IoCManager.Resolve<IPrototypeManager>().Index<ShaderPrototype>("Greyscale").Instance();
Owner = owner;
_drawNoiseGenerator = new NoiseGenerator(NoiseGenerator.NoiseType.Fbm);
_owner = owner;
_drawNoiseGenerator = new();
_drawNoiseGenerator.SetFractalType(FastNoiseLite.FractalType.FBm);
_drawNoiseGenerator.SetFrequency(0.5f);
var resourceCache = IoCManager.Resolve<IResourceCache>();
@@ -98,7 +100,7 @@ namespace Content.Client.ParticleAccelerator.UI
MouseFilter = MouseFilterMode.Pass
});
_stateSpinBox = new SpinBox {Value = 0, IsValid = StrengthSpinBoxValid,};
_stateSpinBox = new SpinBox { Value = 0, IsValid = StrengthSpinBoxValid };
_stateSpinBox.InitDefaultButtons();
_stateSpinBox.ValueChanged += PowerStateChanged;
_stateSpinBox.LineEditDisabled = true;
@@ -227,7 +229,8 @@ namespace Content.Client.ParticleAccelerator.UI
Align = Label.AlignMode.Center
},
serviceManual
}
},
Visible = false,
}),
}
},
@@ -267,9 +270,9 @@ namespace Content.Client.ParticleAccelerator.UI
new Control {MinSize = imgSize},
(_powerBoxTexture = Segment("power_box")),
new Control {MinSize = imgSize},
(_emitterLeftTexture = Segment("emitter_left")),
(_emitterCenterTexture = Segment("emitter_center")),
(_emitterRightTexture = Segment("emitter_right")),
(_emitterStarboardTexture = Segment("emitter_starboard")),
(_emitterForeTexture = Segment("emitter_fore")),
(_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 =>
{
@@ -336,7 +339,7 @@ namespace Content.Client.ParticleAccelerator.UI
private bool StrengthSpinBoxValid(int n)
{
return (n >= 0 && n <= 3 && !_blockSpinBox);
return n >= 0 && n <= _maxStrength && !_blockSpinBox;
}
private void PowerStateChanged(ValueChangedEventArgs e)
@@ -356,16 +359,15 @@ namespace Content.Client.ParticleAccelerator.UI
case 3:
newState = ParticleAcceleratorPowerState.Level2;
break;
// They can't reach this level anyway and I just want to fix the bugginess for now.
//case 4:
// newState = ParticleAcceleratorPowerState.Level3;
// break;
case 4:
newState = ParticleAcceleratorPowerState.Level3;
break;
default:
return;
}
_stateSpinBox.SetButtonDisabled(true);
Owner.SendPowerStateMessage(newState);
_owner.SendPowerStateMessage(newState);
}
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
@@ -402,14 +404,15 @@ namespace Content.Client.ParticleAccelerator.UI
});
_shouldContinueAnimating = false;
_alarmControl.StopAnimation("warningAnim");
_alarmControl.Visible = false;
if (maxState == ParticleAcceleratorPowerState.Level3 && enabled && assembled)
_maxStrength = maxState == ParticleAcceleratorPowerState.Level3 ? 4 : 3;
if (_maxStrength > 3 && enabled && assembled)
{
_shouldContinueAnimating = true;
if (!_alarmControl.Visible)
_alarmControl.PlayAnimation(_alarmControlAnimation, "warningAnim");
}
else
_shouldContinueAnimating = false;
}
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)
{
_endCapTexture.SetPowerState(updateMessage, updateMessage.EndCapExists);
_fuelChamberTexture.SetPowerState(updateMessage, updateMessage.FuelChamberExists);
_controlBoxTexture.SetPowerState(updateMessage, true);
_fuelChamberTexture.SetPowerState(updateMessage, updateMessage.FuelChamberExists);
_powerBoxTexture.SetPowerState(updateMessage, updateMessage.PowerBoxExists);
_emitterCenterTexture.SetPowerState(updateMessage, updateMessage.EmitterCenterExists);
_emitterLeftTexture.SetPowerState(updateMessage, updateMessage.EmitterLeftExists);
_emitterRightTexture.SetPowerState(updateMessage, updateMessage.EmitterRightExists);
_emitterStarboardTexture.SetPowerState(updateMessage, updateMessage.EmitterStarboardExists);
_emitterForeTexture.SetPowerState(updateMessage, updateMessage.EmitterForeExists);
_emitterPortTexture.SetPowerState(updateMessage, updateMessage.EmitterPortExists);
}
protected override void FrameUpdate(FrameEventArgs args)
@@ -453,7 +456,7 @@ namespace Content.Client.ParticleAccelerator.UI
var watts = 0;
if (_lastDraw != 0)
{
var val = _drawNoiseGenerator.GetNoise(_time);
var val = _drawNoiseGenerator.GetNoise(_time, 0f);
watts = (int) (_lastDraw + val * 5);
}

View File

@@ -20,9 +20,9 @@ public sealed class MachineBoardTest
"MachineParticleAcceleratorFuelChamberCircuitboard",
"MachineParticleAcceleratorFuelChamberCircuitboard",
"MachineParticleAcceleratorPowerBoxCircuitboard",
"MachineParticleAcceleratorEmitterLeftCircuitboard",
"MachineParticleAcceleratorEmitterCenterCircuitboard",
"MachineParticleAcceleratorEmitterRightCircuitboard",
"MachineParticleAcceleratorEmitterStarboardCircuitboard",
"MachineParticleAcceleratorEmitterForeCircuitboard",
"MachineParticleAcceleratorEmitterPortCircuitboard",
"ParticleAcceleratorComputerCircuitboard"
};
@@ -46,6 +46,8 @@ public sealed class MachineBoardTest
continue;
var mId = mbc.Prototype;
Assert.Multiple(() =>
{
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),
$"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");
Assert.That(mComp.BoardPrototype, Is.EqualTo(p.ID),
$"Machine {mId}'s BoardPrototype is not equal to it's corresponding machine board, {p.ID}");
});
}
});
@@ -79,6 +82,8 @@ public sealed class MachineBoardTest
continue;
var cId = cbc.Prototype;
Assert.Multiple(() =>
{
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),
$"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");
Assert.That(cComp.BoardPrototype, Is.EqualTo(p.ID),
$"Computer \"{cId}\"'s BoardPrototype is not equal to it's corresponding computer board, \"{p.ID}\"");
});
}
});

View File

@@ -1,24 +1,8 @@
using System.Diagnostics;
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.Server.ParticleAccelerator.Wires;
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>
@@ -26,40 +10,125 @@ namespace Content.Server.ParticleAccelerator.Components
/// Also contains primary logic for actual PA behavior, part scanning, etc...
/// </summary>
[RegisterComponent]
public sealed class ParticleAcceleratorControlBoxComponent : ParticleAcceleratorPartComponent
public sealed class ParticleAcceleratorControlBoxComponent : Component
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
/// <summary>
/// Whether the PA parts have been correctly arranged to make a functional device.
/// </summary>
[ViewVariables]
private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ParticleAcceleratorControlBoxUiKey.Key);
public bool Assembled = false;
/// <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>
[ViewVariables] private ApcPowerReceiverComponent _apcPowerReceiverComponent = default!;
[ViewVariables]
public bool Enabled = false;
[ViewVariables] private ParticleAcceleratorFuelChamberComponent? _partFuelChamber;
[ViewVariables] private ParticleAcceleratorEndCapComponent? _partEndCap;
[ViewVariables] private ParticleAcceleratorPowerBoxComponent? _partPowerBox;
[ViewVariables] private ParticleAcceleratorEmitterComponent? _partEmitterLeft;
[ViewVariables] private ParticleAcceleratorEmitterComponent? _partEmitterCenter;
[ViewVariables] private ParticleAcceleratorEmitterComponent? _partEmitterRight;
[ViewVariables] private ParticleAcceleratorPowerState _selectedStrength = ParticleAcceleratorPowerState.Standby;
/// <summary>
/// Whether the PA actually has the power necessary to fire.
/// Requires <see cref="Enabled"/> to be true.
/// </summary>
[ViewVariables]
public bool Powered = false;
[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
[ViewVariables] private bool _isEnabled;
/// <summary>
/// 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)
[ViewVariables] private bool _isPowered;
[ViewVariables] private bool _wireInterfaceBlocked;
[ViewVariables] private bool _wirePowerBlocked;
[ViewVariables] private bool _wireLimiterCut;
[ViewVariables] private bool _wireStrengthCut;
[ViewVariables] private CancellationTokenSource? _fireCancelTokenSrc;
/// <summary>
/// The maximum strength level this particle accelerator can be set to operate at.
/// Modified by <see cref="ParticleAcceleratorLimiterWireAction"/>.
/// </summary>
[ViewVariables]
public ParticleAcceleratorPowerState MaxStrength = ParticleAcceleratorPowerState.Level2;
/// <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>
/// 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...
// I have reflected that here to be authentic.
[ViewVariables(VVAccess.ReadWrite)] [DataField("fireDelay")] private TimeSpan _firingDelay = TimeSpan.FromSeconds(6);
[ViewVariables(VVAccess.ReadWrite)] [DataField("powerDrawBase")] private int _powerDrawBase = 500;
[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,
[DataField("chargeTime")]
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan ChargeTime = TimeSpan.FromSeconds(6.0);
/// <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>
Strength,
[ViewVariables]
public bool InterfaceDisabled = false;
/// <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>
Interface,
[ViewVariables]
public bool StrengthLocked = false;
/// <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>
Limiter,
/// <summary>
/// Does Nothing
/// </summary>
Nothing
}
}
[ViewVariables]
public bool CanBeEnabled = true;
}

View File

@@ -1,25 +1,17 @@
using Content.Shared.Singularity.Components;
namespace Content.Server.ParticleAccelerator.Components
{
namespace Content.Server.ParticleAccelerator.Components;
[RegisterComponent]
public sealed class ParticleAcceleratorEmitterComponent : Component
{
[DataField("emittedPrototype")]
[ViewVariables(VVAccess.ReadWrite)]
public string EmittedPrototype = "ParticlesProjectile";
[DataField("emitterType")]
public ParticleAcceleratorEmitterType Type = ParticleAcceleratorEmitterType.Center;
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);
}
[ViewVariables(VVAccess.ReadWrite)]
public ParticleAcceleratorEmitterType Type = ParticleAcceleratorEmitterType.Fore;
public override string ToString()
{
@@ -29,8 +21,7 @@ namespace Content.Server.ParticleAccelerator.Components
public enum ParticleAcceleratorEmitterType
{
Left,
Center,
Right
}
Port,
Fore,
Starboard
}

View File

@@ -1,39 +1,8 @@
namespace Content.Server.ParticleAccelerator.Components
{
namespace Content.Server.ParticleAccelerator.Components;
[RegisterComponent]
[Virtual]
public class ParticleAcceleratorPartComponent : Component
public sealed 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();
}
}
[ViewVariables]
public EntityUid? Master;
}

View File

@@ -1,16 +1,6 @@
using Content.Server.Power.Components;
namespace Content.Server.ParticleAccelerator.Components;
namespace Content.Server.ParticleAccelerator.Components;
[RegisterComponent]
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 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
{
[Dependency] private readonly IEntityManager _entMan = default!;
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.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;
@@ -7,11 +13,361 @@ public sealed partial class ParticleAcceleratorSystem
{
private void InitializeControlBoxSystem()
{
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, ComponentShutdown>(OnComponentShutdown);
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 JetBrains.Annotations;
using Robust.Shared.Physics.Components;
using Robust.Server.Player;
using Robust.Shared.Map.Components;
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
{
private void InitializePartSystem()
{
SubscribeLocalEvent<ParticleAcceleratorPartComponent, ComponentShutdown>(OnComponentShutdown);
SubscribeLocalEvent<ParticleAcceleratorPartComponent, MoveEvent>(OnMoveEvent);
SubscribeLocalEvent<ParticleAcceleratorPartComponent, PhysicsBodyTypeChangedEvent>(BodyTypeChanged);
}
private static void BodyTypeChanged(
EntityUid uid,
ParticleAcceleratorPartComponent component,
ref PhysicsBodyTypeChangedEvent args)
public void RescanParts(EntityUid uid, IPlayerSession? user = null, ParticleAcceleratorControlBoxComponent? controller = null)
{
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,8 +1,8 @@
using Content.Server.ParticleAccelerator.Components;
using Content.Server.Power.EntitySystems;
namespace Content.Server.ParticleAccelerator.EntitySystems
{
namespace Content.Server.ParticleAccelerator.EntitySystems;
public sealed partial class ParticleAcceleratorSystem
{
private void InitializePowerBoxSystem()
@@ -10,13 +10,19 @@ namespace Content.Server.ParticleAccelerator.EntitySystems
SubscribeLocalEvent<ParticleAcceleratorPowerBoxComponent, PowerConsumerReceivedChanged>(PowerBoxReceivedChanged);
}
private void PowerBoxReceivedChanged(
EntityUid uid,
ParticleAcceleratorPowerBoxComponent component,
ref PowerConsumerReceivedChanged args)
private void PowerBoxReceivedChanged(EntityUid uid, ParticleAcceleratorPowerBoxComponent component, ref PowerConsumerReceivedChanged args)
{
if (TryComp(uid, out ParticleAcceleratorPartComponent? paPart))
paPart.Master?.PowerBoxReceivedChanged(args);
}
if (!TryComp<ParticleAcceleratorPartComponent>(uid, out var part))
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;
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()
{
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

@@ -43,8 +43,8 @@ public sealed class SingularityGeneratorSystem : EntitySystem
if (!Resolve(uid, ref comp))
return;
SetPower(comp, 0);
EntityManager.SpawnEntity(comp.SpawnPrototype, Transform(comp.Owner).Coordinates);
SetPower(uid, 0, comp);
EntityManager.SpawnEntity(comp.SpawnPrototype, Transform(uid).Coordinates);
}
#region Getters/Setters
@@ -54,15 +54,18 @@ public sealed class SingularityGeneratorSystem : EntitySystem
/// </summary>
/// <param name="comp">The singularity generator component.</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;
if (value == oldValue)
return;
comp.Power = value;
if (comp.Power >= comp.Threshold && oldValue < comp.Threshold)
OnPassThreshold(comp.Owner, comp);
OnPassThreshold(uid, comp);
}
/// <summary>
@@ -71,45 +74,19 @@ public sealed class SingularityGeneratorSystem : EntitySystem
/// </summary>
/// <param name="comp">The singularity generator component.</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;
if (value == comp.Threshold)
return;
comp.Power = value;
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);
}
/// <summary>
/// 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
@@ -123,10 +100,10 @@ public sealed class SingularityGeneratorSystem : EntitySystem
/// <param name="args">The state of the beginning of the collision.</param>
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(
singularityGeneratorComponent,
args.OtherEntity,
singularityGeneratorComponent.Power + component.State switch
{
ParticleAcceleratorPowerState.Standby => 0,
@@ -135,7 +112,9 @@ public sealed class SingularityGeneratorSystem : EntitySystem
ParticleAcceleratorPowerState.Level2 => 4,
ParticleAcceleratorPowerState.Level3 => 8,
_ => 0
});
},
singularityGeneratorComponent
);
EntityManager.QueueDeleteEntity(uid);
}
}

View File

@@ -1,5 +1,6 @@
using Content.Server.Administration;
using Content.Server.ParticleAccelerator.Components;
using Content.Server.ParticleAccelerator.EntitySystems;
using Content.Server.Singularity.Components;
using Content.Server.Singularity.EntitySystems;
using Content.Shared.Administration;
@@ -44,12 +45,18 @@ namespace Content.Server.Singularity
}
// 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();
comp.SetStrength(ParticleAcceleratorPowerState.Level0);
comp.SwitchOn();
paSystem.RescanParts(paId, controller: paControl);
if (!paControl.Assembled)
continue;
paSystem.SetStrength(paId, ParticleAcceleratorPowerState.Level0, comp: paControl);
paSystem.SwitchOn(paId, comp: paControl);
}
shell.WriteLine("Done!");
}
}

View File

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

View File

@@ -14,3 +14,7 @@ wire-name-power = POWR
wire-name-arcade-invincible = MNGR
wire-name-vending-contraband = MNGR
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
contents:
- id: MachineParticleAcceleratorEndCapCircuitboard
- id: MachineParticleAcceleratorEmitterLeftCircuitboard
- id: MachineParticleAcceleratorEmitterCenterCircuitboard
- id: MachineParticleAcceleratorEmitterRightCircuitboard
- id: MachineParticleAcceleratorEmitterStarboardCircuitboard
- id: MachineParticleAcceleratorEmitterForeCircuitboard
- id: MachineParticleAcceleratorEmitterPortCircuitboard
- id: MachineParticleAcceleratorFuelChamberCircuitboard
- id: MachineParticleAcceleratorPowerBoxCircuitboard
- id: ParticleAcceleratorComputerCircuitboard

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -75,3 +75,12 @@
wires:
- !type:PowerWireAction
- !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>
<GuideEntityEmbed Entity="ParticleAcceleratorEmitterLeft" Caption="Emitter L"/>
<GuideEntityEmbed Entity="ParticleAcceleratorEmitterCenter" Caption="Emitter C"/>
<GuideEntityEmbed Entity="ParticleAcceleratorEmitterRight" Caption="Emitter R"/>
<GuideEntityEmbed Entity="ParticleAcceleratorEmitterStarboard" Caption="Starboard Emitter"/>
<GuideEntityEmbed Entity="ParticleAcceleratorEmitterFore" Caption="Fore Emitter"/>
<GuideEntityEmbed Entity="ParticleAcceleratorEmitterPort" Caption="Port Emitter"/>
</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.

View File

@@ -60,3 +60,17 @@ OrGate: null
IHSVoidsuit: null
ClothingHeadHelmetIHSVoidHelm: 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