Add multipart machines system (#35969)

This commit is contained in:
BarryNorfolk
2025-06-02 16:02:41 +02:00
committed by GitHub
parent 9a38d66df2
commit b2d0f7ed28
32 changed files with 987 additions and 331 deletions

View File

@@ -1,6 +1,7 @@
using Content.Server.ParticleAccelerator.Components;
using Content.Server.Power.Components;
using Content.Shared.Database;
using Content.Shared.Machines.Components;
using Content.Shared.Singularity.Components;
using Robust.Shared.Utility;
using System.Diagnostics;
@@ -10,6 +11,8 @@ using Content.Shared.Power;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Content.Shared.ParticleAccelerator;
using Content.Shared.Machines.Events;
namespace Content.Server.ParticleAccelerator.EntitySystems;
@@ -20,12 +23,11 @@ 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);
SubscribeLocalEvent<ParticleAcceleratorControlBoxComponent, MultipartMachineAssemblyStateChanged>(OnMachineAssembledChanged);
}
public override void Update(float frameTime)
@@ -40,14 +42,12 @@ public sealed partial class ParticleAcceleratorSystem
}
[Conditional("DEBUG")]
private void EverythingIsWellToFire(ParticleAcceleratorControlBoxComponent controller)
private void EverythingIsWellToFire(ParticleAcceleratorControlBoxComponent controller,
Entity<MultipartMachineComponent> machine)
{
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));
DebugTools.Assert(machine.Comp.IsAssembled);
}
public void Fire(EntityUid uid, TimeSpan curTime, ParticleAcceleratorControlBoxComponent? comp = null)
@@ -58,12 +58,17 @@ public sealed partial class ParticleAcceleratorSystem
comp.LastFire = curTime;
comp.NextFire = curTime + comp.ChargeTime;
EverythingIsWellToFire(comp);
if (!TryComp<MultipartMachineComponent>(uid, out var machineComp))
return;
var machine = (uid, machineComp);
EverythingIsWellToFire(comp, machine);
var strength = comp.SelectedStrength;
FireEmitter(comp.PortEmitter!.Value, strength);
FireEmitter(comp.ForeEmitter!.Value, strength);
FireEmitter(comp.StarboardEmitter!.Value, strength);
FireEmitter(_multipartMachine.GetPartEntity(machine, AcceleratorParts.PortEmitter)!.Value, strength);
FireEmitter(_multipartMachine.GetPartEntity(machine, AcceleratorParts.ForeEmitter)!.Value, strength);
FireEmitter(_multipartMachine.GetPartEntity(machine, AcceleratorParts.StarboardEmitter)!.Value, strength);
}
public void SwitchOn(EntityUid uid, EntityUid? user = null, ParticleAcceleratorControlBoxComponent? comp = null)
@@ -71,7 +76,7 @@ public sealed partial class ParticleAcceleratorSystem
if (!Resolve(uid, ref comp))
return;
DebugTools.Assert(comp.Assembled);
DebugTools.Assert(_multipartMachine.IsAssembled((uid, null)));
if (comp.Enabled || !comp.CanBeEnabled)
return;
@@ -82,9 +87,11 @@ public sealed partial class ParticleAcceleratorSystem
comp.Enabled = true;
UpdatePowerDraw(uid, comp);
if (!TryComp<PowerConsumerComponent>(comp.PowerBox, out var powerConsumer)
|| powerConsumer.ReceivedPower >= powerConsumer.DrawRate * ParticleAcceleratorControlBoxComponent.RequiredPowerRatio)
if (!TryComp<PowerConsumerComponent>(_multipartMachine.GetPartEntity(uid, AcceleratorParts.PowerBox), out var powerConsumer)
|| powerConsumer.ReceivedPower >= powerConsumer.DrawRate * ParticleAcceleratorControlBoxComponent.RequiredPowerRatio)
{
PowerOn(uid, comp);
}
UpdateUI(uid, comp);
}
@@ -112,7 +119,7 @@ public sealed partial class ParticleAcceleratorSystem
return;
DebugTools.Assert(comp.Enabled);
DebugTools.Assert(comp.Assembled);
DebugTools.Assert(_multipartMachine.IsAssembled((uid, null)));
if (comp.Powered)
return;
@@ -211,7 +218,10 @@ public sealed partial class ParticleAcceleratorSystem
return;
}
EverythingIsWellToFire(comp);
if (!TryComp<MultipartMachineComponent>(uid, out var machine))
return;
EverythingIsWellToFire(comp, (uid, machine));
var curTime = _gameTiming.CurTime;
comp.LastFire = curTime;
@@ -223,7 +233,8 @@ public sealed partial class ParticleAcceleratorSystem
{
if (!Resolve(uid, ref comp))
return;
if (!TryComp<PowerConsumerComponent>(comp.PowerBox, out var powerConsumer))
if (!TryComp<PowerConsumerComponent>(_multipartMachine.GetPartEntity(uid, AcceleratorParts.PowerBox), out var powerConsumer))
return;
var powerDraw = comp.BasePowerDraw;
@@ -244,30 +255,35 @@ public sealed partial class ParticleAcceleratorSystem
var draw = 0f;
var receive = 0f;
if (TryComp<PowerConsumerComponent>(comp.PowerBox, out var powerConsumer))
if (TryComp<PowerConsumerComponent>(_multipartMachine.GetPartEntity(uid, AcceleratorParts.PowerBox), out var powerConsumer))
{
draw = powerConsumer.DrawRate;
receive = powerConsumer.ReceivedPower;
}
_uiSystem.SetUiState(uid,
ParticleAcceleratorControlBoxUiKey.Key,
new ParticleAcceleratorUIState(
comp.Assembled,
if (!TryComp<MultipartMachineComponent>(uid, out var machineComp))
return;
var machine = (uid, machineComp);
var uiState = new ParticleAcceleratorUIState(
machineComp.IsAssembled,
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,
(int)draw,
(int)receive,
_multipartMachine.HasPart(machine, AcceleratorParts.StarboardEmitter),
_multipartMachine.HasPart(machine, AcceleratorParts.ForeEmitter),
_multipartMachine.HasPart(machine, AcceleratorParts.PortEmitter),
_multipartMachine.HasPart(machine, AcceleratorParts.PowerBox),
_multipartMachine.HasPart(machine, AcceleratorParts.FuelChamber),
_multipartMachine.HasPart(machine, AcceleratorParts.EndCap),
comp.InterfaceDisabled,
comp.MaxStrength,
comp.StrengthLocked
));
);
_uiSystem.SetUiState(uid, ParticleAcceleratorControlBoxUiKey.Key, uiState);
}
private void UpdateAppearance(EntityUid uid, ParticleAcceleratorControlBoxComponent? comp = null, AppearanceComponent? appearance = null)
@@ -292,55 +308,58 @@ public sealed partial class ParticleAcceleratorSystem
var state = controller.Powered ? (ParticleAcceleratorVisualState) controller.SelectedStrength : ParticleAcceleratorVisualState.Unpowered;
if (!TryComp<MultipartMachineComponent>(uid, out var machineComp))
return;
var machine = (uid, machineComp);
// 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);
if (_multipartMachine.TryGetPartEntity(machine, AcceleratorParts.FuelChamber, out var fuelChamber))
_appearanceSystem.SetData(fuelChamber.Value, ParticleAcceleratorVisuals.VisualState, state);
if (_multipartMachine.TryGetPartEntity(machine, AcceleratorParts.PowerBox, out var powerBox))
_appearanceSystem.SetData(powerBox.Value, ParticleAcceleratorVisuals.VisualState, state);
if (_multipartMachine.TryGetPartEntity(machine, AcceleratorParts.PortEmitter, out var portEmitter))
_appearanceSystem.SetData(portEmitter.Value, ParticleAcceleratorVisuals.VisualState, state);
if (_multipartMachine.TryGetPartEntity(machine, AcceleratorParts.ForeEmitter, out var foreEmitter))
_appearanceSystem.SetData(foreEmitter.Value, ParticleAcceleratorVisuals.VisualState, state);
if (_multipartMachine.TryGetPartEntity(machine, AcceleratorParts.StarboardEmitter, out var starboardEmitter))
_appearanceSystem.SetData(starboardEmitter.Value, ParticleAcceleratorVisuals.VisualState, state);
//no endcap because it has no powerlevel-sprites
}
private IEnumerable<EntityUid> AllParts(EntityUid uid, ParticleAcceleratorControlBoxComponent? comp = null)
/// <summary>
/// Handles when a multipart machine has had some assembled/disassembled state change, or had parts added/removed.
/// </summary>
/// <param name="ent">Multipart machine entity</param>
/// <param name="args">Args for this event</param>
private void OnMachineAssembledChanged(Entity<ParticleAcceleratorControlBoxComponent> ent, ref MultipartMachineAssemblyStateChanged args)
{
if (Resolve(uid, ref comp))
if (args.IsAssembled)
{
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;
UpdatePowerDraw(ent, ent.Comp);
UpdateUI(ent, ent.Comp);
}
}
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))
else
{
if (partQuery.TryGetComponent(part, out var partData))
partData.Master = null;
if (ent.Comp.Powered)
{
SwitchOff(ent, args.User, ent.Comp);
}
else
{
UpdateAppearance(ent, ent.Comp);
UpdateUI(ent, ent.Comp);
}
// Because the parts are already removed from the multipart machine, updating the visual appearance won't find any valid entities.
// We know which parts have been removed so we can update the visual state to unpowered in a more manual way here.
foreach (var (key, part) in args.PartsRemoved)
{
if (key is AcceleratorParts.EndCap)
continue; // No endcap powerlevel-sprites
_appearanceSystem.SetData(part, ParticleAcceleratorVisuals.VisualState, ParticleAcceleratorVisualState.Unpowered);
}
}
}
@@ -365,7 +384,7 @@ public sealed partial class ParticleAcceleratorSystem
if (msg.Enabled)
{
if (comp.Assembled)
if (_multipartMachine.IsAssembled((uid, null)))
SwitchOn(uid, msg.Actor, comp);
}
else
@@ -397,9 +416,13 @@ public sealed partial class ParticleAcceleratorSystem
if (TryComp<ApcPowerReceiverComponent>(uid, out var apcPower) && !apcPower.Powered)
return;
RescanParts(uid, msg.Actor, comp);
if (!TryComp<MultipartMachineComponent>(uid, out var machineComp))
return;
UpdateUI(uid, comp);
// User has requested a manual rescan of the machine, if anything HAS changed that the multipart
// machine system has missed then a AssemblyStateChanged event will be raised at the machine.
var machine = new Entity<MultipartMachineComponent>(uid, machineComp);
_multipartMachine.Rescan(machine, msg.Actor);
}
public static int GetPANumericalLevel(ParticleAcceleratorPowerState state)

View File

@@ -1,5 +1,6 @@
using Content.Server.ParticleAccelerator.Components;
using Content.Server.Singularity.Components;
using Content.Shared.ParticleAccelerator.Components;
using Content.Shared.Projectiles;
using Content.Shared.Singularity.Components;
using Robust.Shared.Physics.Components;

View File

@@ -1,177 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server.ParticleAccelerator.Components;
using JetBrains.Annotations;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Events;
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);
}
public void RescanParts(EntityUid uid, EntityUid? user = null, ParticleAcceleratorControlBoxComponent? controller = null)
{
if (!Resolve(uid, ref controller))
return;
if (controller.CurrentlyRescanning)
return;
var partQuery = GetEntityQuery<ParticleAcceleratorPartComponent>();
foreach (var part in AllParts(uid, controller))
{
if (partQuery.TryGetComponent(part, out var partState))
partState.Master = null;
}
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)
{
SwitchOff(uid, user, controller);
return;
}
var gridUid = xform.GridUid;
if (gridUid == null || gridUid != xform.ParentUid || !TryComp<MapGridComponent>(gridUid, out var grid))
{
SwitchOff(uid, user, controller);
return;
}
// Find fuel chamber first by scanning cardinals.
var fuelQuery = GetEntityQuery<ParticleAcceleratorFuelChamberComponent>();
foreach (var adjacent in _mapSystem.GetCardinalNeighborCells(gridUid.Value, grid, xform.Coordinates))
{
if (fuelQuery.HasComponent(adjacent)
&& partQuery.TryGetComponent(adjacent, out var partState)
&& partState.Master == null)
{
controller.FuelChamber = adjacent;
break;
}
}
if (controller.FuelChamber == null)
{
SwitchOff(uid, user, controller);
return;
}
// When we call SetLocalRotation down there to rotate the control box,
// that ends up re-entrantly calling RescanParts() through the move event.
// You'll have to take my word for it that that breaks everything, yeah?
controller.CurrentlyRescanning = true;
// Automatically rotate the control box sprite to face the fuel chamber
var fuelXform = xformQuery.GetComponent(controller.FuelChamber!.Value);
var fuelDir = (fuelXform.LocalPosition - xform.LocalPosition).GetDir();
_transformSystem.SetLocalRotation(uid, fuelDir.ToAngle(), 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 rotation = fuelXform.LocalRotation;
var offsetVect = rotation.GetCardinalDir().ToIntVec();
var orthoOffsetVect = new Vector2i(-offsetVect.Y, offsetVect.X);
var positionFuelChamber = _mapSystem.TileIndicesFor(gridUid!.Value, grid, fuelXform.Coordinates); // n // n: End Cap
var positionEndCap = positionFuelChamber - offsetVect; // CF // C: Control Box, F: Fuel Chamber
var positionPowerBox = positionFuelChamber + offsetVect; // P // P: Power Box
var positionPortEmitter = positionFuelChamber + offsetVect * 2 + orthoOffsetVect; // EEE // E: Emitter (Starboard, Fore, Port)
var positionForeEmitter = positionFuelChamber + offsetVect * 2;
var positionStarboardEmitter = positionFuelChamber + offsetVect * 2 - orthoOffsetVect;
ScanPart<ParticleAcceleratorEndCapComponent>(gridUid.Value, positionEndCap, rotation, out controller.EndCap, out _, grid);
ScanPart<ParticleAcceleratorPowerBoxComponent>(gridUid.Value, positionPowerBox, rotation, out controller.PowerBox, out _, 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;
}
controller.CurrentlyRescanning = false;
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 : IComponent
{
if (!Resolve(uid, ref grid))
{
part = null;
comp = default;
return false;
}
var compQuery = GetEntityQuery<T>();
foreach (var entity in _mapSystem.GetAnchoredEntities(uid, grid, coordinates))
{
if (compQuery.TryGetComponent(entity, out comp)
&& TryComp<ParticleAcceleratorPartComponent>(entity, out var partState) && partState.Master == null
&& (rotation == null || Transform(entity).LocalRotation.EqualsApprox(rotation!.Value.Theta)))
{
part = entity;
return true;
}
}
part = null;
comp = default;
return false;
}
private void OnComponentShutdown(EntityUid uid, ParticleAcceleratorPartComponent comp, ComponentShutdown args)
{
if (Exists(comp.Master))
RescanParts(comp.Master!.Value);
}
private void BodyTypeChanged(EntityUid uid, ParticleAcceleratorPartComponent comp, ref PhysicsBodyTypeChangedEvent args)
{
if (Exists(comp.Master))
RescanParts(comp.Master!.Value);
}
private void OnMoveEvent(EntityUid uid, ParticleAcceleratorPartComponent comp, ref MoveEvent args)
{
if (Exists(comp.Master))
RescanParts(comp.Master!.Value);
}
}

View File

@@ -1,5 +1,7 @@
using Content.Server.ParticleAccelerator.Components;
using Content.Server.ParticleAccelerator.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Machines.Components;
using Content.Shared.ParticleAccelerator.Components;
namespace Content.Server.ParticleAccelerator.EntitySystems;
@@ -12,7 +14,7 @@ public sealed partial class ParticleAcceleratorSystem
private void PowerBoxReceivedChanged(EntityUid uid, ParticleAcceleratorPowerBoxComponent component, ref PowerConsumerReceivedChanged args)
{
if (!TryComp<ParticleAcceleratorPartComponent>(uid, out var part))
if (!TryComp<MultipartMachinePartComponent>(uid, out var part))
return;
if (!TryComp<ParticleAcceleratorControlBoxComponent>(part.Master, out var controller))
return;

View File

@@ -1,6 +1,7 @@
using Content.Server.Administration.Logs;
using Content.Server.Chat.Managers;
using Content.Server.Projectiles;
using Content.Server.Machines.EntitySystems;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
using Robust.Server.GameObjects;
@@ -19,13 +20,12 @@ public sealed partial class ParticleAcceleratorSystem : EntitySystem
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
[Dependency] private readonly MultipartMachineSystem _multipartMachine = default!;
public override void Initialize()
{
base.Initialize();
InitializeControlBoxSystem();
InitializePartSystem();
InitializePowerBoxSystem();
}
}